flutter_app/lib/cwtch/cwtchNotifier.dart

281 lines
13 KiB
Dart
Raw Normal View History

2021-03-10 17:40:14 +00:00
import 'dart:convert';
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';
import '../model.dart';
2021-03-10 17:40:14 +00:00
import '../settings.dart';
// 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
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;
late AppState appState;
CwtchNotifier(ProfileListState pcn, Settings settingsCN, ErrorHandler errorCN, TorStatus torStatusCN, NotificationsManager notificationManagerP, AppState appStateCN) {
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;
appState = appStateCN;
}
void handleMessage(String type, dynamic data) {
switch (type) {
case "CwtchStarted":
appState.SetCwtchInit();
break;
case "CwtchStartError":
appState.SetAppError(data["Error"]);
break;
case "NewPeer":
// 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":
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",
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"]),
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;
case "GroupCreated":
// Retrieve Server Status from Cache...
String status = "";
ServerInfoState? serverInfoState = profileCN.getProfile(data["ProfileOnion"])?.serverList.getServer(data["GroupServer"]);
if (serverInfoState != null) {
status = serverInfoState.status;
}
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"]) == null) {
profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], data["GroupID"],
isInvitation: false, imagePath: data["PicturePath"], nickname: data["GroupName"], status: status, server: data["GroupServer"], isGroup: true, lastMessageTime: DateTime.now()));
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], DateTime.now());
}
break;
case "PeerDeleted":
profileCN.delete(data["Identity"]);
// todo standarize
error.handleUpdate("deleteprofile.success");
break;
case "DeleteContact":
profileCN.getProfile(data["ProfileOnion"])?.contactList.removeContact(data["RemotePeer"]);
break;
case "DeleteGroup":
profileCN.getProfile(data["ProfileOnion"])?.contactList.removeContact(data["GroupID"]);
break;
2021-03-12 12:31:21 +00:00
case "PeerStateChange":
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
profileCN.getProfile(data["ProfileOnion"])?.contactList.resort();
2021-03-12 12:31:21 +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++;
}
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;
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++;
}
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"];
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-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;
case "IndexedFailure":
print("IndexedFailure: $data");
var idx = data["Index"];
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.getMessageKey(idx);
try {
var message = Provider.of<MessageState>(key!.currentContext!, listen: false);
message.error = true;
} catch (e) {
// ignore, we likely have an old key that has been replaced with an actual signature
}
break;
case "SendMessageToGroupError":
// from me (already displayed - do not update counter)
print("SendMessageToGroupError: $data");
var idx = data["Signature"];
var key = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.getMessageKey(idx);
if (key == null) break;
try {
2021-05-25 00:11:39 +00:00
var message = Provider.of<MessageState>(key.currentContext!, listen: false);
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");
// 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) {
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"]));
break;
case "SetAttribute":
if (data["Key"] == "public.name") {
profileCN.getProfile(data["ProfileOnion"])?.nickname = data["Data"];
} else {
print("unhandled set attribute event: $type $data");
}
break;
case "NetworkError":
var isOnline = data["Status"] == "Success";
profileCN.getProfile(data["ProfileOnion"])?.isOnline = isOnline;
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;
case "ACNVersion":
print("acn version: $data");
torStatus.updateVersion(data["Data"]);
break;
2021-05-31 23:20:55 +00:00
case "UpdateServerInfo":
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");
// 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"]);
if (serverInfoState != null) {
2021-06-09 19:57:22 +00:00
print("Got server status: " + serverInfoState.status);
status = serverInfoState.status;
}
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,
imagePath: data["PicturePath"],
nickname: groupInvite["GroupName"],
server: groupInvite["ServerHost"],
status: status,
isGroup: true,
lastMessageTime: DateTime.now()));
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(groupInvite["GroupID"], DateTime.now());
2021-05-05 20:43:53 +00:00
}
}
break;
case "AcceptGroupInvite":
print("accept group invite: $data");
2021-06-04 21:38:29 +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");
// Update the Server Cache
profileCN.getProfile(data["ProfileOnion"])?.updateServerStatusCache(data["GroupServer"], data["ConnectionState"]);
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"];
}
});
profileCN.getProfile(data["ProfileOnion"])?.contactList.resort();
2021-04-22 21:15:27 +00:00
break;
case "SetGroupAttribute":
if (data["Key"] == "local.name") {
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"]) != null) {
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.nickname = data["Data"];
}
} else {
print("unhandled set group attribute event: $type $data");
}
break;
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;
default:
print("unhandled event: $type $data");
}
}
}