2021-07-06 19:46:39 +00:00
|
|
|
import 'dart:convert';
|
2021-11-26 22:25:21 +00:00
|
|
|
import 'package:cwtch/config.dart';
|
2022-01-20 20:58:14 +00:00
|
|
|
import 'package:cwtch/cwtch/cwtch.dart';
|
2022-01-20 14:13:54 +00:00
|
|
|
import 'package:flutter/material.dart';
|
2021-07-06 19:46:39 +00:00
|
|
|
import 'package:flutter/widgets.dart';
|
|
|
|
import 'package:provider/provider.dart';
|
|
|
|
|
|
|
|
import '../main.dart';
|
2022-01-20 14:13:54 +00:00
|
|
|
import 'messagecache.dart';
|
2021-09-21 21:57:40 +00:00
|
|
|
import 'messages/filemessage.dart';
|
2021-07-06 19:46:39 +00:00
|
|
|
import 'messages/invitemessage.dart';
|
|
|
|
import 'messages/malformedmessage.dart';
|
|
|
|
import 'messages/quotedmessage.dart';
|
|
|
|
import 'messages/textmessage.dart';
|
2022-01-18 21:26:52 +00:00
|
|
|
import 'profile.dart';
|
2021-07-06 19:46:39 +00:00
|
|
|
|
2021-07-07 17:05:25 +00:00
|
|
|
// Define the overlays
|
|
|
|
const TextMessageOverlay = 1;
|
|
|
|
const QuotedMessageOverlay = 10;
|
|
|
|
const SuggestContactOverlay = 100;
|
|
|
|
const InviteGroupOverlay = 101;
|
2021-09-21 21:57:40 +00:00
|
|
|
const FileShareOverlay = 200;
|
2021-07-07 17:05:25 +00:00
|
|
|
|
|
|
|
// Defines the length of the tor v3 onion address. Code using this constant will
|
|
|
|
// need to updated when we allow multiple different identifiers. At which time
|
|
|
|
// it will likely be prudent to define a proper Contact wrapper.
|
|
|
|
const TorV3ContactHandleLength = 56;
|
|
|
|
|
2021-07-07 18:31:16 +00:00
|
|
|
// Defines the length of a Cwtch v2 Group.
|
|
|
|
const GroupConversationHandleLength = 32;
|
|
|
|
|
2021-07-06 19:46:39 +00:00
|
|
|
abstract class Message {
|
|
|
|
MessageMetadata getMetadata();
|
2022-01-20 14:13:54 +00:00
|
|
|
|
2021-12-06 20:25:17 +00:00
|
|
|
Widget getWidget(BuildContext context, Key key);
|
2022-01-20 14:13:54 +00:00
|
|
|
|
2021-07-06 19:46:39 +00:00
|
|
|
Widget getPreviewWidget(BuildContext context);
|
|
|
|
}
|
|
|
|
|
2021-12-06 20:25:17 +00:00
|
|
|
Message compileOverlay(MessageMetadata metadata, String messageData) {
|
|
|
|
try {
|
|
|
|
dynamic message = jsonDecode(messageData);
|
|
|
|
var content = message['d'] as dynamic;
|
|
|
|
var overlay = int.parse(message['o'].toString());
|
|
|
|
|
|
|
|
switch (overlay) {
|
|
|
|
case TextMessageOverlay:
|
|
|
|
return TextMessage(metadata, content);
|
|
|
|
case SuggestContactOverlay:
|
|
|
|
case InviteGroupOverlay:
|
|
|
|
return InviteMessage(overlay, metadata, content);
|
|
|
|
case QuotedMessageOverlay:
|
|
|
|
return QuotedMessage(metadata, content);
|
|
|
|
case FileShareOverlay:
|
|
|
|
return FileMessage(metadata, content);
|
|
|
|
default:
|
|
|
|
// Metadata is valid, content is not..
|
|
|
|
return MalformedMessage(metadata);
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
return MalformedMessage(metadata);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-20 20:58:14 +00:00
|
|
|
abstract class CacheHandler {
|
2022-03-23 23:08:19 +00:00
|
|
|
//Future<MessageInfo?> lookup(MessageCache cache);
|
|
|
|
//Future<MessageInfo?> fetch(Cwtch cwtch, String profileOnion, int conversationIdentifier, MessageCache cache);
|
|
|
|
|
|
|
|
Future<MessageInfo?> get(Cwtch cwtch, String profileOnion, int conversationIdentifier, MessageCache cache);
|
|
|
|
//void add(MessageCache cache, MessageInfo messageInfo);
|
2022-01-20 20:58:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class ByIndex implements CacheHandler {
|
|
|
|
int index;
|
|
|
|
|
|
|
|
ByIndex(this.index);
|
|
|
|
|
2022-03-23 23:08:19 +00:00
|
|
|
Future<MessageInfo?> lookup(MessageCache cache) async {
|
|
|
|
var msg = cache.getByIndex(index);
|
|
|
|
return msg;
|
2022-01-20 20:58:14 +00:00
|
|
|
}
|
|
|
|
|
2022-03-23 23:08:19 +00:00
|
|
|
Future<MessageInfo?> get( Cwtch cwtch, String profileOnion, int conversationIdentifier, MessageCache cache) async {
|
|
|
|
var chunk = 40;
|
|
|
|
if (chunk > cache.storageMessageCount - index) {
|
|
|
|
chunk = cache.storageMessageCount - index;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (index < cache.cacheByIndex.length) {
|
|
|
|
return cache.getByIndex(index);
|
|
|
|
}
|
|
|
|
cache.lockIndexs(index, index+chunk);
|
|
|
|
var msgs = await cwtch.GetMessages(profileOnion, conversationIdentifier, index, chunk);
|
|
|
|
int i = 0; // declared here for use in finally to unlock
|
|
|
|
try {
|
|
|
|
List<dynamic> messagesWrapper = jsonDecode(msgs);
|
|
|
|
|
|
|
|
for(; i < messagesWrapper.length; i++) {
|
|
|
|
var messageInfo = messageWrapperToInfo(profileOnion, conversationIdentifier, messagesWrapper[i]);
|
|
|
|
cache.addIndexed(messageInfo, index + i);
|
|
|
|
}
|
|
|
|
//messageWrapperToInfo
|
|
|
|
} catch (e, stacktrace) {
|
|
|
|
EnvironmentConfig.debugLog("Error: Getting indexed messages $index to ${index+chunk} failed parsing: " + e.toString() + " " + stacktrace.toString());
|
|
|
|
} finally {
|
|
|
|
// todo unlock remaining and mark malformed
|
|
|
|
if (i != chunk) {
|
|
|
|
cache.malformIndexes(index+i, index+chunk);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return cache.getByIndex(index);
|
2022-01-20 20:58:14 +00:00
|
|
|
}
|
|
|
|
|
2022-03-23 23:08:19 +00:00
|
|
|
void add(MessageCache cache, MessageInfo messageInfo) {
|
|
|
|
cache.addIndexed(messageInfo, index);
|
2022-01-20 20:58:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class ById implements CacheHandler {
|
|
|
|
int id;
|
|
|
|
|
|
|
|
ById(this.id);
|
|
|
|
|
2022-03-23 23:08:19 +00:00
|
|
|
Future<MessageInfo?> lookup(MessageCache cache) {
|
|
|
|
return Future<MessageInfo?>.value(cache.getById(id));
|
2022-01-20 20:58:14 +00:00
|
|
|
}
|
|
|
|
|
2022-03-23 23:08:19 +00:00
|
|
|
Future<MessageInfo?> fetch(Cwtch cwtch, String profileOnion, int conversationIdentifier, MessageCache cache) async {
|
|
|
|
var rawMessageEnvelope = await cwtch.GetMessageByID(profileOnion, conversationIdentifier, id);
|
|
|
|
var messageInfo = messageJsonToInfo(profileOnion, conversationIdentifier, rawMessageEnvelope);
|
|
|
|
if (messageInfo == null) {
|
|
|
|
return Future.value(null);
|
|
|
|
}
|
|
|
|
cache.addUnindexed(messageInfo);
|
|
|
|
return Future.value(messageInfo);
|
2022-01-20 14:13:54 +00:00
|
|
|
}
|
2022-01-20 20:58:14 +00:00
|
|
|
|
2022-03-23 23:08:19 +00:00
|
|
|
Future<MessageInfo?> get(Cwtch cwtch, String profileOnion, int conversationIdentifier, MessageCache cache) async {
|
|
|
|
var messageInfo = await lookup(cache);
|
|
|
|
if (messageInfo != null) {
|
|
|
|
return Future.value(messageInfo);
|
|
|
|
}
|
|
|
|
return fetch(cwtch, profileOnion, conversationIdentifier, cache);
|
2022-01-20 20:58:14 +00:00
|
|
|
}
|
2022-03-23 23:08:19 +00:00
|
|
|
|
2022-01-20 20:58:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class ByContentHash implements CacheHandler {
|
|
|
|
String hash;
|
|
|
|
|
|
|
|
ByContentHash(this.hash);
|
|
|
|
|
2022-03-23 23:08:19 +00:00
|
|
|
Future<MessageInfo?> lookup(MessageCache cache) {
|
|
|
|
return Future<MessageInfo?>.value(cache.getByContentHash(hash));
|
2022-01-20 20:58:14 +00:00
|
|
|
}
|
|
|
|
|
2022-03-23 23:08:19 +00:00
|
|
|
Future<MessageInfo?> fetch(Cwtch cwtch, String profileOnion, int conversationIdentifier, MessageCache cache) async {
|
|
|
|
var rawMessageEnvelope = await cwtch.GetMessageByContentHash(profileOnion, conversationIdentifier, hash);
|
|
|
|
var messageInfo = messageJsonToInfo(profileOnion, conversationIdentifier, rawMessageEnvelope);
|
|
|
|
if (messageInfo == null) {
|
|
|
|
return Future.value(null);
|
|
|
|
}
|
|
|
|
cache.addUnindexed(messageInfo);
|
|
|
|
return Future.value(messageInfo);
|
2022-01-20 14:13:54 +00:00
|
|
|
}
|
|
|
|
|
2022-03-23 23:08:19 +00:00
|
|
|
Future<MessageInfo?> get( Cwtch cwtch, String profileOnion, int conversationIdentifier, MessageCache cache) async {
|
|
|
|
var messageInfo = await lookup(cache);
|
2022-01-20 14:13:54 +00:00
|
|
|
if (messageInfo != null) {
|
2022-03-23 23:08:19 +00:00
|
|
|
return Future.value(messageInfo);
|
2022-01-20 14:13:54 +00:00
|
|
|
}
|
2022-03-23 23:08:19 +00:00
|
|
|
return fetch(cwtch, profileOnion, conversationIdentifier, cache);
|
|
|
|
}
|
2022-01-20 14:13:54 +00:00
|
|
|
}
|
|
|
|
|
2022-03-23 23:08:19 +00:00
|
|
|
Future<Message> messageHandler(BuildContext context, String profileOnion, int conversationIdentifier, CacheHandler cacheHandler) async {
|
|
|
|
var malformedMetadata = MessageMetadata(profileOnion, conversationIdentifier, 0, DateTime.now(), "", "", "", <String, String>{}, false, true, false, "");
|
|
|
|
var cwtch = Provider.of<FlwtchState>(context, listen: false).cwtch;
|
|
|
|
|
|
|
|
MessageCache? cache;
|
2021-12-15 21:43:14 +00:00
|
|
|
try {
|
2022-03-23 23:08:19 +00:00
|
|
|
cache = Provider
|
|
|
|
.of<ProfileInfoState>(context, listen: false)
|
|
|
|
.contactList
|
|
|
|
.getContact(conversationIdentifier)
|
|
|
|
?.messageCache;
|
|
|
|
if (cache == null) {
|
|
|
|
EnvironmentConfig.debugLog("error: cannot get message cache for profile: $profileOnion conversation: $conversationIdentifier");
|
|
|
|
return MalformedMessage(malformedMetadata);
|
2021-12-06 20:25:17 +00:00
|
|
|
}
|
2021-12-15 21:43:14 +00:00
|
|
|
} catch (e) {
|
2022-01-20 14:13:54 +00:00
|
|
|
EnvironmentConfig.debugLog("message handler exception on get from cache: $e");
|
2021-12-15 21:43:14 +00:00
|
|
|
// provider check failed...make an expensive call...
|
2022-03-23 23:08:19 +00:00
|
|
|
return MalformedMessage(malformedMetadata);
|
|
|
|
}
|
|
|
|
|
|
|
|
MessageInfo? messageInfo = await cacheHandler.get(cwtch, profileOnion, conversationIdentifier, cache);
|
|
|
|
|
|
|
|
if (messageInfo != null) {
|
|
|
|
return compileOverlay(messageInfo.metadata, messageInfo.wrapper);
|
|
|
|
} else {
|
|
|
|
return MalformedMessage(malformedMetadata);
|
2021-12-06 20:25:17 +00:00
|
|
|
}
|
2022-01-20 14:13:54 +00:00
|
|
|
}
|
2021-12-06 20:25:17 +00:00
|
|
|
|
2022-03-23 23:08:19 +00:00
|
|
|
MessageInfo? messageJsonToInfo(String profileOnion, int conversationIdentifier, dynamic messageJson) {
|
2021-07-06 19:46:39 +00:00
|
|
|
try {
|
2022-03-23 23:08:19 +00:00
|
|
|
dynamic messageWrapper = jsonDecode(messageJson);
|
|
|
|
|
|
|
|
if (messageWrapper == null || messageWrapper['Message'] == '' || messageWrapper['Message'] == '{}') {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return messageWrapperToInfo(profileOnion, conversationIdentifier, messageWrapper);
|
|
|
|
} catch (e, stacktrace) {
|
|
|
|
EnvironmentConfig.debugLog("message handler exception on parse message and cache: " + e.toString() + " " + stacktrace.toString());
|
|
|
|
return null;
|
2021-07-06 19:46:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-23 23:08:19 +00:00
|
|
|
MessageInfo messageWrapperToInfo(String profileOnion, int conversationIdentifier, dynamic messageWrapper) {
|
|
|
|
// Construct the initial metadata
|
|
|
|
var messageID = messageWrapper['ID'];
|
|
|
|
var timestamp = DateTime.tryParse(messageWrapper['Timestamp'])!;
|
|
|
|
var senderHandle = messageWrapper['PeerID'];
|
|
|
|
var senderImage = messageWrapper['ContactImage'];
|
|
|
|
var attributes = messageWrapper['Attributes'];
|
|
|
|
var ackd = messageWrapper['Acknowledged'];
|
|
|
|
var error = messageWrapper['Error'] != null;
|
|
|
|
var signature = messageWrapper['Signature'];
|
|
|
|
var contenthash = messageWrapper['ContentHash'];
|
|
|
|
var metadata = MessageMetadata(profileOnion, conversationIdentifier, messageID, timestamp, senderHandle, senderImage, signature, attributes, ackd, error, false, contenthash);
|
|
|
|
var messageInfo = new MessageInfo(metadata, messageWrapper['Message']);
|
|
|
|
|
|
|
|
return messageInfo;
|
|
|
|
}
|
|
|
|
|
2021-07-06 19:46:39 +00:00
|
|
|
class MessageMetadata extends ChangeNotifier {
|
|
|
|
// meta-metadata
|
|
|
|
final String profileOnion;
|
2021-11-18 23:44:54 +00:00
|
|
|
final int conversationIdentifier;
|
|
|
|
final int messageID;
|
2021-07-06 19:46:39 +00:00
|
|
|
|
|
|
|
final DateTime timestamp;
|
|
|
|
final String senderHandle;
|
|
|
|
final String? senderImage;
|
2021-12-01 12:17:48 +00:00
|
|
|
final dynamic _attributes;
|
2021-07-06 19:46:39 +00:00
|
|
|
bool _ackd;
|
|
|
|
bool _error;
|
2021-12-17 01:04:29 +00:00
|
|
|
final bool isAuto;
|
2021-07-06 19:46:39 +00:00
|
|
|
|
|
|
|
final String? signature;
|
2022-03-23 23:08:19 +00:00
|
|
|
final String contenthash;
|
2021-07-06 19:46:39 +00:00
|
|
|
|
2021-12-01 12:17:48 +00:00
|
|
|
dynamic get attributes => this._attributes;
|
2021-07-06 19:46:39 +00:00
|
|
|
|
|
|
|
bool get ackd => this._ackd;
|
2022-01-20 14:13:54 +00:00
|
|
|
|
2021-07-06 19:46:39 +00:00
|
|
|
set ackd(bool newVal) {
|
|
|
|
this._ackd = newVal;
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool get error => this._error;
|
2022-01-20 14:13:54 +00:00
|
|
|
|
2021-07-06 19:46:39 +00:00
|
|
|
set error(bool newVal) {
|
|
|
|
this._error = newVal;
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
|
2021-12-18 00:54:30 +00:00
|
|
|
MessageMetadata(
|
2022-03-23 23:08:19 +00:00
|
|
|
this.profileOnion, this.conversationIdentifier, this.messageID, this.timestamp, this.senderHandle, this.senderImage, this.signature, this._attributes, this._ackd, this._error, this.isAuto, this.contenthash);
|
2021-07-06 19:46:39 +00:00
|
|
|
}
|