feat(all): Move all managers to the new data system

This commit is contained in:
PapaTutuWawa 2023-06-04 21:53:47 +02:00
parent 8270185027
commit 79d7e3ba64
33 changed files with 498 additions and 431 deletions

View File

@ -27,7 +27,9 @@ import 'package:moxxmpp/src/stanza.dart';
import 'package:moxxmpp/src/stringxml.dart'; import 'package:moxxmpp/src/stringxml.dart';
import 'package:moxxmpp/src/types/result.dart'; import 'package:moxxmpp/src/types/result.dart';
import 'package:moxxmpp/src/util/queue.dart'; import 'package:moxxmpp/src/util/queue.dart';
import 'package:moxxmpp/src/util/typed_map.dart';
import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart'; import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
import 'package:moxxmpp/src/xeps/xep_0198/types.dart';
import 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart'; import 'package:moxxmpp/src/xeps/xep_0198/xep_0198.dart';
import 'package:moxxmpp/src/xeps/xep_0352.dart'; import 'package:moxxmpp/src/xeps/xep_0352.dart';
import 'package:synchronized/synchronized.dart'; import 'package:synchronized/synchronized.dart';
@ -474,8 +476,8 @@ class XmppConnection {
initial: StanzaHandlerData( initial: StanzaHandlerData(
false, false,
false, false,
null,
newStanza, newStanza,
TypedMap(),
encrypted: details.encrypted, encrypted: details.encrypted,
forceEncryption: details.forceEncryption, forceEncryption: details.forceEncryption,
), ),
@ -531,14 +533,15 @@ class XmppConnection {
// Run post-send handlers // Run post-send handlers
_log.fine('Running post stanza handlers..'); _log.fine('Running post stanza handlers..');
final extensions = TypedMap()
..set(StreamManagementData(details.excludeFromStreamManagement));
await _runOutgoingPostStanzaHandlers( await _runOutgoingPostStanzaHandlers(
newStanza, newStanza,
initial: StanzaHandlerData( initial: StanzaHandlerData(
false, false,
false, false,
null,
newStanza, newStanza,
excludeFromStreamManagement: details.excludeFromStreamManagement, extensions,
), ),
); );
_log.fine('Done'); _log.fine('Done');
@ -653,7 +656,7 @@ class XmppConnection {
Stanza stanza, { Stanza stanza, {
StanzaHandlerData? initial, StanzaHandlerData? initial,
}) async { }) async {
var state = initial ?? StanzaHandlerData(false, false, null, stanza); var state = initial ?? StanzaHandlerData(false, false, stanza, TypedMap());
for (final handler in handlers) { for (final handler in handlers) {
if (handler.matches(state.stanza)) { if (handler.matches(state.stanza)) {
state = await handler.callback(state.stanza, state); state = await handler.callback(state.stanza, state);
@ -728,7 +731,7 @@ class XmppConnection {
// it. // it.
final incomingPreHandlers = await _runIncomingPreStanzaHandlers(stanza); final incomingPreHandlers = await _runIncomingPreStanzaHandlers(stanza);
final prefix = incomingPreHandlers.encrypted && final prefix = incomingPreHandlers.encrypted &&
incomingPreHandlers.other['encryption_error'] == null incomingPreHandlers.encryptionError == null
? '(Encrypted) ' ? '(Encrypted) '
: ''; : '';
_log.finest('<== $prefix${incomingPreHandlers.stanza.toXml()}'); _log.finest('<== $prefix${incomingPreHandlers.stanza.toXml()}');
@ -747,10 +750,10 @@ class XmppConnection {
initial: StanzaHandlerData( initial: StanzaHandlerData(
false, false,
incomingPreHandlers.cancel, incomingPreHandlers.cancel,
incomingPreHandlers.cancelReason,
incomingPreHandlers.stanza, incomingPreHandlers.stanza,
incomingPreHandlers.extensions,
encrypted: incomingPreHandlers.encrypted, encrypted: incomingPreHandlers.encrypted,
other: incomingPreHandlers.other, cancelReason: incomingPreHandlers.cancelReason,
), ),
); );
if (!incomingHandlers.done) { if (!incomingHandlers.done) {

View File

@ -1,82 +1,45 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:moxxmpp/src/stanza.dart'; import 'package:moxxmpp/src/stanza.dart';
import 'package:moxxmpp/src/xeps/xep_0066.dart'; import 'package:moxxmpp/src/util/typed_map.dart';
import 'package:moxxmpp/src/xeps/xep_0085.dart';
import 'package:moxxmpp/src/xeps/xep_0203.dart';
import 'package:moxxmpp/src/xeps/xep_0359.dart';
import 'package:moxxmpp/src/xeps/xep_0380.dart';
import 'package:moxxmpp/src/xeps/xep_0385.dart';
import 'package:moxxmpp/src/xeps/xep_0424.dart';
import 'package:moxxmpp/src/xeps/xep_0444.dart';
import 'package:moxxmpp/src/xeps/xep_0446.dart';
import 'package:moxxmpp/src/xeps/xep_0447.dart';
import 'package:moxxmpp/src/xeps/xep_0461.dart';
part 'data.freezed.dart'; class StanzaHandlerData {
StanzaHandlerData(
this.done,
this.cancel,
this.stanza,
this.extensions, {
this.cancelReason,
this.encryptionError,
this.encrypted = false,
this.forceEncryption = false,
});
@freezed /// Indicates to the runner that processing is now done. This means that all
class StanzaHandlerData with _$StanzaHandlerData { /// pre-processing is done and no other handlers should be consulted.
factory StanzaHandlerData( bool done;
// Indicates to the runner that processing is now done. This means that all
// pre-processing is done and no other handlers should be consulted.
bool done,
// Indicates to the runner that processing is to be cancelled and no further handlers
// should run. The stanza also will not be sent.
bool cancel,
// The reason why we cancelled the processing and sending
dynamic cancelReason,
// The stanza that is being dealt with. SHOULD NOT be overwritten, unless it is absolutely
// necessary, e.g. with Message Carbons or OMEMO
Stanza stanza, {
// Whether the stanza is retransmitted. Only useful in the context of outgoing
// stanza handlers. MUST NOT be overwritten.
@Default(false) bool retransmitted,
StatelessMediaSharingData? sims,
StatelessFileSharingData? sfs,
OOBData? oob,
// XEP-0359 <origin-id />'s id attribute, if available. /// Indicates to the runner that processing is to be cancelled and no further handlers
String? originId, /// should run. The stanza also will not be sent.
bool cancel;
/// The reason why we cancelled the processing and sending.
Object? cancelReason;
/// The reason why an encryption or decryption failed.
Object? encryptionError;
/// The stanza that is being dealt with. SHOULD NOT be overwritten, unless it is
/// absolutely necessary, e.g. with Message Carbons or OMEMO.
Stanza stanza;
/// Whether the stanza was received encrypted
bool encrypted;
// XEP-0359 <stanza-id /> elements, if available.
List<StanzaId>? stanzaIds,
ReplyData? reply,
ChatState? chatState,
@Default(false) bool isCarbon,
@Default(false) bool deliveryReceiptRequested,
@Default(false) bool isMarkable,
// File Upload Notifications
// A notification
FileMetadataData? fun,
// The stanza id this replaces
String? funReplacement,
// The stanza id this cancels
String? funCancellation,
// Whether the stanza was received encrypted
@Default(false) bool encrypted,
// If true, forces the encryption manager to encrypt to the JID, even if it // If true, forces the encryption manager to encrypt to the JID, even if it
// would not normally. In the case of OMEMO: If shouldEncrypt returns false // would not normally. In the case of OMEMO: If shouldEncrypt returns false
// but forceEncryption is true, then the OMEMO manager will try to encrypt // but forceEncryption is true, then the OMEMO manager will try to encrypt
// to the JID anyway. // to the JID anyway.
@Default(false) bool forceEncryption, bool forceEncryption;
// The stated type of encryption used, if any was used
ExplicitEncryptionType? encryptionType, /// Additional data from other managers.
// Delayed Delivery final TypedMap extensions;
DelayedDelivery? delayedDelivery,
// This is for stanza handlers that are not part of the XMPP library but still need
// pass data around.
@Default(<String, dynamic>{}) Map<String, dynamic> other,
// If non-null, then it indicates the origin Id of the message that should be
// retracted
MessageRetractionData? messageRetraction,
// If non-null, then the message is a correction for the specified stanza Id
String? lastMessageCorrectionSid,
// Reactions data
MessageReactions? messageReactions,
// The Id of the sticker pack this sticker belongs to
String? stickerPackId,
// Flag indicating whether the stanza should be excluded from stream management's
// resending behaviour
@Default(false) bool excludeFromStreamManagement,
}) = _StanzaHandlerData;
} }

View File

@ -12,15 +12,18 @@ import 'package:moxxmpp/src/xeps/staging/file_upload_notification.dart';
import 'package:moxxmpp/src/xeps/xep_0066.dart'; import 'package:moxxmpp/src/xeps/xep_0066.dart';
import 'package:moxxmpp/src/xeps/xep_0085.dart'; import 'package:moxxmpp/src/xeps/xep_0085.dart';
import 'package:moxxmpp/src/xeps/xep_0184.dart'; import 'package:moxxmpp/src/xeps/xep_0184.dart';
import 'package:moxxmpp/src/xeps/xep_0280.dart';
import 'package:moxxmpp/src/xeps/xep_0308.dart'; import 'package:moxxmpp/src/xeps/xep_0308.dart';
import 'package:moxxmpp/src/xeps/xep_0333.dart'; import 'package:moxxmpp/src/xeps/xep_0333.dart';
import 'package:moxxmpp/src/xeps/xep_0334.dart'; import 'package:moxxmpp/src/xeps/xep_0334.dart';
import 'package:moxxmpp/src/xeps/xep_0359.dart'; import 'package:moxxmpp/src/xeps/xep_0359.dart';
import 'package:moxxmpp/src/xeps/xep_0385.dart';
import 'package:moxxmpp/src/xeps/xep_0424.dart'; import 'package:moxxmpp/src/xeps/xep_0424.dart';
import 'package:moxxmpp/src/xeps/xep_0444.dart'; import 'package:moxxmpp/src/xeps/xep_0444.dart';
import 'package:moxxmpp/src/xeps/xep_0446.dart'; import 'package:moxxmpp/src/xeps/xep_0446.dart';
import 'package:moxxmpp/src/xeps/xep_0447.dart'; import 'package:moxxmpp/src/xeps/xep_0447.dart';
import 'package:moxxmpp/src/xeps/xep_0448.dart'; import 'package:moxxmpp/src/xeps/xep_0448.dart';
import 'package:moxxmpp/src/xeps/xep_0449.dart';
import 'package:moxxmpp/src/xeps/xep_0461.dart'; import 'package:moxxmpp/src/xeps/xep_0461.dart';
/// Data used to build a message stanza. /// Data used to build a message stanza.
@ -100,7 +103,7 @@ class MessageManager extends XmppManagerBase {
final hints = List<MessageProcessingHint>.empty(growable: true); final hints = List<MessageProcessingHint>.empty(growable: true);
for (final element for (final element
in message.findTagsByXmlns(messageProcessingHintsXmlns)) { in message.findTagsByXmlns(messageProcessingHintsXmlns)) {
hints.add(messageProcessingHintFromXml(element)); hints.add(MessageProcessingHint.fromName(element.tag));
} }
getAttributes().sendEvent( getAttributes().sendEvent(
@ -109,32 +112,37 @@ class MessageManager extends XmppManagerBase {
fromJid: JID.fromString(message.attributes['from']! as String), fromJid: JID.fromString(message.attributes['from']! as String),
toJid: JID.fromString(message.attributes['to']! as String), toJid: JID.fromString(message.attributes['to']! as String),
sid: message.attributes['id']! as String, sid: message.attributes['id']! as String,
originId: state.originId, originId: state.extensions.get<StableIdData>()?.originId,
stanzaIds: state.stanzaIds, stanzaIds: state.extensions.get<StableIdData>()?.stanzaIds,
isCarbon: state.isCarbon, isCarbon: state.extensions.get<CarbonsData>()?.isCarbon ?? false,
deliveryReceiptRequested: state.deliveryReceiptRequested, deliveryReceiptRequested: state.extensions
isMarkable: state.isMarkable, .get<MessageDeliveryReceiptData>()
?.receiptRequested ??
false,
isMarkable: state.extensions.get<ChatMarkerData>()?.isMarkable ?? false,
type: message.attributes['type'] as String?, type: message.attributes['type'] as String?,
oob: state.oob, oob: state.extensions.get<OOBData>(),
sfs: state.sfs, sfs: state.extensions.get<StatelessFileSharingData>(),
sims: state.sims, sims: state.extensions.get<StatelessMediaSharingData>(),
reply: state.reply, reply: state.extensions.get<ReplyData>(),
chatState: state.chatState, chatState: state.extensions.get<ChatState>(),
fun: state.fun, fun: state.extensions.get<FileUploadNotificationData>()?.metadata,
funReplacement: state.funReplacement, funReplacement:
funCancellation: state.funCancellation, state.extensions.get<FileUploadNotificationReplacementData>()?.id,
funCancellation:
state.extensions.get<FileUploadNotificationCancellationData>()?.id,
encrypted: state.encrypted, encrypted: state.encrypted,
messageRetraction: state.messageRetraction, messageRetraction: state.extensions.get<MessageRetractionData>(),
messageCorrectionId: state.lastMessageCorrectionSid, messageCorrectionId: state.extensions.get<MessageRetractionData>()?.id,
messageReactions: state.messageReactions, messageReactions: state.extensions.get<MessageReactions>(),
messageProcessingHints: hints.isEmpty ? null : hints, messageProcessingHints: hints.isEmpty ? null : hints,
stickerPackId: state.stickerPackId, stickerPackId: state.extensions.get<StickersData>()?.stickerPackId,
other: state.other, other: {},
error: StanzaError.fromStanza(message), error: StanzaError.fromStanza(message),
), ),
); );
return state.copyWith(done: true); return state..done = true;
} }
/// Send a message to to with the content body. If deliveryRequest is true, then /// Send a message to to with the content body. If deliveryRequest is true, then
@ -142,7 +150,7 @@ class MessageManager extends XmppManagerBase {
/// If id is non-null, then it will be the id of the message stanza. /// If id is non-null, then it will be the id of the message stanza.
/// element to this id. If originId is non-null, then it will create an "origin-id" /// element to this id. If originId is non-null, then it will create an "origin-id"
/// child in the message stanza and set its id to originId. /// child in the message stanza and set its id to originId.
void sendMessage(MessageDetails details) { Future<void> sendMessage(MessageDetails details) async {
assert( assert(
implies( implies(
details.quoteBody != null, details.quoteBody != null,
@ -216,7 +224,7 @@ class MessageManager extends XmppManagerBase {
stanza.addChild(makeChatMarkerMarkable()); stanza.addChild(makeChatMarkerMarkable());
} }
if (details.originId != null) { if (details.originId != null) {
stanza.addChild(makeOriginIdElement(details.originId!)); stanza.addChild(StableIdData(details.originId, null).toOriginIdElement());
} }
if (details.sfs != null) { if (details.sfs != null) {
@ -226,17 +234,13 @@ class MessageManager extends XmppManagerBase {
if (source is StatelessFileSharingUrlSource && if (source is StatelessFileSharingUrlSource &&
details.setOOBFallbackBody) { details.setOOBFallbackBody) {
// SFS recommends OOB as a fallback // SFS recommends OOB as a fallback
stanza.addChild(constructOOBNode(OOBData(url: source.url))); stanza.addChild(OOBData(source.url, null).toXML());
} }
} }
if (details.chatState != null) { if (details.chatState != null) {
stanza.addChild( stanza.addChild(
// TODO(Unknown): Move this into xep_0085.dart details.chatState!.toXML(),
XMLNode.xmlns(
tag: chatStateToString(details.chatState!),
xmlns: chatStateXmlns,
),
); );
} }
@ -293,9 +297,9 @@ class MessageManager extends XmppManagerBase {
if (details.lastMessageCorrectionId != null) { if (details.lastMessageCorrectionId != null) {
stanza.addChild( stanza.addChild(
makeLastMessageCorrectionEdit( LastMessageCorrectionData(
details.lastMessageCorrectionId!, details.lastMessageCorrectionId!,
), ).toXML(),
); );
} }
@ -305,7 +309,7 @@ class MessageManager extends XmppManagerBase {
if (details.messageProcessingHints != null) { if (details.messageProcessingHints != null) {
for (final hint in details.messageProcessingHints!) { for (final hint in details.messageProcessingHints!) {
stanza.addChild(hint.toXml()); stanza.addChild(hint.toXML());
} }
} }
@ -321,7 +325,7 @@ class MessageManager extends XmppManagerBase {
); );
} }
getAttributes().sendStanza( await getAttributes().sendStanza(
StanzaDetails( StanzaDetails(
stanza, stanza,
awaitable: false, awaitable: false,

View File

@ -66,7 +66,7 @@ class PresenceManager extends XmppManagerBase {
from: JID.fromString(presence.from!), from: JID.fromString(presence.from!),
), ),
); );
return state.copyWith(done: true); return state..done = true;
} }
default: default:
break; break;
@ -78,7 +78,7 @@ class PresenceManager extends XmppManagerBase {
getAttributes().sendEvent( getAttributes().sendEvent(
PresenceReceivedEvent(JID.fromString(presence.from!), presence), PresenceReceivedEvent(JID.fromString(presence.from!), presence),
); );
return state.copyWith(done: true); return state..done = true;
} }
return state; return state;

View File

@ -145,7 +145,7 @@ class RosterManager extends XmppManagerBase {
logger.warning( logger.warning(
'Roster push invalid! Unexpected from attribute: ${stanza.toXml()}', 'Roster push invalid! Unexpected from attribute: ${stanza.toXml()}',
); );
return state.copyWith(done: true); return state..done = true;
} }
final query = stanza.firstTag('query', xmlns: rosterXmlns)!; final query = stanza.firstTag('query', xmlns: rosterXmlns)!;
@ -154,7 +154,7 @@ class RosterManager extends XmppManagerBase {
if (item == null) { if (item == null) {
logger.warning('Received empty roster push'); logger.warning('Received empty roster push');
return state.copyWith(done: true); return state..done = true;
} }
unawaited( unawaited(
@ -177,7 +177,7 @@ class RosterManager extends XmppManagerBase {
[], [],
); );
return state.copyWith(done: true); return state..done = true;
} }
/// Shared code between requesting rosters without and with roster versioning, if /// Shared code between requesting rosters without and with roster versioning, if

View File

@ -7,9 +7,32 @@ import 'package:moxxmpp/src/stanza.dart';
import 'package:moxxmpp/src/xeps/xep_0446.dart'; import 'package:moxxmpp/src/xeps/xep_0446.dart';
/// NOTE: Specified by https://github.com/PapaTutuWawa/custom-xeps/blob/master/xep-xxxx-file-upload-notifications.md /// NOTE: Specified by https://github.com/PapaTutuWawa/custom-xeps/blob/master/xep-xxxx-file-upload-notifications.md
const fileUploadNotificationXmlns = 'proto:urn:xmpp:fun:0'; const fileUploadNotificationXmlns = 'proto:urn:xmpp:fun:0';
/// Indicates a file upload notification.
class FileUploadNotificationData {
const FileUploadNotificationData(this.metadata);
/// The file metadata indicated in the upload notification.
final FileMetadataData metadata;
}
/// Indicates that a file upload has been cancelled.
class FileUploadNotificationCancellationData {
const FileUploadNotificationCancellationData(this.id);
/// The id of the upload notifiaction that is cancelled.
final String id;
}
/// Indicates that a file upload has been completed.
class FileUploadNotificationReplacementData {
const FileUploadNotificationReplacementData(this.id);
/// The id of the upload notifiaction that is replaced.
final String id;
}
class FileUploadNotificationManager extends XmppManagerBase { class FileUploadNotificationManager extends XmppManagerBase {
FileUploadNotificationManager() : super(fileUploadNotificationManager); FileUploadNotificationManager() : super(fileUploadNotificationManager);
@ -47,10 +70,13 @@ class FileUploadNotificationManager extends XmppManagerBase {
) async { ) async {
final funElement = final funElement =
message.firstTag('file-upload', xmlns: fileUploadNotificationXmlns)!; message.firstTag('file-upload', xmlns: fileUploadNotificationXmlns)!;
return state.copyWith( return state
fun: FileMetadataData.fromXML( ..extensions.set(
FileUploadNotificationData(
FileMetadataData.fromXML(
funElement.firstTag('file', xmlns: fileMetadataXmlns)!, funElement.firstTag('file', xmlns: fileMetadataXmlns)!,
), ),
),
); );
} }
@ -60,8 +86,11 @@ class FileUploadNotificationManager extends XmppManagerBase {
) async { ) async {
final element = final element =
message.firstTag('replaces', xmlns: fileUploadNotificationXmlns)!; message.firstTag('replaces', xmlns: fileUploadNotificationXmlns)!;
return state.copyWith( return state
funReplacement: element.attributes['id']! as String, ..extensions.set(
FileUploadNotificationReplacementData(
element.attributes['id']! as String,
),
); );
} }
@ -71,8 +100,11 @@ class FileUploadNotificationManager extends XmppManagerBase {
) async { ) async {
final element = final element =
message.firstTag('cancels', xmlns: fileUploadNotificationXmlns)!; message.firstTag('cancels', xmlns: fileUploadNotificationXmlns)!;
return state.copyWith( return state
funCancellation: element.attributes['id']! as String, ..extensions.set(
FileUploadNotificationCancellationData(
element.attributes['id']! as String,
),
); );
} }
} }

View File

@ -184,7 +184,7 @@ class DiscoManager extends XmppManagerBase {
], ],
); );
return state.copyWith(done: true); return state..done = true;
} }
await reply( await reply(
@ -195,7 +195,7 @@ class DiscoManager extends XmppManagerBase {
], ],
); );
return state.copyWith(done: true); return state..done = true;
} }
Future<StanzaHandlerData> _onDiscoItemsRequest( Future<StanzaHandlerData> _onDiscoItemsRequest(
@ -223,7 +223,7 @@ class DiscoManager extends XmppManagerBase {
], ],
); );
return state.copyWith(done: true); return state..done = true;
} }
return state; return state;

View File

@ -76,7 +76,7 @@ class VCardManager extends XmppManagerBase {
} }
} }
return state.copyWith(done: true); return state..done = true;
} }
VCardPhoto? _parseVCardPhoto(XMLNode? node) { VCardPhoto? _parseVCardPhoto(XMLNode? node) {

View File

@ -114,7 +114,7 @@ class PubSubManager extends XmppManagerBase {
), ),
); );
return state.copyWith(done: true); return state..done = true;
} }
Future<int> _getNodeItemCount(JID jid, String node) async { Future<int> _getNodeItemCount(JID jid, String node) async {

View File

@ -8,26 +8,24 @@ import 'package:moxxmpp/src/stringxml.dart';
/// A data class representing the jabber:x:oob tag. /// A data class representing the jabber:x:oob tag.
class OOBData { class OOBData {
const OOBData({this.url, this.desc}); const OOBData(this.url, this.desc);
/// The communicated URL of the OOB data
final String? url; final String? url;
/// The description of the url.
final String? desc; final String? desc;
}
XMLNode constructOOBNode(OOBData data) {
final children = List<XMLNode>.empty(growable: true);
if (data.url != null) {
children.add(XMLNode(tag: 'url', text: data.url));
}
if (data.desc != null) {
children.add(XMLNode(tag: 'desc', text: data.desc));
}
XMLNode toXML() {
return XMLNode.xmlns( return XMLNode.xmlns(
tag: 'x', tag: 'x',
xmlns: oobDataXmlns, xmlns: oobDataXmlns,
children: children, children: [
if (url != null) XMLNode(tag: 'url', text: url),
if (desc != null) XMLNode(tag: 'desc', text: desc),
],
); );
}
} }
class OOBManager extends XmppManagerBase { class OOBManager extends XmppManagerBase {
@ -59,10 +57,11 @@ class OOBManager extends XmppManagerBase {
final url = x.firstTag('url'); final url = x.firstTag('url');
final desc = x.firstTag('desc'); final desc = x.firstTag('desc');
return state.copyWith( return state
oob: OOBData( ..extensions.set(
url: url?.innerText(), OOBData(
desc: desc?.innerText(), url?.innerText(),
desc?.innerText(),
), ),
); );
} }

View File

@ -6,39 +6,54 @@ import 'package:moxxmpp/src/namespaces.dart';
import 'package:moxxmpp/src/stanza.dart'; import 'package:moxxmpp/src/stanza.dart';
import 'package:moxxmpp/src/stringxml.dart'; import 'package:moxxmpp/src/stringxml.dart';
enum ChatState { active, composing, paused, inactive, gone } enum ChatState {
active,
composing,
paused,
inactive,
gone;
ChatState chatStateFromString(String raw) { factory ChatState.fromString(String state) {
switch (raw) { switch (state) {
case 'active': case 'active':
{
return ChatState.active; return ChatState.active;
}
case 'composing': case 'composing':
{
return ChatState.composing; return ChatState.composing;
}
case 'paused': case 'paused':
{
return ChatState.paused; return ChatState.paused;
}
case 'inactive': case 'inactive':
{
return ChatState.inactive; return ChatState.inactive;
}
case 'gone': case 'gone':
{
return ChatState.gone; return ChatState.gone;
}
default: default:
{
return ChatState.gone; return ChatState.gone;
} }
} }
@override
String toString() {
switch (this) {
case ChatState.active:
return 'active';
case ChatState.composing:
return 'composing';
case ChatState.paused:
return 'paused';
case ChatState.inactive:
return 'inactive';
case ChatState.gone:
return 'gone';
}
}
XMLNode toXML() {
return XMLNode.xmlns(
tag: toString(),
xmlns: chatStateXmlns,
);
}
} }
String chatStateToString(ChatState state) => state.toString().split('.').last;
class ChatStateManager extends XmppManagerBase { class ChatStateManager extends XmppManagerBase {
ChatStateManager() : super(chatStateManager); ChatStateManager() : super(chatStateManager);
@ -64,61 +79,27 @@ class ChatStateManager extends XmppManagerBase {
StanzaHandlerData state, StanzaHandlerData state,
) async { ) async {
final element = state.stanza.firstTagByXmlns(chatStateXmlns)!; final element = state.stanza.firstTagByXmlns(chatStateXmlns)!;
ChatState? chatState; state.extensions.set(ChatState.fromString(element.tag));
return state;
switch (element.tag) {
case 'active':
{
chatState = ChatState.active;
}
break;
case 'composing':
{
chatState = ChatState.composing;
}
break;
case 'paused':
{
chatState = ChatState.paused;
}
break;
case 'inactive':
{
chatState = ChatState.inactive;
}
break;
case 'gone':
{
chatState = ChatState.gone;
}
break;
default:
{
logger.warning("Received invalid chat state '${element.tag}'");
}
}
return state.copyWith(chatState: chatState);
} }
/// Send a chat state notification to [to]. You can specify the type attribute /// Send a chat state notification to [to]. You can specify the type attribute
/// of the message with [messageType]. /// of the message with [messageType].
void sendChatState( Future<void> sendChatState(
ChatState state, ChatState state,
String to, { String to, {
String messageType = 'chat', String messageType = 'chat',
}) { }) async {
final tagName = state.toString().split('.').last; await getAttributes().sendStanza(
getAttributes().sendStanza(
StanzaDetails( StanzaDetails(
Stanza.message( Stanza.message(
to: to, to: to,
type: messageType, type: messageType,
children: [ children: [
XMLNode.xmlns(tag: tagName, xmlns: chatStateXmlns), XMLNode.xmlns(tag: state.toString(), xmlns: chatStateXmlns),
], ],
), ),
awaitable: false,
), ),
); );
} }

View File

@ -8,6 +8,14 @@ import 'package:moxxmpp/src/namespaces.dart';
import 'package:moxxmpp/src/stanza.dart'; import 'package:moxxmpp/src/stanza.dart';
import 'package:moxxmpp/src/stringxml.dart'; import 'package:moxxmpp/src/stringxml.dart';
class MessageDeliveryReceiptData {
const MessageDeliveryReceiptData(this.receiptRequested);
/// Indicates whether a delivery receipt is requested or not.
final bool receiptRequested;
}
// TODO: Merge those two functions into [MessageDeliveryReceiptData]
XMLNode makeMessageDeliveryRequest() { XMLNode makeMessageDeliveryRequest() {
return XMLNode.xmlns( return XMLNode.xmlns(
tag: 'request', tag: 'request',
@ -56,7 +64,7 @@ class MessageDeliveryReceiptManager extends XmppManagerBase {
Stanza message, Stanza message,
StanzaHandlerData state, StanzaHandlerData state,
) async { ) async {
return state.copyWith(deliveryReceiptRequested: true); return state..extensions.set(const MessageDeliveryReceiptData(true));
} }
Future<StanzaHandlerData> _onDeliveryReceiptReceived( Future<StanzaHandlerData> _onDeliveryReceiptReceived(
@ -64,16 +72,16 @@ class MessageDeliveryReceiptManager extends XmppManagerBase {
StanzaHandlerData state, StanzaHandlerData state,
) async { ) async {
final received = message.firstTag('received', xmlns: deliveryXmlns)!; final received = message.firstTag('received', xmlns: deliveryXmlns)!;
for (final item in message.children) { // for (final item in message.children) {
if (!['origin-id', 'stanza-id', 'delay', 'store', 'received'] // if (!['origin-id', 'stanza-id', 'delay', 'store', 'received']
.contains(item.tag)) { // .contains(item.tag)) {
logger.info( // logger.info(
"Won't handle stanza as delivery receipt because we found an '${item.tag}' element", // "Won't handle stanza as delivery receipt because we found an '${item.tag}' element",
); // );
return state.copyWith(done: true); // return state.copyWith(done: true);
} // }
} // }
getAttributes().sendEvent( getAttributes().sendEvent(
DeliveryReceiptReceivedEvent( DeliveryReceiptReceivedEvent(
@ -81,6 +89,6 @@ class MessageDeliveryReceiptManager extends XmppManagerBase {
id: received.attributes['id']! as String, id: received.attributes['id']! as String,
), ),
); );
return state.copyWith(done: true); return state..done = true;
} }
} }

View File

@ -70,7 +70,7 @@ class BlockingManager extends XmppManagerBase {
), ),
); );
return state.copyWith(done: true); return state..done = true;
} }
Future<StanzaHandlerData> _unblockPush( Future<StanzaHandlerData> _unblockPush(
@ -92,7 +92,7 @@ class BlockingManager extends XmppManagerBase {
); );
} }
return state.copyWith(done: true); return state..done = true;
} }
Future<bool> block(List<String> items) async { Future<bool> block(List<String> items) async {

View File

@ -0,0 +1,6 @@
class StreamManagementData {
const StreamManagementData(this.exclude);
/// Whether the stanza should be exluded from the StreamManagement's resend queue.
final bool exclude;
}

View File

@ -15,6 +15,7 @@ import 'package:moxxmpp/src/xeps/xep_0198/errors.dart';
import 'package:moxxmpp/src/xeps/xep_0198/negotiator.dart'; import 'package:moxxmpp/src/xeps/xep_0198/negotiator.dart';
import 'package:moxxmpp/src/xeps/xep_0198/nonzas.dart'; import 'package:moxxmpp/src/xeps/xep_0198/nonzas.dart';
import 'package:moxxmpp/src/xeps/xep_0198/state.dart'; import 'package:moxxmpp/src/xeps/xep_0198/state.dart';
import 'package:moxxmpp/src/xeps/xep_0198/types.dart';
import 'package:synchronized/synchronized.dart'; import 'package:synchronized/synchronized.dart';
const xmlUintMax = 4294967296; // 2**32 const xmlUintMax = 4294967296; // 2**32
@ -402,7 +403,9 @@ class StreamManagementManager extends XmppManagerBase {
if (isStreamManagementEnabled()) { if (isStreamManagementEnabled()) {
await _incrementC2S(); await _incrementC2S();
if (state.excludeFromStreamManagement) return state; if (state.extensions.get<StreamManagementData>()?.exclude ?? false) {
return state;
}
_unackedStanzas[_state.c2s] = stanza; _unackedStanzas[_state.c2s] = stanza;
await _sendAckRequest(); await _sendAckRequest();

View File

@ -1,4 +1,5 @@
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:moxxmpp/src/jid.dart';
import 'package:moxxmpp/src/managers/base.dart'; import 'package:moxxmpp/src/managers/base.dart';
import 'package:moxxmpp/src/managers/data.dart'; import 'package:moxxmpp/src/managers/data.dart';
import 'package:moxxmpp/src/managers/handlers.dart'; import 'package:moxxmpp/src/managers/handlers.dart';
@ -7,10 +8,14 @@ import 'package:moxxmpp/src/namespaces.dart';
import 'package:moxxmpp/src/stanza.dart'; import 'package:moxxmpp/src/stanza.dart';
@immutable @immutable
class DelayedDelivery { class DelayedDeliveryData {
const DelayedDelivery(this.from, this.timestamp); const DelayedDeliveryData(this.from, this.timestamp);
/// The timestamp the message was originally sent.
final DateTime timestamp; final DateTime timestamp;
final String from;
/// The JID that originally sent the message.
final JID from;
} }
class DelayedDeliveryManager extends XmppManagerBase { class DelayedDeliveryManager extends XmppManagerBase {
@ -23,6 +28,8 @@ class DelayedDeliveryManager extends XmppManagerBase {
List<StanzaHandler> getIncomingStanzaHandlers() => [ List<StanzaHandler> getIncomingStanzaHandlers() => [
StanzaHandler( StanzaHandler(
stanzaTag: 'message', stanzaTag: 'message',
tagName: 'delay',
tagXmlns: delayedDeliveryXmlns,
callback: _onIncomingMessage, callback: _onIncomingMessage,
priority: 200, priority: 200,
), ),
@ -32,12 +39,12 @@ class DelayedDeliveryManager extends XmppManagerBase {
Stanza stanza, Stanza stanza,
StanzaHandlerData state, StanzaHandlerData state,
) async { ) async {
final delay = stanza.firstTag('delay', xmlns: delayedDeliveryXmlns); final delay = stanza.firstTag('delay', xmlns: delayedDeliveryXmlns)!;
if (delay == null) return state;
return state.copyWith( return state
delayedDelivery: DelayedDelivery( ..extensions.set(
delay.attributes['from']! as String, DelayedDeliveryData(
JID.fromString(delay.attributes['from']! as String),
DateTime.parse(delay.attributes['stamp']! as String), DateTime.parse(delay.attributes['stamp']! as String),
), ),
); );

View File

@ -14,6 +14,13 @@ import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
import 'package:moxxmpp/src/xeps/xep_0297.dart'; import 'package:moxxmpp/src/xeps/xep_0297.dart';
import 'package:moxxmpp/src/xeps/xep_0386.dart'; import 'package:moxxmpp/src/xeps/xep_0386.dart';
class CarbonsData {
const CarbonsData(this.isCarbon);
/// Indicates whether this message is a carbon.
final bool isCarbon;
}
/// This manager class implements support for XEP-0280. /// This manager class implements support for XEP-0280.
class CarbonsManager extends XmppManagerBase { class CarbonsManager extends XmppManagerBase {
CarbonsManager() : super(carbonsManager); CarbonsManager() : super(carbonsManager);
@ -77,15 +84,14 @@ class CarbonsManager extends XmppManagerBase {
) async { ) async {
final from = JID.fromString(message.attributes['from']! as String); final from = JID.fromString(message.attributes['from']! as String);
final received = message.firstTag('received', xmlns: carbonsXmlns)!; final received = message.firstTag('received', xmlns: carbonsXmlns)!;
if (!isCarbonValid(from)) return state.copyWith(done: true); if (!isCarbonValid(from)) return state..done = true;
final forwarded = received.firstTag('forwarded', xmlns: forwardedXmlns)!; final forwarded = received.firstTag('forwarded', xmlns: forwardedXmlns)!;
final carbon = unpackForwarded(forwarded); final carbon = unpackForwarded(forwarded);
return state.copyWith( return state
isCarbon: true, ..extensions.set(const CarbonsData(true))
stanza: carbon, ..stanza = carbon;
);
} }
Future<StanzaHandlerData> _onMessageSent( Future<StanzaHandlerData> _onMessageSent(
@ -94,15 +100,14 @@ class CarbonsManager extends XmppManagerBase {
) async { ) async {
final from = JID.fromString(message.attributes['from']! as String); final from = JID.fromString(message.attributes['from']! as String);
final sent = message.firstTag('sent', xmlns: carbonsXmlns)!; final sent = message.firstTag('sent', xmlns: carbonsXmlns)!;
if (!isCarbonValid(from)) return state.copyWith(done: true); if (!isCarbonValid(from)) return state..done = true;
final forwarded = sent.firstTag('forwarded', xmlns: forwardedXmlns)!; final forwarded = sent.firstTag('forwarded', xmlns: forwardedXmlns)!;
final carbon = unpackForwarded(forwarded); final carbon = unpackForwarded(forwarded);
return state.copyWith( return state
isCarbon: true, ..extensions.set(const CarbonsData(true))
stanza: carbon, ..stanza = carbon;
);
} }
/// Send a request to the server, asking it to enable Message Carbons. /// Send a request to the server, asking it to enable Message Carbons.

View File

@ -6,14 +6,21 @@ import 'package:moxxmpp/src/namespaces.dart';
import 'package:moxxmpp/src/stanza.dart'; import 'package:moxxmpp/src/stanza.dart';
import 'package:moxxmpp/src/stringxml.dart'; import 'package:moxxmpp/src/stringxml.dart';
XMLNode makeLastMessageCorrectionEdit(String id) { class LastMessageCorrectionData {
const LastMessageCorrectionData(this.id);
/// The id the LMC applies to.
final String id;
XMLNode toXML() {
return XMLNode.xmlns( return XMLNode.xmlns(
tag: 'replace', tag: 'replace',
xmlns: lmcXmlns, xmlns: lmcXmlns,
attributes: <String, String>{ attributes: {
'id': id, 'id': id,
}, },
); );
}
} }
class LastMessageCorrectionManager extends XmppManagerBase { class LastMessageCorrectionManager extends XmppManagerBase {
@ -42,8 +49,9 @@ class LastMessageCorrectionManager extends XmppManagerBase {
StanzaHandlerData state, StanzaHandlerData state,
) async { ) async {
final edit = stanza.firstTag('replace', xmlns: lmcXmlns)!; final edit = stanza.firstTag('replace', xmlns: lmcXmlns)!;
return state.copyWith( return state
lastMessageCorrectionSid: edit.attributes['id']! as String, ..extensions.set(
LastMessageCorrectionData(edit.attributes['id']! as String),
); );
} }
} }

View File

@ -8,6 +8,13 @@ import 'package:moxxmpp/src/namespaces.dart';
import 'package:moxxmpp/src/stanza.dart'; import 'package:moxxmpp/src/stanza.dart';
import 'package:moxxmpp/src/stringxml.dart'; import 'package:moxxmpp/src/stringxml.dart';
class ChatMarkerData {
const ChatMarkerData(this.isMarkable);
/// Indicates whether the message can be replied to with a chat marker.
final bool isMarkable;
}
XMLNode makeChatMarkerMarkable() { XMLNode makeChatMarkerMarkable() {
return XMLNode.xmlns( return XMLNode.xmlns(
tag: 'markable', tag: 'markable',
@ -54,7 +61,9 @@ class ChatMarkerManager extends XmppManagerBase {
final marker = message.firstTagByXmlns(chatMarkersXmlns)!; final marker = message.firstTagByXmlns(chatMarkersXmlns)!;
// Handle the <markable /> explicitly // Handle the <markable /> explicitly
if (marker.tag == 'markable') return state.copyWith(isMarkable: true); if (marker.tag == 'markable') {
return state..extensions.set(const ChatMarkerData(true));
}
if (!['received', 'displayed', 'acknowledged'].contains(marker.tag)) { if (!['received', 'displayed', 'acknowledged'].contains(marker.tag)) {
logger.warning("Unknown message marker '${marker.tag}' found."); logger.warning("Unknown message marker '${marker.tag}' found.");
@ -68,6 +77,6 @@ class ChatMarkerManager extends XmppManagerBase {
); );
} }
return state.copyWith(done: true); return state..done = true;
} }
} }

View File

@ -5,11 +5,10 @@ enum MessageProcessingHint {
noPermanentStore, noPermanentStore,
noStore, noStore,
noCopies, noCopies,
store, store;
}
MessageProcessingHint messageProcessingHintFromXml(XMLNode element) { factory MessageProcessingHint.fromName(String name) {
switch (element.tag) { switch (name) {
case 'no-permanent-store': case 'no-permanent-store':
return MessageProcessingHint.noPermanentStore; return MessageProcessingHint.noPermanentStore;
case 'no-store': case 'no-store':
@ -20,12 +19,11 @@ MessageProcessingHint messageProcessingHintFromXml(XMLNode element) {
return MessageProcessingHint.store; return MessageProcessingHint.store;
} }
assert(false, 'Invalid Message Processing Hint: ${element.tag}'); assert(false, 'Invalid Message Processing Hint: $name');
return MessageProcessingHint.noStore; return MessageProcessingHint.noStore;
} }
extension XmlExtension on MessageProcessingHint { XMLNode toXML() {
XMLNode toXml() {
String tag; String tag;
switch (this) { switch (this) {
case MessageProcessingHint.noPermanentStore: case MessageProcessingHint.noPermanentStore:

View File

@ -7,6 +7,28 @@ import 'package:moxxmpp/src/namespaces.dart';
import 'package:moxxmpp/src/stanza.dart'; import 'package:moxxmpp/src/stanza.dart';
import 'package:moxxmpp/src/stringxml.dart'; import 'package:moxxmpp/src/stringxml.dart';
class StableIdData {
const StableIdData(this.originId, this.stanzaIds);
/// <origin-id />
final String? originId;
/// Stanza ids
final List<StanzaId>? stanzaIds;
XMLNode toOriginIdElement() {
assert(
originId != null,
'Can only build the XML element if originId != null',
);
return XMLNode.xmlns(
tag: 'origin-id',
xmlns: stableIdXmlns,
attributes: {'id': originId!},
);
}
}
/// Representation of a <stanza-id /> element. /// Representation of a <stanza-id /> element.
class StanzaId { class StanzaId {
const StanzaId( const StanzaId(
@ -32,14 +54,6 @@ class StanzaId {
} }
} }
XMLNode makeOriginIdElement(String id) {
return XMLNode.xmlns(
tag: 'origin-id',
xmlns: stableIdXmlns,
attributes: {'id': id},
);
}
class StableIdManager extends XmppManagerBase { class StableIdManager extends XmppManagerBase {
StableIdManager() : super(stableIdManager); StableIdManager() : super(stableIdManager);
@ -86,9 +100,12 @@ class StableIdManager extends XmppManagerBase {
.toList(); .toList();
} }
return state.copyWith( return state
originId: originId, ..extensions.set(
stanzaIds: stanzaIds, StableIdData(
originId,
stanzaIds,
),
); );
} }
} }

View File

@ -13,30 +13,10 @@ enum ExplicitEncryptionType {
omemo, omemo,
omemo1, omemo1,
omemo2, omemo2,
unknown, unknown;
}
String _explicitEncryptionTypeToString(ExplicitEncryptionType type) { factory ExplicitEncryptionType.fromNamespace(String namespace) {
switch (type) { switch (namespace) {
case ExplicitEncryptionType.otr:
return emeOtr;
case ExplicitEncryptionType.legacyOpenPGP:
return emeLegacyOpenPGP;
case ExplicitEncryptionType.openPGP:
return emeOpenPGP;
case ExplicitEncryptionType.omemo:
return emeOmemo;
case ExplicitEncryptionType.omemo1:
return emeOmemo1;
case ExplicitEncryptionType.omemo2:
return emeOmemo2;
case ExplicitEncryptionType.unknown:
return '';
}
}
ExplicitEncryptionType _explicitEncryptionTypeFromString(String str) {
switch (str) {
case emeOtr: case emeOtr:
return ExplicitEncryptionType.otr; return ExplicitEncryptionType.otr;
case emeLegacyOpenPGP: case emeLegacyOpenPGP:
@ -52,18 +32,38 @@ ExplicitEncryptionType _explicitEncryptionTypeFromString(String str) {
default: default:
return ExplicitEncryptionType.unknown; return ExplicitEncryptionType.unknown;
} }
} }
/// Create an <encryption /> element with [type] indicating which type of encryption was String toNamespace() {
/// used. switch (this) {
XMLNode buildEmeElement(ExplicitEncryptionType type) { case ExplicitEncryptionType.otr:
return emeOtr;
case ExplicitEncryptionType.legacyOpenPGP:
return emeLegacyOpenPGP;
case ExplicitEncryptionType.openPGP:
return emeOpenPGP;
case ExplicitEncryptionType.omemo:
return emeOmemo;
case ExplicitEncryptionType.omemo1:
return emeOmemo1;
case ExplicitEncryptionType.omemo2:
return emeOmemo2;
case ExplicitEncryptionType.unknown:
return '';
}
}
/// Create an <encryption /> element with an xmlns indicating what type of encryption was
/// used.
XMLNode toXML() {
return XMLNode.xmlns( return XMLNode.xmlns(
tag: 'encryption', tag: 'encryption',
xmlns: emeXmlns, xmlns: emeXmlns,
attributes: <String, String>{ attributes: <String, String>{
'namespace': _explicitEncryptionTypeToString(type), 'namespace': toNamespace(),
}, },
); );
}
} }
class EmeManager extends XmppManagerBase { class EmeManager extends XmppManagerBase {
@ -91,8 +91,9 @@ class EmeManager extends XmppManagerBase {
) async { ) async {
final encryption = message.firstTag('encryption', xmlns: emeXmlns)!; final encryption = message.firstTag('encryption', xmlns: emeXmlns)!;
return state.copyWith( return state
encryptionType: _explicitEncryptionTypeFromString( ..extensions.set(
ExplicitEncryptionType.fromNamespace(
encryption.attributes['namespace']! as String, encryption.attributes['namespace']! as String,
), ),
); );

View File

@ -1,6 +1,21 @@
import 'package:omemo_dart/omemo_dart.dart';
/// A simple wrapper class for defining elements that should not be encrypted. /// A simple wrapper class for defining elements that should not be encrypted.
class DoNotEncrypt { class DoNotEncrypt {
const DoNotEncrypt(this.tag, this.xmlns); const DoNotEncrypt(this.tag, this.xmlns);
/// The tag of the element.
final String tag; final String tag;
/// The xmlns attribute of the element.
final String xmlns; final String xmlns;
} }
/// An encryption error caused by OMEMO.
class OmemoEncryptionError {
const OmemoEncryptionError(this.jids, this.devices);
/// See omemo_dart's EncryptionResult for info on these fields.
final Map<String, OmemoException> jids;
final Map<RatchetMapKey, OmemoException> devices;
}

View File

@ -16,6 +16,7 @@ import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart'; import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
import 'package:moxxmpp/src/xeps/xep_0060/errors.dart'; import 'package:moxxmpp/src/xeps/xep_0060/errors.dart';
import 'package:moxxmpp/src/xeps/xep_0060/xep_0060.dart'; import 'package:moxxmpp/src/xeps/xep_0060/xep_0060.dart';
import 'package:moxxmpp/src/xeps/xep_0203.dart';
import 'package:moxxmpp/src/xeps/xep_0280.dart'; import 'package:moxxmpp/src/xeps/xep_0280.dart';
import 'package:moxxmpp/src/xeps/xep_0334.dart'; import 'package:moxxmpp/src/xeps/xep_0334.dart';
import 'package:moxxmpp/src/xeps/xep_0380.dart'; import 'package:moxxmpp/src/xeps/xep_0380.dart';
@ -276,7 +277,7 @@ abstract class BaseOmemoManager extends XmppManagerBase {
// Add a storage hint in case this is a message // Add a storage hint in case this is a message
// Taken from the example at // Taken from the example at
// https://xmpp.org/extensions/xep-0384.html#message-structure-description. // https://xmpp.org/extensions/xep-0384.html#message-structure-description.
MessageProcessingHint.store.toXml(), MessageProcessingHint.store.toXML(),
], ],
), ),
awaitable: false, awaitable: false,
@ -363,18 +364,17 @@ abstract class BaseOmemoManager extends XmppManagerBase {
logger.finest('Encryption done'); logger.finest('Encryption done');
if (!result.isSuccess(2)) { if (!result.isSuccess(2)) {
final other = Map<String, dynamic>.from(state.other); return state
other['encryption_error_jids'] = result.jidEncryptionErrors; ..cancel = true
other['encryption_error_devices'] = result.deviceEncryptionErrors;
return state.copyWith(
other: other,
// If we have no device list for toJid, then the contact most likely does not // If we have no device list for toJid, then the contact most likely does not
// support OMEMO:2 // support OMEMO:2
cancelReason: result.jidEncryptionErrors[toJid.toString()] ..cancelReason = result.jidEncryptionErrors[toJid.toString()]
is NoKeyMaterialAvailableException is NoKeyMaterialAvailableException
? OmemoNotSupportedForContactException() ? OmemoNotSupportedForContactException()
: UnknownOmemoError(), : UnknownOmemoError()
cancel: true, ..encryptionError = OmemoEncryptionError(
result.jidEncryptionErrors,
result.deviceEncryptionErrors,
); );
} }
@ -389,19 +389,16 @@ abstract class BaseOmemoManager extends XmppManagerBase {
if (stanza.tag == 'message') { if (stanza.tag == 'message') {
children children
// Add EME data // Add EME data
..add(buildEmeElement(ExplicitEncryptionType.omemo2)) ..add(ExplicitEncryptionType.omemo2.toXML())
// Add a storage hint in case this is a message // Add a storage hint in case this is a message
// Taken from the example at // Taken from the example at
// https://xmpp.org/extensions/xep-0384.html#message-structure-description. // https://xmpp.org/extensions/xep-0384.html#message-structure-description.
..add(MessageProcessingHint.store.toXml()); ..add(MessageProcessingHint.store.toXML());
} }
return state.copyWith( return state
stanza: state.stanza.copyWith( ..stanza = state.stanza.copyWith(children: children)
children: children, ..encrypted = true;
),
encrypted: true,
);
} }
/// This function is called whenever a message is to be encrypted. If it returns true, /// This function is called whenever a message is to be encrypted. If it returns true,
@ -444,17 +441,19 @@ abstract class BaseOmemoManager extends XmppManagerBase {
OmemoIncomingStanza( OmemoIncomingStanza(
fromJid.toString(), fromJid.toString(),
sid, sid,
state.delayedDelivery?.timestamp.millisecondsSinceEpoch ?? state.extensions
.get<DelayedDeliveryData>()
?.timestamp
.millisecondsSinceEpoch ??
DateTime.now().millisecondsSinceEpoch, DateTime.now().millisecondsSinceEpoch,
keys, keys,
payloadElement?.innerText(), payloadElement?.innerText(),
), ),
); );
final other = Map<String, dynamic>.from(state.other);
var children = stanza.children; var children = stanza.children;
if (result.error != null) { if (result.error != null) {
other['encryption_error'] = result.error; state.encryptionError = result.error;
} else { } else {
children = stanza.children children = stanza.children
.where( .where(
@ -471,11 +470,9 @@ abstract class BaseOmemoManager extends XmppManagerBase {
envelope = XMLNode.fromString(result.payload!); envelope = XMLNode.fromString(result.payload!);
} on XmlParserException catch (_) { } on XmlParserException catch (_) {
logger.warning('Failed to parse envelope payload: ${result.payload!}'); logger.warning('Failed to parse envelope payload: ${result.payload!}');
other['encryption_error'] = InvalidEnvelopePayloadException(); return state
return state.copyWith( ..encrypted = true
encrypted: true, ..encryptionError = InvalidEnvelopePayloadException();
other: other,
);
} }
final envelopeChildren = envelope.firstTag('content')?.children; final envelopeChildren = envelope.firstTag('content')?.children;
@ -489,13 +486,13 @@ abstract class BaseOmemoManager extends XmppManagerBase {
} }
if (!checkAffixElements(envelope, stanza.from!, ourJid)) { if (!checkAffixElements(envelope, stanza.from!, ourJid)) {
other['encryption_error'] = InvalidAffixElementsException(); state.encryptionError = InvalidAffixElementsException();
} }
} }
return state.copyWith( return state
encrypted: true, ..encrypted = true
stanza: Stanza( ..stanza = Stanza(
to: stanza.to, to: stanza.to,
from: stanza.from, from: stanza.from,
id: stanza.id, id: stanza.id,
@ -503,8 +500,6 @@ abstract class BaseOmemoManager extends XmppManagerBase {
children: children, children: children,
tag: stanza.tag, tag: stanza.tag,
attributes: Map<String, String>.from(stanza.attributes), attributes: Map<String, String>.from(stanza.attributes),
),
other: other,
); );
} }

View File

@ -98,7 +98,7 @@ class SIMSManager extends XmppManagerBase {
final references = message.findTags('reference', xmlns: referenceXmlns); final references = message.findTags('reference', xmlns: referenceXmlns);
for (final ref in references) { for (final ref in references) {
final sims = ref.firstTag('media-sharing', xmlns: simsXmlns); final sims = ref.firstTag('media-sharing', xmlns: simsXmlns);
if (sims != null) return state.copyWith(sims: parseSIMSElement(sims)); if (sims != null) return state..extensions.set(parseSIMSElement(sims));
} }
return state; return state;

View File

@ -47,8 +47,9 @@ class MessageRetractionManager extends XmppManagerBase {
final isFallbackBody = final isFallbackBody =
message.firstTag('fallback', xmlns: fallbackIndicationXmlns) != null; message.firstTag('fallback', xmlns: fallbackIndicationXmlns) != null;
return state.copyWith( return state
messageRetraction: MessageRetractionData( ..extensions.set(
MessageRetractionData(
applyTo.attributes['id']! as String, applyTo.attributes['id']! as String,
isFallbackBody ? message.firstTag('body')?.innerText() : null, isFallbackBody ? message.firstTag('body')?.innerText() : null,
), ),

View File

@ -55,8 +55,9 @@ class MessageReactionsManager extends XmppManagerBase {
) async { ) async {
final reactionsElement = final reactionsElement =
message.firstTag('reactions', xmlns: messageReactionsXmlns)!; message.firstTag('reactions', xmlns: messageReactionsXmlns)!;
return state.copyWith( return state
messageReactions: MessageReactions( ..extensions.set(
MessageReactions(
reactionsElement.attributes['id']! as String, reactionsElement.attributes['id']! as String,
reactionsElement.children reactionsElement.children
.where((c) => c.tag == 'reaction') .where((c) => c.tag == 'reaction')

View File

@ -135,10 +135,9 @@ class SFSManager extends XmppManagerBase {
) async { ) async {
final sfs = message.firstTag('file-sharing', xmlns: sfsXmlns)!; final sfs = message.firstTag('file-sharing', xmlns: sfsXmlns)!;
return state.copyWith( return state
sfs: StatelessFileSharingData.fromXML( ..extensions.set(
sfs, StatelessFileSharingData.fromXML(sfs),
),
); );
} }
} }

View File

@ -227,6 +227,13 @@ class StickerPack {
} }
} }
class StickersData {
const StickersData(this.stickerPackId);
/// The id of the sticker pack the referenced sticker is from.
final String stickerPackId;
}
class StickersManager extends XmppManagerBase { class StickersManager extends XmppManagerBase {
StickersManager() : super(stickersManager); StickersManager() : super(stickersManager);
@ -249,8 +256,9 @@ class StickersManager extends XmppManagerBase {
StanzaHandlerData state, StanzaHandlerData state,
) async { ) async {
final sticker = stanza.firstTag('sticker', xmlns: stickersXmlns)!; final sticker = stanza.firstTag('sticker', xmlns: stickersXmlns)!;
return state.copyWith( return state
stickerPackId: sticker.attributes['pack']! as String, ..extensions.set(
StickersData(sticker.attributes['pack']! as String),
); );
} }

View File

@ -103,8 +103,9 @@ class MessageRepliesManager extends XmppManagerBase {
end = int.parse(body.attributes['end']! as String); end = int.parse(body.attributes['end']! as String);
} }
return state.copyWith( return state
reply: ReplyData( ..extensions.set(
ReplyData(
id: id, id: id,
to: to, to: to,
start: start, start: start,

View File

@ -1,4 +1,5 @@
import 'package:moxxmpp/moxxmpp.dart'; import 'package:moxxmpp/moxxmpp.dart';
import 'package:moxxmpp/src/util/typed_map.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
final stanza1 = Stanza.iq( final stanza1 = Stanza.iq(
@ -16,8 +17,8 @@ void main() {
callback: (stanza, _) async => StanzaHandlerData( callback: (stanza, _) async => StanzaHandlerData(
true, true,
false, false,
null,
stanza, stanza,
TypedMap(),
), ),
); );
@ -38,8 +39,8 @@ void main() {
callback: (stanza, _) async => StanzaHandlerData( callback: (stanza, _) async => StanzaHandlerData(
true, true,
false, false,
null,
stanza, stanza,
TypedMap(),
), ),
tagXmlns: 'owo', tagXmlns: 'owo',
); );
@ -59,8 +60,8 @@ void main() {
return StanzaHandlerData( return StanzaHandlerData(
true, true,
false, false,
null,
stanza, stanza,
TypedMap(),
); );
}, },
stanzaTag: 'iq', stanzaTag: 'iq',
@ -77,8 +78,8 @@ void main() {
StanzaHandlerData( StanzaHandlerData(
false, false,
false, false,
null,
stanza2, stanza2,
TypedMap(),
), ),
); );
expect(run, true); expect(run, true);
@ -89,8 +90,8 @@ void main() {
callback: (stanza, _) async => StanzaHandlerData( callback: (stanza, _) async => StanzaHandlerData(
true, true,
false, false,
null,
stanza, stanza,
TypedMap(),
), ),
tagName: 'tag', tagName: 'tag',
); );
@ -107,8 +108,8 @@ void main() {
callback: (stanza, _) async => StanzaHandlerData( callback: (stanza, _) async => StanzaHandlerData(
true, true,
false, false,
null,
stanza, stanza,
TypedMap(),
), ),
tagName: 'tag', tagName: 'tag',
stanzaTag: 'iq', stanzaTag: 'iq',
@ -127,8 +128,8 @@ void main() {
callback: (stanza, _) async => StanzaHandlerData( callback: (stanza, _) async => StanzaHandlerData(
true, true,
false, false,
null,
stanza, stanza,
TypedMap(),
), ),
xmlns: componentAcceptXmlns, xmlns: componentAcceptXmlns,
); );
@ -147,8 +148,8 @@ void main() {
callback: (stanza, _) async => StanzaHandlerData( callback: (stanza, _) async => StanzaHandlerData(
true, true,
false, false,
null,
stanza, stanza,
TypedMap(),
), ),
tagName: '1', tagName: '1',
priority: 100, priority: 100,
@ -157,8 +158,8 @@ void main() {
callback: (stanza, _) async => StanzaHandlerData( callback: (stanza, _) async => StanzaHandlerData(
true, true,
false, false,
null,
stanza, stanza,
TypedMap(),
), ),
tagName: '2', tagName: '2',
), ),
@ -166,8 +167,8 @@ void main() {
callback: (stanza, _) async => StanzaHandlerData( callback: (stanza, _) async => StanzaHandlerData(
true, true,
false, false,
null,
stanza, stanza,
TypedMap(),
), ),
tagName: '3', tagName: '3',
priority: 50, priority: 50,

View File

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:moxxmpp/moxxmpp.dart'; import 'package:moxxmpp/moxxmpp.dart';
import 'package:moxxmpp/src/util/typed_map.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import '../helpers/logging.dart'; import '../helpers/logging.dart';
import '../helpers/xmpp.dart'; import '../helpers/xmpp.dart';
@ -15,8 +16,8 @@ Future<void> runIncomingStanzaHandlers(
StanzaHandlerData( StanzaHandlerData(
false, false,
false, false,
null,
stanza, stanza,
TypedMap(),
), ),
); );
} }
@ -34,8 +35,8 @@ Future<void> runOutgoingStanzaHandlers(
StanzaHandlerData( StanzaHandlerData(
false, false,
false, false,
null,
stanza, stanza,
TypedMap(),
), ),
); );
} }

View File

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:moxxmpp/moxxmpp.dart'; import 'package:moxxmpp/moxxmpp.dart';
import 'package:moxxmpp/src/util/typed_map.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'helpers/logging.dart'; import 'helpers/logging.dart';
import 'helpers/xmpp.dart'; import 'helpers/xmpp.dart';
@ -78,8 +79,8 @@ Future<bool> testRosterManager(
StanzaHandlerData( StanzaHandlerData(
false, false,
false, false,
null,
stanza, stanza,
TypedMap(),
), ),
); );
} }
@ -335,8 +336,8 @@ void main() {
StanzaHandlerData( StanzaHandlerData(
false, false,
false, false,
null,
maliciousStanza, maliciousStanza,
TypedMap(),
), ),
); );
} }