Merge pull request 'Replace StanzaHandlerData with something more extensible' (#42) from feat/type-map-rework into master
Reviewed-on: https://codeberg.org/moxxy/moxxmpp/pulls/42
This commit is contained in:
commit
327f695a40
@ -1,4 +1,4 @@
|
|||||||
## 0.3.2
|
## 0.4.0
|
||||||
|
|
||||||
- **BREAKING**: Remove `lastResource` from `XmppConnection`'s `connect` method. Instead, set the `StreamManagementNegotiator`'s `resource` attribute instead. Since the resource can only really be restored by stream management, this is no issue.
|
- **BREAKING**: Remove `lastResource` from `XmppConnection`'s `connect` method. Instead, set the `StreamManagementNegotiator`'s `resource` attribute instead. Since the resource can only really be restored by stream management, this is no issue.
|
||||||
- **BREAKING**: Changed order of parameters of `CryptographicHashManager.hashFromData`
|
- **BREAKING**: Changed order of parameters of `CryptographicHashManager.hashFromData`
|
||||||
@ -11,6 +11,11 @@
|
|||||||
- **BREAKING**: `PubSubManager` and `UserAvatarManager` now use `JID` instead of `String`.
|
- **BREAKING**: `PubSubManager` and `UserAvatarManager` now use `JID` instead of `String`.
|
||||||
- **BREAKING**: `XmppConnection.sendStanza` not only takes a `StanzaDetails` argument.
|
- **BREAKING**: `XmppConnection.sendStanza` not only takes a `StanzaDetails` argument.
|
||||||
- Sent stanzas are now kept in a queue until sent.
|
- Sent stanzas are now kept in a queue until sent.
|
||||||
|
- **BREAKING**: `MessageManager.sendMessage` does not use `MessageDetails` anymore. Instead, use `TypedMap`.
|
||||||
|
- `MessageManager` now allows registering callbacks for adding data whenever a message is sent.
|
||||||
|
- **BREAKING**: `MessageEvent` now makes use of `TypedMap`.
|
||||||
|
- **BREAKING**: Removed `PresenceReceivedEvent`. Use a manager registering handlers with priority greater than `[PresenceManager.presenceHandlerPriority]` instead.
|
||||||
|
- **BREAKING**: `ChatState.toString()` is now `ChatState.toName()`
|
||||||
|
|
||||||
## 0.3.1
|
## 0.3.1
|
||||||
|
|
||||||
|
@ -18,7 +18,6 @@ export 'package:moxxmpp/src/managers/namespaces.dart';
|
|||||||
export 'package:moxxmpp/src/managers/priorities.dart';
|
export 'package:moxxmpp/src/managers/priorities.dart';
|
||||||
export 'package:moxxmpp/src/message.dart';
|
export 'package:moxxmpp/src/message.dart';
|
||||||
export 'package:moxxmpp/src/namespaces.dart';
|
export 'package:moxxmpp/src/namespaces.dart';
|
||||||
export 'package:moxxmpp/src/negotiators/manager.dart';
|
|
||||||
export 'package:moxxmpp/src/negotiators/namespaces.dart';
|
export 'package:moxxmpp/src/negotiators/namespaces.dart';
|
||||||
export 'package:moxxmpp/src/negotiators/negotiator.dart';
|
export 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||||
export 'package:moxxmpp/src/ping.dart';
|
export 'package:moxxmpp/src/ping.dart';
|
||||||
@ -40,6 +39,7 @@ export 'package:moxxmpp/src/socket.dart';
|
|||||||
export 'package:moxxmpp/src/stanza.dart';
|
export 'package:moxxmpp/src/stanza.dart';
|
||||||
export 'package:moxxmpp/src/stringxml.dart';
|
export 'package:moxxmpp/src/stringxml.dart';
|
||||||
export 'package:moxxmpp/src/types/result.dart';
|
export 'package:moxxmpp/src/types/result.dart';
|
||||||
|
export 'package:moxxmpp/src/util/typed_map.dart';
|
||||||
export 'package:moxxmpp/src/xeps/staging/extensible_file_thumbnails.dart';
|
export 'package:moxxmpp/src/xeps/staging/extensible_file_thumbnails.dart';
|
||||||
export 'package:moxxmpp/src/xeps/staging/fast.dart';
|
export 'package:moxxmpp/src/xeps/staging/fast.dart';
|
||||||
export 'package:moxxmpp/src/xeps/staging/file_upload_notification.dart';
|
export 'package:moxxmpp/src/xeps/staging/file_upload_notification.dart';
|
||||||
|
@ -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';
|
||||||
@ -405,8 +407,7 @@ class XmppConnection {
|
|||||||
|
|
||||||
/// Returns true if we can send data through the socket.
|
/// Returns true if we can send data through the socket.
|
||||||
Future<bool> _canSendData() async {
|
Future<bool> _canSendData() async {
|
||||||
return [XmppConnectionState.connected, XmppConnectionState.connecting]
|
return await getConnectionState() == XmppConnectionState.connected;
|
||||||
.contains(await getConnectionState());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends a stanza described by [details] to the server. Until sent, the stanza is
|
/// Sends a stanza described by [details] to the server. Until sent, the stanza is
|
||||||
@ -424,13 +425,17 @@ class XmppConnection {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final completer = details.awaitable ? Completer<XMLNode>() : null;
|
final completer = details.awaitable ? Completer<XMLNode>() : null;
|
||||||
await _stanzaQueue.enqueueStanza(
|
final entry = StanzaQueueEntry(
|
||||||
StanzaQueueEntry(
|
details,
|
||||||
details,
|
completer,
|
||||||
completer,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (details.bypassQueue) {
|
||||||
|
await _sendStanzaImpl(entry);
|
||||||
|
} else {
|
||||||
|
await _stanzaQueue.enqueueStanza(entry);
|
||||||
|
}
|
||||||
|
|
||||||
return completer?.future;
|
return completer?.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -471,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,
|
||||||
),
|
),
|
||||||
@ -523,18 +528,20 @@ class XmppConnection {
|
|||||||
if (await _canSendData()) {
|
if (await _canSendData()) {
|
||||||
_socket.write(data.stanza.toXml());
|
_socket.write(data.stanza.toXml());
|
||||||
} else {
|
} else {
|
||||||
_log.fine('Not sending dat as _canSendData() returned false.');
|
_log.fine('Not sending data as _canSendData() returned false.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run post-send handlers
|
// Run post-send handlers
|
||||||
_log.fine('Running post stanza handlers..');
|
_log.fine('Running post stanza handlers..');
|
||||||
|
final extensions = TypedMap<StanzaHandlerExtension>()
|
||||||
|
..set(StreamManagementData(details.excludeFromStreamManagement));
|
||||||
await _runOutgoingPostStanzaHandlers(
|
await _runOutgoingPostStanzaHandlers(
|
||||||
newStanza,
|
newStanza,
|
||||||
initial: StanzaHandlerData(
|
initial: StanzaHandlerData(
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
null,
|
|
||||||
newStanza,
|
newStanza,
|
||||||
|
extensions,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
_log.fine('Done');
|
_log.fine('Done');
|
||||||
@ -649,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);
|
||||||
@ -724,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()}');
|
||||||
@ -743,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) {
|
||||||
@ -835,7 +842,7 @@ class XmppConnection {
|
|||||||
await _reconnectionPolicy.setShouldReconnect(false);
|
await _reconnectionPolicy.setShouldReconnect(false);
|
||||||
|
|
||||||
if (triggeredByUser) {
|
if (triggeredByUser) {
|
||||||
getPresenceManager()?.sendUnavailablePresence();
|
await getPresenceManager()?.sendUnavailablePresence();
|
||||||
}
|
}
|
||||||
|
|
||||||
_socket.prepareDisconnect();
|
_socket.prepareDisconnect();
|
||||||
|
@ -4,18 +4,11 @@ import 'package:moxxmpp/src/jid.dart';
|
|||||||
import 'package:moxxmpp/src/managers/data.dart';
|
import 'package:moxxmpp/src/managers/data.dart';
|
||||||
import 'package:moxxmpp/src/roster/roster.dart';
|
import 'package:moxxmpp/src/roster/roster.dart';
|
||||||
import 'package:moxxmpp/src/stanza.dart';
|
import 'package:moxxmpp/src/stanza.dart';
|
||||||
|
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
import 'package:moxxmpp/src/xeps/xep_0030/types.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_0066.dart';
|
import 'package:moxxmpp/src/xeps/xep_0084.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0085.dart';
|
import 'package:moxxmpp/src/xeps/xep_0333.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0334.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_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';
|
|
||||||
|
|
||||||
abstract class XmppEvent {}
|
abstract class XmppEvent {}
|
||||||
|
|
||||||
@ -74,60 +67,42 @@ class RosterUpdatedEvent extends XmppEvent {
|
|||||||
|
|
||||||
/// Triggered when a message is received
|
/// Triggered when a message is received
|
||||||
class MessageEvent extends XmppEvent {
|
class MessageEvent extends XmppEvent {
|
||||||
MessageEvent({
|
MessageEvent(
|
||||||
required this.body,
|
this.from,
|
||||||
required this.fromJid,
|
this.to,
|
||||||
required this.toJid,
|
this.id,
|
||||||
required this.sid,
|
this.encrypted,
|
||||||
required this.isCarbon,
|
this.extensions, {
|
||||||
required this.deliveryReceiptRequested,
|
|
||||||
required this.isMarkable,
|
|
||||||
required this.encrypted,
|
|
||||||
required this.other,
|
|
||||||
this.originId,
|
|
||||||
this.stanzaIds,
|
|
||||||
this.error,
|
|
||||||
this.type,
|
this.type,
|
||||||
this.oob,
|
this.error,
|
||||||
this.sfs,
|
this.encryptionError,
|
||||||
this.sims,
|
|
||||||
this.reply,
|
|
||||||
this.chatState,
|
|
||||||
this.fun,
|
|
||||||
this.funReplacement,
|
|
||||||
this.funCancellation,
|
|
||||||
this.messageRetraction,
|
|
||||||
this.messageCorrectionId,
|
|
||||||
this.messageReactions,
|
|
||||||
this.messageProcessingHints,
|
|
||||||
this.stickerPackId,
|
|
||||||
});
|
});
|
||||||
final StanzaError? error;
|
|
||||||
final String body;
|
/// The from attribute of the message.
|
||||||
final JID fromJid;
|
final JID from;
|
||||||
final JID toJid;
|
|
||||||
final String sid;
|
/// The to attribute of the message.
|
||||||
|
final JID to;
|
||||||
|
|
||||||
|
/// The id attribute of the message.
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
/// The type attribute of the message.
|
||||||
final String? type;
|
final String? type;
|
||||||
final String? originId;
|
|
||||||
final List<StanzaId>? stanzaIds;
|
final StanzaError? error;
|
||||||
final bool isCarbon;
|
|
||||||
final bool deliveryReceiptRequested;
|
/// Flag indicating whether the message was encrypted.
|
||||||
final bool isMarkable;
|
|
||||||
final OOBData? oob;
|
|
||||||
final StatelessFileSharingData? sfs;
|
|
||||||
final StatelessMediaSharingData? sims;
|
|
||||||
final ReplyData? reply;
|
|
||||||
final ChatState? chatState;
|
|
||||||
final FileMetadataData? fun;
|
|
||||||
final String? funReplacement;
|
|
||||||
final String? funCancellation;
|
|
||||||
final bool encrypted;
|
final bool encrypted;
|
||||||
final MessageRetractionData? messageRetraction;
|
|
||||||
final String? messageCorrectionId;
|
/// The error in case an encryption error occurred.
|
||||||
final MessageReactions? messageReactions;
|
final Object? encryptionError;
|
||||||
final List<MessageProcessingHint>? messageProcessingHints;
|
|
||||||
final String? stickerPackId;
|
/// Data added by other handlers.
|
||||||
final Map<String, dynamic> other;
|
final TypedMap<StanzaHandlerExtension> extensions;
|
||||||
|
|
||||||
|
/// Shorthand for extensions.get<T>().
|
||||||
|
T? get<T>() => extensions.get<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Triggered when a client responds to our delivery receipt request
|
/// Triggered when a client responds to our delivery receipt request
|
||||||
@ -138,13 +113,19 @@ class DeliveryReceiptReceivedEvent extends XmppEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ChatMarkerEvent extends XmppEvent {
|
class ChatMarkerEvent extends XmppEvent {
|
||||||
ChatMarkerEvent({
|
ChatMarkerEvent(
|
||||||
required this.type,
|
this.from,
|
||||||
required this.from,
|
this.type,
|
||||||
required this.id,
|
this.id,
|
||||||
});
|
);
|
||||||
|
|
||||||
|
/// The entity that sent the chat marker.
|
||||||
final JID from;
|
final JID from;
|
||||||
final String type;
|
|
||||||
|
/// The type of chat marker that was sent.
|
||||||
|
final ChatMarker type;
|
||||||
|
|
||||||
|
/// The id of the message that the marker applies to.
|
||||||
final String id;
|
final String id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,13 +149,6 @@ class ResourceBoundEvent extends XmppEvent {
|
|||||||
final String resource;
|
final String resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Triggered when we receive presence
|
|
||||||
class PresenceReceivedEvent extends XmppEvent {
|
|
||||||
PresenceReceivedEvent(this.jid, this.presence);
|
|
||||||
final JID jid;
|
|
||||||
final Stanza presence;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Triggered when we are starting an connection attempt
|
/// Triggered when we are starting an connection attempt
|
||||||
class ConnectingEvent extends XmppEvent {}
|
class ConnectingEvent extends XmppEvent {}
|
||||||
|
|
||||||
@ -192,15 +166,35 @@ class SubscriptionRequestReceivedEvent extends XmppEvent {
|
|||||||
final JID from;
|
final JID from;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Triggered when we receive a new or updated avatar
|
/// Triggered when we receive a new or updated avatar via XEP-0084
|
||||||
class AvatarUpdatedEvent extends XmppEvent {
|
class UserAvatarUpdatedEvent extends XmppEvent {
|
||||||
AvatarUpdatedEvent({
|
UserAvatarUpdatedEvent(
|
||||||
required this.jid,
|
this.jid,
|
||||||
required this.base64,
|
this.metadata,
|
||||||
required this.hash,
|
);
|
||||||
});
|
|
||||||
final String jid;
|
/// The JID of the user updating their avatar.
|
||||||
|
final JID jid;
|
||||||
|
|
||||||
|
/// The metadata of the avatar.
|
||||||
|
final List<UserAvatarMetadata> metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Triggered when we receive a new or updated avatar via XEP-0054
|
||||||
|
class VCardAvatarUpdatedEvent extends XmppEvent {
|
||||||
|
VCardAvatarUpdatedEvent(
|
||||||
|
this.jid,
|
||||||
|
this.base64,
|
||||||
|
this.hash,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// The JID of the entity that updated their avatar.
|
||||||
|
final JID jid;
|
||||||
|
|
||||||
|
/// The base64-encoded avatar data.
|
||||||
final String base64;
|
final String base64;
|
||||||
|
|
||||||
|
/// The SHA-1 hash of the avatar.
|
||||||
final String hash;
|
final String hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,79 +1,47 @@
|
|||||||
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';
|
abstract class StanzaHandlerExtension {}
|
||||||
|
|
||||||
@freezed
|
class StanzaHandlerData {
|
||||||
class StanzaHandlerData with _$StanzaHandlerData {
|
StanzaHandlerData(
|
||||||
factory StanzaHandlerData(
|
this.done,
|
||||||
// Indicates to the runner that processing is now done. This means that all
|
this.cancel,
|
||||||
// pre-processing is done and no other handlers should be consulted.
|
this.stanza,
|
||||||
bool done,
|
this.extensions, {
|
||||||
// Indicates to the runner that processing is to be cancelled and no further handlers
|
this.cancelReason,
|
||||||
// should run. The stanza also will not be sent.
|
this.encryptionError,
|
||||||
bool cancel,
|
this.encrypted = false,
|
||||||
// The reason why we cancelled the processing and sending
|
this.forceEncryption = false,
|
||||||
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 now done. This means that all
|
||||||
String? originId,
|
/// pre-processing is done and no other handlers should be consulted.
|
||||||
|
bool done;
|
||||||
|
|
||||||
// XEP-0359 <stanza-id /> elements, if available.
|
/// Indicates to the runner that processing is to be cancelled and no further handlers
|
||||||
List<StanzaId>? stanzaIds,
|
/// should run. The stanza also will not be sent.
|
||||||
ReplyData? reply,
|
bool cancel;
|
||||||
ChatState? chatState,
|
|
||||||
@Default(false) bool isCarbon,
|
/// The reason why we cancelled the processing and sending.
|
||||||
@Default(false) bool deliveryReceiptRequested,
|
Object? cancelReason;
|
||||||
@Default(false) bool isMarkable,
|
|
||||||
// File Upload Notifications
|
/// The reason why an encryption or decryption failed.
|
||||||
// A notification
|
Object? encryptionError;
|
||||||
FileMetadataData? fun,
|
|
||||||
// The stanza id this replaces
|
/// The stanza that is being dealt with. SHOULD NOT be overwritten, unless it is
|
||||||
String? funReplacement,
|
/// absolutely necessary, e.g. with Message Carbons or OMEMO.
|
||||||
// The stanza id this cancels
|
Stanza stanza;
|
||||||
String? funCancellation,
|
|
||||||
// Whether the stanza was received encrypted
|
/// Whether the stanza was received encrypted
|
||||||
@Default(false) bool encrypted,
|
bool encrypted;
|
||||||
// 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
|
// If true, forces the encryption manager to encrypt to the JID, even if it
|
||||||
// but forceEncryption is true, then the OMEMO manager will try to encrypt
|
// would not normally. In the case of OMEMO: If shouldEncrypt returns false
|
||||||
// to the JID anyway.
|
// but forceEncryption is true, then the OMEMO manager will try to encrypt
|
||||||
@Default(false) bool forceEncryption,
|
// to the JID anyway.
|
||||||
// The stated type of encryption used, if any was used
|
bool forceEncryption;
|
||||||
ExplicitEncryptionType? encryptionType,
|
|
||||||
// Delayed Delivery
|
/// Additional data from other managers.
|
||||||
DelayedDelivery? delayedDelivery,
|
final TypedMap<StanzaHandlerExtension> extensions;
|
||||||
// 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,
|
|
||||||
}) = _StanzaHandlerData;
|
|
||||||
}
|
}
|
||||||
|
@ -1,793 +0,0 @@
|
|||||||
// coverage:ignore-file
|
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// ignore_for_file: type=lint
|
|
||||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
|
||||||
|
|
||||||
part of 'data.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// FreezedGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
T _$identity<T>(T value) => value;
|
|
||||||
|
|
||||||
final _privateConstructorUsedError = UnsupportedError(
|
|
||||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
mixin _$StanzaHandlerData {
|
|
||||||
// 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 get done =>
|
|
||||||
throw _privateConstructorUsedError; // Indicates to the runner that processing is to be cancelled and no further handlers
|
|
||||||
// should run. The stanza also will not be sent.
|
|
||||||
bool get cancel =>
|
|
||||||
throw _privateConstructorUsedError; // The reason why we cancelled the processing and sending
|
|
||||||
dynamic get cancelReason =>
|
|
||||||
throw _privateConstructorUsedError; // The stanza that is being dealt with. SHOULD NOT be overwritten, unless it is absolutely
|
|
||||||
// necessary, e.g. with Message Carbons or OMEMO
|
|
||||||
Stanza get stanza =>
|
|
||||||
throw _privateConstructorUsedError; // Whether the stanza is retransmitted. Only useful in the context of outgoing
|
|
||||||
// stanza handlers. MUST NOT be overwritten.
|
|
||||||
bool get retransmitted => throw _privateConstructorUsedError;
|
|
||||||
StatelessMediaSharingData? get sims => throw _privateConstructorUsedError;
|
|
||||||
StatelessFileSharingData? get sfs => throw _privateConstructorUsedError;
|
|
||||||
OOBData? get oob =>
|
|
||||||
throw _privateConstructorUsedError; // XEP-0359 <origin-id />'s id attribute, if available.
|
|
||||||
String? get originId =>
|
|
||||||
throw _privateConstructorUsedError; // XEP-0359 <stanza-id /> elements, if available.
|
|
||||||
List<StanzaId>? get stanzaIds => throw _privateConstructorUsedError;
|
|
||||||
ReplyData? get reply => throw _privateConstructorUsedError;
|
|
||||||
ChatState? get chatState => throw _privateConstructorUsedError;
|
|
||||||
bool get isCarbon => throw _privateConstructorUsedError;
|
|
||||||
bool get deliveryReceiptRequested => throw _privateConstructorUsedError;
|
|
||||||
bool get isMarkable =>
|
|
||||||
throw _privateConstructorUsedError; // File Upload Notifications
|
|
||||||
// A notification
|
|
||||||
FileMetadataData? get fun =>
|
|
||||||
throw _privateConstructorUsedError; // The stanza id this replaces
|
|
||||||
String? get funReplacement =>
|
|
||||||
throw _privateConstructorUsedError; // The stanza id this cancels
|
|
||||||
String? get funCancellation =>
|
|
||||||
throw _privateConstructorUsedError; // Whether the stanza was received encrypted
|
|
||||||
bool get encrypted =>
|
|
||||||
throw _privateConstructorUsedError; // 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
|
|
||||||
// but forceEncryption is true, then the OMEMO manager will try to encrypt
|
|
||||||
// to the JID anyway.
|
|
||||||
bool get forceEncryption =>
|
|
||||||
throw _privateConstructorUsedError; // The stated type of encryption used, if any was used
|
|
||||||
ExplicitEncryptionType? get encryptionType =>
|
|
||||||
throw _privateConstructorUsedError; // Delayed Delivery
|
|
||||||
DelayedDelivery? get delayedDelivery =>
|
|
||||||
throw _privateConstructorUsedError; // This is for stanza handlers that are not part of the XMPP library but still need
|
|
||||||
// pass data around.
|
|
||||||
Map<String, dynamic> get other =>
|
|
||||||
throw _privateConstructorUsedError; // If non-null, then it indicates the origin Id of the message that should be
|
|
||||||
// retracted
|
|
||||||
MessageRetractionData? get messageRetraction =>
|
|
||||||
throw _privateConstructorUsedError; // If non-null, then the message is a correction for the specified stanza Id
|
|
||||||
String? get lastMessageCorrectionSid =>
|
|
||||||
throw _privateConstructorUsedError; // Reactions data
|
|
||||||
MessageReactions? get messageReactions =>
|
|
||||||
throw _privateConstructorUsedError; // The Id of the sticker pack this sticker belongs to
|
|
||||||
String? get stickerPackId => throw _privateConstructorUsedError;
|
|
||||||
|
|
||||||
@JsonKey(ignore: true)
|
|
||||||
$StanzaHandlerDataCopyWith<StanzaHandlerData> get copyWith =>
|
|
||||||
throw _privateConstructorUsedError;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract class $StanzaHandlerDataCopyWith<$Res> {
|
|
||||||
factory $StanzaHandlerDataCopyWith(
|
|
||||||
StanzaHandlerData value, $Res Function(StanzaHandlerData) then) =
|
|
||||||
_$StanzaHandlerDataCopyWithImpl<$Res, StanzaHandlerData>;
|
|
||||||
@useResult
|
|
||||||
$Res call(
|
|
||||||
{bool done,
|
|
||||||
bool cancel,
|
|
||||||
dynamic cancelReason,
|
|
||||||
Stanza stanza,
|
|
||||||
bool retransmitted,
|
|
||||||
StatelessMediaSharingData? sims,
|
|
||||||
StatelessFileSharingData? sfs,
|
|
||||||
OOBData? oob,
|
|
||||||
String? originId,
|
|
||||||
List<StanzaId>? stanzaIds,
|
|
||||||
ReplyData? reply,
|
|
||||||
ChatState? chatState,
|
|
||||||
bool isCarbon,
|
|
||||||
bool deliveryReceiptRequested,
|
|
||||||
bool isMarkable,
|
|
||||||
FileMetadataData? fun,
|
|
||||||
String? funReplacement,
|
|
||||||
String? funCancellation,
|
|
||||||
bool encrypted,
|
|
||||||
bool forceEncryption,
|
|
||||||
ExplicitEncryptionType? encryptionType,
|
|
||||||
DelayedDelivery? delayedDelivery,
|
|
||||||
Map<String, dynamic> other,
|
|
||||||
MessageRetractionData? messageRetraction,
|
|
||||||
String? lastMessageCorrectionSid,
|
|
||||||
MessageReactions? messageReactions,
|
|
||||||
String? stickerPackId});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
class _$StanzaHandlerDataCopyWithImpl<$Res, $Val extends StanzaHandlerData>
|
|
||||||
implements $StanzaHandlerDataCopyWith<$Res> {
|
|
||||||
_$StanzaHandlerDataCopyWithImpl(this._value, this._then);
|
|
||||||
|
|
||||||
// ignore: unused_field
|
|
||||||
final $Val _value;
|
|
||||||
// ignore: unused_field
|
|
||||||
final $Res Function($Val) _then;
|
|
||||||
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
@override
|
|
||||||
$Res call({
|
|
||||||
Object? done = null,
|
|
||||||
Object? cancel = null,
|
|
||||||
Object? cancelReason = freezed,
|
|
||||||
Object? stanza = null,
|
|
||||||
Object? retransmitted = null,
|
|
||||||
Object? sims = freezed,
|
|
||||||
Object? sfs = freezed,
|
|
||||||
Object? oob = freezed,
|
|
||||||
Object? originId = freezed,
|
|
||||||
Object? stanzaIds = freezed,
|
|
||||||
Object? reply = freezed,
|
|
||||||
Object? chatState = freezed,
|
|
||||||
Object? isCarbon = null,
|
|
||||||
Object? deliveryReceiptRequested = null,
|
|
||||||
Object? isMarkable = null,
|
|
||||||
Object? fun = freezed,
|
|
||||||
Object? funReplacement = freezed,
|
|
||||||
Object? funCancellation = freezed,
|
|
||||||
Object? encrypted = null,
|
|
||||||
Object? forceEncryption = null,
|
|
||||||
Object? encryptionType = freezed,
|
|
||||||
Object? delayedDelivery = freezed,
|
|
||||||
Object? other = null,
|
|
||||||
Object? messageRetraction = freezed,
|
|
||||||
Object? lastMessageCorrectionSid = freezed,
|
|
||||||
Object? messageReactions = freezed,
|
|
||||||
Object? stickerPackId = freezed,
|
|
||||||
}) {
|
|
||||||
return _then(_value.copyWith(
|
|
||||||
done: null == done
|
|
||||||
? _value.done
|
|
||||||
: done // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
cancel: null == cancel
|
|
||||||
? _value.cancel
|
|
||||||
: cancel // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
cancelReason: freezed == cancelReason
|
|
||||||
? _value.cancelReason
|
|
||||||
: cancelReason // ignore: cast_nullable_to_non_nullable
|
|
||||||
as dynamic,
|
|
||||||
stanza: null == stanza
|
|
||||||
? _value.stanza
|
|
||||||
: stanza // ignore: cast_nullable_to_non_nullable
|
|
||||||
as Stanza,
|
|
||||||
retransmitted: null == retransmitted
|
|
||||||
? _value.retransmitted
|
|
||||||
: retransmitted // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
sims: freezed == sims
|
|
||||||
? _value.sims
|
|
||||||
: sims // ignore: cast_nullable_to_non_nullable
|
|
||||||
as StatelessMediaSharingData?,
|
|
||||||
sfs: freezed == sfs
|
|
||||||
? _value.sfs
|
|
||||||
: sfs // ignore: cast_nullable_to_non_nullable
|
|
||||||
as StatelessFileSharingData?,
|
|
||||||
oob: freezed == oob
|
|
||||||
? _value.oob
|
|
||||||
: oob // ignore: cast_nullable_to_non_nullable
|
|
||||||
as OOBData?,
|
|
||||||
originId: freezed == originId
|
|
||||||
? _value.originId
|
|
||||||
: originId // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,
|
|
||||||
stanzaIds: freezed == stanzaIds
|
|
||||||
? _value.stanzaIds
|
|
||||||
: stanzaIds // ignore: cast_nullable_to_non_nullable
|
|
||||||
as List<StanzaId>?,
|
|
||||||
reply: freezed == reply
|
|
||||||
? _value.reply
|
|
||||||
: reply // ignore: cast_nullable_to_non_nullable
|
|
||||||
as ReplyData?,
|
|
||||||
chatState: freezed == chatState
|
|
||||||
? _value.chatState
|
|
||||||
: chatState // ignore: cast_nullable_to_non_nullable
|
|
||||||
as ChatState?,
|
|
||||||
isCarbon: null == isCarbon
|
|
||||||
? _value.isCarbon
|
|
||||||
: isCarbon // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
deliveryReceiptRequested: null == deliveryReceiptRequested
|
|
||||||
? _value.deliveryReceiptRequested
|
|
||||||
: deliveryReceiptRequested // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
isMarkable: null == isMarkable
|
|
||||||
? _value.isMarkable
|
|
||||||
: isMarkable // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
fun: freezed == fun
|
|
||||||
? _value.fun
|
|
||||||
: fun // ignore: cast_nullable_to_non_nullable
|
|
||||||
as FileMetadataData?,
|
|
||||||
funReplacement: freezed == funReplacement
|
|
||||||
? _value.funReplacement
|
|
||||||
: funReplacement // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,
|
|
||||||
funCancellation: freezed == funCancellation
|
|
||||||
? _value.funCancellation
|
|
||||||
: funCancellation // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,
|
|
||||||
encrypted: null == encrypted
|
|
||||||
? _value.encrypted
|
|
||||||
: encrypted // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
forceEncryption: null == forceEncryption
|
|
||||||
? _value.forceEncryption
|
|
||||||
: forceEncryption // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
encryptionType: freezed == encryptionType
|
|
||||||
? _value.encryptionType
|
|
||||||
: encryptionType // ignore: cast_nullable_to_non_nullable
|
|
||||||
as ExplicitEncryptionType?,
|
|
||||||
delayedDelivery: freezed == delayedDelivery
|
|
||||||
? _value.delayedDelivery
|
|
||||||
: delayedDelivery // ignore: cast_nullable_to_non_nullable
|
|
||||||
as DelayedDelivery?,
|
|
||||||
other: null == other
|
|
||||||
? _value.other
|
|
||||||
: other // ignore: cast_nullable_to_non_nullable
|
|
||||||
as Map<String, dynamic>,
|
|
||||||
messageRetraction: freezed == messageRetraction
|
|
||||||
? _value.messageRetraction
|
|
||||||
: messageRetraction // ignore: cast_nullable_to_non_nullable
|
|
||||||
as MessageRetractionData?,
|
|
||||||
lastMessageCorrectionSid: freezed == lastMessageCorrectionSid
|
|
||||||
? _value.lastMessageCorrectionSid
|
|
||||||
: lastMessageCorrectionSid // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,
|
|
||||||
messageReactions: freezed == messageReactions
|
|
||||||
? _value.messageReactions
|
|
||||||
: messageReactions // ignore: cast_nullable_to_non_nullable
|
|
||||||
as MessageReactions?,
|
|
||||||
stickerPackId: freezed == stickerPackId
|
|
||||||
? _value.stickerPackId
|
|
||||||
: stickerPackId // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,
|
|
||||||
) as $Val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract class _$$_StanzaHandlerDataCopyWith<$Res>
|
|
||||||
implements $StanzaHandlerDataCopyWith<$Res> {
|
|
||||||
factory _$$_StanzaHandlerDataCopyWith(_$_StanzaHandlerData value,
|
|
||||||
$Res Function(_$_StanzaHandlerData) then) =
|
|
||||||
__$$_StanzaHandlerDataCopyWithImpl<$Res>;
|
|
||||||
@override
|
|
||||||
@useResult
|
|
||||||
$Res call(
|
|
||||||
{bool done,
|
|
||||||
bool cancel,
|
|
||||||
dynamic cancelReason,
|
|
||||||
Stanza stanza,
|
|
||||||
bool retransmitted,
|
|
||||||
StatelessMediaSharingData? sims,
|
|
||||||
StatelessFileSharingData? sfs,
|
|
||||||
OOBData? oob,
|
|
||||||
String? originId,
|
|
||||||
List<StanzaId>? stanzaIds,
|
|
||||||
ReplyData? reply,
|
|
||||||
ChatState? chatState,
|
|
||||||
bool isCarbon,
|
|
||||||
bool deliveryReceiptRequested,
|
|
||||||
bool isMarkable,
|
|
||||||
FileMetadataData? fun,
|
|
||||||
String? funReplacement,
|
|
||||||
String? funCancellation,
|
|
||||||
bool encrypted,
|
|
||||||
bool forceEncryption,
|
|
||||||
ExplicitEncryptionType? encryptionType,
|
|
||||||
DelayedDelivery? delayedDelivery,
|
|
||||||
Map<String, dynamic> other,
|
|
||||||
MessageRetractionData? messageRetraction,
|
|
||||||
String? lastMessageCorrectionSid,
|
|
||||||
MessageReactions? messageReactions,
|
|
||||||
String? stickerPackId});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
class __$$_StanzaHandlerDataCopyWithImpl<$Res>
|
|
||||||
extends _$StanzaHandlerDataCopyWithImpl<$Res, _$_StanzaHandlerData>
|
|
||||||
implements _$$_StanzaHandlerDataCopyWith<$Res> {
|
|
||||||
__$$_StanzaHandlerDataCopyWithImpl(
|
|
||||||
_$_StanzaHandlerData _value, $Res Function(_$_StanzaHandlerData) _then)
|
|
||||||
: super(_value, _then);
|
|
||||||
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
@override
|
|
||||||
$Res call({
|
|
||||||
Object? done = null,
|
|
||||||
Object? cancel = null,
|
|
||||||
Object? cancelReason = freezed,
|
|
||||||
Object? stanza = null,
|
|
||||||
Object? retransmitted = null,
|
|
||||||
Object? sims = freezed,
|
|
||||||
Object? sfs = freezed,
|
|
||||||
Object? oob = freezed,
|
|
||||||
Object? originId = freezed,
|
|
||||||
Object? stanzaIds = freezed,
|
|
||||||
Object? reply = freezed,
|
|
||||||
Object? chatState = freezed,
|
|
||||||
Object? isCarbon = null,
|
|
||||||
Object? deliveryReceiptRequested = null,
|
|
||||||
Object? isMarkable = null,
|
|
||||||
Object? fun = freezed,
|
|
||||||
Object? funReplacement = freezed,
|
|
||||||
Object? funCancellation = freezed,
|
|
||||||
Object? encrypted = null,
|
|
||||||
Object? forceEncryption = null,
|
|
||||||
Object? encryptionType = freezed,
|
|
||||||
Object? delayedDelivery = freezed,
|
|
||||||
Object? other = null,
|
|
||||||
Object? messageRetraction = freezed,
|
|
||||||
Object? lastMessageCorrectionSid = freezed,
|
|
||||||
Object? messageReactions = freezed,
|
|
||||||
Object? stickerPackId = freezed,
|
|
||||||
}) {
|
|
||||||
return _then(_$_StanzaHandlerData(
|
|
||||||
null == done
|
|
||||||
? _value.done
|
|
||||||
: done // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
null == cancel
|
|
||||||
? _value.cancel
|
|
||||||
: cancel // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
freezed == cancelReason
|
|
||||||
? _value.cancelReason
|
|
||||||
: cancelReason // ignore: cast_nullable_to_non_nullable
|
|
||||||
as dynamic,
|
|
||||||
null == stanza
|
|
||||||
? _value.stanza
|
|
||||||
: stanza // ignore: cast_nullable_to_non_nullable
|
|
||||||
as Stanza,
|
|
||||||
retransmitted: null == retransmitted
|
|
||||||
? _value.retransmitted
|
|
||||||
: retransmitted // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
sims: freezed == sims
|
|
||||||
? _value.sims
|
|
||||||
: sims // ignore: cast_nullable_to_non_nullable
|
|
||||||
as StatelessMediaSharingData?,
|
|
||||||
sfs: freezed == sfs
|
|
||||||
? _value.sfs
|
|
||||||
: sfs // ignore: cast_nullable_to_non_nullable
|
|
||||||
as StatelessFileSharingData?,
|
|
||||||
oob: freezed == oob
|
|
||||||
? _value.oob
|
|
||||||
: oob // ignore: cast_nullable_to_non_nullable
|
|
||||||
as OOBData?,
|
|
||||||
originId: freezed == originId
|
|
||||||
? _value.originId
|
|
||||||
: originId // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,
|
|
||||||
stanzaIds: freezed == stanzaIds
|
|
||||||
? _value._stanzaIds
|
|
||||||
: stanzaIds // ignore: cast_nullable_to_non_nullable
|
|
||||||
as List<StanzaId>?,
|
|
||||||
reply: freezed == reply
|
|
||||||
? _value.reply
|
|
||||||
: reply // ignore: cast_nullable_to_non_nullable
|
|
||||||
as ReplyData?,
|
|
||||||
chatState: freezed == chatState
|
|
||||||
? _value.chatState
|
|
||||||
: chatState // ignore: cast_nullable_to_non_nullable
|
|
||||||
as ChatState?,
|
|
||||||
isCarbon: null == isCarbon
|
|
||||||
? _value.isCarbon
|
|
||||||
: isCarbon // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
deliveryReceiptRequested: null == deliveryReceiptRequested
|
|
||||||
? _value.deliveryReceiptRequested
|
|
||||||
: deliveryReceiptRequested // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
isMarkable: null == isMarkable
|
|
||||||
? _value.isMarkable
|
|
||||||
: isMarkable // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
fun: freezed == fun
|
|
||||||
? _value.fun
|
|
||||||
: fun // ignore: cast_nullable_to_non_nullable
|
|
||||||
as FileMetadataData?,
|
|
||||||
funReplacement: freezed == funReplacement
|
|
||||||
? _value.funReplacement
|
|
||||||
: funReplacement // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,
|
|
||||||
funCancellation: freezed == funCancellation
|
|
||||||
? _value.funCancellation
|
|
||||||
: funCancellation // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,
|
|
||||||
encrypted: null == encrypted
|
|
||||||
? _value.encrypted
|
|
||||||
: encrypted // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
forceEncryption: null == forceEncryption
|
|
||||||
? _value.forceEncryption
|
|
||||||
: forceEncryption // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
encryptionType: freezed == encryptionType
|
|
||||||
? _value.encryptionType
|
|
||||||
: encryptionType // ignore: cast_nullable_to_non_nullable
|
|
||||||
as ExplicitEncryptionType?,
|
|
||||||
delayedDelivery: freezed == delayedDelivery
|
|
||||||
? _value.delayedDelivery
|
|
||||||
: delayedDelivery // ignore: cast_nullable_to_non_nullable
|
|
||||||
as DelayedDelivery?,
|
|
||||||
other: null == other
|
|
||||||
? _value._other
|
|
||||||
: other // ignore: cast_nullable_to_non_nullable
|
|
||||||
as Map<String, dynamic>,
|
|
||||||
messageRetraction: freezed == messageRetraction
|
|
||||||
? _value.messageRetraction
|
|
||||||
: messageRetraction // ignore: cast_nullable_to_non_nullable
|
|
||||||
as MessageRetractionData?,
|
|
||||||
lastMessageCorrectionSid: freezed == lastMessageCorrectionSid
|
|
||||||
? _value.lastMessageCorrectionSid
|
|
||||||
: lastMessageCorrectionSid // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,
|
|
||||||
messageReactions: freezed == messageReactions
|
|
||||||
? _value.messageReactions
|
|
||||||
: messageReactions // ignore: cast_nullable_to_non_nullable
|
|
||||||
as MessageReactions?,
|
|
||||||
stickerPackId: freezed == stickerPackId
|
|
||||||
? _value.stickerPackId
|
|
||||||
: stickerPackId // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
|
|
||||||
class _$_StanzaHandlerData implements _StanzaHandlerData {
|
|
||||||
_$_StanzaHandlerData(this.done, this.cancel, this.cancelReason, this.stanza,
|
|
||||||
{this.retransmitted = false,
|
|
||||||
this.sims,
|
|
||||||
this.sfs,
|
|
||||||
this.oob,
|
|
||||||
this.originId,
|
|
||||||
final List<StanzaId>? stanzaIds,
|
|
||||||
this.reply,
|
|
||||||
this.chatState,
|
|
||||||
this.isCarbon = false,
|
|
||||||
this.deliveryReceiptRequested = false,
|
|
||||||
this.isMarkable = false,
|
|
||||||
this.fun,
|
|
||||||
this.funReplacement,
|
|
||||||
this.funCancellation,
|
|
||||||
this.encrypted = false,
|
|
||||||
this.forceEncryption = false,
|
|
||||||
this.encryptionType,
|
|
||||||
this.delayedDelivery,
|
|
||||||
final Map<String, dynamic> other = const <String, dynamic>{},
|
|
||||||
this.messageRetraction,
|
|
||||||
this.lastMessageCorrectionSid,
|
|
||||||
this.messageReactions,
|
|
||||||
this.stickerPackId})
|
|
||||||
: _stanzaIds = stanzaIds,
|
|
||||||
_other = other;
|
|
||||||
|
|
||||||
// Indicates to the runner that processing is now done. This means that all
|
|
||||||
// pre-processing is done and no other handlers should be consulted.
|
|
||||||
@override
|
|
||||||
final 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.
|
|
||||||
@override
|
|
||||||
final bool cancel;
|
|
||||||
// The reason why we cancelled the processing and sending
|
|
||||||
@override
|
|
||||||
final 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
|
|
||||||
@override
|
|
||||||
final Stanza stanza;
|
|
||||||
// Whether the stanza is retransmitted. Only useful in the context of outgoing
|
|
||||||
// stanza handlers. MUST NOT be overwritten.
|
|
||||||
@override
|
|
||||||
@JsonKey()
|
|
||||||
final bool retransmitted;
|
|
||||||
@override
|
|
||||||
final StatelessMediaSharingData? sims;
|
|
||||||
@override
|
|
||||||
final StatelessFileSharingData? sfs;
|
|
||||||
@override
|
|
||||||
final OOBData? oob;
|
|
||||||
// XEP-0359 <origin-id />'s id attribute, if available.
|
|
||||||
@override
|
|
||||||
final String? originId;
|
|
||||||
// XEP-0359 <stanza-id /> elements, if available.
|
|
||||||
final List<StanzaId>? _stanzaIds;
|
|
||||||
// XEP-0359 <stanza-id /> elements, if available.
|
|
||||||
@override
|
|
||||||
List<StanzaId>? get stanzaIds {
|
|
||||||
final value = _stanzaIds;
|
|
||||||
if (value == null) return null;
|
|
||||||
if (_stanzaIds is EqualUnmodifiableListView) return _stanzaIds;
|
|
||||||
// ignore: implicit_dynamic_type
|
|
||||||
return EqualUnmodifiableListView(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
final ReplyData? reply;
|
|
||||||
@override
|
|
||||||
final ChatState? chatState;
|
|
||||||
@override
|
|
||||||
@JsonKey()
|
|
||||||
final bool isCarbon;
|
|
||||||
@override
|
|
||||||
@JsonKey()
|
|
||||||
final bool deliveryReceiptRequested;
|
|
||||||
@override
|
|
||||||
@JsonKey()
|
|
||||||
final bool isMarkable;
|
|
||||||
// File Upload Notifications
|
|
||||||
// A notification
|
|
||||||
@override
|
|
||||||
final FileMetadataData? fun;
|
|
||||||
// The stanza id this replaces
|
|
||||||
@override
|
|
||||||
final String? funReplacement;
|
|
||||||
// The stanza id this cancels
|
|
||||||
@override
|
|
||||||
final String? funCancellation;
|
|
||||||
// Whether the stanza was received encrypted
|
|
||||||
@override
|
|
||||||
@JsonKey()
|
|
||||||
final bool encrypted;
|
|
||||||
// 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
|
|
||||||
// but forceEncryption is true, then the OMEMO manager will try to encrypt
|
|
||||||
// to the JID anyway.
|
|
||||||
@override
|
|
||||||
@JsonKey()
|
|
||||||
final bool forceEncryption;
|
|
||||||
// The stated type of encryption used, if any was used
|
|
||||||
@override
|
|
||||||
final ExplicitEncryptionType? encryptionType;
|
|
||||||
// Delayed Delivery
|
|
||||||
@override
|
|
||||||
final DelayedDelivery? delayedDelivery;
|
|
||||||
// This is for stanza handlers that are not part of the XMPP library but still need
|
|
||||||
// pass data around.
|
|
||||||
final Map<String, dynamic> _other;
|
|
||||||
// This is for stanza handlers that are not part of the XMPP library but still need
|
|
||||||
// pass data around.
|
|
||||||
@override
|
|
||||||
@JsonKey()
|
|
||||||
Map<String, dynamic> get other {
|
|
||||||
if (_other is EqualUnmodifiableMapView) return _other;
|
|
||||||
// ignore: implicit_dynamic_type
|
|
||||||
return EqualUnmodifiableMapView(_other);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If non-null, then it indicates the origin Id of the message that should be
|
|
||||||
// retracted
|
|
||||||
@override
|
|
||||||
final MessageRetractionData? messageRetraction;
|
|
||||||
// If non-null, then the message is a correction for the specified stanza Id
|
|
||||||
@override
|
|
||||||
final String? lastMessageCorrectionSid;
|
|
||||||
// Reactions data
|
|
||||||
@override
|
|
||||||
final MessageReactions? messageReactions;
|
|
||||||
// The Id of the sticker pack this sticker belongs to
|
|
||||||
@override
|
|
||||||
final String? stickerPackId;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'StanzaHandlerData(done: $done, cancel: $cancel, cancelReason: $cancelReason, stanza: $stanza, retransmitted: $retransmitted, sims: $sims, sfs: $sfs, oob: $oob, originId: $originId, stanzaIds: $stanzaIds, reply: $reply, chatState: $chatState, isCarbon: $isCarbon, deliveryReceiptRequested: $deliveryReceiptRequested, isMarkable: $isMarkable, fun: $fun, funReplacement: $funReplacement, funCancellation: $funCancellation, encrypted: $encrypted, forceEncryption: $forceEncryption, encryptionType: $encryptionType, delayedDelivery: $delayedDelivery, other: $other, messageRetraction: $messageRetraction, lastMessageCorrectionSid: $lastMessageCorrectionSid, messageReactions: $messageReactions, stickerPackId: $stickerPackId)';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(dynamic other) {
|
|
||||||
return identical(this, other) ||
|
|
||||||
(other.runtimeType == runtimeType &&
|
|
||||||
other is _$_StanzaHandlerData &&
|
|
||||||
(identical(other.done, done) || other.done == done) &&
|
|
||||||
(identical(other.cancel, cancel) || other.cancel == cancel) &&
|
|
||||||
const DeepCollectionEquality()
|
|
||||||
.equals(other.cancelReason, cancelReason) &&
|
|
||||||
(identical(other.stanza, stanza) || other.stanza == stanza) &&
|
|
||||||
(identical(other.retransmitted, retransmitted) ||
|
|
||||||
other.retransmitted == retransmitted) &&
|
|
||||||
(identical(other.sims, sims) || other.sims == sims) &&
|
|
||||||
(identical(other.sfs, sfs) || other.sfs == sfs) &&
|
|
||||||
(identical(other.oob, oob) || other.oob == oob) &&
|
|
||||||
(identical(other.originId, originId) ||
|
|
||||||
other.originId == originId) &&
|
|
||||||
const DeepCollectionEquality()
|
|
||||||
.equals(other._stanzaIds, _stanzaIds) &&
|
|
||||||
(identical(other.reply, reply) || other.reply == reply) &&
|
|
||||||
(identical(other.chatState, chatState) ||
|
|
||||||
other.chatState == chatState) &&
|
|
||||||
(identical(other.isCarbon, isCarbon) ||
|
|
||||||
other.isCarbon == isCarbon) &&
|
|
||||||
(identical(
|
|
||||||
other.deliveryReceiptRequested, deliveryReceiptRequested) ||
|
|
||||||
other.deliveryReceiptRequested == deliveryReceiptRequested) &&
|
|
||||||
(identical(other.isMarkable, isMarkable) ||
|
|
||||||
other.isMarkable == isMarkable) &&
|
|
||||||
(identical(other.fun, fun) || other.fun == fun) &&
|
|
||||||
(identical(other.funReplacement, funReplacement) ||
|
|
||||||
other.funReplacement == funReplacement) &&
|
|
||||||
(identical(other.funCancellation, funCancellation) ||
|
|
||||||
other.funCancellation == funCancellation) &&
|
|
||||||
(identical(other.encrypted, encrypted) ||
|
|
||||||
other.encrypted == encrypted) &&
|
|
||||||
(identical(other.forceEncryption, forceEncryption) ||
|
|
||||||
other.forceEncryption == forceEncryption) &&
|
|
||||||
(identical(other.encryptionType, encryptionType) ||
|
|
||||||
other.encryptionType == encryptionType) &&
|
|
||||||
(identical(other.delayedDelivery, delayedDelivery) ||
|
|
||||||
other.delayedDelivery == delayedDelivery) &&
|
|
||||||
const DeepCollectionEquality().equals(other._other, this._other) &&
|
|
||||||
(identical(other.messageRetraction, messageRetraction) ||
|
|
||||||
other.messageRetraction == messageRetraction) &&
|
|
||||||
(identical(
|
|
||||||
other.lastMessageCorrectionSid, lastMessageCorrectionSid) ||
|
|
||||||
other.lastMessageCorrectionSid == lastMessageCorrectionSid) &&
|
|
||||||
(identical(other.messageReactions, messageReactions) ||
|
|
||||||
other.messageReactions == messageReactions) &&
|
|
||||||
(identical(other.stickerPackId, stickerPackId) ||
|
|
||||||
other.stickerPackId == stickerPackId));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hashAll([
|
|
||||||
runtimeType,
|
|
||||||
done,
|
|
||||||
cancel,
|
|
||||||
const DeepCollectionEquality().hash(cancelReason),
|
|
||||||
stanza,
|
|
||||||
retransmitted,
|
|
||||||
sims,
|
|
||||||
sfs,
|
|
||||||
oob,
|
|
||||||
originId,
|
|
||||||
const DeepCollectionEquality().hash(_stanzaIds),
|
|
||||||
reply,
|
|
||||||
chatState,
|
|
||||||
isCarbon,
|
|
||||||
deliveryReceiptRequested,
|
|
||||||
isMarkable,
|
|
||||||
fun,
|
|
||||||
funReplacement,
|
|
||||||
funCancellation,
|
|
||||||
encrypted,
|
|
||||||
forceEncryption,
|
|
||||||
encryptionType,
|
|
||||||
delayedDelivery,
|
|
||||||
const DeepCollectionEquality().hash(_other),
|
|
||||||
messageRetraction,
|
|
||||||
lastMessageCorrectionSid,
|
|
||||||
messageReactions,
|
|
||||||
stickerPackId
|
|
||||||
]);
|
|
||||||
|
|
||||||
@JsonKey(ignore: true)
|
|
||||||
@override
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
_$$_StanzaHandlerDataCopyWith<_$_StanzaHandlerData> get copyWith =>
|
|
||||||
__$$_StanzaHandlerDataCopyWithImpl<_$_StanzaHandlerData>(
|
|
||||||
this, _$identity);
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class _StanzaHandlerData implements StanzaHandlerData {
|
|
||||||
factory _StanzaHandlerData(final bool done, final bool cancel,
|
|
||||||
final dynamic cancelReason, final Stanza stanza,
|
|
||||||
{final bool retransmitted,
|
|
||||||
final StatelessMediaSharingData? sims,
|
|
||||||
final StatelessFileSharingData? sfs,
|
|
||||||
final OOBData? oob,
|
|
||||||
final String? originId,
|
|
||||||
final List<StanzaId>? stanzaIds,
|
|
||||||
final ReplyData? reply,
|
|
||||||
final ChatState? chatState,
|
|
||||||
final bool isCarbon,
|
|
||||||
final bool deliveryReceiptRequested,
|
|
||||||
final bool isMarkable,
|
|
||||||
final FileMetadataData? fun,
|
|
||||||
final String? funReplacement,
|
|
||||||
final String? funCancellation,
|
|
||||||
final bool encrypted,
|
|
||||||
final bool forceEncryption,
|
|
||||||
final ExplicitEncryptionType? encryptionType,
|
|
||||||
final DelayedDelivery? delayedDelivery,
|
|
||||||
final Map<String, dynamic> other,
|
|
||||||
final MessageRetractionData? messageRetraction,
|
|
||||||
final String? lastMessageCorrectionSid,
|
|
||||||
final MessageReactions? messageReactions,
|
|
||||||
final String? stickerPackId}) = _$_StanzaHandlerData;
|
|
||||||
|
|
||||||
@override // 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 get done;
|
|
||||||
@override // Indicates to the runner that processing is to be cancelled and no further handlers
|
|
||||||
// should run. The stanza also will not be sent.
|
|
||||||
bool get cancel;
|
|
||||||
@override // The reason why we cancelled the processing and sending
|
|
||||||
dynamic get cancelReason;
|
|
||||||
@override // The stanza that is being dealt with. SHOULD NOT be overwritten, unless it is absolutely
|
|
||||||
// necessary, e.g. with Message Carbons or OMEMO
|
|
||||||
Stanza get stanza;
|
|
||||||
@override // Whether the stanza is retransmitted. Only useful in the context of outgoing
|
|
||||||
// stanza handlers. MUST NOT be overwritten.
|
|
||||||
bool get retransmitted;
|
|
||||||
@override
|
|
||||||
StatelessMediaSharingData? get sims;
|
|
||||||
@override
|
|
||||||
StatelessFileSharingData? get sfs;
|
|
||||||
@override
|
|
||||||
OOBData? get oob;
|
|
||||||
@override // XEP-0359 <origin-id />'s id attribute, if available.
|
|
||||||
String? get originId;
|
|
||||||
@override // XEP-0359 <stanza-id /> elements, if available.
|
|
||||||
List<StanzaId>? get stanzaIds;
|
|
||||||
@override
|
|
||||||
ReplyData? get reply;
|
|
||||||
@override
|
|
||||||
ChatState? get chatState;
|
|
||||||
@override
|
|
||||||
bool get isCarbon;
|
|
||||||
@override
|
|
||||||
bool get deliveryReceiptRequested;
|
|
||||||
@override
|
|
||||||
bool get isMarkable;
|
|
||||||
@override // File Upload Notifications
|
|
||||||
// A notification
|
|
||||||
FileMetadataData? get fun;
|
|
||||||
@override // The stanza id this replaces
|
|
||||||
String? get funReplacement;
|
|
||||||
@override // The stanza id this cancels
|
|
||||||
String? get funCancellation;
|
|
||||||
@override // Whether the stanza was received encrypted
|
|
||||||
bool get encrypted;
|
|
||||||
@override // 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
|
|
||||||
// but forceEncryption is true, then the OMEMO manager will try to encrypt
|
|
||||||
// to the JID anyway.
|
|
||||||
bool get forceEncryption;
|
|
||||||
@override // The stated type of encryption used, if any was used
|
|
||||||
ExplicitEncryptionType? get encryptionType;
|
|
||||||
@override // Delayed Delivery
|
|
||||||
DelayedDelivery? get delayedDelivery;
|
|
||||||
@override // This is for stanza handlers that are not part of the XMPP library but still need
|
|
||||||
// pass data around.
|
|
||||||
Map<String, dynamic> get other;
|
|
||||||
@override // If non-null, then it indicates the origin Id of the message that should be
|
|
||||||
// retracted
|
|
||||||
MessageRetractionData? get messageRetraction;
|
|
||||||
@override // If non-null, then the message is a correction for the specified stanza Id
|
|
||||||
String? get lastMessageCorrectionSid;
|
|
||||||
@override // Reactions data
|
|
||||||
MessageReactions? get messageReactions;
|
|
||||||
@override // The Id of the sticker pack this sticker belongs to
|
|
||||||
String? get stickerPackId;
|
|
||||||
@override
|
|
||||||
@JsonKey(ignore: true)
|
|
||||||
_$$_StanzaHandlerDataCopyWith<_$_StanzaHandlerData> get copyWith =>
|
|
||||||
throw _privateConstructorUsedError;
|
|
||||||
}
|
|
@ -31,3 +31,4 @@ const lastMessageCorrectionManager = 'org.moxxmpp.lastmessagecorrectionmanager';
|
|||||||
const messageReactionsManager = 'org.moxxmpp.messagereactionsmanager';
|
const messageReactionsManager = 'org.moxxmpp.messagereactionsmanager';
|
||||||
const stickersManager = 'org.moxxmpp.stickersmanager';
|
const stickersManager = 'org.moxxmpp.stickersmanager';
|
||||||
const entityCapabilitiesManager = 'org.moxxmpp.entitycapabilities';
|
const entityCapabilitiesManager = 'org.moxxmpp.entitycapabilities';
|
||||||
|
const messageProcessingHintManager = 'org.moxxmpp.messageprocessinghint';
|
||||||
|
@ -1,89 +1,71 @@
|
|||||||
import 'package:moxlib/moxlib.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/events.dart';
|
||||||
import 'package:moxxmpp/src/jid.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';
|
||||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||||
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';
|
||||||
import 'package:moxxmpp/src/xeps/staging/file_upload_notification.dart';
|
import 'package:moxxmpp/src/util/typed_map.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_0184.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0308.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0333.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0334.dart';
|
|
||||||
import 'package:moxxmpp/src/xeps/xep_0359.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_0447.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.
|
/// A callback that is called whenever a message is sent using
|
||||||
///
|
/// [MessageManager.sendMessage]. The input the typed map that is passed to
|
||||||
/// [setOOBFallbackBody] indicates, when using SFS, whether a OOB fallback should be
|
/// sendMessage.
|
||||||
/// added. This is recommended when sharing files but may cause issues when the message
|
typedef MessageSendingCallback = List<XMLNode> Function(
|
||||||
/// stanza should include a SFS element without any fallbacks.
|
TypedMap<StanzaHandlerExtension>,
|
||||||
class MessageDetails {
|
);
|
||||||
const MessageDetails({
|
|
||||||
required this.to,
|
/// The raw content of the <body /> element.
|
||||||
this.body,
|
class MessageBodyData implements StanzaHandlerExtension {
|
||||||
this.requestDeliveryReceipt = false,
|
const MessageBodyData(this.body);
|
||||||
this.requestChatMarkers = true,
|
|
||||||
this.id,
|
/// The content of the <body /> element.
|
||||||
this.originId,
|
|
||||||
this.quoteBody,
|
|
||||||
this.quoteId,
|
|
||||||
this.quoteFrom,
|
|
||||||
this.chatState,
|
|
||||||
this.sfs,
|
|
||||||
this.fun,
|
|
||||||
this.funReplacement,
|
|
||||||
this.funCancellation,
|
|
||||||
this.shouldEncrypt = false,
|
|
||||||
this.messageRetraction,
|
|
||||||
this.lastMessageCorrectionId,
|
|
||||||
this.messageReactions,
|
|
||||||
this.messageProcessingHints,
|
|
||||||
this.stickerPackId,
|
|
||||||
this.setOOBFallbackBody = true,
|
|
||||||
});
|
|
||||||
final String to;
|
|
||||||
final String? body;
|
final String? body;
|
||||||
final bool requestDeliveryReceipt;
|
|
||||||
final bool requestChatMarkers;
|
XMLNode toXML() {
|
||||||
final String? id;
|
return XMLNode(
|
||||||
final String? originId;
|
tag: 'body',
|
||||||
final String? quoteBody;
|
text: body,
|
||||||
final String? quoteId;
|
);
|
||||||
final String? quoteFrom;
|
}
|
||||||
final ChatState? chatState;
|
}
|
||||||
final StatelessFileSharingData? sfs;
|
|
||||||
final FileMetadataData? fun;
|
/// The id attribute of the message stanza.
|
||||||
final String? funReplacement;
|
class MessageIdData implements StanzaHandlerExtension {
|
||||||
final String? funCancellation;
|
const MessageIdData(this.id);
|
||||||
final bool shouldEncrypt;
|
|
||||||
final MessageRetractionData? messageRetraction;
|
/// The id attribute of the stanza.
|
||||||
final String? lastMessageCorrectionId;
|
final String id;
|
||||||
final MessageReactions? messageReactions;
|
|
||||||
final String? stickerPackId;
|
|
||||||
final List<MessageProcessingHint>? messageProcessingHints;
|
|
||||||
final bool setOOBFallbackBody;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class MessageManager extends XmppManagerBase {
|
class MessageManager extends XmppManagerBase {
|
||||||
MessageManager() : super(messageManager);
|
MessageManager() : super(messageManager);
|
||||||
|
|
||||||
|
/// The priority of the message handler. If a handler should run before this one,
|
||||||
|
/// which emits the [MessageEvent] event and terminates processing, make sure it
|
||||||
|
/// has a priority greater than [messageHandlerPriority].
|
||||||
|
static int messageHandlerPriority = -100;
|
||||||
|
|
||||||
|
/// A list of callbacks that are called when a message is sent in order to add
|
||||||
|
/// appropriate child elements.
|
||||||
|
final List<MessageSendingCallback> _messageSendingCallbacks =
|
||||||
|
List<MessageSendingCallback>.empty(growable: true);
|
||||||
|
|
||||||
|
void registerMessageSendingCallback(MessageSendingCallback callback) {
|
||||||
|
_messageSendingCallbacks.add(callback);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<StanzaHandler> getIncomingStanzaHandlers() => [
|
List<StanzaHandler> getIncomingStanzaHandlers() => [
|
||||||
StanzaHandler(
|
StanzaHandler(
|
||||||
stanzaTag: 'message',
|
stanzaTag: 'message',
|
||||||
callback: _onMessage,
|
callback: _onMessage,
|
||||||
priority: -100,
|
priority: messageHandlerPriority,
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -94,238 +76,69 @@ class MessageManager extends XmppManagerBase {
|
|||||||
Stanza _,
|
Stanza _,
|
||||||
StanzaHandlerData state,
|
StanzaHandlerData state,
|
||||||
) async {
|
) async {
|
||||||
final message = state.stanza;
|
|
||||||
final body = message.firstTag('body');
|
|
||||||
|
|
||||||
final hints = List<MessageProcessingHint>.empty(growable: true);
|
|
||||||
for (final element
|
|
||||||
in message.findTagsByXmlns(messageProcessingHintsXmlns)) {
|
|
||||||
hints.add(messageProcessingHintFromXml(element));
|
|
||||||
}
|
|
||||||
|
|
||||||
getAttributes().sendEvent(
|
getAttributes().sendEvent(
|
||||||
MessageEvent(
|
MessageEvent(
|
||||||
body: body != null ? body.innerText() : '',
|
JID.fromString(state.stanza.attributes['from']! as String),
|
||||||
fromJid: JID.fromString(message.attributes['from']! as String),
|
JID.fromString(state.stanza.attributes['to']! as String),
|
||||||
toJid: JID.fromString(message.attributes['to']! as String),
|
state.stanza.attributes['id']! as String,
|
||||||
sid: message.attributes['id']! as String,
|
state.encrypted,
|
||||||
originId: state.originId,
|
state.extensions,
|
||||||
stanzaIds: state.stanzaIds,
|
type: state.stanza.attributes['type'] as String?,
|
||||||
isCarbon: state.isCarbon,
|
error: StanzaError.fromStanza(state.stanza),
|
||||||
deliveryReceiptRequested: state.deliveryReceiptRequested,
|
encryptionError: state.encryptionError,
|
||||||
isMarkable: state.isMarkable,
|
|
||||||
type: message.attributes['type'] as String?,
|
|
||||||
oob: state.oob,
|
|
||||||
sfs: state.sfs,
|
|
||||||
sims: state.sims,
|
|
||||||
reply: state.reply,
|
|
||||||
chatState: state.chatState,
|
|
||||||
fun: state.fun,
|
|
||||||
funReplacement: state.funReplacement,
|
|
||||||
funCancellation: state.funCancellation,
|
|
||||||
encrypted: state.encrypted,
|
|
||||||
messageRetraction: state.messageRetraction,
|
|
||||||
messageCorrectionId: state.lastMessageCorrectionSid,
|
|
||||||
messageReactions: state.messageReactions,
|
|
||||||
messageProcessingHints: hints.isEmpty ? null : hints,
|
|
||||||
stickerPackId: state.stickerPackId,
|
|
||||||
other: state.other,
|
|
||||||
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 an unawaitable message to [to]. [extensions] is a typed map that contains
|
||||||
/// the message will also request a delivery receipt from the receiver.
|
/// data for building the message.
|
||||||
/// If id is non-null, then it will be the id of the message stanza.
|
Future<void> sendMessage(
|
||||||
/// element to this id. If originId is non-null, then it will create an "origin-id"
|
JID to,
|
||||||
/// child in the message stanza and set its id to originId.
|
TypedMap<StanzaHandlerExtension> extensions,
|
||||||
void sendMessage(MessageDetails details) {
|
) async {
|
||||||
assert(
|
await getAttributes().sendStanza(
|
||||||
implies(
|
|
||||||
details.quoteBody != null,
|
|
||||||
details.quoteFrom != null && details.quoteId != null,
|
|
||||||
),
|
|
||||||
'When quoting a message, then quoteFrom and quoteId must also be non-null',
|
|
||||||
);
|
|
||||||
|
|
||||||
final stanza = Stanza.message(
|
|
||||||
to: details.to,
|
|
||||||
type: 'chat',
|
|
||||||
id: details.id,
|
|
||||||
children: [],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (details.quoteBody != null) {
|
|
||||||
final quote = QuoteData.fromBodies(details.quoteBody!, details.body!);
|
|
||||||
|
|
||||||
stanza
|
|
||||||
..addChild(
|
|
||||||
XMLNode(tag: 'body', text: quote.body),
|
|
||||||
)
|
|
||||||
..addChild(
|
|
||||||
XMLNode.xmlns(
|
|
||||||
tag: 'reply',
|
|
||||||
xmlns: replyXmlns,
|
|
||||||
attributes: {'to': details.quoteFrom!, 'id': details.quoteId!},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
..addChild(
|
|
||||||
XMLNode.xmlns(
|
|
||||||
tag: 'fallback',
|
|
||||||
xmlns: fallbackXmlns,
|
|
||||||
attributes: {'for': replyXmlns},
|
|
||||||
children: [
|
|
||||||
XMLNode(
|
|
||||||
tag: 'body',
|
|
||||||
attributes: <String, String>{
|
|
||||||
'start': '0',
|
|
||||||
'end': '${quote.fallbackLength}',
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
var body = details.body;
|
|
||||||
if (details.sfs != null && details.setOOBFallbackBody) {
|
|
||||||
// TODO(Unknown): Maybe find a better solution
|
|
||||||
final firstSource = details.sfs!.sources.first;
|
|
||||||
if (firstSource is StatelessFileSharingUrlSource) {
|
|
||||||
body = firstSource.url;
|
|
||||||
} else if (firstSource is StatelessFileSharingEncryptedSource) {
|
|
||||||
body = firstSource.source.url;
|
|
||||||
}
|
|
||||||
} else if (details.messageRetraction?.fallback != null) {
|
|
||||||
body = details.messageRetraction!.fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (body != null) {
|
|
||||||
stanza.addChild(
|
|
||||||
XMLNode(tag: 'body', text: body),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (details.requestDeliveryReceipt) {
|
|
||||||
stanza.addChild(makeMessageDeliveryRequest());
|
|
||||||
}
|
|
||||||
if (details.requestChatMarkers) {
|
|
||||||
stanza.addChild(makeChatMarkerMarkable());
|
|
||||||
}
|
|
||||||
if (details.originId != null) {
|
|
||||||
stanza.addChild(makeOriginIdElement(details.originId!));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (details.sfs != null) {
|
|
||||||
stanza.addChild(details.sfs!.toXML());
|
|
||||||
|
|
||||||
final source = details.sfs!.sources.first;
|
|
||||||
if (source is StatelessFileSharingUrlSource &&
|
|
||||||
details.setOOBFallbackBody) {
|
|
||||||
// SFS recommends OOB as a fallback
|
|
||||||
stanza.addChild(constructOOBNode(OOBData(url: source.url)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (details.chatState != null) {
|
|
||||||
stanza.addChild(
|
|
||||||
// TODO(Unknown): Move this into xep_0085.dart
|
|
||||||
XMLNode.xmlns(
|
|
||||||
tag: chatStateToString(details.chatState!),
|
|
||||||
xmlns: chatStateXmlns,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (details.fun != null) {
|
|
||||||
stanza.addChild(
|
|
||||||
XMLNode.xmlns(
|
|
||||||
tag: 'file-upload',
|
|
||||||
xmlns: fileUploadNotificationXmlns,
|
|
||||||
children: [
|
|
||||||
details.fun!.toXML(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (details.funReplacement != null) {
|
|
||||||
stanza.addChild(
|
|
||||||
XMLNode.xmlns(
|
|
||||||
tag: 'replaces',
|
|
||||||
xmlns: fileUploadNotificationXmlns,
|
|
||||||
attributes: <String, String>{
|
|
||||||
'id': details.funReplacement!,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (details.messageRetraction != null) {
|
|
||||||
stanza.addChild(
|
|
||||||
XMLNode.xmlns(
|
|
||||||
tag: 'apply-to',
|
|
||||||
xmlns: fasteningXmlns,
|
|
||||||
attributes: <String, String>{
|
|
||||||
'id': details.messageRetraction!.id,
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
XMLNode.xmlns(
|
|
||||||
tag: 'retract',
|
|
||||||
xmlns: messageRetractionXmlns,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (details.messageRetraction!.fallback != null) {
|
|
||||||
stanza.addChild(
|
|
||||||
XMLNode.xmlns(
|
|
||||||
tag: 'fallback',
|
|
||||||
xmlns: fallbackIndicationXmlns,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (details.lastMessageCorrectionId != null) {
|
|
||||||
stanza.addChild(
|
|
||||||
makeLastMessageCorrectionEdit(
|
|
||||||
details.lastMessageCorrectionId!,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (details.messageReactions != null) {
|
|
||||||
stanza.addChild(details.messageReactions!.toXml());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (details.messageProcessingHints != null) {
|
|
||||||
for (final hint in details.messageProcessingHints!) {
|
|
||||||
stanza.addChild(hint.toXml());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (details.stickerPackId != null) {
|
|
||||||
stanza.addChild(
|
|
||||||
XMLNode.xmlns(
|
|
||||||
tag: 'sticker',
|
|
||||||
xmlns: stickersXmlns,
|
|
||||||
attributes: {
|
|
||||||
'pack': details.stickerPackId!,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getAttributes().sendStanza(
|
|
||||||
StanzaDetails(
|
StanzaDetails(
|
||||||
stanza,
|
Stanza.message(
|
||||||
|
to: to.toString(),
|
||||||
|
id: extensions.get<MessageIdData>()?.id,
|
||||||
|
type: 'chat',
|
||||||
|
children: _messageSendingCallbacks
|
||||||
|
.map((c) => c(extensions))
|
||||||
|
.flattened
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
awaitable: false,
|
awaitable: false,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<XMLNode> _messageSendingCallback(
|
||||||
|
TypedMap<StanzaHandlerExtension> extensions,
|
||||||
|
) {
|
||||||
|
if (extensions.get<ReplyData>() != null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (extensions.get<StickersData>() != null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (extensions.get<StatelessFileSharingData>() != null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (extensions.get<OOBData>() != null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final data = extensions.get<MessageBodyData>();
|
||||||
|
return data != null ? [data.toXML()] : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> postRegisterCallback() async {
|
||||||
|
await super.postRegisterCallback();
|
||||||
|
|
||||||
|
// Register the sending callback
|
||||||
|
registerMessageSendingCallback(_messageSendingCallback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ const fullStanzaXmlns = 'urn:ietf:params:xml:ns:xmpp-stanzas';
|
|||||||
// RFC 6121
|
// RFC 6121
|
||||||
const rosterXmlns = 'jabber:iq:roster';
|
const rosterXmlns = 'jabber:iq:roster';
|
||||||
const rosterVersioningXmlns = 'urn:xmpp:features:rosterver';
|
const rosterVersioningXmlns = 'urn:xmpp:features:rosterver';
|
||||||
|
const subscriptionPreApprovalXmlns = 'urn:xmpp:features:pre-approval';
|
||||||
|
|
||||||
// XEP-0004
|
// XEP-0004
|
||||||
const dataFormsXmlns = 'jabber:x:data';
|
const dataFormsXmlns = 'jabber:x:data';
|
||||||
@ -96,7 +97,7 @@ const httpFileUploadXmlns = 'urn:xmpp:http:upload:0';
|
|||||||
// XEP-0372
|
// XEP-0372
|
||||||
const referenceXmlns = 'urn:xmpp:reference:0';
|
const referenceXmlns = 'urn:xmpp:reference:0';
|
||||||
|
|
||||||
// XEP-380
|
// XEP-0380
|
||||||
const emeXmlns = 'urn:xmpp:eme:0';
|
const emeXmlns = 'urn:xmpp:eme:0';
|
||||||
const emeOtr = 'urn:xmpp:otr:0';
|
const emeOtr = 'urn:xmpp:otr:0';
|
||||||
const emeLegacyOpenPGP = 'jabber:x:encrypted';
|
const emeLegacyOpenPGP = 'jabber:x:encrypted';
|
||||||
|
@ -1 +0,0 @@
|
|||||||
|
|
@ -11,3 +11,4 @@ const sasl2Negotiator = 'org.moxxmpp.sasl.sasl2';
|
|||||||
const bind2Negotiator = 'org.moxxmpp.bind2';
|
const bind2Negotiator = 'org.moxxmpp.bind2';
|
||||||
const saslFASTNegotiator = 'org.moxxmpp.sasl.fast';
|
const saslFASTNegotiator = 'org.moxxmpp.sasl.fast';
|
||||||
const carbonsNegotiator = 'org.moxxmpp.bind2.carbons';
|
const carbonsNegotiator = 'org.moxxmpp.bind2.carbons';
|
||||||
|
const presenceNegotiator = 'org.moxxmpp.core.presence';
|
||||||
|
@ -6,14 +6,44 @@ import 'package:moxxmpp/src/managers/data.dart';
|
|||||||
import 'package:moxxmpp/src/managers/handlers.dart';
|
import 'package:moxxmpp/src/managers/handlers.dart';
|
||||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
|
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
||||||
|
import 'package:moxxmpp/src/negotiators/negotiator.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';
|
||||||
|
import 'package:moxxmpp/src/types/result.dart';
|
||||||
|
|
||||||
/// A function that will be called when presence, outside of subscription request
|
/// A function that will be called when presence, outside of subscription request
|
||||||
/// management, will be sent. Useful for managers that want to add [XMLNode]s to said
|
/// management, will be sent. Useful for managers that want to add [XMLNode]s to said
|
||||||
/// presence.
|
/// presence.
|
||||||
typedef PresencePreSendCallback = Future<List<XMLNode>> Function();
|
typedef PresencePreSendCallback = Future<List<XMLNode>> Function();
|
||||||
|
|
||||||
|
/// A pseudo-negotiator that does not really negotiate anything. Instead, its purpose
|
||||||
|
/// is to look for a stream feature indicating that we can pre-approve subscription
|
||||||
|
/// requests, shown by [PresenceNegotiator.preApprovalSupported].
|
||||||
|
class PresenceNegotiator extends XmppFeatureNegotiatorBase {
|
||||||
|
PresenceNegotiator()
|
||||||
|
: super(11, false, subscriptionPreApprovalXmlns, presenceNegotiator);
|
||||||
|
|
||||||
|
/// Flag indicating whether presence subscription pre-approval is supported
|
||||||
|
bool _supported = false;
|
||||||
|
bool get preApprovalSupported => _supported;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Result<NegotiatorState, NegotiatorError>> negotiate(
|
||||||
|
XMLNode nonza,
|
||||||
|
) async {
|
||||||
|
_supported = true;
|
||||||
|
return const Result(NegotiatorState.done);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void reset() {
|
||||||
|
_supported = false;
|
||||||
|
|
||||||
|
super.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A mandatory manager that handles initial presence sending, sending of subscription
|
/// A mandatory manager that handles initial presence sending, sending of subscription
|
||||||
/// request management requests and triggers events for incoming presence stanzas.
|
/// request management requests and triggers events for incoming presence stanzas.
|
||||||
class PresenceManager extends XmppManagerBase {
|
class PresenceManager extends XmppManagerBase {
|
||||||
@ -23,11 +53,17 @@ class PresenceManager extends XmppManagerBase {
|
|||||||
final List<PresencePreSendCallback> _presenceCallbacks =
|
final List<PresencePreSendCallback> _presenceCallbacks =
|
||||||
List.empty(growable: true);
|
List.empty(growable: true);
|
||||||
|
|
||||||
|
/// The priority of the presence handler. If a handler should run before this one,
|
||||||
|
/// which terminates processing, make sure the handler has a priority greater than
|
||||||
|
/// [presenceHandlerPriority].
|
||||||
|
static int presenceHandlerPriority = -100;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<StanzaHandler> getIncomingStanzaHandlers() => [
|
List<StanzaHandler> getIncomingStanzaHandlers() => [
|
||||||
StanzaHandler(
|
StanzaHandler(
|
||||||
stanzaTag: 'presence',
|
stanzaTag: 'presence',
|
||||||
callback: _onPresence,
|
callback: _onPresence,
|
||||||
|
priority: presenceHandlerPriority,
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -66,7 +102,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;
|
||||||
@ -75,10 +111,7 @@ class PresenceManager extends XmppManagerBase {
|
|||||||
if (presence.from != null) {
|
if (presence.from != null) {
|
||||||
logger.finest("Received presence from '${presence.from}'");
|
logger.finest("Received presence from '${presence.from}'");
|
||||||
|
|
||||||
getAttributes().sendEvent(
|
return state..done = true;
|
||||||
PresenceReceivedEvent(JID.fromString(presence.from!), presence),
|
|
||||||
);
|
|
||||||
return state.copyWith(done: true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
@ -112,24 +145,82 @@ class PresenceManager extends XmppManagerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Send an unavailable presence with no 'to' attribute.
|
/// Send an unavailable presence with no 'to' attribute.
|
||||||
void sendUnavailablePresence() {
|
Future<void> sendUnavailablePresence() async {
|
||||||
getAttributes().sendStanza(
|
// Bypass the queue so that this get's sent immediately.
|
||||||
|
// If we do it like this, we can also block the disconnection
|
||||||
|
// until we're actually ready.
|
||||||
|
await getAttributes().sendStanza(
|
||||||
StanzaDetails(
|
StanzaDetails(
|
||||||
Stanza.presence(
|
Stanza.presence(
|
||||||
type: 'unavailable',
|
type: 'unavailable',
|
||||||
),
|
),
|
||||||
awaitable: false,
|
awaitable: false,
|
||||||
|
bypassQueue: true,
|
||||||
|
excludeFromStreamManagement: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Similar to [requestSubscription], but it also tells the server to automatically
|
||||||
|
/// accept a subscription request from [to], should it arrive.
|
||||||
|
/// This requires a [PresenceNegotiator] to be registered as this feature is optional.
|
||||||
|
///
|
||||||
|
/// Returns true, when the stanza was sent. Returns false, when the stanza was not sent,
|
||||||
|
/// for example because the server does not support subscription pre-approvals.
|
||||||
|
Future<bool> preApproveSubscription(JID to) async {
|
||||||
|
final negotiator = getAttributes()
|
||||||
|
.getNegotiatorById<PresenceNegotiator>(presenceNegotiator);
|
||||||
|
assert(negotiator != null, 'No PresenceNegotiator registered');
|
||||||
|
|
||||||
|
if (!negotiator!.preApprovalSupported) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await getAttributes().sendStanza(
|
||||||
|
StanzaDetails(
|
||||||
|
Stanza.presence(
|
||||||
|
type: 'subscribed',
|
||||||
|
to: to.toString(),
|
||||||
|
),
|
||||||
|
awaitable: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends a subscription request to [to].
|
||||||
|
Future<void> requestSubscription(JID to) async {
|
||||||
|
await getAttributes().sendStanza(
|
||||||
|
StanzaDetails(
|
||||||
|
Stanza.presence(
|
||||||
|
type: 'subscribe',
|
||||||
|
to: to.toString(),
|
||||||
|
),
|
||||||
|
awaitable: false,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends a subscription request to [to].
|
/// Accept a subscription request from [to].
|
||||||
void sendSubscriptionRequest(String to) {
|
Future<void> acceptSubscriptionRequest(JID to) async {
|
||||||
getAttributes().sendStanza(
|
await getAttributes().sendStanza(
|
||||||
StanzaDetails(
|
StanzaDetails(
|
||||||
Stanza.presence(
|
Stanza.presence(
|
||||||
type: 'subscribe',
|
type: 'subscribed',
|
||||||
to: to,
|
to: to.toString(),
|
||||||
|
),
|
||||||
|
awaitable: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a subscription request rejection to [to].
|
||||||
|
Future<void> rejectSubscriptionRequest(JID to) async {
|
||||||
|
await getAttributes().sendStanza(
|
||||||
|
StanzaDetails(
|
||||||
|
Stanza.presence(
|
||||||
|
type: 'unsubscribed',
|
||||||
|
to: to.toString(),
|
||||||
),
|
),
|
||||||
awaitable: false,
|
awaitable: false,
|
||||||
),
|
),
|
||||||
@ -137,38 +228,12 @@ class PresenceManager extends XmppManagerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Sends an unsubscription request to [to].
|
/// Sends an unsubscription request to [to].
|
||||||
void sendUnsubscriptionRequest(String to) {
|
Future<void> unsubscribe(JID to) async {
|
||||||
getAttributes().sendStanza(
|
await getAttributes().sendStanza(
|
||||||
StanzaDetails(
|
StanzaDetails(
|
||||||
Stanza.presence(
|
Stanza.presence(
|
||||||
type: 'unsubscribe',
|
type: 'unsubscribe',
|
||||||
to: to,
|
to: to.toString(),
|
||||||
),
|
|
||||||
awaitable: false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Accept a presence subscription request for [to].
|
|
||||||
void sendSubscriptionRequestApproval(String to) {
|
|
||||||
getAttributes().sendStanza(
|
|
||||||
StanzaDetails(
|
|
||||||
Stanza.presence(
|
|
||||||
type: 'subscribed',
|
|
||||||
to: to,
|
|
||||||
),
|
|
||||||
awaitable: false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reject a presence subscription request for [to].
|
|
||||||
void sendSubscriptionRequestRejection(String to) {
|
|
||||||
getAttributes().sendStanza(
|
|
||||||
StanzaDetails(
|
|
||||||
Stanza.presence(
|
|
||||||
type: 'unsubscribed',
|
|
||||||
to: to,
|
|
||||||
),
|
),
|
||||||
awaitable: false,
|
awaitable: false,
|
||||||
),
|
),
|
||||||
|
@ -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
|
||||||
@ -223,15 +223,21 @@ class RosterManager extends XmppManagerBase {
|
|||||||
return Result(result);
|
return Result(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Requests the roster following RFC 6121.
|
/// Requests the roster following RFC 6121. If [useRosterVersion] is set to false, then
|
||||||
Future<Result<RosterRequestResult, RosterError>> requestRoster() async {
|
/// roster versioning will not be used, even if the server supports it and we have a last
|
||||||
|
/// known roster version.
|
||||||
|
Future<Result<RosterRequestResult, RosterError>> requestRoster({
|
||||||
|
bool useRosterVersion = true,
|
||||||
|
}) async {
|
||||||
final attrs = getAttributes();
|
final attrs = getAttributes();
|
||||||
final query = XMLNode.xmlns(
|
final query = XMLNode.xmlns(
|
||||||
tag: 'query',
|
tag: 'query',
|
||||||
xmlns: rosterXmlns,
|
xmlns: rosterXmlns,
|
||||||
);
|
);
|
||||||
final rosterVersion = await _stateManager.getRosterVersion();
|
final rosterVersion = await _stateManager.getRosterVersion();
|
||||||
if (rosterVersion != null && rosterVersioningAvailable()) {
|
if (rosterVersion != null &&
|
||||||
|
rosterVersioningAvailable() &&
|
||||||
|
useRosterVersion) {
|
||||||
query.attributes['ver'] = rosterVersion;
|
query.attributes['ver'] = rosterVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,8 @@ class StanzaDetails {
|
|||||||
this.awaitable = true,
|
this.awaitable = true,
|
||||||
this.encrypted = false,
|
this.encrypted = false,
|
||||||
this.forceEncryption = false,
|
this.forceEncryption = false,
|
||||||
|
this.bypassQueue = false,
|
||||||
|
this.excludeFromStreamManagement = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The stanza to send.
|
/// The stanza to send.
|
||||||
@ -23,6 +25,16 @@ class StanzaDetails {
|
|||||||
final bool encrypted;
|
final bool encrypted;
|
||||||
|
|
||||||
final bool forceEncryption;
|
final bool forceEncryption;
|
||||||
|
|
||||||
|
/// Bypasses being put into the queue. Useful for sending stanzas that must go out
|
||||||
|
/// now, where it's okay if it does not get sent.
|
||||||
|
/// This should never have to be set to true.
|
||||||
|
final bool bypassQueue;
|
||||||
|
|
||||||
|
/// This makes the Stream Management implementation, when available, ignore the stanza,
|
||||||
|
/// meaning that it gets counted but excluded from resending.
|
||||||
|
/// This should never have to be set to true.
|
||||||
|
final bool excludeFromStreamManagement;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A simple description of the <error /> element that may be inside a stanza
|
/// A simple description of the <error /> element that may be inside a stanza
|
||||||
|
23
packages/moxxmpp/lib/src/util/typed_map.dart
Normal file
23
packages/moxxmpp/lib/src/util/typed_map.dart
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/// A map, similar to Map, but always uses the type of the value as the key.
|
||||||
|
class TypedMap<B> {
|
||||||
|
/// Create an empty typed map.
|
||||||
|
TypedMap();
|
||||||
|
|
||||||
|
/// Create a typed map from a list of values.
|
||||||
|
TypedMap.fromList(List<B> items) {
|
||||||
|
for (final item in items) {
|
||||||
|
_data[item.runtimeType] = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The internal mapping of type -> data
|
||||||
|
final Map<Object, B> _data = {};
|
||||||
|
|
||||||
|
/// Associate the type of [value] with [value] in the map.
|
||||||
|
void set<T extends B>(T value) {
|
||||||
|
_data[T] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the object of type [T] from the map, if it has been stored.
|
||||||
|
T? get<T>() => _data[T] as T?;
|
||||||
|
}
|
@ -2,14 +2,70 @@ 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';
|
||||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||||
|
import 'package:moxxmpp/src/message.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
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/util/typed_map.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 implements StanzaHandlerExtension {
|
||||||
|
const FileUploadNotificationData(this.metadata);
|
||||||
|
|
||||||
|
/// The file metadata indicated in the upload notification.
|
||||||
|
final FileMetadataData metadata;
|
||||||
|
|
||||||
|
XMLNode toXML() {
|
||||||
|
return XMLNode.xmlns(
|
||||||
|
tag: 'file-upload',
|
||||||
|
xmlns: fileUploadNotificationXmlns,
|
||||||
|
children: [
|
||||||
|
metadata.toXML(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indicates that a file upload has been cancelled.
|
||||||
|
class FileUploadNotificationCancellationData implements StanzaHandlerExtension {
|
||||||
|
const FileUploadNotificationCancellationData(this.id);
|
||||||
|
|
||||||
|
/// The id of the upload notifiaction that is cancelled.
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
XMLNode toXML() {
|
||||||
|
return XMLNode.xmlns(
|
||||||
|
tag: 'cancelled',
|
||||||
|
xmlns: fileUploadNotificationXmlns,
|
||||||
|
attributes: {
|
||||||
|
'id': id,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indicates that a file upload has been completed.
|
||||||
|
class FileUploadNotificationReplacementData implements StanzaHandlerExtension {
|
||||||
|
const FileUploadNotificationReplacementData(this.id);
|
||||||
|
|
||||||
|
/// The id of the upload notifiaction that is replaced.
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
XMLNode toXML() {
|
||||||
|
return XMLNode.xmlns(
|
||||||
|
tag: 'replaces',
|
||||||
|
xmlns: fileUploadNotificationXmlns,
|
||||||
|
attributes: {
|
||||||
|
'id': id,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class FileUploadNotificationManager extends XmppManagerBase {
|
class FileUploadNotificationManager extends XmppManagerBase {
|
||||||
FileUploadNotificationManager() : super(fileUploadNotificationManager);
|
FileUploadNotificationManager() : super(fileUploadNotificationManager);
|
||||||
|
|
||||||
@ -47,11 +103,14 @@ 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(
|
||||||
funElement.firstTag('file', xmlns: fileMetadataXmlns)!,
|
FileUploadNotificationData(
|
||||||
),
|
FileMetadataData.fromXML(
|
||||||
);
|
funElement.firstTag('file', xmlns: fileMetadataXmlns)!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<StanzaHandlerData> _onFileUploadNotificationReplacementReceived(
|
Future<StanzaHandlerData> _onFileUploadNotificationReplacementReceived(
|
||||||
@ -60,9 +119,12 @@ 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,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<StanzaHandlerData> _onFileUploadNotificationCancellationReceived(
|
Future<StanzaHandlerData> _onFileUploadNotificationCancellationReceived(
|
||||||
@ -71,8 +133,42 @@ 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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<XMLNode> _messageSendingCallback(
|
||||||
|
TypedMap<StanzaHandlerExtension> extensions,
|
||||||
|
) {
|
||||||
|
final fun = extensions.get<FileUploadNotificationData>();
|
||||||
|
if (fun != null) {
|
||||||
|
return [fun.toXML()];
|
||||||
|
}
|
||||||
|
|
||||||
|
final cancel = extensions.get<FileUploadNotificationCancellationData>();
|
||||||
|
if (cancel != null) {
|
||||||
|
return [cancel.toXML()];
|
||||||
|
}
|
||||||
|
|
||||||
|
final replace = extensions.get<FileUploadNotificationReplacementData>();
|
||||||
|
if (replace != null) {
|
||||||
|
return [replace.toXML()];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> postRegisterCallback() async {
|
||||||
|
await super.postRegisterCallback();
|
||||||
|
|
||||||
|
// Register the sending callback
|
||||||
|
getAttributes()
|
||||||
|
.getManagerById<MessageManager>(messageManager)
|
||||||
|
?.registerMessageSendingCallback(_messageSendingCallback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -66,11 +66,7 @@ class VCardManager extends XmppManagerBase {
|
|||||||
final binval = vcardResult.get<VCard>().photo?.binval;
|
final binval = vcardResult.get<VCard>().photo?.binval;
|
||||||
if (binval != null) {
|
if (binval != null) {
|
||||||
getAttributes().sendEvent(
|
getAttributes().sendEvent(
|
||||||
AvatarUpdatedEvent(
|
VCardAvatarUpdatedEvent(JID.fromString(from), binval, hash),
|
||||||
jid: from,
|
|
||||||
base64: binval,
|
|
||||||
hash: hash,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
logger.warning('No avatar data found');
|
logger.warning('No avatar data found');
|
||||||
@ -80,7 +76,7 @@ class VCardManager extends XmppManagerBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return state.copyWith(done: true);
|
return state..done = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
VCardPhoto? _parseVCardPhoto(XMLNode? node) {
|
VCardPhoto? _parseVCardPhoto(XMLNode? node) {
|
||||||
|
@ -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 {
|
||||||
@ -179,13 +179,13 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Result<PubSubError, bool>> subscribe(String jid, String node) async {
|
Future<Result<PubSubError, bool>> subscribe(JID jid, String node) async {
|
||||||
final attrs = getAttributes();
|
final attrs = getAttributes();
|
||||||
final result = (await attrs.sendStanza(
|
final result = (await attrs.sendStanza(
|
||||||
StanzaDetails(
|
StanzaDetails(
|
||||||
Stanza.iq(
|
Stanza.iq(
|
||||||
type: 'set',
|
type: 'set',
|
||||||
to: jid,
|
to: jid.toString(),
|
||||||
children: [
|
children: [
|
||||||
XMLNode.xmlns(
|
XMLNode.xmlns(
|
||||||
tag: 'pubsub',
|
tag: 'pubsub',
|
||||||
@ -222,13 +222,13 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
return Result(subscription.attributes['subscription'] == 'subscribed');
|
return Result(subscription.attributes['subscription'] == 'subscribed');
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Result<PubSubError, bool>> unsubscribe(String jid, String node) async {
|
Future<Result<PubSubError, bool>> unsubscribe(JID jid, String node) async {
|
||||||
final attrs = getAttributes();
|
final attrs = getAttributes();
|
||||||
final result = (await attrs.sendStanza(
|
final result = (await attrs.sendStanza(
|
||||||
StanzaDetails(
|
StanzaDetails(
|
||||||
Stanza.iq(
|
Stanza.iq(
|
||||||
type: 'set',
|
type: 'set',
|
||||||
to: jid,
|
to: jid.toString(),
|
||||||
children: [
|
children: [
|
||||||
XMLNode.xmlns(
|
XMLNode.xmlns(
|
||||||
tag: 'pubsub',
|
tag: 'pubsub',
|
||||||
@ -398,14 +398,14 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<Result<PubSubError, List<PubSubItem>>> getItems(
|
Future<Result<PubSubError, List<PubSubItem>>> getItems(
|
||||||
String jid,
|
JID jid,
|
||||||
String node,
|
String node,
|
||||||
) async {
|
) async {
|
||||||
final result = (await getAttributes().sendStanza(
|
final result = (await getAttributes().sendStanza(
|
||||||
StanzaDetails(
|
StanzaDetails(
|
||||||
Stanza.iq(
|
Stanza.iq(
|
||||||
type: 'get',
|
type: 'get',
|
||||||
to: jid,
|
to: jid.toString(),
|
||||||
children: [
|
children: [
|
||||||
XMLNode.xmlns(
|
XMLNode.xmlns(
|
||||||
tag: 'pubsub',
|
tag: 'pubsub',
|
||||||
|
@ -2,32 +2,32 @@ 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';
|
||||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||||
|
import 'package:moxxmpp/src/message.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
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';
|
||||||
|
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||||
|
|
||||||
/// A data class representing the jabber:x:oob tag.
|
/// A data class representing the jabber:x:oob tag.
|
||||||
class OOBData {
|
class OOBData implements StanzaHandlerExtension {
|
||||||
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) {
|
XMLNode toXML() {
|
||||||
final children = List<XMLNode>.empty(growable: true);
|
return XMLNode.xmlns(
|
||||||
|
tag: 'x',
|
||||||
if (data.url != null) {
|
xmlns: oobDataXmlns,
|
||||||
children.add(XMLNode(tag: 'url', text: data.url));
|
children: [
|
||||||
|
if (url != null) XMLNode(tag: 'url', text: url),
|
||||||
|
if (desc != null) XMLNode(tag: 'desc', text: desc),
|
||||||
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (data.desc != null) {
|
|
||||||
children.add(XMLNode(tag: 'desc', text: data.desc));
|
|
||||||
}
|
|
||||||
|
|
||||||
return XMLNode.xmlns(
|
|
||||||
tag: 'x',
|
|
||||||
xmlns: oobDataXmlns,
|
|
||||||
children: children,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class OOBManager extends XmppManagerBase {
|
class OOBManager extends XmppManagerBase {
|
||||||
@ -59,11 +59,33 @@ 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(),
|
||||||
);
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<XMLNode> _messageSendingCallback(
|
||||||
|
TypedMap<StanzaHandlerExtension> extensions,
|
||||||
|
) {
|
||||||
|
final data = extensions.get<OOBData>();
|
||||||
|
return data != null
|
||||||
|
? [
|
||||||
|
data.toXML(),
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> postRegisterCallback() async {
|
||||||
|
await super.postRegisterCallback();
|
||||||
|
|
||||||
|
// Register the sending callback
|
||||||
|
getAttributes()
|
||||||
|
.getManagerById<MessageManager>(messageManager)
|
||||||
|
?.registerMessageSendingCallback(_messageSendingCallback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:convert';
|
||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/events.dart';
|
||||||
import 'package:moxxmpp/src/jid.dart';
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
import 'package:moxxmpp/src/managers/base.dart';
|
import 'package:moxxmpp/src/managers/base.dart';
|
||||||
@ -15,10 +16,17 @@ abstract class AvatarError {}
|
|||||||
|
|
||||||
class UnknownAvatarError extends AvatarError {}
|
class UnknownAvatarError extends AvatarError {}
|
||||||
|
|
||||||
class UserAvatar {
|
class UserAvatarData {
|
||||||
const UserAvatar({required this.base64, required this.hash});
|
const UserAvatarData(this.base64, this.hash);
|
||||||
|
|
||||||
|
/// The base64-encoded avatar data.
|
||||||
final String base64;
|
final String base64;
|
||||||
|
|
||||||
|
/// The SHA-1 hash of the raw avatar data.
|
||||||
final String hash;
|
final String hash;
|
||||||
|
|
||||||
|
/// The raw avatar data.
|
||||||
|
List<int> get data => base64Decode(base64);
|
||||||
}
|
}
|
||||||
|
|
||||||
class UserAvatarMetadata {
|
class UserAvatarMetadata {
|
||||||
@ -27,21 +35,44 @@ class UserAvatarMetadata {
|
|||||||
this.length,
|
this.length,
|
||||||
this.width,
|
this.width,
|
||||||
this.height,
|
this.height,
|
||||||
this.mime,
|
this.type,
|
||||||
|
this.url,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// The amount of bytes in the file
|
factory UserAvatarMetadata.fromXML(XMLNode node) {
|
||||||
|
assert(
|
||||||
|
node.tag == 'metadata' &&
|
||||||
|
node.attributes['xmlns'] == userAvatarMetadataXmlns,
|
||||||
|
'<metadata /> element required',
|
||||||
|
);
|
||||||
|
|
||||||
|
final width = node.attributes['width'] as String?;
|
||||||
|
final height = node.attributes['height'] as String?;
|
||||||
|
return UserAvatarMetadata(
|
||||||
|
node.attributes['id']! as String,
|
||||||
|
int.parse(node.attributes['bytes']! as String),
|
||||||
|
width != null ? int.parse(width) : null,
|
||||||
|
height != null ? int.parse(height) : null,
|
||||||
|
node.attributes['type']! as String,
|
||||||
|
node.attributes['url'] as String?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The amount of bytes in the file.
|
||||||
final int length;
|
final int length;
|
||||||
|
|
||||||
/// The identifier of the avatar
|
/// The identifier of the avatar.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
/// Image proportions
|
/// Image proportions.
|
||||||
final int width;
|
final int? width;
|
||||||
final int height;
|
final int? height;
|
||||||
|
|
||||||
/// The MIME type of the avatar
|
/// The URL where the avatar can be found.
|
||||||
final String mime;
|
final String? url;
|
||||||
|
|
||||||
|
/// The MIME type of the avatar.
|
||||||
|
final String type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// NOTE: This class requires a PubSubManager
|
/// NOTE: This class requires a PubSubManager
|
||||||
@ -51,13 +82,18 @@ class UserAvatarManager extends XmppManagerBase {
|
|||||||
PubSubManager _getPubSubManager() =>
|
PubSubManager _getPubSubManager() =>
|
||||||
getAttributes().getManagerById(pubsubManager)! as PubSubManager;
|
getAttributes().getManagerById(pubsubManager)! as PubSubManager;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<String> getDiscoFeatures() => [
|
||||||
|
'$userAvatarMetadataXmlns+notify',
|
||||||
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onXmppEvent(XmppEvent event) async {
|
Future<void> onXmppEvent(XmppEvent event) async {
|
||||||
if (event is PubSubNotificationEvent) {
|
if (event is PubSubNotificationEvent) {
|
||||||
if (event.item.node != userAvatarDataXmlns) return;
|
if (event.item.node != userAvatarMetadataXmlns) return;
|
||||||
|
|
||||||
if (event.item.payload.tag != 'data' ||
|
if (event.item.payload.tag != 'metadata' ||
|
||||||
event.item.payload.attributes['xmlns'] != userAvatarDataXmlns) {
|
event.item.payload.attributes['xmlns'] != userAvatarMetadataXmlns) {
|
||||||
logger.warning(
|
logger.warning(
|
||||||
'Received avatar update from ${event.from} but the payload is invalid. Ignoring...',
|
'Received avatar update from ${event.from} but the payload is invalid. Ignoring...',
|
||||||
);
|
);
|
||||||
@ -65,10 +101,12 @@ class UserAvatarManager extends XmppManagerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getAttributes().sendEvent(
|
getAttributes().sendEvent(
|
||||||
AvatarUpdatedEvent(
|
UserAvatarUpdatedEvent(
|
||||||
jid: event.from,
|
JID.fromString(event.from),
|
||||||
base64: event.item.payload.innerText(),
|
event.item.payload
|
||||||
hash: event.item.id,
|
.findTags('metadata', xmlns: userAvatarMetadataXmlns)
|
||||||
|
.map(UserAvatarMetadata.fromXML)
|
||||||
|
.toList(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -80,7 +118,7 @@ class UserAvatarManager extends XmppManagerBase {
|
|||||||
|
|
||||||
/// Requests the avatar from [jid]. Returns the avatar data if the request was
|
/// Requests the avatar from [jid]. Returns the avatar data if the request was
|
||||||
/// successful. Null otherwise
|
/// successful. Null otherwise
|
||||||
Future<Result<AvatarError, UserAvatar>> getUserAvatar(String jid) async {
|
Future<Result<AvatarError, UserAvatarData>> getUserAvatar(JID jid) async {
|
||||||
final pubsub = _getPubSubManager();
|
final pubsub = _getPubSubManager();
|
||||||
final resultsRaw = await pubsub.getItems(jid, userAvatarDataXmlns);
|
final resultsRaw = await pubsub.getItems(jid, userAvatarDataXmlns);
|
||||||
if (resultsRaw.isType<PubSubError>()) return Result(UnknownAvatarError());
|
if (resultsRaw.isType<PubSubError>()) return Result(UnknownAvatarError());
|
||||||
@ -90,9 +128,9 @@ class UserAvatarManager extends XmppManagerBase {
|
|||||||
|
|
||||||
final item = results[0];
|
final item = results[0];
|
||||||
return Result(
|
return Result(
|
||||||
UserAvatar(
|
UserAvatarData(
|
||||||
base64: item.payload.innerText(),
|
item.payload.innerText(),
|
||||||
hash: item.id,
|
item.id,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -146,7 +184,7 @@ class UserAvatarManager extends XmppManagerBase {
|
|||||||
'bytes': metadata.length.toString(),
|
'bytes': metadata.length.toString(),
|
||||||
'height': metadata.height.toString(),
|
'height': metadata.height.toString(),
|
||||||
'width': metadata.width.toString(),
|
'width': metadata.width.toString(),
|
||||||
'type': metadata.mime,
|
'type': metadata.type,
|
||||||
'id': metadata.id,
|
'id': metadata.id,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -163,14 +201,14 @@ class UserAvatarManager extends XmppManagerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Subscribe the data and metadata node of [jid].
|
/// Subscribe the data and metadata node of [jid].
|
||||||
Future<Result<AvatarError, bool>> subscribe(String jid) async {
|
Future<Result<AvatarError, bool>> subscribe(JID jid) async {
|
||||||
await _getPubSubManager().subscribe(jid, userAvatarMetadataXmlns);
|
await _getPubSubManager().subscribe(jid, userAvatarMetadataXmlns);
|
||||||
|
|
||||||
return const Result(true);
|
return const Result(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unsubscribe the data and metadata node of [jid].
|
/// Unsubscribe the data and metadata node of [jid].
|
||||||
Future<Result<AvatarError, bool>> unsubscribe(String jid) async {
|
Future<Result<AvatarError, bool>> unsubscribe(JID jid) async {
|
||||||
await _getPubSubManager().subscribe(jid, userAvatarMetadataXmlns);
|
await _getPubSubManager().subscribe(jid, userAvatarMetadataXmlns);
|
||||||
|
|
||||||
return const Result(true);
|
return const Result(true);
|
||||||
|
@ -2,43 +2,59 @@ 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';
|
||||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||||
|
import 'package:moxxmpp/src/message.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
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';
|
||||||
|
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||||
|
|
||||||
enum ChatState { active, composing, paused, inactive, gone }
|
enum ChatState implements StanzaHandlerExtension {
|
||||||
|
active,
|
||||||
|
composing,
|
||||||
|
paused,
|
||||||
|
inactive,
|
||||||
|
gone;
|
||||||
|
|
||||||
ChatState chatStateFromString(String raw) {
|
factory ChatState.fromName(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:
|
|
||||||
{
|
throw Exception('Invalid chat state $state');
|
||||||
return ChatState.gone;
|
}
|
||||||
}
|
|
||||||
|
String toName() {
|
||||||
|
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: toName(),
|
||||||
|
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,62 +80,55 @@ class ChatStateManager extends XmppManagerBase {
|
|||||||
StanzaHandlerData state,
|
StanzaHandlerData state,
|
||||||
) async {
|
) async {
|
||||||
final element = state.stanza.firstTagByXmlns(chatStateXmlns)!;
|
final element = state.stanza.firstTagByXmlns(chatStateXmlns)!;
|
||||||
ChatState? chatState;
|
|
||||||
|
|
||||||
switch (element.tag) {
|
try {
|
||||||
case 'active':
|
state.extensions.set(ChatState.fromName(element.tag));
|
||||||
{
|
} catch (_) {
|
||||||
chatState = ChatState.active;
|
logger.finest('Ignoring invalid chat state ${element.tag}');
|
||||||
}
|
|
||||||
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);
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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),
|
state.toXML(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
awaitable: false,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<XMLNode> _messageSendingCallback(
|
||||||
|
TypedMap<StanzaHandlerExtension> extensions,
|
||||||
|
) {
|
||||||
|
final data = extensions.get<ChatState>();
|
||||||
|
return data != null
|
||||||
|
? [
|
||||||
|
data.toXML(),
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> postRegisterCallback() async {
|
||||||
|
await super.postRegisterCallback();
|
||||||
|
|
||||||
|
// Register the sending callback
|
||||||
|
getAttributes()
|
||||||
|
.getManagerById<MessageManager>(messageManager)
|
||||||
|
?.registerMessageSendingCallback(_messageSendingCallback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,13 @@ import 'package:meta/meta.dart';
|
|||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/events.dart';
|
||||||
import 'package:moxxmpp/src/jid.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/handlers.dart';
|
||||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/presence.dart';
|
import 'package:moxxmpp/src/presence.dart';
|
||||||
import 'package:moxxmpp/src/rfcs/rfc_4790.dart';
|
import 'package:moxxmpp/src/rfcs/rfc_4790.dart';
|
||||||
|
import 'package:moxxmpp/src/stanza.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
import 'package:moxxmpp/src/util/list.dart';
|
import 'package:moxxmpp/src/util/list.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0004.dart';
|
import 'package:moxxmpp/src/xeps/xep_0004.dart';
|
||||||
@ -105,7 +108,20 @@ class EntityCapabilitiesManager extends XmppManagerBase {
|
|||||||
Future<bool> isSupported() async => true;
|
Future<bool> isSupported() async => true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<String> getDiscoFeatures() => [capsXmlns];
|
List<String> getDiscoFeatures() => [
|
||||||
|
capsXmlns,
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<StanzaHandler> getIncomingStanzaHandlers() => [
|
||||||
|
StanzaHandler(
|
||||||
|
stanzaTag: 'presence',
|
||||||
|
tagName: 'c',
|
||||||
|
tagXmlns: capsXmlns,
|
||||||
|
callback: onPresence,
|
||||||
|
priority: PresenceManager.presenceHandlerPriority + 1,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
/// Computes, if required, the capability hash of the data provided by
|
/// Computes, if required, the capability hash of the data provided by
|
||||||
/// the DiscoManager.
|
/// the DiscoManager.
|
||||||
@ -159,33 +175,38 @@ class EntityCapabilitiesManager extends XmppManagerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
Future<void> onPresence(PresenceReceivedEvent event) async {
|
Future<StanzaHandlerData> onPresence(
|
||||||
final c = event.presence.firstTag('c', xmlns: capsXmlns);
|
Stanza stanza,
|
||||||
if (c == null) {
|
StanzaHandlerData state,
|
||||||
return;
|
) async {
|
||||||
|
if (stanza.from == null) {
|
||||||
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final from = JID.fromString(stanza.from!);
|
||||||
|
final c = stanza.firstTag('c', xmlns: capsXmlns)!;
|
||||||
|
|
||||||
final hashFunctionName = c.attributes['hash'] as String?;
|
final hashFunctionName = c.attributes['hash'] as String?;
|
||||||
final capabilityNode = c.attributes['node'] as String?;
|
final capabilityNode = c.attributes['node'] as String?;
|
||||||
final ver = c.attributes['ver'] as String?;
|
final ver = c.attributes['ver'] as String?;
|
||||||
if (hashFunctionName == null || capabilityNode == null || ver == null) {
|
if (hashFunctionName == null || capabilityNode == null || ver == null) {
|
||||||
return;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we know of the hash
|
// Check if we know of the hash
|
||||||
final isCached =
|
final isCached =
|
||||||
await _cacheLock.synchronized(() => _capHashCache.containsKey(ver));
|
await _cacheLock.synchronized(() => _capHashCache.containsKey(ver));
|
||||||
if (isCached) {
|
if (isCached) {
|
||||||
return;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
final dm = getAttributes().getManagerById<DiscoManager>(discoManager)!;
|
final dm = getAttributes().getManagerById<DiscoManager>(discoManager)!;
|
||||||
final discoRequest = await dm.discoInfoQuery(
|
final discoRequest = await dm.discoInfoQuery(
|
||||||
event.jid,
|
from,
|
||||||
node: capabilityNode,
|
node: capabilityNode,
|
||||||
);
|
);
|
||||||
if (discoRequest.isType<DiscoError>()) {
|
if (discoRequest.isType<DiscoError>()) {
|
||||||
return;
|
return state;
|
||||||
}
|
}
|
||||||
final discoInfo = discoRequest.get<DiscoInfo>();
|
final discoInfo = discoRequest.get<DiscoInfo>();
|
||||||
|
|
||||||
@ -194,13 +215,13 @@ class EntityCapabilitiesManager extends XmppManagerBase {
|
|||||||
await dm.addCachedDiscoInfo(
|
await dm.addCachedDiscoInfo(
|
||||||
MapEntry<DiscoCacheKey, DiscoInfo>(
|
MapEntry<DiscoCacheKey, DiscoInfo>(
|
||||||
DiscoCacheKey(
|
DiscoCacheKey(
|
||||||
event.jid,
|
from,
|
||||||
null,
|
null,
|
||||||
),
|
),
|
||||||
discoInfo,
|
discoInfo,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the disco#info result according to XEP-0115 § 5.4
|
// Validate the disco#info result according to XEP-0115 § 5.4
|
||||||
@ -214,7 +235,7 @@ class EntityCapabilitiesManager extends XmppManagerBase {
|
|||||||
logger.warning(
|
logger.warning(
|
||||||
'Malformed disco#info response: More than one equal identity',
|
'Malformed disco#info response: More than one equal identity',
|
||||||
);
|
);
|
||||||
return;
|
return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,7 +246,7 @@ class EntityCapabilitiesManager extends XmppManagerBase {
|
|||||||
logger.warning(
|
logger.warning(
|
||||||
'Malformed disco#info response: More than one equal feature',
|
'Malformed disco#info response: More than one equal feature',
|
||||||
);
|
);
|
||||||
return;
|
return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,7 +274,7 @@ class EntityCapabilitiesManager extends XmppManagerBase {
|
|||||||
logger.warning(
|
logger.warning(
|
||||||
'Malformed disco#info response: Extended Info FORM_TYPE contains more than one value(s) of different value.',
|
'Malformed disco#info response: Extended Info FORM_TYPE contains more than one value(s) of different value.',
|
||||||
);
|
);
|
||||||
return;
|
return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,7 +289,7 @@ class EntityCapabilitiesManager extends XmppManagerBase {
|
|||||||
logger.warning(
|
logger.warning(
|
||||||
'Malformed disco#info response: More than one Extended Disco Info forms with the same FORM_TYPE value',
|
'Malformed disco#info response: More than one Extended Disco Info forms with the same FORM_TYPE value',
|
||||||
);
|
);
|
||||||
return;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the field type is hidden
|
// Check if the field type is hidden
|
||||||
@ -297,14 +318,16 @@ class EntityCapabilitiesManager extends XmppManagerBase {
|
|||||||
|
|
||||||
if (computedCapabilityHash == ver) {
|
if (computedCapabilityHash == ver) {
|
||||||
await _cacheLock.synchronized(() {
|
await _cacheLock.synchronized(() {
|
||||||
_jidToCapHashCache[event.jid.toString()] = ver;
|
_jidToCapHashCache[from.toString()] = ver;
|
||||||
_capHashCache[ver] = newDiscoInfo;
|
_capHashCache[ver] = newDiscoInfo;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
logger.warning(
|
logger.warning(
|
||||||
'Capability hash mismatch from ${event.jid}: Received "$ver", expected "$computedCapabilityHash".',
|
'Capability hash mismatch from $from: Received "$ver", expected "$computedCapabilityHash".',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
@ -315,9 +338,7 @@ class EntityCapabilitiesManager extends XmppManagerBase {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onXmppEvent(XmppEvent event) async {
|
Future<void> onXmppEvent(XmppEvent event) async {
|
||||||
if (event is PresenceReceivedEvent) {
|
if (event is StreamNegotiationsDoneEvent) {
|
||||||
unawaited(onPresence(event));
|
|
||||||
} else if (event is StreamNegotiationsDoneEvent) {
|
|
||||||
// Clear the JID to cap. hash mapping.
|
// Clear the JID to cap. hash mapping.
|
||||||
await _cacheLock.synchronized(_jidToCapHashCache.clear);
|
await _cacheLock.synchronized(_jidToCapHashCache.clear);
|
||||||
}
|
}
|
||||||
|
@ -4,23 +4,43 @@ 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';
|
||||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||||
|
import 'package:moxxmpp/src/message.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
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';
|
||||||
|
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||||
|
|
||||||
XMLNode makeMessageDeliveryRequest() {
|
class MessageDeliveryReceiptData implements StanzaHandlerExtension {
|
||||||
return XMLNode.xmlns(
|
const MessageDeliveryReceiptData(this.receiptRequested);
|
||||||
tag: 'request',
|
|
||||||
xmlns: deliveryXmlns,
|
/// 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
XMLNode makeMessageDeliveryResponse(String id) {
|
class MessageDeliveryReceivedData implements StanzaHandlerExtension {
|
||||||
return XMLNode.xmlns(
|
const MessageDeliveryReceivedData(this.id);
|
||||||
tag: 'received',
|
|
||||||
xmlns: deliveryXmlns,
|
/// The stanza id of the message we received.
|
||||||
attributes: {'id': id},
|
final String id;
|
||||||
);
|
|
||||||
|
XMLNode toXML() {
|
||||||
|
return XMLNode.xmlns(
|
||||||
|
tag: 'received',
|
||||||
|
xmlns: deliveryXmlns,
|
||||||
|
attributes: {'id': id},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MessageDeliveryReceiptManager extends XmppManagerBase {
|
class MessageDeliveryReceiptManager extends XmppManagerBase {
|
||||||
@ -56,7 +76,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 +84,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 +101,27 @@ 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<XMLNode> _messageSendingCallback(
|
||||||
|
TypedMap<StanzaHandlerExtension> extensions,
|
||||||
|
) {
|
||||||
|
final data = extensions.get<MessageDeliveryReceivedData>();
|
||||||
|
return data != null
|
||||||
|
? [
|
||||||
|
data.toXML(),
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> postRegisterCallback() async {
|
||||||
|
await super.postRegisterCallback();
|
||||||
|
|
||||||
|
// Register the sending callback
|
||||||
|
getAttributes()
|
||||||
|
.getManagerById<MessageManager>(messageManager)
|
||||||
|
?.registerMessageSendingCallback(_messageSendingCallback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
8
packages/moxxmpp/lib/src/xeps/xep_0198/types.dart
Normal file
8
packages/moxxmpp/lib/src/xeps/xep_0198/types.dart
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import 'package:moxxmpp/src/managers/data.dart';
|
||||||
|
|
||||||
|
class StreamManagementData implements StanzaHandlerExtension {
|
||||||
|
const StreamManagementData(this.exclude);
|
||||||
|
|
||||||
|
/// Whether the stanza should be exluded from the StreamManagement's resend queue.
|
||||||
|
final bool exclude;
|
||||||
|
}
|
@ -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
|
||||||
@ -399,10 +400,14 @@ class StreamManagementManager extends XmppManagerBase {
|
|||||||
Stanza stanza,
|
Stanza stanza,
|
||||||
StanzaHandlerData state,
|
StanzaHandlerData state,
|
||||||
) async {
|
) async {
|
||||||
await _incrementC2S();
|
|
||||||
_unackedStanzas[_state.c2s] = stanza;
|
|
||||||
|
|
||||||
if (isStreamManagementEnabled()) {
|
if (isStreamManagementEnabled()) {
|
||||||
|
await _incrementC2S();
|
||||||
|
|
||||||
|
if (state.extensions.get<StreamManagementData>()?.exclude ?? false) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
_unackedStanzas[_state.c2s] = stanza;
|
||||||
await _sendAckRequest();
|
await _sendAckRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -414,6 +419,8 @@ class StreamManagementManager extends XmppManagerBase {
|
|||||||
_unackedStanzas.clear();
|
_unackedStanzas.clear();
|
||||||
|
|
||||||
for (final stanza in stanzas) {
|
for (final stanza in stanzas) {
|
||||||
|
logger
|
||||||
|
.finest('Resending ${stanza.tag} with id ${stanza.attributes["id"]}');
|
||||||
await getAttributes().sendStanza(
|
await getAttributes().sendStanza(
|
||||||
StanzaDetails(
|
StanzaDetails(
|
||||||
stanza,
|
stanza,
|
||||||
|
@ -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 implements StanzaHandlerExtension {
|
||||||
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,14 +39,14 @@ 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(
|
||||||
DateTime.parse(delay.attributes['stamp']! as String),
|
JID.fromString(delay.attributes['from']! as String),
|
||||||
),
|
DateTime.parse(delay.attributes['stamp']! as String),
|
||||||
);
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 implements StanzaHandlerExtension {
|
||||||
|
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.
|
||||||
|
@ -66,7 +66,7 @@ enum HashFunction {
|
|||||||
return HashFunction.blake2b512;
|
return HashFunction.blake2b512;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw Exception();
|
throw Exception('Invalid hash function $name');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Like [HashFunction.fromName], but returns null if the hash function is unknown
|
/// Like [HashFunction.fromName], but returns null if the hash function is unknown
|
||||||
|
@ -2,18 +2,27 @@ 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';
|
||||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||||
|
import 'package:moxxmpp/src/message.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
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';
|
||||||
|
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||||
|
|
||||||
XMLNode makeLastMessageCorrectionEdit(String id) {
|
class LastMessageCorrectionData implements StanzaHandlerExtension {
|
||||||
return XMLNode.xmlns(
|
const LastMessageCorrectionData(this.id);
|
||||||
tag: 'replace',
|
|
||||||
xmlns: lmcXmlns,
|
/// The id the LMC applies to.
|
||||||
attributes: <String, String>{
|
final String id;
|
||||||
'id': id,
|
|
||||||
},
|
XMLNode toXML() {
|
||||||
);
|
return XMLNode.xmlns(
|
||||||
|
tag: 'replace',
|
||||||
|
xmlns: lmcXmlns,
|
||||||
|
attributes: {
|
||||||
|
'id': id,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LastMessageCorrectionManager extends XmppManagerBase {
|
class LastMessageCorrectionManager extends XmppManagerBase {
|
||||||
@ -42,8 +51,30 @@ 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),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<XMLNode> _messageSendingCallback(
|
||||||
|
TypedMap<StanzaHandlerExtension> extensions,
|
||||||
|
) {
|
||||||
|
final data = extensions.get<LastMessageCorrectionData>();
|
||||||
|
return data != null
|
||||||
|
? [
|
||||||
|
data.toXML(),
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> postRegisterCallback() async {
|
||||||
|
await super.postRegisterCallback();
|
||||||
|
|
||||||
|
// Register the sending callback
|
||||||
|
getAttributes()
|
||||||
|
.getManagerById<MessageManager>(messageManager)
|
||||||
|
?.registerMessageSendingCallback(_messageSendingCallback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,27 +4,86 @@ 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';
|
||||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||||
|
import 'package:moxxmpp/src/message.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
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';
|
||||||
|
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||||
|
|
||||||
XMLNode makeChatMarkerMarkable() {
|
enum ChatMarker {
|
||||||
return XMLNode.xmlns(
|
received,
|
||||||
tag: 'markable',
|
displayed,
|
||||||
xmlns: chatMarkersXmlns,
|
acknowledged;
|
||||||
);
|
|
||||||
|
factory ChatMarker.fromName(String name) {
|
||||||
|
switch (name) {
|
||||||
|
case 'received':
|
||||||
|
return ChatMarker.received;
|
||||||
|
case 'displayed':
|
||||||
|
return ChatMarker.displayed;
|
||||||
|
case 'acknowledged':
|
||||||
|
return ChatMarker.acknowledged;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw Exception('Invalid chat marker $name');
|
||||||
|
}
|
||||||
|
|
||||||
|
XMLNode toXML() {
|
||||||
|
String tag;
|
||||||
|
switch (this) {
|
||||||
|
case ChatMarker.received:
|
||||||
|
tag = 'received';
|
||||||
|
break;
|
||||||
|
case ChatMarker.displayed:
|
||||||
|
tag = 'displayed';
|
||||||
|
break;
|
||||||
|
case ChatMarker.acknowledged:
|
||||||
|
tag = 'acknowledged';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return XMLNode.xmlns(
|
||||||
|
tag: tag,
|
||||||
|
xmlns: chatMarkersXmlns,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
XMLNode makeChatMarker(String tag, String id) {
|
class MarkableData implements StanzaHandlerExtension {
|
||||||
assert(
|
const MarkableData(this.isMarkable);
|
||||||
['received', 'displayed', 'acknowledged'].contains(tag),
|
|
||||||
'Invalid chat marker',
|
/// Indicates whether the message can be replied to with a chat marker.
|
||||||
);
|
final bool isMarkable;
|
||||||
return XMLNode.xmlns(
|
|
||||||
tag: tag,
|
XMLNode toXML() {
|
||||||
xmlns: chatMarkersXmlns,
|
assert(isMarkable, '');
|
||||||
attributes: {'id': id},
|
|
||||||
);
|
return XMLNode.xmlns(
|
||||||
|
tag: 'markable',
|
||||||
|
xmlns: chatMarkersXmlns,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChatMarkerData implements StanzaHandlerExtension {
|
||||||
|
const ChatMarkerData(this.marker, this.id);
|
||||||
|
|
||||||
|
/// The actual chat state
|
||||||
|
final ChatMarker marker;
|
||||||
|
|
||||||
|
/// The ID the chat marker applies to
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
XMLNode toXML() {
|
||||||
|
final tag = marker.toXML();
|
||||||
|
return XMLNode.xmlns(
|
||||||
|
tag: tag.tag,
|
||||||
|
xmlns: chatMarkersXmlns,
|
||||||
|
attributes: {
|
||||||
|
'id': id,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChatMarkerManager extends XmppManagerBase {
|
class ChatMarkerManager extends XmppManagerBase {
|
||||||
@ -51,23 +110,52 @@ class ChatMarkerManager extends XmppManagerBase {
|
|||||||
Stanza message,
|
Stanza message,
|
||||||
StanzaHandlerData state,
|
StanzaHandlerData state,
|
||||||
) async {
|
) async {
|
||||||
final marker = message.firstTagByXmlns(chatMarkersXmlns)!;
|
final element = message.firstTagByXmlns(chatMarkersXmlns)!;
|
||||||
|
|
||||||
// Handle the <markable /> explicitly
|
// Handle the <markable /> explicitly
|
||||||
if (marker.tag == 'markable') return state.copyWith(isMarkable: true);
|
if (element.tag == 'markable') {
|
||||||
|
return state..extensions.set(const MarkableData(true));
|
||||||
if (!['received', 'displayed', 'acknowledged'].contains(marker.tag)) {
|
|
||||||
logger.warning("Unknown message marker '${marker.tag}' found.");
|
|
||||||
} else {
|
|
||||||
getAttributes().sendEvent(
|
|
||||||
ChatMarkerEvent(
|
|
||||||
from: JID.fromString(message.from!),
|
|
||||||
type: marker.tag,
|
|
||||||
id: marker.attributes['id']! as String,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return state.copyWith(done: true);
|
try {
|
||||||
|
getAttributes().sendEvent(
|
||||||
|
ChatMarkerEvent(
|
||||||
|
JID.fromString(message.from!),
|
||||||
|
ChatMarker.fromName(element.tag),
|
||||||
|
element.attributes['id']! as String,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (_) {
|
||||||
|
logger.warning("Unknown message marker '${element.tag}' found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return state..done = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<XMLNode> _messageSendingCallback(
|
||||||
|
TypedMap<StanzaHandlerExtension> extensions,
|
||||||
|
) {
|
||||||
|
final children = List<XMLNode>.empty(growable: true);
|
||||||
|
final marker = extensions.get<ChatMarkerData>();
|
||||||
|
if (marker != null) {
|
||||||
|
children.add(marker.toXML());
|
||||||
|
}
|
||||||
|
|
||||||
|
final markable = extensions.get<MarkableData>();
|
||||||
|
if (markable != null) {
|
||||||
|
children.add(markable.toXML());
|
||||||
|
}
|
||||||
|
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> postRegisterCallback() async {
|
||||||
|
await super.postRegisterCallback();
|
||||||
|
|
||||||
|
// Register the sending callback
|
||||||
|
getAttributes()
|
||||||
|
.getManagerById<MessageManager>(messageManager)
|
||||||
|
?.registerMessageSendingCallback(_messageSendingCallback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,31 +1,36 @@
|
|||||||
|
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/message.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
|
import 'package:moxxmpp/src/stanza.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
|
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||||
|
|
||||||
enum MessageProcessingHint {
|
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':
|
||||||
return MessageProcessingHint.noStore;
|
return MessageProcessingHint.noStore;
|
||||||
case 'no-copy':
|
case 'no-copy':
|
||||||
return MessageProcessingHint.noCopies;
|
return MessageProcessingHint.noCopies;
|
||||||
case 'store':
|
case 'store':
|
||||||
return MessageProcessingHint.store;
|
return MessageProcessingHint.store;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(false, 'Invalid Message Processing Hint: $name');
|
||||||
|
return MessageProcessingHint.noStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(false, 'Invalid Message Processing Hint: ${element.tag}');
|
XMLNode toXML() {
|
||||||
return MessageProcessingHint.noStore;
|
|
||||||
}
|
|
||||||
|
|
||||||
extension XmlExtension on MessageProcessingHint {
|
|
||||||
XMLNode toXml() {
|
|
||||||
String tag;
|
String tag;
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case MessageProcessingHint.noPermanentStore:
|
case MessageProcessingHint.noPermanentStore:
|
||||||
@ -48,3 +53,60 @@ extension XmlExtension on MessageProcessingHint {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MessageProcessingHintData implements StanzaHandlerExtension {
|
||||||
|
const MessageProcessingHintData(this.hints);
|
||||||
|
|
||||||
|
/// The attached message processing hints.
|
||||||
|
final List<MessageProcessingHint> hints;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MessageProcessingHintManager extends XmppManagerBase {
|
||||||
|
MessageProcessingHintManager() : super(messageProcessingHintManager);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> isSupported() async => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<StanzaHandler> getIncomingStanzaHandlers() => [
|
||||||
|
StanzaHandler(
|
||||||
|
stanzaTag: 'message',
|
||||||
|
tagXmlns: messageProcessingHintsXmlns,
|
||||||
|
callback: _onMessage,
|
||||||
|
// Before the message handler
|
||||||
|
priority: -99,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
Future<StanzaHandlerData> _onMessage(
|
||||||
|
Stanza stanza,
|
||||||
|
StanzaHandlerData state,
|
||||||
|
) async {
|
||||||
|
final elements = stanza.findTagsByXmlns(messageProcessingHintsXmlns);
|
||||||
|
return state
|
||||||
|
..extensions.set(
|
||||||
|
MessageProcessingHintData(
|
||||||
|
elements
|
||||||
|
.map((element) => MessageProcessingHint.fromName(element.tag))
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<XMLNode> _messageSendingCallback(
|
||||||
|
TypedMap<StanzaHandlerExtension> extensions,
|
||||||
|
) {
|
||||||
|
final data = extensions.get<MessageProcessingHintData>();
|
||||||
|
return data != null ? data.hints.map((hint) => hint.toXML()).toList() : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> postRegisterCallback() async {
|
||||||
|
await super.postRegisterCallback();
|
||||||
|
|
||||||
|
// Register the sending callback
|
||||||
|
getAttributes()
|
||||||
|
.getManagerById<MessageManager>(messageManager)
|
||||||
|
?.registerMessageSendingCallback(_messageSendingCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3,9 +3,11 @@ 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';
|
||||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||||
|
import 'package:moxxmpp/src/message.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
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';
|
||||||
|
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||||
|
|
||||||
/// Representation of a <stanza-id /> element.
|
/// Representation of a <stanza-id /> element.
|
||||||
class StanzaId {
|
class StanzaId {
|
||||||
@ -20,7 +22,7 @@ class StanzaId {
|
|||||||
/// The JID the id was generated by.
|
/// The JID the id was generated by.
|
||||||
final JID by;
|
final JID by;
|
||||||
|
|
||||||
XMLNode toXml() {
|
XMLNode toXML() {
|
||||||
return XMLNode.xmlns(
|
return XMLNode.xmlns(
|
||||||
tag: 'stanza-id',
|
tag: 'stanza-id',
|
||||||
xmlns: stableIdXmlns,
|
xmlns: stableIdXmlns,
|
||||||
@ -32,12 +34,38 @@ class StanzaId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
XMLNode makeOriginIdElement(String id) {
|
class StableIdData implements StanzaHandlerExtension {
|
||||||
return XMLNode.xmlns(
|
const StableIdData(this.originId, this.stanzaIds);
|
||||||
tag: 'origin-id',
|
|
||||||
xmlns: stableIdXmlns,
|
/// <origin-id />
|
||||||
attributes: {'id': 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!},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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()),
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class StableIdManager extends XmppManagerBase {
|
class StableIdManager extends XmppManagerBase {
|
||||||
@ -86,9 +114,29 @@ class StableIdManager extends XmppManagerBase {
|
|||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
return state.copyWith(
|
return state
|
||||||
originId: originId,
|
..extensions.set(
|
||||||
stanzaIds: stanzaIds,
|
StableIdData(
|
||||||
);
|
originId,
|
||||||
|
stanzaIds,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<XMLNode> _messageSendingCallback(
|
||||||
|
TypedMap<StanzaHandlerExtension> extensions,
|
||||||
|
) {
|
||||||
|
final data = extensions.get<StableIdData>();
|
||||||
|
return data != null ? data.toXML() : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> postRegisterCallback() async {
|
||||||
|
await super.postRegisterCallback();
|
||||||
|
|
||||||
|
// Register the sending callback
|
||||||
|
getAttributes()
|
||||||
|
.getManagerById<MessageManager>(messageManager)
|
||||||
|
?.registerMessageSendingCallback(_messageSendingCallback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,64 +6,64 @@ 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 ExplicitEncryptionType {
|
enum ExplicitEncryptionType implements StanzaHandlerExtension {
|
||||||
otr,
|
otr,
|
||||||
legacyOpenPGP,
|
legacyOpenPGP,
|
||||||
openPGP,
|
openPGP,
|
||||||
omemo,
|
omemo,
|
||||||
omemo1,
|
omemo1,
|
||||||
omemo2,
|
omemo2,
|
||||||
unknown,
|
unknown;
|
||||||
}
|
|
||||||
|
|
||||||
String _explicitEncryptionTypeToString(ExplicitEncryptionType type) {
|
factory ExplicitEncryptionType.fromNamespace(String namespace) {
|
||||||
switch (type) {
|
switch (namespace) {
|
||||||
case ExplicitEncryptionType.otr:
|
case emeOtr:
|
||||||
return emeOtr;
|
return ExplicitEncryptionType.otr;
|
||||||
case ExplicitEncryptionType.legacyOpenPGP:
|
case emeLegacyOpenPGP:
|
||||||
return emeLegacyOpenPGP;
|
return ExplicitEncryptionType.legacyOpenPGP;
|
||||||
case ExplicitEncryptionType.openPGP:
|
case emeOpenPGP:
|
||||||
return emeOpenPGP;
|
return ExplicitEncryptionType.openPGP;
|
||||||
case ExplicitEncryptionType.omemo:
|
case emeOmemo:
|
||||||
return emeOmemo;
|
return ExplicitEncryptionType.omemo;
|
||||||
case ExplicitEncryptionType.omemo1:
|
case emeOmemo1:
|
||||||
return emeOmemo1;
|
return ExplicitEncryptionType.omemo1;
|
||||||
case ExplicitEncryptionType.omemo2:
|
case emeOmemo2:
|
||||||
return emeOmemo2;
|
return ExplicitEncryptionType.omemo2;
|
||||||
case ExplicitEncryptionType.unknown:
|
default:
|
||||||
return '';
|
return ExplicitEncryptionType.unknown;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ExplicitEncryptionType _explicitEncryptionTypeFromString(String str) {
|
String toNamespace() {
|
||||||
switch (str) {
|
switch (this) {
|
||||||
case emeOtr:
|
case ExplicitEncryptionType.otr:
|
||||||
return ExplicitEncryptionType.otr;
|
return emeOtr;
|
||||||
case emeLegacyOpenPGP:
|
case ExplicitEncryptionType.legacyOpenPGP:
|
||||||
return ExplicitEncryptionType.legacyOpenPGP;
|
return emeLegacyOpenPGP;
|
||||||
case emeOpenPGP:
|
case ExplicitEncryptionType.openPGP:
|
||||||
return ExplicitEncryptionType.openPGP;
|
return emeOpenPGP;
|
||||||
case emeOmemo:
|
case ExplicitEncryptionType.omemo:
|
||||||
return ExplicitEncryptionType.omemo;
|
return emeOmemo;
|
||||||
case emeOmemo1:
|
case ExplicitEncryptionType.omemo1:
|
||||||
return ExplicitEncryptionType.omemo1;
|
return emeOmemo1;
|
||||||
case emeOmemo2:
|
case ExplicitEncryptionType.omemo2:
|
||||||
return ExplicitEncryptionType.omemo2;
|
return emeOmemo2;
|
||||||
default:
|
case ExplicitEncryptionType.unknown:
|
||||||
return ExplicitEncryptionType.unknown;
|
return '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Create an <encryption /> element with [type] indicating which type of encryption was
|
/// Create an <encryption /> element with an xmlns indicating what type of encryption was
|
||||||
/// used.
|
/// used.
|
||||||
XMLNode buildEmeElement(ExplicitEncryptionType type) {
|
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,10 +91,11 @@ 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(
|
||||||
encryption.attributes['namespace']! as String,
|
ExplicitEncryptionType.fromNamespace(
|
||||||
),
|
encryption.attributes['namespace']! as String,
|
||||||
);
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
@ -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,19 +364,18 @@ 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,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final encrypted = _buildEncryptedElement(
|
final encrypted = _buildEncryptedElement(
|
||||||
@ -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,9 +500,7 @@ 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,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience function that attempts to retrieve the raw XML payload from the
|
/// Convenience function that attempts to retrieve the raw XML payload from the
|
||||||
@ -516,8 +511,7 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
|||||||
JID jid,
|
JID jid,
|
||||||
) async {
|
) async {
|
||||||
final pm = getAttributes().getManagerById<PubSubManager>(pubsubManager)!;
|
final pm = getAttributes().getManagerById<PubSubManager>(pubsubManager)!;
|
||||||
final result =
|
final result = await pm.getItems(jid.toBare(), omemoDevicesXmlns);
|
||||||
await pm.getItems(jid.toBare().toString(), omemoDevicesXmlns);
|
|
||||||
if (result.isType<PubSubError>()) return Result(UnknownOmemoError());
|
if (result.isType<PubSubError>()) return Result(UnknownOmemoError());
|
||||||
return Result(result.get<List<PubSubItem>>().first.payload);
|
return Result(result.get<List<PubSubItem>>().first.payload);
|
||||||
}
|
}
|
||||||
@ -543,7 +537,7 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
|||||||
) async {
|
) async {
|
||||||
// TODO(Unknown): Should we query the device list first?
|
// TODO(Unknown): Should we query the device list first?
|
||||||
final pm = getAttributes().getManagerById<PubSubManager>(pubsubManager)!;
|
final pm = getAttributes().getManagerById<PubSubManager>(pubsubManager)!;
|
||||||
final bundlesRaw = await pm.getItems(jid.toString(), omemoBundlesXmlns);
|
final bundlesRaw = await pm.getItems(jid, omemoBundlesXmlns);
|
||||||
if (bundlesRaw.isType<PubSubError>()) return Result(UnknownOmemoError());
|
if (bundlesRaw.isType<PubSubError>()) return Result(UnknownOmemoError());
|
||||||
|
|
||||||
final bundles = bundlesRaw
|
final bundles = bundlesRaw
|
||||||
@ -639,7 +633,7 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
|||||||
/// Subscribes to the device list PubSub node of [jid].
|
/// Subscribes to the device list PubSub node of [jid].
|
||||||
Future<void> subscribeToDeviceListImpl(String jid) async {
|
Future<void> subscribeToDeviceListImpl(String jid) async {
|
||||||
final pm = getAttributes().getManagerById<PubSubManager>(pubsubManager)!;
|
final pm = getAttributes().getManagerById<PubSubManager>(pubsubManager)!;
|
||||||
await pm.subscribe(jid, omemoDevicesXmlns);
|
await pm.subscribe(JID.fromString(jid), omemoDevicesXmlns);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to find out if [jid] supports omemo:2.
|
/// Attempts to find out if [jid] supports omemo:2.
|
||||||
|
@ -7,7 +7,7 @@ import 'package:moxxmpp/src/stanza.dart';
|
|||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
import 'package:moxxmpp/src/xeps/staging/extensible_file_thumbnails.dart';
|
import 'package:moxxmpp/src/xeps/staging/extensible_file_thumbnails.dart';
|
||||||
|
|
||||||
class StatelessMediaSharingData {
|
class StatelessMediaSharingData implements StanzaHandlerExtension {
|
||||||
const StatelessMediaSharingData({
|
const StatelessMediaSharingData({
|
||||||
required this.mediaType,
|
required this.mediaType,
|
||||||
required this.size,
|
required this.size,
|
||||||
@ -70,7 +70,9 @@ StatelessMediaSharingData parseSIMSElement(XMLNode node) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated('Not maintained')
|
||||||
class SIMSManager extends XmppManagerBase {
|
class SIMSManager extends XmppManagerBase {
|
||||||
|
@Deprecated('Not maintained')
|
||||||
SIMSManager() : super(simsManager);
|
SIMSManager() : super(simsManager);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -98,7 +100,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;
|
||||||
|
@ -2,12 +2,19 @@ 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';
|
||||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||||
|
import 'package:moxxmpp/src/message.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
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/util/typed_map.dart';
|
||||||
|
|
||||||
class MessageRetractionData {
|
class MessageRetractionData implements StanzaHandlerExtension {
|
||||||
MessageRetractionData(this.id, this.fallback);
|
MessageRetractionData(this.id, this.fallback);
|
||||||
|
|
||||||
|
/// A potential fallback message to set the body to when retracting.
|
||||||
final String? fallback;
|
final String? fallback;
|
||||||
|
|
||||||
|
/// The id of the message that is retracted.
|
||||||
final String id;
|
final String id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,11 +54,55 @@ 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(
|
||||||
applyTo.attributes['id']! as String,
|
MessageRetractionData(
|
||||||
isFallbackBody ? message.firstTag('body')?.innerText() : null,
|
applyTo.attributes['id']! as String,
|
||||||
),
|
isFallbackBody ? message.firstTag('body')?.innerText() : null,
|
||||||
);
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<XMLNode> _messageSendingCallback(
|
||||||
|
TypedMap<StanzaHandlerExtension> 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,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> postRegisterCallback() async {
|
||||||
|
await super.postRegisterCallback();
|
||||||
|
|
||||||
|
// Register the sending callback
|
||||||
|
getAttributes()
|
||||||
|
.getManagerById<MessageManager>(messageManager)
|
||||||
|
?.registerMessageSendingCallback(_messageSendingCallback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,16 +2,18 @@ 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';
|
||||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||||
|
import 'package:moxxmpp/src/message.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
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';
|
||||||
|
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||||
|
|
||||||
class MessageReactions {
|
class MessageReactionsData implements StanzaHandlerExtension {
|
||||||
const MessageReactions(this.messageId, this.emojis);
|
const MessageReactionsData(this.messageId, this.emojis);
|
||||||
final String messageId;
|
final String messageId;
|
||||||
final List<String> emojis;
|
final List<String> emojis;
|
||||||
|
|
||||||
XMLNode toXml() {
|
XMLNode toXML() {
|
||||||
return XMLNode.xmlns(
|
return XMLNode.xmlns(
|
||||||
tag: 'reactions',
|
tag: 'reactions',
|
||||||
xmlns: messageReactionsXmlns,
|
xmlns: messageReactionsXmlns,
|
||||||
@ -55,14 +57,36 @@ 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(
|
||||||
reactionsElement.attributes['id']! as String,
|
MessageReactionsData(
|
||||||
reactionsElement.children
|
reactionsElement.attributes['id']! as String,
|
||||||
.where((c) => c.tag == 'reaction')
|
reactionsElement.children
|
||||||
.map((c) => c.innerText())
|
.where((c) => c.tag == 'reaction')
|
||||||
.toList(),
|
.map((c) => c.innerText())
|
||||||
),
|
.toList(),
|
||||||
);
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<XMLNode> _messageSendingCallback(
|
||||||
|
TypedMap<StanzaHandlerExtension> extensions,
|
||||||
|
) {
|
||||||
|
final data = extensions.get<MessageReactionsData>();
|
||||||
|
return data != null
|
||||||
|
? [
|
||||||
|
data.toXML(),
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> postRegisterCallback() async {
|
||||||
|
await super.postRegisterCallback();
|
||||||
|
|
||||||
|
// Register the sending callback
|
||||||
|
getAttributes()
|
||||||
|
.getManagerById<MessageManager>(messageManager)
|
||||||
|
?.registerMessageSendingCallback(_messageSendingCallback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,12 @@ 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';
|
||||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||||
|
import 'package:moxxmpp/src/message.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
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';
|
||||||
|
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_0446.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0448.dart';
|
import 'package:moxxmpp/src/xeps/xep_0448.dart';
|
||||||
|
|
||||||
@ -70,8 +73,12 @@ List<StatelessFileSharingSource> processStatelessFileSharingSources(
|
|||||||
return sources;
|
return sources;
|
||||||
}
|
}
|
||||||
|
|
||||||
class StatelessFileSharingData {
|
class StatelessFileSharingData implements StanzaHandlerExtension {
|
||||||
const StatelessFileSharingData(this.metadata, this.sources);
|
const StatelessFileSharingData(
|
||||||
|
this.metadata,
|
||||||
|
this.sources, {
|
||||||
|
this.includeOOBFallback = false,
|
||||||
|
});
|
||||||
|
|
||||||
/// Parse [node] as a StatelessFileSharingData element.
|
/// Parse [node] as a StatelessFileSharingData element.
|
||||||
factory StatelessFileSharingData.fromXML(XMLNode node) {
|
factory StatelessFileSharingData.fromXML(XMLNode node) {
|
||||||
@ -88,6 +95,10 @@ class StatelessFileSharingData {
|
|||||||
final FileMetadataData metadata;
|
final FileMetadataData metadata;
|
||||||
final List<StatelessFileSharingSource> sources;
|
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() {
|
XMLNode toXML() {
|
||||||
return XMLNode.xmlns(
|
return XMLNode.xmlns(
|
||||||
tag: 'file-sharing',
|
tag: 'file-sharing',
|
||||||
@ -122,23 +133,54 @@ class SFSManager extends XmppManagerBase {
|
|||||||
tagXmlns: sfsXmlns,
|
tagXmlns: sfsXmlns,
|
||||||
callback: _onMessage,
|
callback: _onMessage,
|
||||||
// Before the message handler
|
// Before the message handler
|
||||||
priority: -99,
|
priority: -98,
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> isSupported() async => true;
|
Future<bool> isSupported() async => true;
|
||||||
|
|
||||||
|
List<XMLNode> _messageSendingCallback(
|
||||||
|
TypedMap<StanzaHandlerExtension> 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(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
Future<StanzaHandlerData> _onMessage(
|
Future<StanzaHandlerData> _onMessage(
|
||||||
Stanza message,
|
Stanza message,
|
||||||
StanzaHandlerData state,
|
StanzaHandlerData state,
|
||||||
) 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),
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> postRegisterCallback() async {
|
||||||
|
await super.postRegisterCallback();
|
||||||
|
|
||||||
|
// Register the sending callback
|
||||||
|
getAttributes()
|
||||||
|
.getManagerById<MessageManager>(messageManager)
|
||||||
|
?.registerMessageSendingCallback(_messageSendingCallback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,13 @@ 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';
|
||||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||||
|
import 'package:moxxmpp/src/message.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/rfcs/rfc_4790.dart';
|
import 'package:moxxmpp/src/rfcs/rfc_4790.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';
|
||||||
import 'package:moxxmpp/src/types/result.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/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_0300.dart';
|
import 'package:moxxmpp/src/xeps/xep_0300.dart';
|
||||||
@ -227,6 +229,19 @@ class StickerPack {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class StickersData implements StanzaHandlerExtension {
|
||||||
|
const StickersData(this.stickerPackId, this.sticker, {this.addBody = true});
|
||||||
|
|
||||||
|
/// The id of the sticker pack the referenced sticker is from.
|
||||||
|
final String stickerPackId;
|
||||||
|
|
||||||
|
/// The metadata of the sticker.
|
||||||
|
final StatelessFileSharingData sticker;
|
||||||
|
|
||||||
|
/// If true, sets the sticker's metadata desc attribute as the message body.
|
||||||
|
final bool addBody;
|
||||||
|
}
|
||||||
|
|
||||||
class StickersManager extends XmppManagerBase {
|
class StickersManager extends XmppManagerBase {
|
||||||
StickersManager() : super(stickersManager);
|
StickersManager() : super(stickersManager);
|
||||||
|
|
||||||
@ -249,9 +264,35 @@ 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,
|
||||||
|
state.extensions.get<StatelessFileSharingData>()!,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<XMLNode> _messageSendingCallback(
|
||||||
|
TypedMap<StanzaHandlerExtension> extensions,
|
||||||
|
) {
|
||||||
|
final data = extensions.get<StickersData>();
|
||||||
|
return data != null
|
||||||
|
? [
|
||||||
|
XMLNode.xmlns(
|
||||||
|
tag: 'sticker',
|
||||||
|
xmlns: stickersXmlns,
|
||||||
|
attributes: {
|
||||||
|
'pack': data.stickerPackId,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
data.sticker.toXML(),
|
||||||
|
|
||||||
|
// Add a body
|
||||||
|
if (data.addBody && data.sticker.metadata.desc != null)
|
||||||
|
MessageBodyData(data.sticker.metadata.desc).toXML(),
|
||||||
|
]
|
||||||
|
: [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Publishes the StickerPack [pack] to the PubSub node of [jid]. If specified, then
|
/// Publishes the StickerPack [pack] to the PubSub node of [jid]. If specified, then
|
||||||
@ -319,4 +360,14 @@ class StickersManager extends XmppManagerBase {
|
|||||||
|
|
||||||
return Result(stickerPack);
|
return Result(stickerPack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> postRegisterCallback() async {
|
||||||
|
await super.postRegisterCallback();
|
||||||
|
|
||||||
|
// Register the sending callback
|
||||||
|
getAttributes()
|
||||||
|
.getManagerById<MessageManager>(messageManager)
|
||||||
|
?.registerMessageSendingCallback(_messageSendingCallback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,38 @@
|
|||||||
import 'package:meta/meta.dart';
|
import 'package:freezed_annotation/freezed_annotation.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';
|
||||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||||
|
import 'package:moxxmpp/src/message.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
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/util/typed_map.dart';
|
||||||
|
|
||||||
/// Data summarizing the XEP-0461 data.
|
/// A reply to a message.
|
||||||
class ReplyData {
|
class ReplyData implements StanzaHandlerExtension {
|
||||||
const ReplyData({
|
const ReplyData(
|
||||||
required this.id,
|
this.id, {
|
||||||
this.to,
|
this.body,
|
||||||
|
this.jid,
|
||||||
this.start,
|
this.start,
|
||||||
this.end,
|
this.end,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The bare JID to whom the reply applies to
|
ReplyData.fromQuoteData(
|
||||||
final String? to;
|
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;
|
final String id;
|
||||||
|
|
||||||
/// The start of the fallback body (inclusive)
|
/// The start of the fallback body (inclusive)
|
||||||
@ -27,18 +41,21 @@ class ReplyData {
|
|||||||
/// The end of the fallback body (exclusive)
|
/// The end of the fallback body (exclusive)
|
||||||
final int? end;
|
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.
|
/// 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
|
/// If either [ReplyData.start] or [ReplyData.end] are null, then body is returned as
|
||||||
/// is.
|
/// is.
|
||||||
String removeFallback(String body) {
|
String? get withoutFallback {
|
||||||
|
if (body == null) return null;
|
||||||
if (start == null || end == null) return body;
|
if (start == null || end == null) return body;
|
||||||
|
|
||||||
return body.replaceRange(start!, end, '');
|
return body!.replaceRange(start!, end, '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Internal class describing how to build a message with a quote fallback body.
|
/// Internal class describing how to build a message with a quote fallback body.
|
||||||
@visibleForTesting
|
|
||||||
class QuoteData {
|
class QuoteData {
|
||||||
const QuoteData(this.body, this.fallbackLength);
|
const QuoteData(this.body, this.fallbackLength);
|
||||||
|
|
||||||
@ -85,13 +102,54 @@ class MessageRepliesManager extends XmppManagerBase {
|
|||||||
@override
|
@override
|
||||||
Future<bool> isSupported() async => true;
|
Future<bool> isSupported() async => true;
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
List<XMLNode> messageSendingCallback(
|
||||||
|
TypedMap<StanzaHandlerExtension> 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(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
}
|
||||||
|
|
||||||
Future<StanzaHandlerData> _onMessage(
|
Future<StanzaHandlerData> _onMessage(
|
||||||
Stanza stanza,
|
Stanza stanza,
|
||||||
StanzaHandlerData state,
|
StanzaHandlerData state,
|
||||||
) async {
|
) async {
|
||||||
final reply = stanza.firstTag('reply', xmlns: replyXmlns)!;
|
final reply = stanza.firstTag('reply', xmlns: replyXmlns)!;
|
||||||
final id = reply.attributes['id']! as String;
|
|
||||||
final to = reply.attributes['to'] as String?;
|
final to = reply.attributes['to'] as String?;
|
||||||
|
final jid = to != null ? JID.fromString(to) : null;
|
||||||
int? start;
|
int? start;
|
||||||
int? end;
|
int? end;
|
||||||
|
|
||||||
@ -103,13 +161,25 @@ 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(
|
||||||
id: id,
|
ReplyData(
|
||||||
to: to,
|
reply.attributes['id']! as String,
|
||||||
start: start,
|
jid: jid,
|
||||||
end: end,
|
start: start,
|
||||||
),
|
end: end,
|
||||||
);
|
body: stanza.firstTag('body')?.innerText(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> postRegisterCallback() async {
|
||||||
|
await super.postRegisterCallback();
|
||||||
|
|
||||||
|
// Register the sending callback
|
||||||
|
getAttributes()
|
||||||
|
.getManagerById<MessageManager>(messageManager)
|
||||||
|
?.registerMessageSendingCallback(messageSendingCallback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
name: moxxmpp
|
name: moxxmpp
|
||||||
description: A pure-Dart XMPP library
|
description: A pure-Dart XMPP library
|
||||||
version: 0.3.2
|
version: 0.4.0
|
||||||
homepage: https://codeberg.org/moxxy/moxxmpp
|
homepage: https://codeberg.org/moxxy/moxxmpp
|
||||||
publish_to: https://git.polynom.me/api/packages/Moxxy/pub
|
publish_to: https://git.polynom.me/api/packages/Moxxy/pub
|
||||||
|
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:moxxmpp/src/connection.dart';
|
import 'package:moxxmpp/src/connection.dart';
|
||||||
import 'package:moxxmpp/src/connectivity.dart';
|
import 'package:moxxmpp/src/connectivity.dart';
|
||||||
|
import 'package:moxxmpp/src/events.dart';
|
||||||
import 'package:moxxmpp/src/handlers/client.dart';
|
import 'package:moxxmpp/src/handlers/client.dart';
|
||||||
import 'package:moxxmpp/src/jid.dart';
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
import 'package:moxxmpp/src/managers/attributes.dart';
|
import 'package:moxxmpp/src/managers/attributes.dart';
|
||||||
import 'package:moxxmpp/src/managers/base.dart';
|
import 'package:moxxmpp/src/managers/base.dart';
|
||||||
import 'package:moxxmpp/src/reconnect.dart';
|
import 'package:moxxmpp/src/reconnect.dart';
|
||||||
import 'package:moxxmpp/src/settings.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 'package:moxxmpp/src/stringxml.dart';
|
||||||
|
|
||||||
import '../helpers/xmpp.dart';
|
import '../helpers/xmpp.dart';
|
||||||
@ -15,15 +16,15 @@ import '../helpers/xmpp.dart';
|
|||||||
/// This class allows registering managers for easier testing.
|
/// This class allows registering managers for easier testing.
|
||||||
class TestingManagerHolder {
|
class TestingManagerHolder {
|
||||||
TestingManagerHolder({
|
TestingManagerHolder({
|
||||||
BaseSocketWrapper? socket,
|
StubTCPSocket? stubSocket,
|
||||||
}) : _socket = socket ?? StubTCPSocket([]);
|
}) : socket = stubSocket ?? StubTCPSocket([]);
|
||||||
|
|
||||||
final BaseSocketWrapper _socket;
|
final StubTCPSocket socket;
|
||||||
|
|
||||||
final Map<String, XmppManagerBase> _managers = {};
|
final Map<String, XmppManagerBase> _managers = {};
|
||||||
|
|
||||||
// The amount of stanzas sent
|
/// A list of events that were triggered.
|
||||||
int sentStanzas = 0;
|
final List<XmppEvent> sentEvents = List.empty(growable: true);
|
||||||
|
|
||||||
static final JID jid = JID.fromString('testuser@example.org/abc123');
|
static final JID jid = JID.fromString('testuser@example.org/abc123');
|
||||||
static final ConnectionSettings settings = ConnectionSettings(
|
static final ConnectionSettings settings = ConnectionSettings(
|
||||||
@ -31,42 +32,40 @@ class TestingManagerHolder {
|
|||||||
password: 'abc123',
|
password: 'abc123',
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<XMLNode> _sendStanza(
|
Future<XMLNode?> _sendStanza(StanzaDetails details) async {
|
||||||
stanza, {
|
socket.write(details.stanza.toXml());
|
||||||
bool addId = true,
|
return null;
|
||||||
bool awaitable = true,
|
|
||||||
bool encrypted = false,
|
|
||||||
bool forceEncryption = false,
|
|
||||||
}) async {
|
|
||||||
sentStanzas++;
|
|
||||||
return XMLNode.fromString('<iq />');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
T? _getManagerById<T extends XmppManagerBase>(String id) {
|
T? _getManagerById<T extends XmppManagerBase>(String id) {
|
||||||
return _managers[id] as T?;
|
return _managers[id] as T?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> register(XmppManagerBase manager) async {
|
Future<void> register(List<XmppManagerBase> managers) async {
|
||||||
manager.register(
|
for (final manager in managers) {
|
||||||
XmppManagerAttributes(
|
manager.register(
|
||||||
sendStanza: _sendStanza,
|
XmppManagerAttributes(
|
||||||
getConnection: () => XmppConnection(
|
sendStanza: _sendStanza,
|
||||||
TestingReconnectionPolicy(),
|
getConnection: () => XmppConnection(
|
||||||
AlwaysConnectedConnectivityManager(),
|
TestingReconnectionPolicy(),
|
||||||
ClientToServerNegotiator(),
|
AlwaysConnectedConnectivityManager(),
|
||||||
_socket,
|
ClientToServerNegotiator(),
|
||||||
|
socket,
|
||||||
|
),
|
||||||
|
getConnectionSettings: () => settings,
|
||||||
|
sendNonza: (_) {},
|
||||||
|
sendEvent: sentEvents.add,
|
||||||
|
getSocket: () => socket,
|
||||||
|
getNegotiatorById: getNegotiatorNullStub,
|
||||||
|
getFullJID: () => jid,
|
||||||
|
getManagerById: _getManagerById,
|
||||||
),
|
),
|
||||||
getConnectionSettings: () => settings,
|
);
|
||||||
sendNonza: (_) {},
|
_managers[manager.id] = manager;
|
||||||
sendEvent: (_) {},
|
}
|
||||||
getSocket: () => _socket,
|
|
||||||
getNegotiatorById: getNegotiatorNullStub,
|
|
||||||
getFullJID: () => jid,
|
|
||||||
getManagerById: _getManagerById,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
await manager.postRegisterCallback();
|
for (final manager in managers) {
|
||||||
_managers[manager.id] = manager;
|
await manager.postRegisterCallback();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,8 @@ void main() {
|
|||||||
callback: (stanza, _) async => StanzaHandlerData(
|
callback: (stanza, _) async => StanzaHandlerData(
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
null,
|
|
||||||
stanza,
|
stanza,
|
||||||
|
TypedMap(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -38,8 +38,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 +59,8 @@ void main() {
|
|||||||
return StanzaHandlerData(
|
return StanzaHandlerData(
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
null,
|
|
||||||
stanza,
|
stanza,
|
||||||
|
TypedMap(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
stanzaTag: 'iq',
|
stanzaTag: 'iq',
|
||||||
@ -77,8 +77,8 @@ void main() {
|
|||||||
StanzaHandlerData(
|
StanzaHandlerData(
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
null,
|
|
||||||
stanza2,
|
stanza2,
|
||||||
|
TypedMap(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
expect(run, true);
|
expect(run, true);
|
||||||
@ -89,8 +89,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 +107,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 +127,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 +147,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 +157,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 +166,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,
|
||||||
|
39
packages/moxxmpp/test/type_map_test.dart
Normal file
39
packages/moxxmpp/test/type_map_test.dart
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
abstract class BaseType {}
|
||||||
|
|
||||||
|
class TestType1 implements BaseType {
|
||||||
|
const TestType1(this.i);
|
||||||
|
final int i;
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestType2 implements BaseType {
|
||||||
|
const TestType2(this.j);
|
||||||
|
final bool j;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('Test storing data in the type map', () {
|
||||||
|
// Set
|
||||||
|
final map = TypedMap<BaseType>()
|
||||||
|
..set(const TestType1(1))
|
||||||
|
..set(const TestType2(false));
|
||||||
|
|
||||||
|
// And access
|
||||||
|
expect(map.get<TestType1>()?.i, 1);
|
||||||
|
expect(map.get<TestType2>()?.j, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Test storing data in the type map using a list', () {
|
||||||
|
// Set
|
||||||
|
final map = TypedMap<BaseType>.fromList([
|
||||||
|
const TestType1(1),
|
||||||
|
const TestType2(false),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// And access
|
||||||
|
expect(map.get<TestType1>()?.i, 1);
|
||||||
|
expect(map.get<TestType2>()?.j, false);
|
||||||
|
});
|
||||||
|
}
|
@ -120,8 +120,7 @@ void main() {
|
|||||||
final ecm = EntityCapabilitiesManager('');
|
final ecm = EntityCapabilitiesManager('');
|
||||||
final dm = DiscoManager([]);
|
final dm = DiscoManager([]);
|
||||||
|
|
||||||
await tm.register(dm);
|
await tm.register([dm, ecm]);
|
||||||
await tm.register(ecm);
|
|
||||||
|
|
||||||
// Inject a capability hash into the cache
|
// Inject a capability hash into the cache
|
||||||
final aliceJid = JID.fromString('alice@example.org/abc123');
|
final aliceJid = JID.fromString('alice@example.org/abc123');
|
||||||
@ -140,7 +139,7 @@ void main() {
|
|||||||
// Query Alice's device
|
// Query Alice's device
|
||||||
final result = await dm.discoInfoQuery(aliceJid);
|
final result = await dm.discoInfoQuery(aliceJid);
|
||||||
expect(result.isType<DiscoError>(), false);
|
expect(result.isType<DiscoError>(), false);
|
||||||
expect(tm.sentStanzas, 0);
|
expect(tm.socket.getState(), 0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -55,8 +55,10 @@ void main() {
|
|||||||
() async {
|
() async {
|
||||||
final manager = PubSubManager();
|
final manager = PubSubManager();
|
||||||
final tm = TestingManagerHolder();
|
final tm = TestingManagerHolder();
|
||||||
await tm.register(StubbedDiscoManager(false));
|
await tm.register([
|
||||||
await tm.register(manager);
|
StubbedDiscoManager(false),
|
||||||
|
manager,
|
||||||
|
]);
|
||||||
|
|
||||||
final result = await manager.preprocessPublishOptions(
|
final result = await manager.preprocessPublishOptions(
|
||||||
JID.fromString('pubsub.server.example.org'),
|
JID.fromString('pubsub.server.example.org'),
|
||||||
@ -72,8 +74,10 @@ void main() {
|
|||||||
() async {
|
() async {
|
||||||
final manager = PubSubManager();
|
final manager = PubSubManager();
|
||||||
final tm = TestingManagerHolder();
|
final tm = TestingManagerHolder();
|
||||||
await tm.register(StubbedDiscoManager(true));
|
await tm.register([
|
||||||
await tm.register(manager);
|
StubbedDiscoManager(true),
|
||||||
|
manager,
|
||||||
|
]);
|
||||||
|
|
||||||
final result = await manager.preprocessPublishOptions(
|
final result = await manager.preprocessPublishOptions(
|
||||||
JID.fromString('pubsub.server.example.org'),
|
JID.fromString('pubsub.server.example.org'),
|
||||||
@ -168,7 +172,6 @@ void main() {
|
|||||||
PubSubManager(),
|
PubSubManager(),
|
||||||
DiscoManager([]),
|
DiscoManager([]),
|
||||||
PresenceManager(),
|
PresenceManager(),
|
||||||
MessageManager(),
|
|
||||||
RosterManager(TestingRosterStateManager(null, [])),
|
RosterManager(TestingRosterStateManager(null, [])),
|
||||||
]);
|
]);
|
||||||
await connection.registerFeatureNegotiators([
|
await connection.registerFeatureNegotiators([
|
||||||
|
@ -319,27 +319,28 @@ void main() {
|
|||||||
final tm = TestingManagerHolder();
|
final tm = TestingManagerHolder();
|
||||||
final manager = EntityCapabilitiesManager('');
|
final manager = EntityCapabilitiesManager('');
|
||||||
|
|
||||||
await tm.register(StubbedDiscoManager());
|
await tm.register([
|
||||||
await tm.register(manager);
|
StubbedDiscoManager(),
|
||||||
|
manager,
|
||||||
|
]);
|
||||||
|
|
||||||
await manager.onPresence(
|
final stanza = Stanza.presence(
|
||||||
PresenceReceivedEvent(
|
from: aliceJid.toString(),
|
||||||
aliceJid,
|
children: [
|
||||||
Stanza.presence(
|
XMLNode.xmlns(
|
||||||
from: aliceJid.toString(),
|
tag: 'c',
|
||||||
children: [
|
xmlns: capsXmlns,
|
||||||
XMLNode.xmlns(
|
attributes: {
|
||||||
tag: 'c',
|
'hash': 'sha-1',
|
||||||
xmlns: capsXmlns,
|
'node': 'http://example.org/client',
|
||||||
attributes: {
|
'ver': 'QgayPKawpkPSDYmwT/WM94uAlu0=',
|
||||||
'hash': 'sha-1',
|
},
|
||||||
'node': 'http://example.org/client',
|
|
||||||
'ver': 'QgayPKawpkPSDYmwT/WM94uAlu0=',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
|
);
|
||||||
|
await manager.onPresence(
|
||||||
|
stanza,
|
||||||
|
StanzaHandlerData(false, false, stanza, TypedMap()),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
@ -352,27 +353,28 @@ void main() {
|
|||||||
final tm = TestingManagerHolder();
|
final tm = TestingManagerHolder();
|
||||||
final manager = EntityCapabilitiesManager('');
|
final manager = EntityCapabilitiesManager('');
|
||||||
|
|
||||||
await tm.register(StubbedDiscoManager());
|
await tm.register([
|
||||||
await tm.register(manager);
|
StubbedDiscoManager(),
|
||||||
|
manager,
|
||||||
|
]);
|
||||||
|
|
||||||
await manager.onPresence(
|
final stanza = Stanza.presence(
|
||||||
PresenceReceivedEvent(
|
from: aliceJid.toString(),
|
||||||
aliceJid,
|
children: [
|
||||||
Stanza.presence(
|
XMLNode.xmlns(
|
||||||
from: aliceJid.toString(),
|
tag: 'c',
|
||||||
children: [
|
xmlns: capsXmlns,
|
||||||
XMLNode.xmlns(
|
attributes: {
|
||||||
tag: 'c',
|
'hash': 'sha-1',
|
||||||
xmlns: capsXmlns,
|
'node': 'http://example.org/client',
|
||||||
attributes: {
|
'ver': 'QgayPKawpkPSDYmwT/WM94AAAAA=',
|
||||||
'hash': 'sha-1',
|
},
|
||||||
'node': 'http://example.org/client',
|
|
||||||
'ver': 'QgayPKawpkPSDYmwT/WM94AAAAA=',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
|
);
|
||||||
|
await manager.onPresence(
|
||||||
|
stanza,
|
||||||
|
StanzaHandlerData(false, false, stanza, TypedMap()),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
@ -385,29 +387,28 @@ void main() {
|
|||||||
final tm = TestingManagerHolder();
|
final tm = TestingManagerHolder();
|
||||||
final manager = EntityCapabilitiesManager('');
|
final manager = EntityCapabilitiesManager('');
|
||||||
|
|
||||||
await tm.register(
|
await tm.register([
|
||||||
StubbedDiscoManager()..multipleEqualIdentities = true,
|
StubbedDiscoManager()..multipleEqualIdentities = true,
|
||||||
);
|
manager,
|
||||||
await tm.register(manager);
|
]);
|
||||||
|
|
||||||
await manager.onPresence(
|
final stanza = Stanza.presence(
|
||||||
PresenceReceivedEvent(
|
from: aliceJid.toString(),
|
||||||
aliceJid,
|
children: [
|
||||||
Stanza.presence(
|
XMLNode.xmlns(
|
||||||
from: aliceJid.toString(),
|
tag: 'c',
|
||||||
children: [
|
xmlns: capsXmlns,
|
||||||
XMLNode.xmlns(
|
attributes: {
|
||||||
tag: 'c',
|
'hash': 'sha-1',
|
||||||
xmlns: capsXmlns,
|
'node': 'http://example.org/client',
|
||||||
attributes: {
|
'ver': 'QgayPKawpkPSDYmwT/WM94uAlu0=',
|
||||||
'hash': 'sha-1',
|
},
|
||||||
'node': 'http://example.org/client',
|
|
||||||
'ver': 'QgayPKawpkPSDYmwT/WM94uAlu0=',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
|
);
|
||||||
|
await manager.onPresence(
|
||||||
|
stanza,
|
||||||
|
StanzaHandlerData(false, false, stanza, TypedMap()),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
@ -420,29 +421,28 @@ void main() {
|
|||||||
final tm = TestingManagerHolder();
|
final tm = TestingManagerHolder();
|
||||||
final manager = EntityCapabilitiesManager('');
|
final manager = EntityCapabilitiesManager('');
|
||||||
|
|
||||||
await tm.register(
|
await tm.register([
|
||||||
StubbedDiscoManager()..multipleEqualFeatures = true,
|
StubbedDiscoManager()..multipleEqualFeatures = true,
|
||||||
);
|
manager,
|
||||||
await tm.register(manager);
|
]);
|
||||||
|
|
||||||
await manager.onPresence(
|
final stanza = Stanza.presence(
|
||||||
PresenceReceivedEvent(
|
from: aliceJid.toString(),
|
||||||
aliceJid,
|
children: [
|
||||||
Stanza.presence(
|
XMLNode.xmlns(
|
||||||
from: aliceJid.toString(),
|
tag: 'c',
|
||||||
children: [
|
xmlns: capsXmlns,
|
||||||
XMLNode.xmlns(
|
attributes: {
|
||||||
tag: 'c',
|
'hash': 'sha-1',
|
||||||
xmlns: capsXmlns,
|
'node': 'http://example.org/client',
|
||||||
attributes: {
|
'ver': 'QgayPKawpkPSDYmwT/WM94uAlu0=',
|
||||||
'hash': 'sha-1',
|
},
|
||||||
'node': 'http://example.org/client',
|
|
||||||
'ver': 'QgayPKawpkPSDYmwT/WM94uAlu0=',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
|
);
|
||||||
|
await manager.onPresence(
|
||||||
|
stanza,
|
||||||
|
StanzaHandlerData(false, false, stanza, TypedMap()),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
@ -455,29 +455,28 @@ void main() {
|
|||||||
final tm = TestingManagerHolder();
|
final tm = TestingManagerHolder();
|
||||||
final manager = EntityCapabilitiesManager('');
|
final manager = EntityCapabilitiesManager('');
|
||||||
|
|
||||||
await tm.register(
|
await tm.register([
|
||||||
StubbedDiscoManager()..multipleExtendedFormsWithSameType = true,
|
StubbedDiscoManager()..multipleExtendedFormsWithSameType = true,
|
||||||
);
|
manager,
|
||||||
await tm.register(manager);
|
]);
|
||||||
|
|
||||||
await manager.onPresence(
|
final stanza = Stanza.presence(
|
||||||
PresenceReceivedEvent(
|
from: aliceJid.toString(),
|
||||||
aliceJid,
|
children: [
|
||||||
Stanza.presence(
|
XMLNode.xmlns(
|
||||||
from: aliceJid.toString(),
|
tag: 'c',
|
||||||
children: [
|
xmlns: capsXmlns,
|
||||||
XMLNode.xmlns(
|
attributes: {
|
||||||
tag: 'c',
|
'hash': 'sha-1',
|
||||||
xmlns: capsXmlns,
|
'node': 'http://example.org/client',
|
||||||
attributes: {
|
'ver': 'QgayPKawpkPSDYmwT/WM94uAlu0=',
|
||||||
'hash': 'sha-1',
|
},
|
||||||
'node': 'http://example.org/client',
|
|
||||||
'ver': 'QgayPKawpkPSDYmwT/WM94uAlu0=',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
|
);
|
||||||
|
await manager.onPresence(
|
||||||
|
stanza,
|
||||||
|
StanzaHandlerData(false, false, stanza, TypedMap()),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
@ -490,29 +489,28 @@ void main() {
|
|||||||
final tm = TestingManagerHolder();
|
final tm = TestingManagerHolder();
|
||||||
final manager = EntityCapabilitiesManager('');
|
final manager = EntityCapabilitiesManager('');
|
||||||
|
|
||||||
await tm.register(
|
await tm.register([
|
||||||
StubbedDiscoManager()..invalidExtension1 = true,
|
StubbedDiscoManager()..invalidExtension1 = true,
|
||||||
);
|
manager,
|
||||||
await tm.register(manager);
|
]);
|
||||||
|
|
||||||
await manager.onPresence(
|
final stanza = Stanza.presence(
|
||||||
PresenceReceivedEvent(
|
from: aliceJid.toString(),
|
||||||
aliceJid,
|
children: [
|
||||||
Stanza.presence(
|
XMLNode.xmlns(
|
||||||
from: aliceJid.toString(),
|
tag: 'c',
|
||||||
children: [
|
xmlns: capsXmlns,
|
||||||
XMLNode.xmlns(
|
attributes: {
|
||||||
tag: 'c',
|
'hash': 'sha-1',
|
||||||
xmlns: capsXmlns,
|
'node': 'http://example.org/client',
|
||||||
attributes: {
|
'ver': 'QgayPKawpkPSDYmwT/WM94uAlu0=',
|
||||||
'hash': 'sha-1',
|
},
|
||||||
'node': 'http://example.org/client',
|
|
||||||
'ver': 'QgayPKawpkPSDYmwT/WM94uAlu0=',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
|
);
|
||||||
|
await manager.onPresence(
|
||||||
|
stanza,
|
||||||
|
StanzaHandlerData(false, false, stanza, TypedMap()),
|
||||||
);
|
);
|
||||||
|
|
||||||
final cachedItem = await manager.getCachedDiscoInfoFromJid(aliceJid);
|
final cachedItem = await manager.getCachedDiscoInfoFromJid(aliceJid);
|
||||||
@ -527,29 +525,28 @@ void main() {
|
|||||||
final tm = TestingManagerHolder();
|
final tm = TestingManagerHolder();
|
||||||
final manager = EntityCapabilitiesManager('');
|
final manager = EntityCapabilitiesManager('');
|
||||||
|
|
||||||
await tm.register(
|
await tm.register([
|
||||||
StubbedDiscoManager()..invalidExtension2 = true,
|
StubbedDiscoManager()..invalidExtension2 = true,
|
||||||
);
|
manager,
|
||||||
await tm.register(manager);
|
]);
|
||||||
|
|
||||||
await manager.onPresence(
|
final stanza = Stanza.presence(
|
||||||
PresenceReceivedEvent(
|
from: aliceJid.toString(),
|
||||||
aliceJid,
|
children: [
|
||||||
Stanza.presence(
|
XMLNode.xmlns(
|
||||||
from: aliceJid.toString(),
|
tag: 'c',
|
||||||
children: [
|
xmlns: capsXmlns,
|
||||||
XMLNode.xmlns(
|
attributes: {
|
||||||
tag: 'c',
|
'hash': 'sha-1',
|
||||||
xmlns: capsXmlns,
|
'node': 'http://example.org/client',
|
||||||
attributes: {
|
'ver': 'QgayPKawpkPSDYmwT/WM94uAlu0=',
|
||||||
'hash': 'sha-1',
|
},
|
||||||
'node': 'http://example.org/client',
|
|
||||||
'ver': 'QgayPKawpkPSDYmwT/WM94uAlu0=',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
|
);
|
||||||
|
await manager.onPresence(
|
||||||
|
stanza,
|
||||||
|
StanzaHandlerData(false, false, stanza, TypedMap()),
|
||||||
);
|
);
|
||||||
|
|
||||||
final cachedItem = await manager.getCachedDiscoInfoFromJid(aliceJid);
|
final cachedItem = await manager.getCachedDiscoInfoFromJid(aliceJid);
|
||||||
@ -564,29 +561,28 @@ void main() {
|
|||||||
final tm = TestingManagerHolder();
|
final tm = TestingManagerHolder();
|
||||||
final manager = EntityCapabilitiesManager('');
|
final manager = EntityCapabilitiesManager('');
|
||||||
|
|
||||||
await tm.register(
|
await tm.register([
|
||||||
StubbedDiscoManager()..invalidExtension3 = true,
|
StubbedDiscoManager()..invalidExtension3 = true,
|
||||||
);
|
manager,
|
||||||
await tm.register(manager);
|
]);
|
||||||
|
|
||||||
await manager.onPresence(
|
final stanza = Stanza.presence(
|
||||||
PresenceReceivedEvent(
|
from: aliceJid.toString(),
|
||||||
aliceJid,
|
children: [
|
||||||
Stanza.presence(
|
XMLNode.xmlns(
|
||||||
from: aliceJid.toString(),
|
tag: 'c',
|
||||||
children: [
|
xmlns: capsXmlns,
|
||||||
XMLNode.xmlns(
|
attributes: {
|
||||||
tag: 'c',
|
'hash': 'sha-1',
|
||||||
xmlns: capsXmlns,
|
'node': 'http://example.org/client',
|
||||||
attributes: {
|
'ver': 'QgayPKawpkPSDYmwT/WM94uAlu0=',
|
||||||
'hash': 'sha-1',
|
},
|
||||||
'node': 'http://example.org/client',
|
|
||||||
'ver': 'QgayPKawpkPSDYmwT/WM94uAlu0=',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
|
);
|
||||||
|
await manager.onPresence(
|
||||||
|
stanza,
|
||||||
|
StanzaHandlerData(false, false, stanza, TypedMap()),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
|
@ -15,8 +15,8 @@ Future<void> runIncomingStanzaHandlers(
|
|||||||
StanzaHandlerData(
|
StanzaHandlerData(
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
null,
|
|
||||||
stanza,
|
stanza,
|
||||||
|
TypedMap(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -34,8 +34,8 @@ Future<void> runOutgoingStanzaHandlers(
|
|||||||
StanzaHandlerData(
|
StanzaHandlerData(
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
null,
|
|
||||||
stanza,
|
stanza,
|
||||||
|
TypedMap(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
147
packages/moxxmpp/test/xeps/xep_0334_test.dart
Normal file
147
packages/moxxmpp/test/xeps/xep_0334_test.dart
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
import 'package:moxxmpp/moxxmpp.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import '../helpers/manager.dart';
|
||||||
|
import '../helpers/xmpp.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('Test receiving a message processing hint', () 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(),
|
||||||
|
MessageProcessingHintManager(),
|
||||||
|
]);
|
||||||
|
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">
|
||||||
|
<no-copy xmlns="urn:xmpp:hints"/>
|
||||||
|
<no-store xmlns="urn:xmpp:hints"/>
|
||||||
|
</message>
|
||||||
|
''',
|
||||||
|
);
|
||||||
|
|
||||||
|
await Future<void>.delayed(const Duration(seconds: 2));
|
||||||
|
expect(
|
||||||
|
messageEvent!.extensions
|
||||||
|
.get<MessageProcessingHintData>()!
|
||||||
|
.hints
|
||||||
|
.contains(MessageProcessingHint.noCopies),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
messageEvent!.extensions
|
||||||
|
.get<MessageProcessingHintData>()!
|
||||||
|
.hints
|
||||||
|
.contains(MessageProcessingHint.noStore),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Test sending a message processing hint', () async {
|
||||||
|
final manager = MessageManager();
|
||||||
|
final holder = TestingManagerHolder(
|
||||||
|
stubSocket: StubTCPSocket([
|
||||||
|
StanzaExpectation(
|
||||||
|
'''
|
||||||
|
<message to="user@example.org" type="chat">
|
||||||
|
<no-copy xmlns="urn:xmpp:hints"/>
|
||||||
|
<no-store xmlns="urn:xmpp:hints"/>
|
||||||
|
</message>
|
||||||
|
''',
|
||||||
|
'',
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
|
||||||
|
await holder.register([
|
||||||
|
manager,
|
||||||
|
MessageProcessingHintManager(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
await manager.sendMessage(
|
||||||
|
JID.fromString('user@example.org'),
|
||||||
|
TypedMap()
|
||||||
|
..set(
|
||||||
|
const MessageProcessingHintData([
|
||||||
|
MessageProcessingHint.noCopies,
|
||||||
|
MessageProcessingHint.noStore,
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
@ -13,7 +13,7 @@ void main() {
|
|||||||
<size>3032449</size>
|
<size>3032449</size>
|
||||||
<dimensions>4096x2160</dimensions>
|
<dimensions>4096x2160</dimensions>
|
||||||
<hash xmlns='urn:xmpp:hashes:2' algo='sha3-256'>2XarmwTlNxDAMkvymloX3S5+VbylNrJt/l5QyPa+YoU=</hash>
|
<hash xmlns='urn:xmpp:hashes:2' algo='sha3-256'>2XarmwTlNxDAMkvymloX3S5+VbylNrJt/l5QyPa+YoU=</hash>
|
||||||
<hash xmlns='urn:xmpp:hashes:2' algo='id-blake2b256'>2AfMGH8O7UNPTvUVAM9aK13mpCY=</hash>
|
<hash xmlns='urn:xmpp:hashes:2' algo='blake2b-256'>2AfMGH8O7UNPTvUVAM9aK13mpCY=</hash>
|
||||||
<desc>Photo from the summit.</desc>
|
<desc>Photo from the summit.</desc>
|
||||||
<thumbnail xmlns='urn:xmpp:thumbs:1' uri='cid:sha1+ffd7c8d28e9c5e82afea41f97108c6b4@bob.xmpp.org' media-type='image/png' width='128' height='96'/>
|
<thumbnail xmlns='urn:xmpp:thumbs:1' uri='cid:sha1+ffd7c8d28e9c5e82afea41f97108c6b4@bob.xmpp.org' media-type='image/png' width='128' height='96'/>
|
||||||
</file>
|
</file>
|
||||||
@ -28,11 +28,11 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
sfs.metadata.hashes['sha3-256'],
|
sfs.metadata.hashes[HashFunction.sha3_256],
|
||||||
'2XarmwTlNxDAMkvymloX3S5+VbylNrJt/l5QyPa+YoU=',
|
'2XarmwTlNxDAMkvymloX3S5+VbylNrJt/l5QyPa+YoU=',
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
sfs.metadata.hashes['id-blake2b256'],
|
sfs.metadata.hashes[HashFunction.blake2b256],
|
||||||
'2AfMGH8O7UNPTvUVAM9aK13mpCY=',
|
'2AfMGH8O7UNPTvUVAM9aK13mpCY=',
|
||||||
);
|
);
|
||||||
});
|
});
|
@ -1,7 +1,13 @@
|
|||||||
import 'package:moxxmpp/moxxmpp.dart';
|
import 'package:moxxmpp/moxxmpp.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import '../helpers/logging.dart';
|
||||||
|
import '../helpers/manager.dart';
|
||||||
|
import '../helpers/xmpp.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
initLogger();
|
||||||
|
|
||||||
test('Test parsing a large sticker pack', () {
|
test('Test parsing a large sticker pack', () {
|
||||||
// Example sticker pack based on the "miho" sticker pack by Movim
|
// Example sticker pack based on the "miho" sticker pack by Movim
|
||||||
final rawPack = XMLNode.fromString('''
|
final rawPack = XMLNode.fromString('''
|
||||||
@ -225,4 +231,186 @@ void main() {
|
|||||||
|
|
||||||
expect(pack.stickers.length, 16);
|
expect(pack.stickers.length, 16);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Test sending a sticker', () async {
|
||||||
|
final manager = MessageManager();
|
||||||
|
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,
|
||||||
|
StickersManager(),
|
||||||
|
SFSManager(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
await manager.sendMessage(
|
||||||
|
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));
|
||||||
|
final sticker = messageEvent!.extensions.get<StickersData>()!;
|
||||||
|
final sfs = messageEvent!.extensions.get<StatelessFileSharingData>()!;
|
||||||
|
expect(sticker.stickerPackId, 'EpRv28DHHzFrE4zd+xaNpVb4');
|
||||||
|
expect(sfs.metadata.desc, '😘');
|
||||||
|
expect(
|
||||||
|
sfs.sources.first is StatelessFileSharingUrlSource,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import 'package:moxxmpp/moxxmpp.dart';
|
import 'package:moxxmpp/moxxmpp.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import '../helpers/xmpp.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
test('Test building a singleline quote', () {
|
test('Test building a singleline quote', () {
|
||||||
final quote = QuoteData.fromBodies('Hallo Welt', 'Hello Earth!');
|
final quote = QuoteData.fromBodies('Hallo Welt', 'Hello Earth!');
|
||||||
@ -20,28 +22,250 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Applying a singleline quote', () {
|
test('Applying a singleline quote', () {
|
||||||
const body = '> Hallo Welt\nHello right back!';
|
|
||||||
const reply = ReplyData(
|
const reply = ReplyData(
|
||||||
to: '',
|
'',
|
||||||
id: '',
|
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 13,
|
end: 13,
|
||||||
|
body: '> Hallo Welt\nHello right back!',
|
||||||
);
|
);
|
||||||
|
|
||||||
final bodyWithoutFallback = reply.removeFallback(body);
|
expect(reply.withoutFallback, 'Hello right back!');
|
||||||
expect(bodyWithoutFallback, 'Hello right back!');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Applying a multiline quote', () {
|
test('Applying a multiline quote', () {
|
||||||
const body = "> Hallo Welt\n> How are you?\nI'm fine.\nThank you!";
|
|
||||||
const reply = ReplyData(
|
const reply = ReplyData(
|
||||||
to: '',
|
'',
|
||||||
id: '',
|
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 28,
|
end: 28,
|
||||||
|
body: "> Hallo Welt\n> How are you?\nI'm fine.\nThank you!",
|
||||||
);
|
);
|
||||||
|
|
||||||
final bodyWithoutFallback = reply.removeFallback(body);
|
expect(reply.withoutFallback, "I'm fine.\nThank you!");
|
||||||
expect(bodyWithoutFallback, "I'm fine.\nThank you!");
|
});
|
||||||
|
|
||||||
|
test('Test calling the message sending callback', () {
|
||||||
|
final result = MessageRepliesManager().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!.extensions.get<ReplyData>()!;
|
||||||
|
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!.extensions.get<ReplyData>()!;
|
||||||
|
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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -78,8 +78,8 @@ Future<bool> testRosterManager(
|
|||||||
StanzaHandlerData(
|
StanzaHandlerData(
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
null,
|
|
||||||
stanza,
|
stanza,
|
||||||
|
TypedMap(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -335,8 +335,8 @@ void main() {
|
|||||||
StanzaHandlerData(
|
StanzaHandlerData(
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
null,
|
|
||||||
maliciousStanza,
|
maliciousStanza,
|
||||||
|
TypedMap(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -793,4 +793,20 @@ void main() {
|
|||||||
expect(fakeSocket.getState(), 9);
|
expect(fakeSocket.getState(), 9);
|
||||||
expect(await stanzaFuture != null, true);
|
expect(await stanzaFuture != null, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Test subscription pre-approval parsing', () async {
|
||||||
|
final rawFeatures = XMLNode.fromString(
|
||||||
|
'''
|
||||||
|
<top-level>
|
||||||
|
<test-feature-1 xmlns="invalid:urn:features:1" />
|
||||||
|
<test-feature-2 xmlns="invalid:urn:features:2" />
|
||||||
|
<test-feature-3 xmlns="invalid:urn:features:3" />
|
||||||
|
<test-feature-4 xmlns="invalid:urn:features:4" />
|
||||||
|
<sub xmlns='urn:xmpp:features:pre-approval' />
|
||||||
|
</top-level>
|
||||||
|
''',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(PresenceNegotiator().matchesFeature(rawFeatures.children), true);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user