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 {
MessageInfo ? lookup ( MessageCache cache ) ;
Future < dynamic > fetch ( Cwtch cwtch , String profileOnion , int conversationIdentifier ) ;
void add ( MessageCache cache , MessageInfo messageInfo , String contenthash ) ;
}
class ByIndex implements CacheHandler {
int index ;
ByIndex ( this . index ) ;
MessageInfo ? lookup ( MessageCache cache ) {
return cache . getByIndex ( index ) ;
}
Future < dynamic > fetch ( Cwtch cwtch , String profileOnion , int conversationIdentifier ) {
return cwtch . GetMessage ( profileOnion , conversationIdentifier , index ) ;
}
void add ( MessageCache cache , MessageInfo messageInfo , String contenthash ) {
cache . add ( messageInfo , index , contenthash ) ;
}
}
class ById implements CacheHandler {
int id ;
ById ( this . id ) ;
MessageInfo ? lookup ( MessageCache cache ) {
return cache . getById ( id ) ;
}
Future < dynamic > fetch ( Cwtch cwtch , String profileOnion , int conversationIdentifier ) {
return cwtch . GetMessageByID ( profileOnion , conversationIdentifier , id ) ;
2022-01-20 14:13:54 +00:00
}
2022-01-20 20:58:14 +00:00
void add ( MessageCache cache , MessageInfo messageInfo , String contenthash ) {
cache . addUnindexed ( messageInfo , contenthash ) ;
}
}
class ByContentHash implements CacheHandler {
String hash ;
ByContentHash ( this . hash ) ;
MessageInfo ? lookup ( MessageCache cache ) {
return cache . getByContentHash ( hash ) ;
}
Future < dynamic > fetch ( Cwtch cwtch , String profileOnion , int conversationIdentifier ) {
return cwtch . GetMessageByContentHash ( profileOnion , conversationIdentifier , hash ) ;
2022-01-20 14:13:54 +00:00
}
2022-01-20 20:58:14 +00:00
void add ( MessageCache cache , MessageInfo messageInfo , String contenthash ) {
cache . addUnindexed ( messageInfo , contenthash ) ;
}
}
Future < Message > messageHandler ( BuildContext context , String profileOnion , int conversationIdentifier , CacheHandler cacheHandler ) {
var malformedMetadata = MessageMetadata ( profileOnion , conversationIdentifier , 0 , DateTime . now ( ) , " " , " " , " " , < String , String > { } , false , true , false ) ;
2022-01-20 14:13:54 +00:00
// Hit cache
2022-01-20 20:58:14 +00:00
MessageInfo ? messageInfo = getMessageInfoFromCache ( context , profileOnion , conversationIdentifier , cacheHandler ) ;
2022-01-20 14:13:54 +00:00
if ( messageInfo ! = null ) {
return Future . value ( compileOverlay ( messageInfo . metadata , messageInfo . wrapper ) ) ;
}
// Fetch and Cache
2022-01-20 20:58:14 +00:00
var messageInfoFuture = fetchAndCacheMessageInfo ( context , profileOnion , conversationIdentifier , cacheHandler ) ;
2022-01-20 18:37:09 +00:00
return messageInfoFuture . then ( ( MessageInfo ? messageInfo ) {
2022-01-20 14:13:54 +00:00
if ( messageInfo ! = null ) {
return compileOverlay ( messageInfo . metadata , messageInfo . wrapper ) ;
} else {
return MalformedMessage ( malformedMetadata ) ;
}
} ) ;
}
2022-01-20 20:58:14 +00:00
MessageInfo ? getMessageInfoFromCache ( BuildContext context , String profileOnion , int conversationIdentifier , CacheHandler cacheHandler ) {
2022-01-20 14:13:54 +00:00
// Hit cache
2021-12-15 21:43:14 +00:00
try {
var cache = Provider . of < ProfileInfoState > ( context , listen: false ) . contactList . getContact ( conversationIdentifier ) ? . messageCache ;
2022-01-20 14:13:54 +00:00
if ( cache ! = null ) {
2022-01-20 20:58:14 +00:00
MessageInfo ? messageInfo = cacheHandler . lookup ( cache ) ;
2022-01-20 14:13:54 +00:00
if ( messageInfo ! = null ) {
return messageInfo ;
2021-12-15 21:43:14 +00:00
}
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...
2021-12-06 20:25:17 +00:00
}
2022-01-20 14:13:54 +00:00
return null ;
}
2021-12-06 20:25:17 +00:00
2022-01-20 20:58:14 +00:00
Future < MessageInfo ? > fetchAndCacheMessageInfo ( BuildContext context , String profileOnion , int conversationIdentifier , CacheHandler cacheHandler ) {
2022-01-20 14:13:54 +00:00
// Load and cache
2022-02-08 21:59:24 +00:00
var profileInfostate = Provider . of < ProfileInfoState > ( context , listen: false ) ;
2021-07-06 19:46:39 +00:00
try {
2021-11-26 22:25:21 +00:00
Future < dynamic > rawMessageEnvelopeFuture ;
2022-01-20 20:58:14 +00:00
rawMessageEnvelopeFuture = cacheHandler . fetch ( Provider . of < FlwtchState > ( context , listen: false ) . cwtch , profileOnion , conversationIdentifier ) ;
2021-11-26 22:25:21 +00:00
2021-07-06 19:46:39 +00:00
return rawMessageEnvelopeFuture . then ( ( dynamic rawMessageEnvelope ) {
2021-09-29 20:31:01 +00:00
try {
dynamic messageWrapper = jsonDecode ( rawMessageEnvelope ) ;
2022-01-20 18:05:11 +00:00
// There are 2 conditions in which this error condition can be met:
// 1. The application == nil, in which case this instance of the UI is already
// broken beyond repair, and will either be replaced by a new version, or requires a complete
// restart.
// 2. This index was incremented and we happened to fetch the timeline prior to the messages inclusion.
// This should be rare as Timeline addition/fetching is mutex protected and Dart itself will pipeline the
// calls to libCwtch-go - however because we use goroutines on the backend there is always a chance that one
// will find itself delayed.
// The second case is recoverable by tail-recursing this future.
2021-09-29 20:31:01 +00:00
if ( messageWrapper [ ' Message ' ] = = null | | messageWrapper [ ' Message ' ] = = ' ' | | messageWrapper [ ' Message ' ] = = ' {} ' ) {
return Future . delayed ( Duration ( seconds: 2 ) , ( ) {
print ( " Tail recursive call to messageHandler called. This should be a rare event. If you see multiples of this log over a short period of time please log it as a bug. " ) ;
2022-01-20 20:58:14 +00:00
return fetchAndCacheMessageInfo ( context , profileOnion , conversationIdentifier , cacheHandler ) ;
2021-09-29 20:31:01 +00:00
} ) ;
}
2021-07-06 19:46:39 +00:00
2022-01-20 20:58:14 +00:00
// Construct the initial metadata
2021-11-18 23:44:54 +00:00
var messageID = messageWrapper [ ' ID ' ] ;
2021-09-29 20:31:01 +00:00
var timestamp = DateTime . tryParse ( messageWrapper [ ' Timestamp ' ] ) ! ;
var senderHandle = messageWrapper [ ' PeerID ' ] ;
var senderImage = messageWrapper [ ' ContactImage ' ] ;
2021-12-01 12:17:48 +00:00
var attributes = messageWrapper [ ' Attributes ' ] ;
2021-09-29 20:31:01 +00:00
var ackd = messageWrapper [ ' Acknowledged ' ] ;
var error = messageWrapper [ ' Error ' ] ! = null ;
2021-11-18 23:44:54 +00:00
var signature = messageWrapper [ ' Signature ' ] ;
2022-01-20 14:13:54 +00:00
var contenthash = messageWrapper [ ' ContentHash ' ] ;
var localIndex = messageWrapper [ ' LocalIndex ' ] ;
2022-01-20 18:37:09 +00:00
var metadata = MessageMetadata ( profileOnion , conversationIdentifier , messageID , timestamp , senderHandle , senderImage , signature , attributes , ackd , error , false ) ;
2022-01-20 14:13:54 +00:00
var messageInfo = new MessageInfo ( metadata , messageWrapper [ ' Message ' ] ) ;
2022-02-08 21:59:24 +00:00
var cache = profileInfostate . contactList . getContact ( conversationIdentifier ) ? . messageCache ;
2022-01-20 14:13:54 +00:00
if ( cache ! = null ) {
2022-01-20 20:58:14 +00:00
cacheHandler . add ( cache , messageInfo , contenthash ) ;
2022-01-20 14:13:54 +00:00
}
2021-12-06 20:25:17 +00:00
2022-01-20 14:13:54 +00:00
return messageInfo ;
2022-02-08 21:59:24 +00:00
} catch ( e , stacktrace ) {
EnvironmentConfig . debugLog ( " message handler exception on parse message and cache: " + e . toString ( ) + " " + stacktrace . toString ( ) ) ;
2022-01-20 14:13:54 +00:00
return null ;
2021-07-06 19:46:39 +00:00
}
} ) ;
} catch ( e ) {
2022-01-20 14:13:54 +00:00
EnvironmentConfig . debugLog ( " message handler exeption on get message: $ e " ) ;
return Future . value ( null ) ;
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 ;
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 (
this . profileOnion , this . conversationIdentifier , this . messageID , this . timestamp , this . senderHandle , this . senderImage , this . signature , this . _attributes , this . _ackd , this . _error , this . isAuto ) ;
2021-07-06 19:46:39 +00:00
}