From 6f5de9c4dca3b4a48cf06d77557f0bd2f280720f Mon Sep 17 00:00:00 2001
From: "Alexander \"PapaTutuWawa"
Date: Tue, 6 Jun 2023 14:12:49 +0200
Subject: [PATCH] feat(xep): Implement the message sending callbacks
---
packages/moxxmpp/lib/src/message.dart | 65 ++++-
packages/moxxmpp/lib/src/xeps/xep_0085.dart | 6 +
packages/moxxmpp/lib/src/xeps/xep_0184.dart | 51 +++-
packages/moxxmpp/lib/src/xeps/xep_0308.dart | 10 +
packages/moxxmpp/lib/src/xeps/xep_0334.dart | 10 +
packages/moxxmpp/lib/src/xeps/xep_0359.dart | 62 +++--
packages/moxxmpp/lib/src/xeps/xep_0424.dart | 37 +++
packages/moxxmpp/lib/src/xeps/xep_0444.dart | 12 +-
packages/moxxmpp/lib/src/xeps/xep_0447.dart | 34 ++-
packages/moxxmpp/lib/src/xeps/xep_0449.dart | 27 +-
packages/moxxmpp/lib/src/xeps/xep_0461.dart | 82 +++++-
packages/moxxmpp/test/helpers/manager.dart | 31 +--
packages/moxxmpp/test/xeps/xep_0030_test.dart | 2 +-
packages/moxxmpp/test/xeps/xep_0060_test.dart | 1 -
packages/moxxmpp/test/xeps/xep_0449_test.dart | 186 +++++++++++++
packages/moxxmpp/test/xeps/xep_0461_test.dart | 245 +++++++++++++++++-
16 files changed, 772 insertions(+), 89 deletions(-)
diff --git a/packages/moxxmpp/lib/src/message.dart b/packages/moxxmpp/lib/src/message.dart
index 92d0eed..1995cb5 100644
--- a/packages/moxxmpp/lib/src/message.dart
+++ b/packages/moxxmpp/lib/src/message.dart
@@ -1,3 +1,4 @@
+import 'package:collection/collection.dart';
import 'package:moxlib/moxlib.dart';
import 'package:moxxmpp/src/events.dart';
import 'package:moxxmpp/src/jid.dart';
@@ -8,6 +9,7 @@ import 'package:moxxmpp/src/managers/namespaces.dart';
import 'package:moxxmpp/src/namespaces.dart';
import 'package:moxxmpp/src/stanza.dart';
import 'package:moxxmpp/src/stringxml.dart';
+import 'package:moxxmpp/src/util/typed_map.dart';
import 'package:moxxmpp/src/xeps/staging/file_upload_notification.dart';
import 'package:moxxmpp/src/xeps/xep_0066.dart';
import 'package:moxxmpp/src/xeps/xep_0085.dart';
@@ -26,6 +28,38 @@ import 'package:moxxmpp/src/xeps/xep_0448.dart';
import 'package:moxxmpp/src/xeps/xep_0449.dart';
import 'package:moxxmpp/src/xeps/xep_0461.dart';
+class MessageBodyData {
+ const MessageBodyData(this.body);
+
+ /// The content of the element.
+ final String? body;
+
+ XMLNode toXML() {
+ return XMLNode(
+ tag: 'body',
+ text: body,
+ );
+ }
+
+ static List messageSendingCallback(TypedMap extensions) {
+ if (extensions.get() != null) {
+ return [];
+ }
+
+ final data = extensions.get();
+ return data != null ? [data.toXML()] : [];
+ }
+}
+
+class MessageIdData {
+ const MessageIdData(this.id);
+
+ /// The id attribute of the stanza.
+ final String id;
+}
+
+typedef MessageSendingCallback = List Function(TypedMap);
+
/// Data used to build a message stanza.
///
/// [setOOBFallbackBody] indicates, when using SFS, whether a OOB fallback should be
@@ -79,7 +113,11 @@ class MessageDetails {
}
class MessageManager extends XmppManagerBase {
- MessageManager() : super(messageManager);
+ MessageManager(this.messageSendingCallbacks) : super(messageManager);
+
+ /// A list of callbacks that are called when a message is sent in order to add
+ /// appropriate child elements.
+ final List messageSendingCallbacks;
@override
List getIncomingStanzaHandlers() => [
@@ -145,6 +183,23 @@ class MessageManager extends XmppManagerBase {
return state..done = true;
}
+ Future sendMessage2(JID to, TypedMap extensions) async {
+ await getAttributes().sendStanza(
+ StanzaDetails(
+ Stanza.message(
+ to: to.toString(),
+ id: extensions.get()?.id,
+ type: 'chat',
+ children: messageSendingCallbacks
+ .map((c) => c(extensions))
+ .flattened
+ .toList(),
+ ),
+ awaitable: false,
+ ),
+ );
+ }
+
/// Send a message to to with the content body. If deliveryRequest is true, then
/// the message will also request a delivery receipt from the receiver.
/// If id is non-null, then it will be the id of the message stanza.
@@ -217,9 +272,9 @@ class MessageManager extends XmppManagerBase {
}
}
- if (details.requestDeliveryReceipt) {
- stanza.addChild(makeMessageDeliveryRequest());
- }
+ // if (details.requestDeliveryReceipt) {
+ // stanza.addChild(makeMessageDeliveryRequest());
+ // }
if (details.requestChatMarkers) {
stanza.addChild(makeChatMarkerMarkable());
}
@@ -304,7 +359,7 @@ class MessageManager extends XmppManagerBase {
}
if (details.messageReactions != null) {
- stanza.addChild(details.messageReactions!.toXml());
+ stanza.addChild(details.messageReactions!.toXML());
}
if (details.messageProcessingHints != null) {
diff --git a/packages/moxxmpp/lib/src/xeps/xep_0085.dart b/packages/moxxmpp/lib/src/xeps/xep_0085.dart
index f0d25f7..62c788e 100644
--- a/packages/moxxmpp/lib/src/xeps/xep_0085.dart
+++ b/packages/moxxmpp/lib/src/xeps/xep_0085.dart
@@ -5,6 +5,7 @@ import 'package:moxxmpp/src/managers/namespaces.dart';
import 'package:moxxmpp/src/namespaces.dart';
import 'package:moxxmpp/src/stanza.dart';
import 'package:moxxmpp/src/stringxml.dart';
+import 'package:moxxmpp/src/util/typed_map.dart';
enum ChatState {
active,
@@ -52,6 +53,11 @@ enum ChatState {
xmlns: chatStateXmlns,
);
}
+
+ static List messageSendingCallback(TypedMap extensions) {
+ final data = extensions.get();
+ return data != null ? [data.toXML()] : [];
+ }
}
class ChatStateManager extends XmppManagerBase {
diff --git a/packages/moxxmpp/lib/src/xeps/xep_0184.dart b/packages/moxxmpp/lib/src/xeps/xep_0184.dart
index c0d5e46..aa8e5a5 100644
--- a/packages/moxxmpp/lib/src/xeps/xep_0184.dart
+++ b/packages/moxxmpp/lib/src/xeps/xep_0184.dart
@@ -7,28 +7,53 @@ import 'package:moxxmpp/src/managers/namespaces.dart';
import 'package:moxxmpp/src/namespaces.dart';
import 'package:moxxmpp/src/stanza.dart';
import 'package:moxxmpp/src/stringxml.dart';
+import 'package:moxxmpp/src/util/typed_map.dart';
class MessageDeliveryReceiptData {
const MessageDeliveryReceiptData(this.receiptRequested);
/// Indicates whether a delivery receipt is requested or not.
final bool receiptRequested;
+
+ XMLNode toXML() {
+ assert(
+ receiptRequested,
+ 'This method makes little sense with receiptRequested == false',
+ );
+ return XMLNode.xmlns(
+ tag: 'request',
+ xmlns: deliveryXmlns,
+ );
+ }
+
+ static List messageSendingCallback(TypedMap extensions) {
+ final data = extensions.get();
+ return data != null
+ ? [
+ data.toXML(),
+ ]
+ : [];
+ }
}
-// TODO: Merge those two functions into [MessageDeliveryReceiptData]
-XMLNode makeMessageDeliveryRequest() {
- return XMLNode.xmlns(
- tag: 'request',
- xmlns: deliveryXmlns,
- );
-}
+class MessageDeliveryReceivedData {
+ const MessageDeliveryReceivedData(this.id);
-XMLNode makeMessageDeliveryResponse(String id) {
- return XMLNode.xmlns(
- tag: 'received',
- xmlns: deliveryXmlns,
- attributes: {'id': id},
- );
+ /// The stanza id of the message we received.
+ final String id;
+
+ XMLNode toXML() {
+ return XMLNode.xmlns(
+ tag: 'received',
+ xmlns: deliveryXmlns,
+ attributes: {'id': id},
+ );
+ }
+
+ static List messageSendingCallback(TypedMap extensions) {
+ final data = extensions.get();
+ return data != null ? [data.toXML()] : [];
+ }
}
class MessageDeliveryReceiptManager extends XmppManagerBase {
diff --git a/packages/moxxmpp/lib/src/xeps/xep_0308.dart b/packages/moxxmpp/lib/src/xeps/xep_0308.dart
index 1cc57b8..7621841 100644
--- a/packages/moxxmpp/lib/src/xeps/xep_0308.dart
+++ b/packages/moxxmpp/lib/src/xeps/xep_0308.dart
@@ -5,6 +5,7 @@ import 'package:moxxmpp/src/managers/namespaces.dart';
import 'package:moxxmpp/src/namespaces.dart';
import 'package:moxxmpp/src/stanza.dart';
import 'package:moxxmpp/src/stringxml.dart';
+import 'package:moxxmpp/src/util/typed_map.dart';
class LastMessageCorrectionData {
const LastMessageCorrectionData(this.id);
@@ -21,6 +22,15 @@ class LastMessageCorrectionData {
},
);
}
+
+ static List messageSendingCallback(TypedMap extensions) {
+ final data = extensions.get();
+ return data != null
+ ? [
+ data.toXML(),
+ ]
+ : [];
+ }
}
class LastMessageCorrectionManager extends XmppManagerBase {
diff --git a/packages/moxxmpp/lib/src/xeps/xep_0334.dart b/packages/moxxmpp/lib/src/xeps/xep_0334.dart
index e4bf1b0..5b372a6 100644
--- a/packages/moxxmpp/lib/src/xeps/xep_0334.dart
+++ b/packages/moxxmpp/lib/src/xeps/xep_0334.dart
@@ -1,5 +1,6 @@
import 'package:moxxmpp/src/namespaces.dart';
import 'package:moxxmpp/src/stringxml.dart';
+import 'package:moxxmpp/src/util/typed_map.dart';
enum MessageProcessingHint {
noPermanentStore,
@@ -45,4 +46,13 @@ enum MessageProcessingHint {
xmlns: messageProcessingHintsXmlns,
);
}
+
+ static List messageSendingCallback(TypedMap extensions) {
+ final data = extensions.get();
+ return data != null
+ ? [
+ data.toXML(),
+ ]
+ : [];
+ }
}
diff --git a/packages/moxxmpp/lib/src/xeps/xep_0359.dart b/packages/moxxmpp/lib/src/xeps/xep_0359.dart
index a1cdfb9..4576efd 100644
--- a/packages/moxxmpp/lib/src/xeps/xep_0359.dart
+++ b/packages/moxxmpp/lib/src/xeps/xep_0359.dart
@@ -6,6 +6,32 @@ import 'package:moxxmpp/src/managers/namespaces.dart';
import 'package:moxxmpp/src/namespaces.dart';
import 'package:moxxmpp/src/stanza.dart';
import 'package:moxxmpp/src/stringxml.dart';
+import 'package:moxxmpp/src/util/typed_map.dart';
+
+/// Representation of a element.
+class StanzaId {
+ const StanzaId(
+ this.id,
+ this.by,
+ );
+
+ /// The unique stanza id.
+ final String id;
+
+ /// The JID the id was generated by.
+ final JID by;
+
+ XMLNode toXML() {
+ return XMLNode.xmlns(
+ tag: 'stanza-id',
+ xmlns: stableIdXmlns,
+ attributes: {
+ 'id': id,
+ 'by': by.toString(),
+ },
+ );
+ }
+}
class StableIdData {
const StableIdData(this.originId, this.stanzaIds);
@@ -27,30 +53,22 @@ class StableIdData {
attributes: {'id': originId!},
);
}
-}
-/// Representation of a element.
-class StanzaId {
- const StanzaId(
- this.id,
- this.by,
- );
+ List toXML() {
+ return [
+ if (originId != null)
+ XMLNode.xmlns(
+ tag: 'origin-id',
+ xmlns: stableIdXmlns,
+ attributes: {'id': originId!},
+ ),
+ if (stanzaIds != null) ...stanzaIds!.map((s) => s.toXML()),
+ ];
+ }
- /// The unique stanza id.
- final String id;
-
- /// The JID the id was generated by.
- final JID by;
-
- XMLNode toXml() {
- return XMLNode.xmlns(
- tag: 'stanza-id',
- xmlns: stableIdXmlns,
- attributes: {
- 'id': id,
- 'by': by.toString(),
- },
- );
+ static List messageSendingCallback(TypedMap extensions) {
+ final data = extensions.get();
+ return data != null ? data.toXML() : [];
}
}
diff --git a/packages/moxxmpp/lib/src/xeps/xep_0424.dart b/packages/moxxmpp/lib/src/xeps/xep_0424.dart
index ca5bc62..08e8bc5 100644
--- a/packages/moxxmpp/lib/src/xeps/xep_0424.dart
+++ b/packages/moxxmpp/lib/src/xeps/xep_0424.dart
@@ -4,11 +4,48 @@ import 'package:moxxmpp/src/managers/handlers.dart';
import 'package:moxxmpp/src/managers/namespaces.dart';
import 'package:moxxmpp/src/namespaces.dart';
import 'package:moxxmpp/src/stanza.dart';
+import 'package:moxxmpp/src/stringxml.dart';
+import 'package:moxxmpp/src/util/typed_map.dart';
class MessageRetractionData {
MessageRetractionData(this.id, this.fallback);
+
+ /// A potential fallback message to set the body to when retracting.
final String? fallback;
+
+ /// The id of the message that is retracted.
final String id;
+
+ static List messageSendingCallback(TypedMap extensions) {
+ final data = extensions.get();
+ return data != null
+ ? [
+ XMLNode.xmlns(
+ tag: 'apply-to',
+ xmlns: fasteningXmlns,
+ attributes: {
+ 'id': data.id,
+ },
+ children: [
+ XMLNode.xmlns(
+ tag: 'retract',
+ xmlns: messageRetractionXmlns,
+ ),
+ ],
+ ),
+ if (data.fallback != null)
+ XMLNode(
+ tag: 'body',
+ text: data.fallback,
+ ),
+ if (data.fallback != null)
+ XMLNode.xmlns(
+ tag: 'fallback',
+ xmlns: fallbackIndicationXmlns,
+ ),
+ ]
+ : [];
+ }
}
class MessageRetractionManager extends XmppManagerBase {
diff --git a/packages/moxxmpp/lib/src/xeps/xep_0444.dart b/packages/moxxmpp/lib/src/xeps/xep_0444.dart
index 0e8f9ac..282ec74 100644
--- a/packages/moxxmpp/lib/src/xeps/xep_0444.dart
+++ b/packages/moxxmpp/lib/src/xeps/xep_0444.dart
@@ -5,13 +5,14 @@ import 'package:moxxmpp/src/managers/namespaces.dart';
import 'package:moxxmpp/src/namespaces.dart';
import 'package:moxxmpp/src/stanza.dart';
import 'package:moxxmpp/src/stringxml.dart';
+import 'package:moxxmpp/src/util/typed_map.dart';
class MessageReactions {
const MessageReactions(this.messageId, this.emojis);
final String messageId;
final List emojis;
- XMLNode toXml() {
+ XMLNode toXML() {
return XMLNode.xmlns(
tag: 'reactions',
xmlns: messageReactionsXmlns,
@@ -26,6 +27,15 @@ class MessageReactions {
}).toList(),
);
}
+
+ static List messageSendingCallback(TypedMap extensions) {
+ final data = extensions.get();
+ return data != null
+ ? [
+ data.toXML(),
+ ]
+ : [];
+ }
}
class MessageReactionsManager extends XmppManagerBase {
diff --git a/packages/moxxmpp/lib/src/xeps/xep_0447.dart b/packages/moxxmpp/lib/src/xeps/xep_0447.dart
index f9c8cff..be9b474 100644
--- a/packages/moxxmpp/lib/src/xeps/xep_0447.dart
+++ b/packages/moxxmpp/lib/src/xeps/xep_0447.dart
@@ -6,6 +6,8 @@ import 'package:moxxmpp/src/managers/namespaces.dart';
import 'package:moxxmpp/src/namespaces.dart';
import 'package:moxxmpp/src/stanza.dart';
import 'package:moxxmpp/src/stringxml.dart';
+import 'package:moxxmpp/src/util/typed_map.dart';
+import 'package:moxxmpp/src/xeps/xep_0066.dart';
import 'package:moxxmpp/src/xeps/xep_0446.dart';
import 'package:moxxmpp/src/xeps/xep_0448.dart';
@@ -71,7 +73,11 @@ List processStatelessFileSharingSources(
}
class StatelessFileSharingData {
- const StatelessFileSharingData(this.metadata, this.sources);
+ const StatelessFileSharingData(
+ this.metadata,
+ this.sources, {
+ this.includeOOBFallback = false,
+ });
/// Parse [node] as a StatelessFileSharingData element.
factory StatelessFileSharingData.fromXML(XMLNode node) {
@@ -88,6 +94,10 @@ class StatelessFileSharingData {
final FileMetadataData metadata;
final List sources;
+ /// Flag indicating whether an OOB fallback should be set. The value is only
+ /// relevant in the context of the messageSendingCallback.
+ final bool includeOOBFallback;
+
XMLNode toXML() {
return XMLNode.xmlns(
tag: 'file-sharing',
@@ -109,6 +119,26 @@ class StatelessFileSharingData {
source is StatelessFileSharingUrlSource,
) as StatelessFileSharingUrlSource?;
}
+
+ static List messageSendingCallback(TypedMap extensions) {
+ final data = extensions.get();
+ if (data == null) {
+ return [];
+ }
+
+ // TODO(Unknown): Consider all sources?
+ final source = data.sources.first;
+ OOBData? oob;
+ if (source is StatelessFileSharingUrlSource && data.includeOOBFallback) {
+ // SFS recommends OOB as a fallback
+ oob = OOBData(source.url, null);
+ }
+
+ return [
+ data.toXML(),
+ if (oob != null) oob.toXML(),
+ ];
+ }
}
class SFSManager extends XmppManagerBase {
@@ -122,7 +152,7 @@ class SFSManager extends XmppManagerBase {
tagXmlns: sfsXmlns,
callback: _onMessage,
// Before the message handler
- priority: -99,
+ priority: -98,
)
];
diff --git a/packages/moxxmpp/lib/src/xeps/xep_0449.dart b/packages/moxxmpp/lib/src/xeps/xep_0449.dart
index 1f95556..efda5a5 100644
--- a/packages/moxxmpp/lib/src/xeps/xep_0449.dart
+++ b/packages/moxxmpp/lib/src/xeps/xep_0449.dart
@@ -9,6 +9,7 @@ import 'package:moxxmpp/src/rfcs/rfc_4790.dart';
import 'package:moxxmpp/src/stanza.dart';
import 'package:moxxmpp/src/stringxml.dart';
import 'package:moxxmpp/src/types/result.dart';
+import 'package:moxxmpp/src/util/typed_map.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_0300.dart';
@@ -228,10 +229,29 @@ class StickerPack {
}
class StickersData {
- const StickersData(this.stickerPackId);
+ const StickersData(this.stickerPackId, this.sticker);
/// The id of the sticker pack the referenced sticker is from.
final String stickerPackId;
+
+ /// The metadata of the sticker.
+ final StatelessFileSharingData sticker;
+
+ static List messageSendingCallback(TypedMap extensions) {
+ final data = extensions.get();
+ return data != null
+ ? [
+ XMLNode.xmlns(
+ tag: 'sticker',
+ xmlns: stickersXmlns,
+ attributes: {
+ 'pack': data.stickerPackId,
+ },
+ ),
+ data.sticker.toXML(),
+ ]
+ : [];
+ }
}
class StickersManager extends XmppManagerBase {
@@ -258,7 +278,10 @@ class StickersManager extends XmppManagerBase {
final sticker = stanza.firstTag('sticker', xmlns: stickersXmlns)!;
return state
..extensions.set(
- StickersData(sticker.attributes['pack']! as String),
+ StickersData(
+ sticker.attributes['pack']! as String,
+ state.extensions.get()!,
+ ),
);
}
diff --git a/packages/moxxmpp/lib/src/xeps/xep_0461.dart b/packages/moxxmpp/lib/src/xeps/xep_0461.dart
index 0415d6f..95af407 100644
--- a/packages/moxxmpp/lib/src/xeps/xep_0461.dart
+++ b/packages/moxxmpp/lib/src/xeps/xep_0461.dart
@@ -1,24 +1,36 @@
-import 'package:meta/meta.dart';
+import 'package:moxxmpp/src/jid.dart';
import 'package:moxxmpp/src/managers/base.dart';
import 'package:moxxmpp/src/managers/data.dart';
import 'package:moxxmpp/src/managers/handlers.dart';
import 'package:moxxmpp/src/managers/namespaces.dart';
import 'package:moxxmpp/src/namespaces.dart';
import 'package:moxxmpp/src/stanza.dart';
+import 'package:moxxmpp/src/stringxml.dart';
+import 'package:moxxmpp/src/util/typed_map.dart';
-/// Data summarizing the XEP-0461 data.
+/// A reply to a message.
class ReplyData {
- const ReplyData({
- required this.id,
- this.to,
+ const ReplyData(
+ this.id, {
+ this.body,
+ this.jid,
this.start,
this.end,
});
- /// The bare JID to whom the reply applies to
- final String? to;
+ ReplyData.fromQuoteData(
+ this.id,
+ QuoteData quote, {
+ this.jid,
+ }) : body = quote.body,
+ start = 0,
+ end = quote.fallbackLength;
- /// The stanza ID of the message that is replied to
+ /// The JID of the entity whose message we are replying to.
+ final JID? jid;
+
+ /// The id of the message that is replied to. What id to use depends on what kind
+ /// of message you want to reply to.
final String id;
/// The start of the fallback body (inclusive)
@@ -27,18 +39,59 @@ class ReplyData {
/// The end of the fallback body (exclusive)
final int? end;
+ /// The body of the message.
+ final String? body;
+
/// Applies the metadata to the received body [body] in order to remove the fallback.
/// If either [ReplyData.start] or [ReplyData.end] are null, then body is returned as
/// is.
- String removeFallback(String body) {
+ String? get withoutFallback {
+ if (body == null) return null;
if (start == null || end == null) return body;
- return body.replaceRange(start!, end, '');
+ return body!.replaceRange(start!, end, '');
+ }
+
+ static List messageSendingCallback(TypedMap extensions) {
+ final data = extensions.get();
+ return data != null
+ ? [
+ XMLNode.xmlns(
+ tag: 'reply',
+ xmlns: replyXmlns,
+ attributes: {
+ // The to attribute is optional
+ if (data.jid != null) 'to': data.jid!.toString(),
+
+ 'id': data.id,
+ },
+ ),
+ if (data.body != null)
+ XMLNode(
+ tag: 'body',
+ text: data.body,
+ ),
+ if (data.body != null)
+ XMLNode.xmlns(
+ tag: 'fallback',
+ xmlns: fallbackXmlns,
+ attributes: {'for': replyXmlns},
+ children: [
+ XMLNode(
+ tag: 'body',
+ attributes: {
+ 'start': data.start!.toString(),
+ 'end': data.end!.toString(),
+ },
+ ),
+ ],
+ ),
+ ]
+ : [];
}
}
/// Internal class describing how to build a message with a quote fallback body.
-@visibleForTesting
class QuoteData {
const QuoteData(this.body, this.fallbackLength);
@@ -90,8 +143,8 @@ class MessageRepliesManager extends XmppManagerBase {
StanzaHandlerData state,
) async {
final reply = stanza.firstTag('reply', xmlns: replyXmlns)!;
- final id = reply.attributes['id']! as String;
final to = reply.attributes['to'] as String?;
+ final jid = to != null ? JID.fromString(to) : null;
int? start;
int? end;
@@ -106,10 +159,11 @@ class MessageRepliesManager extends XmppManagerBase {
return state
..extensions.set(
ReplyData(
- id: id,
- to: to,
+ reply.attributes['id']! as String,
+ jid: jid,
start: start,
end: end,
+ body: stanza.firstTag('body')?.innerText(),
),
);
}
diff --git a/packages/moxxmpp/test/helpers/manager.dart b/packages/moxxmpp/test/helpers/manager.dart
index af64e56..a3c02b7 100644
--- a/packages/moxxmpp/test/helpers/manager.dart
+++ b/packages/moxxmpp/test/helpers/manager.dart
@@ -1,13 +1,14 @@
import 'dart:async';
import 'package:moxxmpp/src/connection.dart';
import 'package:moxxmpp/src/connectivity.dart';
+import 'package:moxxmpp/src/events.dart';
import 'package:moxxmpp/src/handlers/client.dart';
import 'package:moxxmpp/src/jid.dart';
import 'package:moxxmpp/src/managers/attributes.dart';
import 'package:moxxmpp/src/managers/base.dart';
import 'package:moxxmpp/src/reconnect.dart';
import 'package:moxxmpp/src/settings.dart';
-import 'package:moxxmpp/src/socket.dart';
+import 'package:moxxmpp/src/stanza.dart';
import 'package:moxxmpp/src/stringxml.dart';
import '../helpers/xmpp.dart';
@@ -15,15 +16,15 @@ import '../helpers/xmpp.dart';
/// This class allows registering managers for easier testing.
class TestingManagerHolder {
TestingManagerHolder({
- BaseSocketWrapper? socket,
- }) : _socket = socket ?? StubTCPSocket([]);
+ StubTCPSocket? stubSocket,
+ }) : socket = stubSocket ?? StubTCPSocket([]);
- final BaseSocketWrapper _socket;
+ final StubTCPSocket socket;
final Map _managers = {};
- // The amount of stanzas sent
- int sentStanzas = 0;
+ /// A list of events that were triggered.
+ final List sentEvents = List.empty(growable: true);
static final JID jid = JID.fromString('testuser@example.org/abc123');
static final ConnectionSettings settings = ConnectionSettings(
@@ -31,15 +32,9 @@ class TestingManagerHolder {
password: 'abc123',
);
- Future _sendStanza(
- stanza, {
- bool addId = true,
- bool awaitable = true,
- bool encrypted = false,
- bool forceEncryption = false,
- }) async {
- sentStanzas++;
- return XMLNode.fromString('');
+ Future _sendStanza(StanzaDetails details) async {
+ socket.write(details.stanza.toXml());
+ return null;
}
T? _getManagerById(String id) {
@@ -54,12 +49,12 @@ class TestingManagerHolder {
TestingReconnectionPolicy(),
AlwaysConnectedConnectivityManager(),
ClientToServerNegotiator(),
- _socket,
+ socket,
),
getConnectionSettings: () => settings,
sendNonza: (_) {},
- sendEvent: (_) {},
- getSocket: () => _socket,
+ sendEvent: sentEvents.add,
+ getSocket: () => socket,
getNegotiatorById: getNegotiatorNullStub,
getFullJID: () => jid,
getManagerById: _getManagerById,
diff --git a/packages/moxxmpp/test/xeps/xep_0030_test.dart b/packages/moxxmpp/test/xeps/xep_0030_test.dart
index 134d9e6..f7ec19f 100644
--- a/packages/moxxmpp/test/xeps/xep_0030_test.dart
+++ b/packages/moxxmpp/test/xeps/xep_0030_test.dart
@@ -140,7 +140,7 @@ void main() {
// Query Alice's device
final result = await dm.discoInfoQuery(aliceJid);
expect(result.isType(), false);
- expect(tm.sentStanzas, 0);
+ expect(tm.socket.getState(), 0);
});
});
}
diff --git a/packages/moxxmpp/test/xeps/xep_0060_test.dart b/packages/moxxmpp/test/xeps/xep_0060_test.dart
index 0f1795b..44d56a0 100644
--- a/packages/moxxmpp/test/xeps/xep_0060_test.dart
+++ b/packages/moxxmpp/test/xeps/xep_0060_test.dart
@@ -168,7 +168,6 @@ void main() {
PubSubManager(),
DiscoManager([]),
PresenceManager(),
- MessageManager(),
RosterManager(TestingRosterStateManager(null, [])),
]);
await connection.registerFeatureNegotiators([
diff --git a/packages/moxxmpp/test/xeps/xep_0449_test.dart b/packages/moxxmpp/test/xeps/xep_0449_test.dart
index 7e4165c..a322e7f 100644
--- a/packages/moxxmpp/test/xeps/xep_0449_test.dart
+++ b/packages/moxxmpp/test/xeps/xep_0449_test.dart
@@ -1,7 +1,14 @@
import 'package:moxxmpp/moxxmpp.dart';
+import 'package:moxxmpp/src/util/typed_map.dart';
import 'package:test/test.dart';
+import '../helpers/logging.dart';
+import '../helpers/manager.dart';
+import '../helpers/xmpp.dart';
+
void main() {
+ initLogger();
+
test('Test parsing a large sticker pack', () {
// Example sticker pack based on the "miho" sticker pack by Movim
final rawPack = XMLNode.fromString('''
@@ -225,4 +232,183 @@ void main() {
expect(pack.stickers.length, 16);
});
+
+ test('Test sending a sticker', () async {
+ final manager = MessageManager([
+ StatelessFileSharingData.messageSendingCallback,
+ StickersData.messageSendingCallback,
+ ]);
+ final holder = TestingManagerHolder(
+ stubSocket: StubTCPSocket([
+ StanzaExpectation(
+ // Example taken from https://xmpp.org/extensions/xep-0449.html#send
+ // - Replaced with and
+ '''
+
+
+
+
+ image/png
+ 😘
+ 67016
+ 512
+ 512
+ gw+6xdCgOcvCYSKuQNrXH33lV9NMzuDf/s0huByCDsY=
+
+
+
+
+
+
+''',
+ '',
+ ),
+ ]),
+ );
+ await holder.register(manager);
+
+ await manager.sendMessage2(
+ JID.fromString('user@example.org'),
+ TypedMap()
+ ..set(
+ StickersData(
+ 'EpRv28DHHzFrE4zd+xaNpVb4',
+ StatelessFileSharingData(
+ const FileMetadataData(
+ mediaType: 'image/png',
+ desc: '😘',
+ size: 67016,
+ width: 512,
+ height: 512,
+ hashes: {
+ HashFunction.sha256:
+ 'gw+6xdCgOcvCYSKuQNrXH33lV9NMzuDf/s0huByCDsY=',
+ },
+ thumbnails: [],
+ ),
+ [
+ StatelessFileSharingUrlSource(
+ 'https://download.montague.lit/51078299-d071-46e1-b6d3-3de4a8ab67d6/sticker_marsey_kiss.png',
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ await Future.delayed(const Duration(seconds: 1));
+
+ expect(holder.socket.getState(), 1);
+ });
+
+ test('Test receiving a sticker', () async {
+ final fakeSocket = StubTCPSocket(
+ [
+ StringExpectation(
+ "",
+ '''
+
+
+
+ PLAIN
+
+ ''',
+ ),
+ StringExpectation(
+ "AHBvbHlub21kaXZpc2lvbgBhYWFh",
+ '',
+ ),
+ StringExpectation(
+ "",
+ '''
+
+
+
+
+
+
+
+
+
+
+
+''',
+ ),
+ StanzaExpectation(
+ '',
+ 'polynomdivision@test.server/MU29eEZn',
+ ignoreId: true,
+ ),
+ ],
+ );
+ final conn = XmppConnection(
+ TestingReconnectionPolicy(),
+ AlwaysConnectedConnectivityManager(),
+ ClientToServerNegotiator(),
+ fakeSocket,
+ )..connectionSettings = ConnectionSettings(
+ jid: JID.fromString('polynomdivision@test.server'),
+ password: 'aaaa',
+ );
+ await conn.registerManagers([
+ MessageManager([]),
+ SFSManager(),
+ StickersManager(),
+ ]);
+ await conn.registerFeatureNegotiators([
+ SaslPlainNegotiator(),
+ ResourceBindingNegotiator(),
+ ]);
+ await conn.connect(
+ shouldReconnect: false,
+ enableReconnectOnSuccess: false,
+ waitUntilLogin: true,
+ );
+
+ MessageEvent? messageEvent;
+ conn.asBroadcastStream().listen((event) {
+ if (event is MessageEvent) {
+ messageEvent = event;
+ }
+ });
+
+ // Send the fake message
+ fakeSocket.injectRawXml(
+ '''
+
+
+
+
+ image/png
+ 😘
+ 67016
+ 512
+ 512
+ gw+6xdCgOcvCYSKuQNrXH33lV9NMzuDf/s0huByCDsY=
+
+
+
+
+
+
+''',
+ );
+
+ await Future.delayed(const Duration(seconds: 2));
+ expect(messageEvent?.stickerPackId, 'EpRv28DHHzFrE4zd+xaNpVb4');
+ expect(messageEvent?.sfs!.metadata.desc, '😘');
+ expect(
+ messageEvent?.sfs!.sources.first is StatelessFileSharingUrlSource,
+ true,
+ );
+ });
}
diff --git a/packages/moxxmpp/test/xeps/xep_0461_test.dart b/packages/moxxmpp/test/xeps/xep_0461_test.dart
index ca4b4d2..07df8b0 100644
--- a/packages/moxxmpp/test/xeps/xep_0461_test.dart
+++ b/packages/moxxmpp/test/xeps/xep_0461_test.dart
@@ -1,6 +1,9 @@
import 'package:moxxmpp/moxxmpp.dart';
+import 'package:moxxmpp/src/util/typed_map.dart';
import 'package:test/test.dart';
+import '../helpers/xmpp.dart';
+
void main() {
test('Test building a singleline quote', () {
final quote = QuoteData.fromBodies('Hallo Welt', 'Hello Earth!');
@@ -20,28 +23,250 @@ void main() {
});
test('Applying a singleline quote', () {
- const body = '> Hallo Welt\nHello right back!';
const reply = ReplyData(
- to: '',
- id: '',
+ '',
start: 0,
end: 13,
+ body: '> Hallo Welt\nHello right back!',
);
- final bodyWithoutFallback = reply.removeFallback(body);
- expect(bodyWithoutFallback, 'Hello right back!');
+ expect(reply.withoutFallback, 'Hello right back!');
});
test('Applying a multiline quote', () {
- const body = "> Hallo Welt\n> How are you?\nI'm fine.\nThank you!";
const reply = ReplyData(
- to: '',
- id: '',
+ '',
start: 0,
end: 28,
+ body: "> Hallo Welt\n> How are you?\nI'm fine.\nThank you!",
);
- final bodyWithoutFallback = reply.removeFallback(body);
- expect(bodyWithoutFallback, "I'm fine.\nThank you!");
+ expect(reply.withoutFallback, "I'm fine.\nThank you!");
+ });
+
+ test('Test calling the message sending callback', () {
+ final result = ReplyData.messageSendingCallback(
+ TypedMap()
+ ..set(
+ ReplyData.fromQuoteData(
+ 'some-random-id',
+ QuoteData.fromBodies(
+ 'Hello world',
+ 'How are you doing?',
+ ),
+ jid: JID.fromString('quoted-user@example.org'),
+ ),
+ ),
+ );
+
+ final reply = result.firstWhere((e) => e.tag == 'reply');
+ final body = result.firstWhere((e) => e.tag == 'body');
+ final fallback = result.firstWhere((e) => e.tag == 'fallback');
+
+ expect(reply.attributes['to'], 'quoted-user@example.org');
+ expect(body.innerText(), '> Hello world\nHow are you doing?');
+ expect(fallback.children.first.attributes['start'], '0');
+ expect(fallback.children.first.attributes['end'], '14');
+ });
+
+ test('Test parsing a reply without fallback', () async {
+ final fakeSocket = StubTCPSocket(
+ [
+ StringExpectation(
+ "",
+ '''
+
+
+
+ PLAIN
+
+ ''',
+ ),
+ StringExpectation(
+ "AHBvbHlub21kaXZpc2lvbgBhYWFh",
+ '',
+ ),
+ StringExpectation(
+ "",
+ '''
+
+
+
+
+
+
+
+
+
+
+
+''',
+ ),
+ StanzaExpectation(
+ '',
+ 'polynomdivision@test.server/MU29eEZn',
+ ignoreId: true,
+ ),
+ ],
+ );
+ final conn = XmppConnection(
+ TestingReconnectionPolicy(),
+ AlwaysConnectedConnectivityManager(),
+ ClientToServerNegotiator(),
+ fakeSocket,
+ )..connectionSettings = ConnectionSettings(
+ jid: JID.fromString('polynomdivision@test.server'),
+ password: 'aaaa',
+ );
+ await conn.registerManagers([
+ MessageManager([]),
+ MessageRepliesManager(),
+ ]);
+ await conn.registerFeatureNegotiators([
+ SaslPlainNegotiator(),
+ ResourceBindingNegotiator(),
+ ]);
+ await conn.connect(
+ shouldReconnect: false,
+ enableReconnectOnSuccess: false,
+ waitUntilLogin: true,
+ );
+
+ MessageEvent? messageEvent;
+ conn.asBroadcastStream().listen((event) {
+ if (event is MessageEvent) {
+ messageEvent = event;
+ }
+ });
+
+ // Send the fake message
+ fakeSocket.injectRawXml(
+ '''
+
+ Great idea!
+
+
+''',
+ );
+
+ await Future.delayed(const Duration(seconds: 2));
+ final reply = messageEvent!.reply!;
+ expect(reply.withoutFallback, 'Great idea!');
+ expect(reply.id, 'message-id1');
+ expect(reply.jid, JID.fromString('anna@example.com/tablet'));
+ expect(reply.start, null);
+ expect(reply.end, null);
+ });
+
+ test('Test parsing a reply with a fallback', () async {
+ final fakeSocket = StubTCPSocket(
+ [
+ StringExpectation(
+ "",
+ '''
+
+
+
+ PLAIN
+
+ ''',
+ ),
+ StringExpectation(
+ "AHBvbHlub21kaXZpc2lvbgBhYWFh",
+ '',
+ ),
+ StringExpectation(
+ "",
+ '''
+
+
+
+
+
+
+
+
+
+
+
+''',
+ ),
+ StanzaExpectation(
+ '',
+ 'polynomdivision@test.server/MU29eEZn',
+ ignoreId: true,
+ ),
+ ],
+ );
+ final conn = XmppConnection(
+ TestingReconnectionPolicy(),
+ AlwaysConnectedConnectivityManager(),
+ ClientToServerNegotiator(),
+ fakeSocket,
+ )..connectionSettings = ConnectionSettings(
+ jid: JID.fromString('polynomdivision@test.server'),
+ password: 'aaaa',
+ );
+ await conn.registerManagers([
+ MessageManager([]),
+ MessageRepliesManager(),
+ ]);
+ await conn.registerFeatureNegotiators([
+ SaslPlainNegotiator(),
+ ResourceBindingNegotiator(),
+ ]);
+ await conn.connect(
+ shouldReconnect: false,
+ enableReconnectOnSuccess: false,
+ waitUntilLogin: true,
+ );
+
+ MessageEvent? messageEvent;
+ conn.asBroadcastStream().listen((event) {
+ if (event is MessageEvent) {
+ messageEvent = event;
+ }
+ });
+
+ // Send the fake message
+ fakeSocket.injectRawXml(
+ '''
+
+ > Anna wrote:\n> We should bake a cake\nGreat idea!
+
+
+
+
+
+''',
+ );
+
+ await Future.delayed(const Duration(seconds: 2));
+ final reply = messageEvent!.reply!;
+ expect(reply.withoutFallback, 'Great idea!');
+ expect(reply.id, 'message-id1');
+ expect(reply.jid, JID.fromString('anna@example.com/laptop'));
+ expect(reply.start, 0);
+ expect(reply.end, 38);
});
}