feat(xep): Implement the message sending callbacks
This commit is contained in:
parent
79d7e3ba64
commit
6f5de9c4dc
@ -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 <body /> element.
|
||||
final String? body;
|
||||
|
||||
XMLNode toXML() {
|
||||
return XMLNode(
|
||||
tag: 'body',
|
||||
text: body,
|
||||
);
|
||||
}
|
||||
|
||||
static List<XMLNode> messageSendingCallback(TypedMap extensions) {
|
||||
if (extensions.get<ReplyData>() != null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
final data = extensions.get<MessageBodyData>();
|
||||
return data != null ? [data.toXML()] : [];
|
||||
}
|
||||
}
|
||||
|
||||
class MessageIdData {
|
||||
const MessageIdData(this.id);
|
||||
|
||||
/// The id attribute of the stanza.
|
||||
final String id;
|
||||
}
|
||||
|
||||
typedef MessageSendingCallback = List<XMLNode> 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<MessageSendingCallback> messageSendingCallbacks;
|
||||
|
||||
@override
|
||||
List<StanzaHandler> getIncomingStanzaHandlers() => [
|
||||
@ -145,6 +183,23 @@ class MessageManager extends XmppManagerBase {
|
||||
return state..done = true;
|
||||
}
|
||||
|
||||
Future<void> sendMessage2(JID to, TypedMap extensions) async {
|
||||
await getAttributes().sendStanza(
|
||||
StanzaDetails(
|
||||
Stanza.message(
|
||||
to: to.toString(),
|
||||
id: extensions.get<MessageIdData>()?.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) {
|
||||
|
@ -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<XMLNode> messageSendingCallback(TypedMap extensions) {
|
||||
final data = extensions.get<ChatState>();
|
||||
return data != null ? [data.toXML()] : [];
|
||||
}
|
||||
}
|
||||
|
||||
class ChatStateManager extends XmppManagerBase {
|
||||
|
@ -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<XMLNode> messageSendingCallback(TypedMap extensions) {
|
||||
final data = extensions.get<MessageDeliveryReceiptData>();
|
||||
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<XMLNode> messageSendingCallback(TypedMap extensions) {
|
||||
final data = extensions.get<MessageDeliveryReceivedData>();
|
||||
return data != null ? [data.toXML()] : [];
|
||||
}
|
||||
}
|
||||
|
||||
class MessageDeliveryReceiptManager extends XmppManagerBase {
|
||||
|
@ -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<XMLNode> messageSendingCallback(TypedMap extensions) {
|
||||
final data = extensions.get<LastMessageCorrectionData>();
|
||||
return data != null
|
||||
? [
|
||||
data.toXML(),
|
||||
]
|
||||
: [];
|
||||
}
|
||||
}
|
||||
|
||||
class LastMessageCorrectionManager extends XmppManagerBase {
|
||||
|
@ -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<XMLNode> messageSendingCallback(TypedMap extensions) {
|
||||
final data = extensions.get<MessageProcessingHint>();
|
||||
return data != null
|
||||
? [
|
||||
data.toXML(),
|
||||
]
|
||||
: [];
|
||||
}
|
||||
}
|
||||
|
@ -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 <stanza-id /> 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 <stanza-id /> element.
|
||||
class StanzaId {
|
||||
const StanzaId(
|
||||
this.id,
|
||||
this.by,
|
||||
);
|
||||
List<XMLNode> 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<XMLNode> messageSendingCallback(TypedMap extensions) {
|
||||
final data = extensions.get<StableIdData>();
|
||||
return data != null ? data.toXML() : [];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<XMLNode> messageSendingCallback(TypedMap extensions) {
|
||||
final data = extensions.get<MessageRetractionData>();
|
||||
return data != null
|
||||
? [
|
||||
XMLNode.xmlns(
|
||||
tag: 'apply-to',
|
||||
xmlns: fasteningXmlns,
|
||||
attributes: <String, String>{
|
||||
'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 {
|
||||
|
@ -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<String> emojis;
|
||||
|
||||
XMLNode toXml() {
|
||||
XMLNode toXML() {
|
||||
return XMLNode.xmlns(
|
||||
tag: 'reactions',
|
||||
xmlns: messageReactionsXmlns,
|
||||
@ -26,6 +27,15 @@ class MessageReactions {
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
static List<XMLNode> messageSendingCallback(TypedMap extensions) {
|
||||
final data = extensions.get<MessageReactions>();
|
||||
return data != null
|
||||
? [
|
||||
data.toXML(),
|
||||
]
|
||||
: [];
|
||||
}
|
||||
}
|
||||
|
||||
class MessageReactionsManager extends XmppManagerBase {
|
||||
|
@ -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<StatelessFileSharingSource> 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<StatelessFileSharingSource> 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<XMLNode> messageSendingCallback(TypedMap extensions) {
|
||||
final data = extensions.get<StatelessFileSharingData>();
|
||||
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,
|
||||
)
|
||||
];
|
||||
|
||||
|
@ -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<XMLNode> messageSendingCallback(TypedMap extensions) {
|
||||
final data = extensions.get<StickersData>();
|
||||
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<StatelessFileSharingData>()!,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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<XMLNode> messageSendingCallback(TypedMap extensions) {
|
||||
final data = extensions.get<ReplyData>();
|
||||
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(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -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<String, XmppManagerBase> _managers = {};
|
||||
|
||||
// The amount of stanzas sent
|
||||
int sentStanzas = 0;
|
||||
/// A list of events that were triggered.
|
||||
final List<XmppEvent> 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<XMLNode> _sendStanza(
|
||||
stanza, {
|
||||
bool addId = true,
|
||||
bool awaitable = true,
|
||||
bool encrypted = false,
|
||||
bool forceEncryption = false,
|
||||
}) async {
|
||||
sentStanzas++;
|
||||
return XMLNode.fromString('<iq />');
|
||||
Future<XMLNode?> _sendStanza(StanzaDetails details) async {
|
||||
socket.write(details.stanza.toXml());
|
||||
return null;
|
||||
}
|
||||
|
||||
T? _getManagerById<T extends XmppManagerBase>(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,
|
||||
|
@ -140,7 +140,7 @@ void main() {
|
||||
// Query Alice's device
|
||||
final result = await dm.discoInfoQuery(aliceJid);
|
||||
expect(result.isType<DiscoError>(), false);
|
||||
expect(tm.sentStanzas, 0);
|
||||
expect(tm.socket.getState(), 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -168,7 +168,6 @@ void main() {
|
||||
PubSubManager(),
|
||||
DiscoManager([]),
|
||||
PresenceManager(),
|
||||
MessageManager(),
|
||||
RosterManager(TestingRosterStateManager(null, [])),
|
||||
]);
|
||||
await connection.registerFeatureNegotiators([
|
||||
|
@ -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 <dimensions /> with <width /> and <height />
|
||||
'''
|
||||
<message to="user@example.org" type="chat">
|
||||
<sticker xmlns='urn:xmpp:stickers:0' pack='EpRv28DHHzFrE4zd+xaNpVb4' />
|
||||
<file-sharing xmlns='urn:xmpp:sfs:0'>
|
||||
<file xmlns='urn:xmpp:file:metadata:0'>
|
||||
<media-type>image/png</media-type>
|
||||
<desc>😘</desc>
|
||||
<size>67016</size>
|
||||
<width>512</width>
|
||||
<height>512</height>
|
||||
<hash xmlns='urn:xmpp:hashes:2' algo='sha-256'>gw+6xdCgOcvCYSKuQNrXH33lV9NMzuDf/s0huByCDsY=</hash>
|
||||
</file>
|
||||
<sources>
|
||||
<url-data xmlns='http://jabber.org/protocol/url-data' target='https://download.montague.lit/51078299-d071-46e1-b6d3-3de4a8ab67d6/sticker_marsey_kiss.png' />
|
||||
</sources>
|
||||
</file-sharing>
|
||||
</message>
|
||||
''',
|
||||
'',
|
||||
),
|
||||
]),
|
||||
);
|
||||
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<void>.delayed(const Duration(seconds: 1));
|
||||
|
||||
expect(holder.socket.getState(), 1);
|
||||
});
|
||||
|
||||
test('Test receiving a sticker', () async {
|
||||
final fakeSocket = StubTCPSocket(
|
||||
[
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
|
||||
<mechanism>PLAIN</mechanism>
|
||||
</mechanisms>
|
||||
</stream:features>''',
|
||||
),
|
||||
StringExpectation(
|
||||
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
|
||||
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
|
||||
),
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
||||
<required/>
|
||||
</bind>
|
||||
<session xmlns="urn:ietf:params:xml:ns:xmpp-session">
|
||||
<optional/>
|
||||
</session>
|
||||
<csi xmlns="urn:xmpp:csi:0"/>
|
||||
<sm xmlns="urn:xmpp:sm:3"/>
|
||||
</stream:features>
|
||||
''',
|
||||
),
|
||||
StanzaExpectation(
|
||||
'<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>',
|
||||
'<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>',
|
||||
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(
|
||||
'''
|
||||
<message id="aaaaaaaaa" from="user@example.org" to="polynomdivision@test.server/abc123" type="chat">
|
||||
<sticker xmlns='urn:xmpp:stickers:0' pack='EpRv28DHHzFrE4zd+xaNpVb4' />
|
||||
<file-sharing xmlns='urn:xmpp:sfs:0'>
|
||||
<file xmlns='urn:xmpp:file:metadata:0'>
|
||||
<media-type>image/png</media-type>
|
||||
<desc>😘</desc>
|
||||
<size>67016</size>
|
||||
<width>512</width>
|
||||
<height>512</height>
|
||||
<hash xmlns='urn:xmpp:hashes:2' algo='sha-256'>gw+6xdCgOcvCYSKuQNrXH33lV9NMzuDf/s0huByCDsY=</hash>
|
||||
</file>
|
||||
<sources>
|
||||
<url-data xmlns='http://jabber.org/protocol/url-data' target='https://download.montague.lit/51078299-d071-46e1-b6d3-3de4a8ab67d6/sticker_marsey_kiss.png' />
|
||||
</sources>
|
||||
</file-sharing>
|
||||
</message>
|
||||
''',
|
||||
);
|
||||
|
||||
await Future<void>.delayed(const Duration(seconds: 2));
|
||||
expect(messageEvent?.stickerPackId, 'EpRv28DHHzFrE4zd+xaNpVb4');
|
||||
expect(messageEvent?.sfs!.metadata.desc, '😘');
|
||||
expect(
|
||||
messageEvent?.sfs!.sources.first is StatelessFileSharingUrlSource,
|
||||
true,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@ -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(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
|
||||
<mechanism>PLAIN</mechanism>
|
||||
</mechanisms>
|
||||
</stream:features>''',
|
||||
),
|
||||
StringExpectation(
|
||||
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
|
||||
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
|
||||
),
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
||||
<required/>
|
||||
</bind>
|
||||
<session xmlns="urn:ietf:params:xml:ns:xmpp-session">
|
||||
<optional/>
|
||||
</session>
|
||||
<csi xmlns="urn:xmpp:csi:0"/>
|
||||
<sm xmlns="urn:xmpp:sm:3"/>
|
||||
</stream:features>
|
||||
''',
|
||||
),
|
||||
StanzaExpectation(
|
||||
'<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>',
|
||||
'<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>',
|
||||
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(
|
||||
'''
|
||||
<message id="aaaaaaaaa" from="user@example.org" to="polynomdivision@test.server/abc123" type="chat">
|
||||
<body>Great idea!</body>
|
||||
<reply to='anna@example.com/tablet' id='message-id1' xmlns='urn:xmpp:reply:0' />
|
||||
</message>
|
||||
''',
|
||||
);
|
||||
|
||||
await Future<void>.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(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
|
||||
<mechanism>PLAIN</mechanism>
|
||||
</mechanisms>
|
||||
</stream:features>''',
|
||||
),
|
||||
StringExpectation(
|
||||
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
|
||||
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
|
||||
),
|
||||
StringExpectation(
|
||||
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
||||
'''
|
||||
<stream:stream
|
||||
xmlns="jabber:client"
|
||||
version="1.0"
|
||||
xmlns:stream="http://etherx.jabber.org/streams"
|
||||
from="test.server"
|
||||
xml:lang="en">
|
||||
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
||||
<required/>
|
||||
</bind>
|
||||
<session xmlns="urn:ietf:params:xml:ns:xmpp-session">
|
||||
<optional/>
|
||||
</session>
|
||||
<csi xmlns="urn:xmpp:csi:0"/>
|
||||
<sm xmlns="urn:xmpp:sm:3"/>
|
||||
</stream:features>
|
||||
''',
|
||||
),
|
||||
StanzaExpectation(
|
||||
'<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>',
|
||||
'<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>',
|
||||
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(
|
||||
'''
|
||||
<message id="aaaaaaaaa" from="user@example.org" to="polynomdivision@test.server/abc123" type="chat">
|
||||
<body>> Anna wrote:\n> We should bake a cake\nGreat idea!</body>
|
||||
<reply to='anna@example.com/laptop' id='message-id1' xmlns='urn:xmpp:reply:0' />
|
||||
<fallback xmlns='urn:xmpp:feature-fallback:0' for='urn:xmpp:reply:0'>
|
||||
<body start="0" end="38" />
|
||||
</fallback>
|
||||
</message>
|
||||
''',
|
||||
);
|
||||
|
||||
await Future<void>.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);
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user