2021-03-10 17:40:14 +00:00
|
|
|
import 'dart:convert';
|
2021-06-01 19:33:19 +00:00
|
|
|
import 'package:cwtch/models/servers.dart';
|
2021-05-25 20:43:13 +00:00
|
|
|
import 'package:cwtch/notification_manager.dart';
|
2021-05-03 02:38:43 +00:00
|
|
|
import 'package:provider/provider.dart';
|
2021-03-10 17:40:14 +00:00
|
|
|
|
2021-05-19 21:39:52 +00:00
|
|
|
import 'package:cwtch/torstatus.dart';
|
2021-04-13 22:29:23 +00:00
|
|
|
|
2021-03-24 23:35:24 +00:00
|
|
|
import '../errorHandler.dart';
|
2021-02-09 04:42:43 +00:00
|
|
|
import '../model.dart';
|
2021-03-10 17:40:14 +00:00
|
|
|
import '../settings.dart';
|
2021-02-09 04:42:43 +00:00
|
|
|
|
2021-02-09 23:47:12 +00:00
|
|
|
// Class that handles libcwtch-go events (received either via ffi with an isolate or gomobile over a method channel from kotlin)
|
|
|
|
// Takes Notifiers and triggers them on appropriate events
|
2021-02-09 04:42:43 +00:00
|
|
|
class CwtchNotifier {
|
2021-05-25 00:11:39 +00:00
|
|
|
late ProfileListState profileCN;
|
|
|
|
late Settings settings;
|
|
|
|
late ErrorHandler error;
|
|
|
|
late TorStatus torStatus;
|
2021-05-25 20:43:13 +00:00
|
|
|
late NotificationsManager notificationManager;
|
2021-06-15 19:58:50 +00:00
|
|
|
late AppState appState;
|
2021-02-09 04:42:43 +00:00
|
|
|
|
2021-06-15 19:58:50 +00:00
|
|
|
CwtchNotifier(ProfileListState pcn, Settings settingsCN, ErrorHandler errorCN, TorStatus torStatusCN, NotificationsManager notificationManagerP, AppState appStateCN) {
|
2021-02-09 04:42:43 +00:00
|
|
|
profileCN = pcn;
|
2021-03-10 17:40:14 +00:00
|
|
|
settings = settingsCN;
|
2021-03-24 23:35:24 +00:00
|
|
|
error = errorCN;
|
2021-04-13 22:29:23 +00:00
|
|
|
torStatus = torStatusCN;
|
2021-05-25 20:43:13 +00:00
|
|
|
notificationManager = notificationManagerP;
|
2021-06-15 19:58:50 +00:00
|
|
|
appState = appStateCN;
|
2021-02-09 04:42:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void handleMessage(String type, dynamic data) {
|
|
|
|
switch (type) {
|
2021-06-15 19:58:50 +00:00
|
|
|
case "CwtchStarted":
|
|
|
|
appState.SetCwtchInit();
|
|
|
|
break;
|
|
|
|
case "CwtchStartError":
|
|
|
|
appState.SetAppError(data["Error"]);
|
|
|
|
break;
|
2021-02-09 04:42:43 +00:00
|
|
|
case "NewPeer":
|
2021-06-24 17:46:19 +00:00
|
|
|
// if tag != v1-defaultPassword then it is either encrypted OR it is an unencrypted account created during pre-beta...
|
|
|
|
profileCN.add(data["Identity"], data["name"], data["picture"], data["ContactsJson"], data["ServerList"], data["Online"] == "true", data["tag"] != "v1-defaultPassword");
|
2021-03-12 12:31:21 +00:00
|
|
|
break;
|
|
|
|
case "PeerCreated":
|
2021-05-28 21:48:55 +00:00
|
|
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(
|
2021-04-28 21:20:09 +00:00
|
|
|
data["ProfileOnion"],
|
|
|
|
data["RemotePeer"],
|
2021-03-16 23:33:03 +00:00
|
|
|
nickname: data["nick"],
|
|
|
|
status: data["status"],
|
2021-03-24 23:35:24 +00:00
|
|
|
imagePath: data["picture"],
|
2021-04-10 02:31:27 +00:00
|
|
|
isBlocked: data["authorization"] == "blocked",
|
|
|
|
isInvitation: data["authorization"] == "unknown",
|
2021-05-31 23:11:39 +00:00
|
|
|
savePeerHistory: data["saveConversationHistory"] == null ? "DeleteHistoryConfirmed" : data["saveConversationHistory"],
|
2021-04-10 02:31:27 +00:00
|
|
|
numMessages: int.parse(data["numMessages"]),
|
|
|
|
numUnread: int.parse(data["unread"]),
|
2021-05-31 23:11:39 +00:00
|
|
|
isGroup: data["isGroup"] == true,
|
2021-04-22 21:15:27 +00:00
|
|
|
server: data["groupServer"],
|
2021-04-13 22:53:15 +00:00
|
|
|
lastMessageTime: DateTime.now(), //show at the top of the contact list even if no messages yet
|
2021-03-16 23:33:03 +00:00
|
|
|
));
|
2021-03-12 12:31:21 +00:00
|
|
|
break;
|
2021-05-28 09:14:21 +00:00
|
|
|
case "GroupCreated":
|
2021-06-01 19:33:19 +00:00
|
|
|
|
|
|
|
// Retrieve Server Status from Cache...
|
|
|
|
String status = "";
|
|
|
|
ServerInfoState? serverInfoState = profileCN.getProfile(data["ProfileOnion"])?.serverList.getServer(data["GroupServer"]);
|
|
|
|
if (serverInfoState != null) {
|
|
|
|
status = serverInfoState.status;
|
|
|
|
}
|
2021-05-28 21:48:55 +00:00
|
|
|
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"]) == null) {
|
|
|
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], data["GroupID"],
|
2021-06-01 19:33:19 +00:00
|
|
|
isInvitation: false, imagePath: data["PicturePath"], nickname: data["GroupName"], status: status, server: data["GroupServer"], isGroup: true, lastMessageTime: DateTime.now()));
|
2021-05-28 21:48:55 +00:00
|
|
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], DateTime.now());
|
2021-05-28 09:14:21 +00:00
|
|
|
}
|
|
|
|
break;
|
2021-06-15 00:28:45 +00:00
|
|
|
case "PeerDeleted":
|
|
|
|
profileCN.delete(data["Identity"]);
|
|
|
|
// todo standarize
|
|
|
|
error.handleUpdate("deleteprofile.success");
|
|
|
|
break;
|
|
|
|
case "DeleteContact":
|
|
|
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.removeContact(data["RemotePeer"]);
|
|
|
|
break;
|
2021-05-28 09:14:21 +00:00
|
|
|
case "DeleteGroup":
|
2021-05-28 21:48:55 +00:00
|
|
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.removeContact(data["GroupID"]);
|
2021-05-28 09:14:21 +00:00
|
|
|
break;
|
2021-03-12 12:31:21 +00:00
|
|
|
case "PeerStateChange":
|
2021-05-28 21:48:55 +00:00
|
|
|
ContactInfoState? contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"]);
|
2021-04-10 02:31:27 +00:00
|
|
|
if (contact != null) {
|
|
|
|
if (data["ConnectionState"] != null) {
|
|
|
|
contact.status = data["ConnectionState"];
|
|
|
|
}
|
2021-04-08 05:07:01 +00:00
|
|
|
if (data["authorization"] != null) {
|
|
|
|
contact.isInvitation = data["authorization"] == "unknown";
|
|
|
|
contact.isBlocked = data["authorization"] == "blocked";
|
|
|
|
}
|
2021-05-19 01:17:50 +00:00
|
|
|
// contact.[status/isBlocked] might change the list's sort order
|
2021-05-28 21:48:55 +00:00
|
|
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.resort();
|
2021-03-12 12:31:21 +00:00
|
|
|
}
|
2021-02-09 04:42:43 +00:00
|
|
|
break;
|
2021-04-08 05:07:01 +00:00
|
|
|
case "NewMessageFromPeer":
|
2021-05-25 20:43:13 +00:00
|
|
|
notificationManager.notify("New Message From Peer!");
|
2021-06-24 07:36:41 +00:00
|
|
|
if (appState.selectedProfile != data["ProfileOnion"] || appState.selectedConversation != data["RemotePeer"]) {
|
|
|
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.unreadMessages++;
|
|
|
|
}
|
2021-05-28 21:48:55 +00:00
|
|
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.totalMessages++;
|
|
|
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["RemotePeer"], DateTime.now());
|
2021-04-08 05:07:01 +00:00
|
|
|
break;
|
2021-05-05 22:02:31 +00:00
|
|
|
case "PeerAcknowledgement":
|
|
|
|
// We don't use these anymore, IndexedAcknowledgement is more suited to the UI front end...
|
|
|
|
break;
|
2021-05-03 02:38:43 +00:00
|
|
|
case "IndexedAcknowledgement":
|
2021-05-05 22:02:31 +00:00
|
|
|
var idx = data["Index"];
|
|
|
|
// We return -1 for protocol message acks if there is no message
|
|
|
|
if (idx == "-1") break;
|
2021-05-28 21:48:55 +00:00
|
|
|
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.getMessageKey(idx);
|
2021-05-03 02:38:43 +00:00
|
|
|
if (key == null) break;
|
2021-05-05 22:02:31 +00:00
|
|
|
try {
|
2021-05-25 00:11:39 +00:00
|
|
|
var message = Provider.of<MessageState>(key.currentContext!, listen: false);
|
2021-05-05 22:02:31 +00:00
|
|
|
if (message == null) break;
|
|
|
|
message.ackd = true;
|
|
|
|
} catch (e) {
|
|
|
|
// ignore, we received an ack for a message that hasn't loaded onto the screen yet...
|
|
|
|
// the protocol was faster than the ui....yay?
|
|
|
|
}
|
2021-05-03 02:38:43 +00:00
|
|
|
break;
|
2021-04-22 21:15:27 +00:00
|
|
|
case "NewMessageFromGroup":
|
2021-05-05 20:43:53 +00:00
|
|
|
if (data["ProfileOnion"] != data["RemotePeer"]) {
|
|
|
|
//not from me
|
2021-06-24 07:36:41 +00:00
|
|
|
if (appState.selectedProfile != data["ProfileOnion"] || appState.selectedConversation != data["GroupID"]) {
|
|
|
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.unreadMessages++;
|
|
|
|
}
|
2021-05-28 21:48:55 +00:00
|
|
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.totalMessages++;
|
|
|
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], DateTime.now());
|
2021-05-05 20:43:53 +00:00
|
|
|
} else {
|
|
|
|
// from me (already displayed - do not update counter)
|
2021-05-05 22:02:31 +00:00
|
|
|
var idx = data["Signature"];
|
2021-05-28 21:48:55 +00:00
|
|
|
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.getMessageKey(idx);
|
2021-05-05 22:02:31 +00:00
|
|
|
if (key == null) break;
|
|
|
|
try {
|
2021-05-25 00:11:39 +00:00
|
|
|
var message = Provider.of<MessageState>(key.currentContext!, listen: false);
|
2021-05-05 22:02:31 +00:00
|
|
|
if (message == null) break;
|
|
|
|
message.ackd = true;
|
|
|
|
} catch (e) {
|
|
|
|
// ignore, we likely have an old key that has been replaced with an actual signature
|
|
|
|
}
|
2021-05-03 18:47:54 +00:00
|
|
|
}
|
2021-04-22 21:15:27 +00:00
|
|
|
break;
|
2021-06-17 22:44:42 +00:00
|
|
|
case "MessageCounterResync":
|
|
|
|
var contactHandle = data["RemotePeer"];
|
|
|
|
if (contactHandle == null || contactHandle == "") contactHandle = data["GroupID"];
|
|
|
|
profileCN.getProfile(data["Identity"])?.contactList.getContact(contactHandle)!.totalMessages = int.parse(data["Data"]);
|
|
|
|
break;
|
2021-05-25 01:09:19 +00:00
|
|
|
case "IndexedFailure":
|
|
|
|
print("IndexedFailure: $data");
|
|
|
|
var idx = data["Index"];
|
2021-05-28 21:48:55 +00:00
|
|
|
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.getMessageKey(idx);
|
2021-05-25 01:09:19 +00:00
|
|
|
try {
|
2021-05-28 21:48:55 +00:00
|
|
|
var message = Provider.of<MessageState>(key!.currentContext!, listen: false);
|
2021-05-25 01:09:19 +00:00
|
|
|
message.error = true;
|
|
|
|
} catch (e) {
|
|
|
|
// ignore, we likely have an old key that has been replaced with an actual signature
|
|
|
|
}
|
|
|
|
break;
|
2021-05-10 23:58:34 +00:00
|
|
|
case "SendMessageToGroupError":
|
|
|
|
// from me (already displayed - do not update counter)
|
|
|
|
print("SendMessageToGroupError: $data");
|
|
|
|
var idx = data["Signature"];
|
2021-05-28 21:48:55 +00:00
|
|
|
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.getMessageKey(idx);
|
2021-05-10 23:58:34 +00:00
|
|
|
if (key == null) break;
|
|
|
|
try {
|
2021-05-25 00:11:39 +00:00
|
|
|
var message = Provider.of<MessageState>(key.currentContext!, listen: false);
|
2021-05-10 23:58:34 +00:00
|
|
|
if (message == null) break;
|
|
|
|
message.error = true;
|
|
|
|
} catch (e) {
|
|
|
|
// ignore, we likely have an old key that has been replaced with an actual signature
|
|
|
|
}
|
|
|
|
break;
|
2021-03-24 23:35:24 +00:00
|
|
|
case "AppError":
|
|
|
|
print("New App Error: $data");
|
2021-06-15 00:28:45 +00:00
|
|
|
// special case for delete error (todo: standardize cwtch errors)
|
|
|
|
if (data["Error"] == "Password did not match") {
|
|
|
|
error.handleUpdate("deleteprofile.error");
|
|
|
|
} else if (data["Data"] != null) {
|
2021-05-24 20:23:36 +00:00
|
|
|
error.handleUpdate(data["Data"]);
|
|
|
|
}
|
2021-03-24 23:35:24 +00:00
|
|
|
break;
|
2021-03-10 17:40:14 +00:00
|
|
|
case "UpdateGlobalSettings":
|
|
|
|
settings.handleUpdate(jsonDecode(data["Data"]));
|
2021-02-09 04:42:43 +00:00
|
|
|
break;
|
2021-04-10 04:01:12 +00:00
|
|
|
case "SetAttribute":
|
|
|
|
if (data["Key"] == "public.name") {
|
2021-05-28 21:48:55 +00:00
|
|
|
profileCN.getProfile(data["ProfileOnion"])?.nickname = data["Data"];
|
2021-04-10 04:01:12 +00:00
|
|
|
} else {
|
2021-05-28 09:14:21 +00:00
|
|
|
print("unhandled set attribute event: $type $data");
|
2021-04-10 04:01:12 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case "NetworkError":
|
|
|
|
var isOnline = data["Status"] == "Success";
|
2021-05-28 21:48:55 +00:00
|
|
|
profileCN.getProfile(data["ProfileOnion"])?.isOnline = isOnline;
|
2021-04-10 04:01:12 +00:00
|
|
|
break;
|
2021-04-13 00:04:51 +00:00
|
|
|
case "ACNStatus":
|
|
|
|
print("acn status: $data");
|
2021-04-13 22:29:23 +00:00
|
|
|
torStatus.handleUpdate(int.parse(data["Progress"]), data["Status"]);
|
2021-04-13 00:04:51 +00:00
|
|
|
break;
|
2021-06-08 02:13:23 +00:00
|
|
|
case "ACNVersion":
|
|
|
|
print("acn version: $data");
|
|
|
|
torStatus.updateVersion(data["Data"]);
|
|
|
|
break;
|
2021-05-31 23:20:55 +00:00
|
|
|
case "UpdateServerInfo":
|
2021-05-28 21:48:55 +00:00
|
|
|
profileCN.getProfile(data["ProfileOnion"])?.replaceServers(data["ServerList"]);
|
2021-04-20 23:54:47 +00:00
|
|
|
break;
|
2021-05-05 20:43:53 +00:00
|
|
|
case "NewGroup":
|
2021-06-09 19:57:22 +00:00
|
|
|
print("new group: $data");
|
2021-05-05 20:43:53 +00:00
|
|
|
String invite = data["GroupInvite"].toString();
|
|
|
|
if (invite.startsWith("torv3")) {
|
|
|
|
String inviteJson = new String.fromCharCodes(base64Decode(invite.substring(5)));
|
|
|
|
dynamic groupInvite = jsonDecode(inviteJson);
|
2021-06-09 19:57:22 +00:00
|
|
|
print("group invite: $groupInvite");
|
2021-06-01 19:33:19 +00:00
|
|
|
|
|
|
|
// Retrieve Server Status from Cache...
|
|
|
|
String status = "";
|
2021-06-09 19:57:22 +00:00
|
|
|
ServerInfoState? serverInfoState = profileCN.getProfile(data["ProfileOnion"])!.serverList.getServer(groupInvite["ServerHost"]);
|
2021-06-01 19:33:19 +00:00
|
|
|
if (serverInfoState != null) {
|
2021-06-09 19:57:22 +00:00
|
|
|
print("Got server status: " + serverInfoState.status);
|
2021-06-01 19:33:19 +00:00
|
|
|
status = serverInfoState.status;
|
|
|
|
}
|
|
|
|
|
2021-05-28 21:48:55 +00:00
|
|
|
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(groupInvite["GroupID"]) == null) {
|
|
|
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], groupInvite["GroupID"],
|
2021-06-09 19:57:22 +00:00
|
|
|
isInvitation: false,
|
2021-06-01 19:33:19 +00:00
|
|
|
imagePath: data["PicturePath"],
|
|
|
|
nickname: groupInvite["GroupName"],
|
|
|
|
server: groupInvite["ServerHost"],
|
|
|
|
status: status,
|
|
|
|
isGroup: true,
|
|
|
|
lastMessageTime: DateTime.now()));
|
2021-05-28 21:48:55 +00:00
|
|
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(groupInvite["GroupID"], DateTime.now());
|
2021-05-05 20:43:53 +00:00
|
|
|
}
|
|
|
|
}
|
2021-04-28 22:26:49 +00:00
|
|
|
break;
|
|
|
|
case "AcceptGroupInvite":
|
|
|
|
print("accept group invite: $data");
|
2021-06-04 21:38:29 +00:00
|
|
|
|
2021-05-28 21:48:55 +00:00
|
|
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.isInvitation = false;
|
|
|
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], DateTime.now());
|
2021-04-22 21:15:27 +00:00
|
|
|
break;
|
|
|
|
case "ServerStateChange":
|
|
|
|
print("server state change: $data");
|
2021-06-01 19:33:19 +00:00
|
|
|
// Update the Server Cache
|
|
|
|
profileCN.getProfile(data["ProfileOnion"])?.updateServerStatusCache(data["GroupServer"], data["ConnectionState"]);
|
2021-05-28 21:48:55 +00:00
|
|
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.contacts.forEach((contact) {
|
2021-05-05 23:12:30 +00:00
|
|
|
if (contact.isGroup == true && contact.server == data["GroupServer"]) {
|
2021-04-22 21:15:27 +00:00
|
|
|
contact.status = data["ConnectionState"];
|
|
|
|
}
|
|
|
|
});
|
2021-05-28 21:48:55 +00:00
|
|
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.resort();
|
2021-04-22 21:15:27 +00:00
|
|
|
break;
|
2021-05-28 09:14:21 +00:00
|
|
|
case "SetGroupAttribute":
|
|
|
|
if (data["Key"] == "local.name") {
|
2021-05-28 21:48:55 +00:00
|
|
|
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"]) != null) {
|
|
|
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.nickname = data["Data"];
|
2021-05-28 09:14:21 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
print("unhandled set group attribute event: $type $data");
|
|
|
|
}
|
|
|
|
break;
|
2021-06-01 19:33:19 +00:00
|
|
|
case "NewRetValMessageFromPeer":
|
|
|
|
if (data["Path"] == "name") {
|
|
|
|
// Update locally on the UI...
|
|
|
|
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"]) != null) {
|
|
|
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.nickname = data["Data"];
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
print("unhandled peer attribute event: $type $data");
|
|
|
|
}
|
|
|
|
break;
|
2021-02-09 04:42:43 +00:00
|
|
|
default:
|
2021-05-28 09:14:21 +00:00
|
|
|
print("unhandled event: $type $data");
|
2021-02-09 04:42:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|