Compare commits

..

No commits in common. "327f695a40c851e2029da4cec7a07f1bafddad46" and "9e0f38154e031b73b0c1ace652be5cc9db7eaac0" have entirely different histories.

54 changed files with 1880 additions and 2211 deletions

View File

@ -1,4 +1,4 @@
## 0.4.0 ## 0.3.2
- **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,11 +11,6 @@
- **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

View File

@ -18,6 +18,7 @@ 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';
@ -39,7 +40,6 @@ 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';

View File

@ -27,9 +27,7 @@ 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';
@ -476,8 +474,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,
), ),
@ -533,15 +531,14 @@ class XmppConnection {
// Run post-send handlers // Run post-send handlers
_log.fine('Running post stanza handlers..'); _log.fine('Running post stanza handlers..');
final extensions = TypedMap<StanzaHandlerExtension>()
..set(StreamManagementData(details.excludeFromStreamManagement));
await _runOutgoingPostStanzaHandlers( await _runOutgoingPostStanzaHandlers(
newStanza, newStanza,
initial: StanzaHandlerData( initial: StanzaHandlerData(
false, false,
false, false,
null,
newStanza, newStanza,
extensions, excludeFromStreamManagement: details.excludeFromStreamManagement,
), ),
); );
_log.fine('Done'); _log.fine('Done');
@ -656,7 +653,7 @@ class XmppConnection {
Stanza stanza, { Stanza stanza, {
StanzaHandlerData? initial, StanzaHandlerData? initial,
}) async { }) async {
var state = initial ?? StanzaHandlerData(false, false, stanza, TypedMap()); var state = initial ?? StanzaHandlerData(false, false, null, stanza);
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);
@ -731,7 +728,7 @@ class XmppConnection {
// it. // it.
final incomingPreHandlers = await _runIncomingPreStanzaHandlers(stanza); final incomingPreHandlers = await _runIncomingPreStanzaHandlers(stanza);
final prefix = incomingPreHandlers.encrypted && final prefix = incomingPreHandlers.encrypted &&
incomingPreHandlers.encryptionError == null incomingPreHandlers.other['encryption_error'] == null
? '(Encrypted) ' ? '(Encrypted) '
: ''; : '';
_log.finest('<== $prefix${incomingPreHandlers.stanza.toXml()}'); _log.finest('<== $prefix${incomingPreHandlers.stanza.toXml()}');
@ -750,10 +747,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,
cancelReason: incomingPreHandlers.cancelReason, other: incomingPreHandlers.other,
), ),
); );
if (!incomingHandlers.done) { if (!incomingHandlers.done) {

View File

@ -4,11 +4,19 @@ 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_0084.dart';
import 'package:moxxmpp/src/xeps/xep_0333.dart'; import 'package:moxxmpp/src/xeps/xep_0085.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 {}
@ -67,42 +75,60 @@ 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({
this.from, required this.body,
this.to, required this.fromJid,
this.id, required this.toJid,
this.encrypted, required this.sid,
this.extensions, { required this.isCarbon,
this.type, required this.deliveryReceiptRequested,
required this.isMarkable,
required this.encrypted,
required this.other,
this.originId,
this.stanzaIds,
this.error, this.error,
this.encryptionError, this.type,
this.oob,
this.sfs,
this.sims,
this.reply,
this.chatState,
this.fun,
this.funReplacement,
this.funCancellation,
this.messageRetraction,
this.messageCorrectionId,
this.messageReactions,
this.messageProcessingHints,
this.stickerPackId,
}); });
/// The from attribute of the message.
final JID from;
/// 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 StanzaError? error; final StanzaError? error;
final String body;
/// Flag indicating whether the message was encrypted. final JID fromJid;
final JID toJid;
final String sid;
final String? type;
final String? originId;
final List<StanzaId>? stanzaIds;
final bool isCarbon;
final bool deliveryReceiptRequested;
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;
/// The error in case an encryption error occurred. final String? messageCorrectionId;
final Object? encryptionError; final MessageReactions? messageReactions;
final List<MessageProcessingHint>? messageProcessingHints;
/// Data added by other handlers. final String? stickerPackId;
final TypedMap<StanzaHandlerExtension> extensions; final Map<String, dynamic> other;
/// 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
@ -113,19 +139,13 @@ class DeliveryReceiptReceivedEvent extends XmppEvent {
} }
class ChatMarkerEvent extends XmppEvent { class ChatMarkerEvent extends XmppEvent {
ChatMarkerEvent( ChatMarkerEvent({
this.from, required this.type,
this.type, required this.from,
this.id, required 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;
} }
@ -149,6 +169,13 @@ 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 {}

View File

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

View File

@ -0,0 +1,823 @@
// 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; // Flag indicating whether the stanza should be excluded from stream management's
// resending behaviour
bool get excludeFromStreamManagement => 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,
bool excludeFromStreamManagement});
}
/// @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,
Object? excludeFromStreamManagement = null,
}) {
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?,
excludeFromStreamManagement: null == excludeFromStreamManagement
? _value.excludeFromStreamManagement
: excludeFromStreamManagement // ignore: cast_nullable_to_non_nullable
as bool,
) 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,
bool excludeFromStreamManagement});
}
/// @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,
Object? excludeFromStreamManagement = null,
}) {
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?,
excludeFromStreamManagement: null == excludeFromStreamManagement
? _value.excludeFromStreamManagement
: excludeFromStreamManagement // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// @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,
this.excludeFromStreamManagement = false})
: _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;
// Flag indicating whether the stanza should be excluded from stream management's
// resending behaviour
@override
@JsonKey()
final bool excludeFromStreamManagement;
@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, excludeFromStreamManagement: $excludeFromStreamManagement)';
}
@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) &&
(identical(other.excludeFromStreamManagement,
excludeFromStreamManagement) ||
other.excludeFromStreamManagement ==
excludeFromStreamManagement));
}
@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,
excludeFromStreamManagement
]);
@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,
final bool excludeFromStreamManagement}) = _$_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 // Flag indicating whether the stanza should be excluded from stream management's
// resending behaviour
bool get excludeFromStreamManagement;
@override
@JsonKey(ignore: true)
_$$_StanzaHandlerDataCopyWith<_$_StanzaHandlerData> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -31,4 +31,3 @@ 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';

View File

@ -1,71 +1,89 @@
import 'package:collection/collection.dart'; import 'package:moxlib/moxlib.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/util/typed_map.dart'; import 'package:moxxmpp/src/xeps/staging/file_upload_notification.dart';
import 'package:moxxmpp/src/xeps/xep_0066.dart'; import 'package:moxxmpp/src/xeps/xep_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_0449.dart'; import 'package:moxxmpp/src/xeps/xep_0448.dart';
import 'package:moxxmpp/src/xeps/xep_0461.dart'; import 'package:moxxmpp/src/xeps/xep_0461.dart';
/// A callback that is called whenever a message is sent using /// Data used to build a message stanza.
/// [MessageManager.sendMessage]. The input the typed map that is passed to ///
/// sendMessage. /// [setOOBFallbackBody] indicates, when using SFS, whether a OOB fallback should be
typedef MessageSendingCallback = List<XMLNode> Function( /// added. This is recommended when sharing files but may cause issues when the message
TypedMap<StanzaHandlerExtension>, /// stanza should include a SFS element without any fallbacks.
); class MessageDetails {
const MessageDetails({
/// The raw content of the <body /> element. required this.to,
class MessageBodyData implements StanzaHandlerExtension { this.body,
const MessageBodyData(this.body); this.requestDeliveryReceipt = false,
this.requestChatMarkers = true,
/// The content of the <body /> element. this.id,
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;
XMLNode toXML() { final bool requestChatMarkers;
return XMLNode( final String? id;
tag: 'body', final String? originId;
text: body, final String? quoteBody;
); final String? quoteId;
} final String? quoteFrom;
} final ChatState? chatState;
final StatelessFileSharingData? sfs;
/// The id attribute of the message stanza. final FileMetadataData? fun;
class MessageIdData implements StanzaHandlerExtension { final String? funReplacement;
const MessageIdData(this.id); final String? funCancellation;
final bool shouldEncrypt;
/// The id attribute of the stanza. final MessageRetractionData? messageRetraction;
final String id; final String? lastMessageCorrectionId;
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: messageHandlerPriority, priority: -100,
) )
]; ];
@ -76,69 +94,238 @@ 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(
JID.fromString(state.stanza.attributes['from']! as String), body: body != null ? body.innerText() : '',
JID.fromString(state.stanza.attributes['to']! as String), fromJid: JID.fromString(message.attributes['from']! as String),
state.stanza.attributes['id']! as String, toJid: JID.fromString(message.attributes['to']! as String),
state.encrypted, sid: message.attributes['id']! as String,
state.extensions, originId: state.originId,
type: state.stanza.attributes['type'] as String?, stanzaIds: state.stanzaIds,
error: StanzaError.fromStanza(state.stanza), isCarbon: state.isCarbon,
encryptionError: state.encryptionError, deliveryReceiptRequested: state.deliveryReceiptRequested,
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..done = true; return state.copyWith(done: true);
} }
/// Send an unawaitable message to [to]. [extensions] is a typed map that contains /// Send a message to to with the content body. If deliveryRequest is true, then
/// data for building the message. /// the message will also request a delivery receipt from the receiver.
Future<void> sendMessage( /// If id is non-null, then it will be the id of the message stanza.
JID to, /// element to this id. If originId is non-null, then it will create an "origin-id"
TypedMap<StanzaHandlerExtension> extensions, /// child in the message stanza and set its id to originId.
) async { void sendMessage(MessageDetails details) {
await getAttributes().sendStanza( assert(
StanzaDetails( implies(
Stanza.message( details.quoteBody != null,
to: to.toString(), details.quoteFrom != null && details.quoteId != null,
id: extensions.get<MessageIdData>()?.id, ),
type: 'chat', 'When quoting a message, then quoteFrom and quoteId must also be non-null',
children: _messageSendingCallbacks );
.map((c) => c(extensions))
.flattened final stanza = Stanza.message(
.toList(), 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(
stanza,
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);
}
} }

View File

@ -9,7 +9,6 @@ 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';
@ -97,7 +96,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-0380 // XEP-380
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';

View File

@ -0,0 +1 @@

View File

@ -11,4 +11,3 @@ 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';

View File

@ -6,44 +6,14 @@ 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 {
@ -53,17 +23,11 @@ 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,
) )
]; ];
@ -102,7 +66,7 @@ class PresenceManager extends XmppManagerBase {
from: JID.fromString(presence.from!), from: JID.fromString(presence.from!),
), ),
); );
return state..done = true; return state.copyWith(done: true);
} }
default: default:
break; break;
@ -111,7 +75,10 @@ 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}'");
return state..done = true; getAttributes().sendEvent(
PresenceReceivedEvent(JID.fromString(presence.from!), presence),
);
return state.copyWith(done: true);
} }
return state; return state;
@ -161,39 +128,13 @@ class PresenceManager extends XmppManagerBase {
); );
} }
/// 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]. /// Sends a subscription request to [to].
Future<void> requestSubscription(JID to) async { // TODO(PapaTutuWawa): Check if we're allowed to pre-approve
Future<void> requestSubscription(JID to, {bool preApprove = false}) async {
await getAttributes().sendStanza( await getAttributes().sendStanza(
StanzaDetails( StanzaDetails(
Stanza.presence( Stanza.presence(
type: 'subscribe', type: preApprove ? 'subscribed' : 'subscribe',
to: to.toString(), to: to.toString(),
), ),
awaitable: false, awaitable: false,
@ -203,15 +144,7 @@ class PresenceManager extends XmppManagerBase {
/// Accept a subscription request from [to]. /// Accept a subscription request from [to].
Future<void> acceptSubscriptionRequest(JID to) async { Future<void> acceptSubscriptionRequest(JID to) async {
await getAttributes().sendStanza( await requestSubscription(to, preApprove: true);
StanzaDetails(
Stanza.presence(
type: 'subscribed',
to: to.toString(),
),
awaitable: false,
),
);
} }
/// Send a subscription request rejection to [to]. /// Send a subscription request rejection to [to].

View File

@ -145,7 +145,7 @@ class RosterManager extends XmppManagerBase {
logger.warning( logger.warning(
'Roster push invalid! Unexpected from attribute: ${stanza.toXml()}', 'Roster push invalid! Unexpected from attribute: ${stanza.toXml()}',
); );
return state..done = true; return state.copyWith(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..done = true; return state.copyWith(done: true);
} }
unawaited( unawaited(
@ -177,7 +177,7 @@ class RosterManager extends XmppManagerBase {
[], [],
); );
return state..done = true; return state.copyWith(done: true);
} }
/// Shared code between requesting rosters without and with roster versioning, if /// Shared code between requesting rosters without and with roster versioning, if

View File

@ -1,23 +0,0 @@
/// 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?;
}

View File

@ -2,70 +2,14 @@ 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);
@ -103,14 +47,11 @@ class FileUploadNotificationManager extends XmppManagerBase {
) async { ) async {
final funElement = final funElement =
message.firstTag('file-upload', xmlns: fileUploadNotificationXmlns)!; message.firstTag('file-upload', xmlns: fileUploadNotificationXmlns)!;
return state return state.copyWith(
..extensions.set( fun: FileMetadataData.fromXML(
FileUploadNotificationData( funElement.firstTag('file', xmlns: fileMetadataXmlns)!,
FileMetadataData.fromXML( ),
funElement.firstTag('file', xmlns: fileMetadataXmlns)!, );
),
),
);
} }
Future<StanzaHandlerData> _onFileUploadNotificationReplacementReceived( Future<StanzaHandlerData> _onFileUploadNotificationReplacementReceived(
@ -119,12 +60,9 @@ class FileUploadNotificationManager extends XmppManagerBase {
) async { ) async {
final element = final element =
message.firstTag('replaces', xmlns: fileUploadNotificationXmlns)!; message.firstTag('replaces', xmlns: fileUploadNotificationXmlns)!;
return state return state.copyWith(
..extensions.set( funReplacement: element.attributes['id']! as String,
FileUploadNotificationReplacementData( );
element.attributes['id']! as String,
),
);
} }
Future<StanzaHandlerData> _onFileUploadNotificationCancellationReceived( Future<StanzaHandlerData> _onFileUploadNotificationCancellationReceived(
@ -133,42 +71,8 @@ class FileUploadNotificationManager extends XmppManagerBase {
) async { ) async {
final element = final element =
message.firstTag('cancels', xmlns: fileUploadNotificationXmlns)!; message.firstTag('cancels', xmlns: fileUploadNotificationXmlns)!;
return state return state.copyWith(
..extensions.set( funCancellation: element.attributes['id']! as String,
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);
} }
} }

View File

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

View File

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

View File

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

View File

@ -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 implements StanzaHandlerExtension { class OOBData {
const OOBData(this.url, this.desc); const OOBData({this.url, this.desc});
/// The communicated URL of the OOB data
final String? url; final String? url;
/// The description of the url.
final String? desc; final String? desc;
}
XMLNode toXML() { XMLNode constructOOBNode(OOBData data) {
return XMLNode.xmlns( final children = List<XMLNode>.empty(growable: true);
tag: 'x',
xmlns: oobDataXmlns, if (data.url != null) {
children: [ children.add(XMLNode(tag: 'url', text: data.url));
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,33 +59,11 @@ class OOBManager extends XmppManagerBase {
final url = x.firstTag('url'); final url = x.firstTag('url');
final desc = x.firstTag('desc'); final desc = x.firstTag('desc');
return state return state.copyWith(
..extensions.set( oob: OOBData(
OOBData( url: url?.innerText(),
url?.innerText(), desc: desc?.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);
} }
} }

View File

@ -2,59 +2,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';
enum ChatState implements StanzaHandlerExtension { enum ChatState { active, composing, paused, inactive, gone }
active,
composing,
paused,
inactive,
gone;
factory ChatState.fromName(String state) { ChatState chatStateFromString(String raw) {
switch (state) { switch (raw) {
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);
@ -80,55 +64,62 @@ class ChatStateManager extends XmppManagerBase {
StanzaHandlerData state, StanzaHandlerData state,
) async { ) async {
final element = state.stanza.firstTagByXmlns(chatStateXmlns)!; final element = state.stanza.firstTagByXmlns(chatStateXmlns)!;
ChatState? chatState;
try { switch (element.tag) {
state.extensions.set(ChatState.fromName(element.tag)); case 'active':
} catch (_) { {
logger.finest('Ignoring invalid chat state ${element.tag}'); chatState = ChatState.active;
}
break;
case 'composing':
{
chatState = ChatState.composing;
}
break;
case 'paused':
{
chatState = ChatState.paused;
}
break;
case 'inactive':
{
chatState = ChatState.inactive;
}
break;
case 'gone':
{
chatState = ChatState.gone;
}
break;
default:
{
logger.warning("Received invalid chat state '${element.tag}'");
}
} }
return state; return state.copyWith(chatState: chatState);
} }
/// Send a chat state notification to [to]. You can specify the type attribute /// Send a chat state notification to [to]. You can specify the type attribute
/// of the message with [messageType]. /// of the message with [messageType].
Future<void> sendChatState( void sendChatState(
ChatState state, ChatState state,
String to, { String to, {
String messageType = 'chat', String messageType = 'chat',
}) async { }) {
await getAttributes().sendStanza( final tagName = state.toString().split('.').last;
getAttributes().sendStanza(
StanzaDetails( StanzaDetails(
Stanza.message( Stanza.message(
to: to, to: to,
type: messageType, type: messageType,
children: [ children: [
state.toXML(), XMLNode.xmlns(tag: tagName, xmlns: chatStateXmlns),
], ],
), ),
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);
}
} }

View File

@ -4,13 +4,10 @@ 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';
@ -108,20 +105,7 @@ class EntityCapabilitiesManager extends XmppManagerBase {
Future<bool> isSupported() async => true; Future<bool> isSupported() async => true;
@override @override
List<String> getDiscoFeatures() => [ List<String> getDiscoFeatures() => [capsXmlns];
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.
@ -175,38 +159,33 @@ class EntityCapabilitiesManager extends XmppManagerBase {
} }
@visibleForTesting @visibleForTesting
Future<StanzaHandlerData> onPresence( Future<void> onPresence(PresenceReceivedEvent event) async {
Stanza stanza, final c = event.presence.firstTag('c', xmlns: capsXmlns);
StanzaHandlerData state, if (c == null) {
) async { return;
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 state; return;
} }
// 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 state; return;
} }
final dm = getAttributes().getManagerById<DiscoManager>(discoManager)!; final dm = getAttributes().getManagerById<DiscoManager>(discoManager)!;
final discoRequest = await dm.discoInfoQuery( final discoRequest = await dm.discoInfoQuery(
from, event.jid,
node: capabilityNode, node: capabilityNode,
); );
if (discoRequest.isType<DiscoError>()) { if (discoRequest.isType<DiscoError>()) {
return state; return;
} }
final discoInfo = discoRequest.get<DiscoInfo>(); final discoInfo = discoRequest.get<DiscoInfo>();
@ -215,13 +194,13 @@ class EntityCapabilitiesManager extends XmppManagerBase {
await dm.addCachedDiscoInfo( await dm.addCachedDiscoInfo(
MapEntry<DiscoCacheKey, DiscoInfo>( MapEntry<DiscoCacheKey, DiscoInfo>(
DiscoCacheKey( DiscoCacheKey(
from, event.jid,
null, null,
), ),
discoInfo, discoInfo,
), ),
); );
return state; return;
} }
// Validate the disco#info result according to XEP-0115 § 5.4 // Validate the disco#info result according to XEP-0115 § 5.4
@ -235,7 +214,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 state; return;
} }
} }
@ -246,7 +225,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 state; return;
} }
} }
@ -274,7 +253,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 state; return;
} }
} }
@ -289,7 +268,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 state; return;
} }
// Check if the field type is hidden // Check if the field type is hidden
@ -318,16 +297,14 @@ class EntityCapabilitiesManager extends XmppManagerBase {
if (computedCapabilityHash == ver) { if (computedCapabilityHash == ver) {
await _cacheLock.synchronized(() { await _cacheLock.synchronized(() {
_jidToCapHashCache[from.toString()] = ver; _jidToCapHashCache[event.jid.toString()] = ver;
_capHashCache[ver] = newDiscoInfo; _capHashCache[ver] = newDiscoInfo;
}); });
} else { } else {
logger.warning( logger.warning(
'Capability hash mismatch from $from: Received "$ver", expected "$computedCapabilityHash".', 'Capability hash mismatch from ${event.jid}: Received "$ver", expected "$computedCapabilityHash".',
); );
} }
return state;
} }
@visibleForTesting @visibleForTesting
@ -338,7 +315,9 @@ class EntityCapabilitiesManager extends XmppManagerBase {
@override @override
Future<void> onXmppEvent(XmppEvent event) async { Future<void> onXmppEvent(XmppEvent event) async {
if (event is StreamNegotiationsDoneEvent) { if (event is PresenceReceivedEvent) {
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);
} }

View File

@ -4,43 +4,23 @@ 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 MessageDeliveryReceiptData implements StanzaHandlerExtension { XMLNode makeMessageDeliveryRequest() {
const MessageDeliveryReceiptData(this.receiptRequested); return XMLNode.xmlns(
tag: 'request',
/// Indicates whether a delivery receipt is requested or not. xmlns: deliveryXmlns,
final bool receiptRequested; );
XMLNode toXML() {
assert(
receiptRequested,
'This method makes little sense with receiptRequested == false',
);
return XMLNode.xmlns(
tag: 'request',
xmlns: deliveryXmlns,
);
}
} }
class MessageDeliveryReceivedData implements StanzaHandlerExtension { XMLNode makeMessageDeliveryResponse(String id) {
const MessageDeliveryReceivedData(this.id); return XMLNode.xmlns(
tag: 'received',
/// The stanza id of the message we received. xmlns: deliveryXmlns,
final String id; attributes: {'id': id},
);
XMLNode toXML() {
return XMLNode.xmlns(
tag: 'received',
xmlns: deliveryXmlns,
attributes: {'id': id},
);
}
} }
class MessageDeliveryReceiptManager extends XmppManagerBase { class MessageDeliveryReceiptManager extends XmppManagerBase {
@ -76,7 +56,7 @@ class MessageDeliveryReceiptManager extends XmppManagerBase {
Stanza message, Stanza message,
StanzaHandlerData state, StanzaHandlerData state,
) async { ) async {
return state..extensions.set(const MessageDeliveryReceiptData(true)); return state.copyWith(deliveryReceiptRequested: true);
} }
Future<StanzaHandlerData> _onDeliveryReceiptReceived( Future<StanzaHandlerData> _onDeliveryReceiptReceived(
@ -84,16 +64,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(
@ -101,27 +81,6 @@ class MessageDeliveryReceiptManager extends XmppManagerBase {
id: received.attributes['id']! as String, id: received.attributes['id']! as String,
), ),
); );
return state..done = true; return state.copyWith(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);
} }
} }

View File

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

View File

@ -1,8 +0,0 @@
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;
}

View File

@ -15,7 +15,6 @@ 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
@ -403,9 +402,7 @@ class StreamManagementManager extends XmppManagerBase {
if (isStreamManagementEnabled()) { if (isStreamManagementEnabled()) {
await _incrementC2S(); await _incrementC2S();
if (state.extensions.get<StreamManagementData>()?.exclude ?? false) { if (state.excludeFromStreamManagement) return state;
return state;
}
_unackedStanzas[_state.c2s] = stanza; _unackedStanzas[_state.c2s] = stanza;
await _sendAckRequest(); await _sendAckRequest();

View File

@ -1,5 +1,4 @@
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';
@ -8,14 +7,10 @@ import 'package:moxxmpp/src/namespaces.dart';
import 'package:moxxmpp/src/stanza.dart'; import 'package:moxxmpp/src/stanza.dart';
@immutable @immutable
class DelayedDeliveryData implements StanzaHandlerExtension { class DelayedDelivery {
const DelayedDeliveryData(this.from, this.timestamp); const DelayedDelivery(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 {
@ -28,8 +23,6 @@ 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,
), ),
@ -39,14 +32,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 return state.copyWith(
..extensions.set( delayedDelivery: DelayedDelivery(
DelayedDeliveryData( delay.attributes['from']! as String,
JID.fromString(delay.attributes['from']! as String), DateTime.parse(delay.attributes['stamp']! as String),
DateTime.parse(delay.attributes['stamp']! as String), ),
), );
);
} }
} }

View File

@ -14,13 +14,6 @@ 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);
@ -84,14 +77,15 @@ 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..done = true; if (!isCarbonValid(from)) return state.copyWith(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 return state.copyWith(
..extensions.set(const CarbonsData(true)) isCarbon: true,
..stanza = carbon; stanza: carbon,
);
} }
Future<StanzaHandlerData> _onMessageSent( Future<StanzaHandlerData> _onMessageSent(
@ -100,14 +94,15 @@ 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..done = true; if (!isCarbonValid(from)) return state.copyWith(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 return state.copyWith(
..extensions.set(const CarbonsData(true)) isCarbon: true,
..stanza = carbon; stanza: carbon,
);
} }
/// Send a request to the server, asking it to enable Message Carbons. /// Send a request to the server, asking it to enable Message Carbons.

View File

@ -66,7 +66,7 @@ enum HashFunction {
return HashFunction.blake2b512; return HashFunction.blake2b512;
} }
throw Exception('Invalid hash function $name'); throw Exception();
} }
/// Like [HashFunction.fromName], but returns null if the hash function is unknown /// Like [HashFunction.fromName], but returns null if the hash function is unknown

View File

@ -2,27 +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 LastMessageCorrectionData implements StanzaHandlerExtension { XMLNode makeLastMessageCorrectionEdit(String id) {
const LastMessageCorrectionData(this.id); return XMLNode.xmlns(
tag: 'replace',
/// The id the LMC applies to. xmlns: lmcXmlns,
final String id; attributes: <String, String>{
'id': id,
XMLNode toXML() { },
return XMLNode.xmlns( );
tag: 'replace',
xmlns: lmcXmlns,
attributes: {
'id': id,
},
);
}
} }
class LastMessageCorrectionManager extends XmppManagerBase { class LastMessageCorrectionManager extends XmppManagerBase {
@ -51,30 +42,8 @@ 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 return state.copyWith(
..extensions.set( lastMessageCorrectionSid: edit.attributes['id']! as String,
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);
} }
} }

View File

@ -4,86 +4,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';
enum ChatMarker { XMLNode makeChatMarkerMarkable() {
received, return XMLNode.xmlns(
displayed, tag: 'markable',
acknowledged; xmlns: chatMarkersXmlns,
);
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,
);
}
} }
class MarkableData implements StanzaHandlerExtension { XMLNode makeChatMarker(String tag, String id) {
const MarkableData(this.isMarkable); assert(
['received', 'displayed', 'acknowledged'].contains(tag),
/// Indicates whether the message can be replied to with a chat marker. 'Invalid chat marker',
final bool isMarkable; );
return XMLNode.xmlns(
XMLNode toXML() { tag: tag,
assert(isMarkable, ''); xmlns: chatMarkersXmlns,
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 {
@ -110,52 +51,23 @@ class ChatMarkerManager extends XmppManagerBase {
Stanza message, Stanza message,
StanzaHandlerData state, StanzaHandlerData state,
) async { ) async {
final element = message.firstTagByXmlns(chatMarkersXmlns)!; final marker = message.firstTagByXmlns(chatMarkersXmlns)!;
// Handle the <markable /> explicitly // Handle the <markable /> explicitly
if (element.tag == 'markable') { if (marker.tag == 'markable') return state.copyWith(isMarkable: true);
return state..extensions.set(const MarkableData(true));
}
try { if (!['received', 'displayed', 'acknowledged'].contains(marker.tag)) {
logger.warning("Unknown message marker '${marker.tag}' found.");
} else {
getAttributes().sendEvent( getAttributes().sendEvent(
ChatMarkerEvent( ChatMarkerEvent(
JID.fromString(message.from!), from: JID.fromString(message.from!),
ChatMarker.fromName(element.tag), type: marker.tag,
element.attributes['id']! as String, id: marker.attributes['id']! as String,
), ),
); );
} catch (_) {
logger.warning("Unknown message marker '${element.tag}' found.");
} }
return state..done = true; return state.copyWith(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);
} }
} }

View File

@ -1,36 +1,31 @@
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,
}
factory MessageProcessingHint.fromName(String name) { MessageProcessingHint messageProcessingHintFromXml(XMLNode element) {
switch (name) { switch (element.tag) {
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;
} }
XMLNode toXML() { assert(false, 'Invalid Message Processing Hint: ${element.tag}');
return MessageProcessingHint.noStore;
}
extension XmlExtension on MessageProcessingHint {
XMLNode toXml() {
String tag; String tag;
switch (this) { switch (this) {
case MessageProcessingHint.noPermanentStore: case MessageProcessingHint.noPermanentStore:
@ -53,60 +48,3 @@ enum 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);
}
}

View File

@ -3,11 +3,9 @@ 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 {
@ -22,7 +20,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,
@ -34,38 +32,12 @@ class StanzaId {
} }
} }
class StableIdData implements StanzaHandlerExtension { XMLNode makeOriginIdElement(String id) {
const StableIdData(this.originId, this.stanzaIds); return XMLNode.xmlns(
tag: 'origin-id',
/// <origin-id /> xmlns: stableIdXmlns,
final String? originId; attributes: {'id': id},
);
/// 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 {
@ -114,29 +86,9 @@ class StableIdManager extends XmppManagerBase {
.toList(); .toList();
} }
return state return state.copyWith(
..extensions.set( originId: originId,
StableIdData( stanzaIds: stanzaIds,
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);
} }
} }

View File

@ -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 implements StanzaHandlerExtension { enum ExplicitEncryptionType {
otr, otr,
legacyOpenPGP, legacyOpenPGP,
openPGP, openPGP,
omemo, omemo,
omemo1, omemo1,
omemo2, omemo2,
unknown; unknown,
}
factory ExplicitEncryptionType.fromNamespace(String namespace) { String _explicitEncryptionTypeToString(ExplicitEncryptionType type) {
switch (namespace) { switch (type) {
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 '';
}
} }
}
String toNamespace() { ExplicitEncryptionType _explicitEncryptionTypeFromString(String str) {
switch (this) { switch (str) {
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;
}
} }
}
/// Create an <encryption /> element with an xmlns indicating what type of encryption was /// Create an <encryption /> element with [type] indicating which type of encryption was
/// used. /// used.
XMLNode toXML() { XMLNode buildEmeElement(ExplicitEncryptionType type) {
return XMLNode.xmlns( return XMLNode.xmlns(
tag: 'encryption', tag: 'encryption',
xmlns: emeXmlns, xmlns: emeXmlns,
attributes: <String, String>{ attributes: <String, String>{
'namespace': toNamespace(), 'namespace': _explicitEncryptionTypeToString(type),
}, },
); );
}
} }
class EmeManager extends XmppManagerBase { class EmeManager extends XmppManagerBase {
@ -91,11 +91,10 @@ class EmeManager extends XmppManagerBase {
) async { ) async {
final encryption = message.firstTag('encryption', xmlns: emeXmlns)!; final encryption = message.firstTag('encryption', xmlns: emeXmlns)!;
return state return state.copyWith(
..extensions.set( encryptionType: _explicitEncryptionTypeFromString(
ExplicitEncryptionType.fromNamespace( encryption.attributes['namespace']! as String,
encryption.attributes['namespace']! as String, ),
), );
);
} }
} }

View File

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

View File

@ -16,7 +16,6 @@ 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';
@ -277,7 +276,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,
@ -364,18 +363,19 @@ abstract class BaseOmemoManager extends XmppManagerBase {
logger.finest('Encryption done'); logger.finest('Encryption done');
if (!result.isSuccess(2)) { if (!result.isSuccess(2)) {
return state final other = Map<String, dynamic>.from(state.other);
..cancel = true other['encryption_error_jids'] = result.jidEncryptionErrors;
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(),
..encryptionError = OmemoEncryptionError( cancel: true,
result.jidEncryptionErrors, );
result.deviceEncryptionErrors,
);
} }
final encrypted = _buildEncryptedElement( final encrypted = _buildEncryptedElement(
@ -389,16 +389,19 @@ abstract class BaseOmemoManager extends XmppManagerBase {
if (stanza.tag == 'message') { if (stanza.tag == 'message') {
children children
// Add EME data // Add EME data
..add(ExplicitEncryptionType.omemo2.toXML()) ..add(buildEmeElement(ExplicitEncryptionType.omemo2))
// 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 return state.copyWith(
..stanza = state.stanza.copyWith(children: children) stanza: state.stanza.copyWith(
..encrypted = true; children: children,
),
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,
@ -441,19 +444,17 @@ abstract class BaseOmemoManager extends XmppManagerBase {
OmemoIncomingStanza( OmemoIncomingStanza(
fromJid.toString(), fromJid.toString(),
sid, sid,
state.extensions state.delayedDelivery?.timestamp.millisecondsSinceEpoch ??
.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) {
state.encryptionError = result.error; other['encryption_error'] = result.error;
} else { } else {
children = stanza.children children = stanza.children
.where( .where(
@ -470,9 +471,11 @@ 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!}');
return state other['encryption_error'] = InvalidEnvelopePayloadException();
..encrypted = true return state.copyWith(
..encryptionError = InvalidEnvelopePayloadException(); encrypted: true,
other: other,
);
} }
final envelopeChildren = envelope.firstTag('content')?.children; final envelopeChildren = envelope.firstTag('content')?.children;
@ -486,13 +489,13 @@ abstract class BaseOmemoManager extends XmppManagerBase {
} }
if (!checkAffixElements(envelope, stanza.from!, ourJid)) { if (!checkAffixElements(envelope, stanza.from!, ourJid)) {
state.encryptionError = InvalidAffixElementsException(); other['encryption_error'] = InvalidAffixElementsException();
} }
} }
return state return state.copyWith(
..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,
@ -500,7 +503,9 @@ 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

View File

@ -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 implements StanzaHandlerExtension { class StatelessMediaSharingData {
const StatelessMediaSharingData({ const StatelessMediaSharingData({
required this.mediaType, required this.mediaType,
required this.size, required this.size,
@ -70,9 +70,7 @@ 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
@ -100,7 +98,7 @@ class SIMSManager extends XmppManagerBase {
final references = message.findTags('reference', xmlns: referenceXmlns); final references = message.findTags('reference', xmlns: referenceXmlns);
for (final ref in references) { for (final ref in references) {
final sims = ref.firstTag('media-sharing', xmlns: simsXmlns); final sims = ref.firstTag('media-sharing', xmlns: simsXmlns);
if (sims != null) return state..extensions.set(parseSIMSElement(sims)); if (sims != null) return state.copyWith(sims: parseSIMSElement(sims));
} }
return state; return state;

View File

@ -2,19 +2,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/util/typed_map.dart';
class MessageRetractionData implements StanzaHandlerExtension { class MessageRetractionData {
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;
} }
@ -54,55 +47,11 @@ class MessageRetractionManager extends XmppManagerBase {
final isFallbackBody = final isFallbackBody =
message.firstTag('fallback', xmlns: fallbackIndicationXmlns) != null; message.firstTag('fallback', xmlns: fallbackIndicationXmlns) != null;
return state return state.copyWith(
..extensions.set( messageRetraction: MessageRetractionData(
MessageRetractionData( applyTo.attributes['id']! as String,
applyTo.attributes['id']! as String, isFallbackBody ? message.firstTag('body')?.innerText() : null,
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);
} }
} }

View File

@ -2,18 +2,16 @@ 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 MessageReactionsData implements StanzaHandlerExtension { class MessageReactions {
const MessageReactionsData(this.messageId, this.emojis); const MessageReactions(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,
@ -57,36 +55,14 @@ class MessageReactionsManager extends XmppManagerBase {
) async { ) async {
final reactionsElement = final reactionsElement =
message.firstTag('reactions', xmlns: messageReactionsXmlns)!; message.firstTag('reactions', xmlns: messageReactionsXmlns)!;
return state return state.copyWith(
..extensions.set( messageReactions: MessageReactions(
MessageReactionsData( reactionsElement.attributes['id']! as String,
reactionsElement.attributes['id']! as String, reactionsElement.children
reactionsElement.children .where((c) => c.tag == 'reaction')
.where((c) => c.tag == 'reaction') .map((c) => c.innerText())
.map((c) => c.innerText()) .toList(),
.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);
} }
} }

View File

@ -3,12 +3,9 @@ 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';
@ -73,12 +70,8 @@ List<StatelessFileSharingSource> processStatelessFileSharingSources(
return sources; return sources;
} }
class StatelessFileSharingData implements StanzaHandlerExtension { class StatelessFileSharingData {
const StatelessFileSharingData( const StatelessFileSharingData(this.metadata, this.sources);
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) {
@ -95,10 +88,6 @@ class StatelessFileSharingData implements StanzaHandlerExtension {
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',
@ -133,54 +122,23 @@ class SFSManager extends XmppManagerBase {
tagXmlns: sfsXmlns, tagXmlns: sfsXmlns,
callback: _onMessage, callback: _onMessage,
// Before the message handler // Before the message handler
priority: -98, priority: -99,
) )
]; ];
@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 return state.copyWith(
..extensions.set( sfs: StatelessFileSharingData.fromXML(
StatelessFileSharingData.fromXML(sfs), sfs,
); ),
} );
@override
Future<void> postRegisterCallback() async {
await super.postRegisterCallback();
// Register the sending callback
getAttributes()
.getManagerById<MessageManager>(messageManager)
?.registerMessageSendingCallback(_messageSendingCallback);
} }
} }

View File

@ -4,13 +4,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/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';
@ -229,19 +227,6 @@ 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);
@ -264,35 +249,9 @@ class StickersManager extends XmppManagerBase {
StanzaHandlerData state, StanzaHandlerData state,
) async { ) async {
final sticker = stanza.firstTag('sticker', xmlns: stickersXmlns)!; final sticker = stanza.firstTag('sticker', xmlns: stickersXmlns)!;
return state return state.copyWith(
..extensions.set( stickerPackId: sticker.attributes['pack']! as String,
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
@ -360,14 +319,4 @@ 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);
}
} }

View File

@ -1,38 +1,24 @@
import 'package:freezed_annotation/freezed_annotation.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';
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';
/// A reply to a message. /// Data summarizing the XEP-0461 data.
class ReplyData implements StanzaHandlerExtension { class ReplyData {
const ReplyData( const ReplyData({
this.id, { required this.id,
this.body, this.to,
this.jid,
this.start, this.start,
this.end, this.end,
}); });
ReplyData.fromQuoteData( /// The bare JID to whom the reply applies to
this.id, final String? to;
QuoteData quote, {
this.jid,
}) : body = quote.body,
start = 0,
end = quote.fallbackLength;
/// The JID of the entity whose message we are replying to. /// The stanza ID of the message that is replied 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)
@ -41,21 +27,18 @@ class ReplyData implements StanzaHandlerExtension {
/// 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? get withoutFallback { String removeFallback(String body) {
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);
@ -102,54 +85,13 @@ 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;
@ -161,25 +103,13 @@ class MessageRepliesManager extends XmppManagerBase {
end = int.parse(body.attributes['end']! as String); end = int.parse(body.attributes['end']! as String);
} }
return state return state.copyWith(
..extensions.set( reply: ReplyData(
ReplyData( id: id,
reply.attributes['id']! as String, to: to,
jid: jid, start: start,
start: start, end: end,
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);
} }
} }

View File

@ -1,6 +1,6 @@
name: moxxmpp name: moxxmpp
description: A pure-Dart XMPP library description: A pure-Dart XMPP library
version: 0.4.0 version: 0.3.2
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

View File

@ -1,14 +1,13 @@
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/stanza.dart'; import 'package:moxxmpp/src/socket.dart';
import 'package:moxxmpp/src/stringxml.dart'; import 'package:moxxmpp/src/stringxml.dart';
import '../helpers/xmpp.dart'; import '../helpers/xmpp.dart';
@ -16,15 +15,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({
StubTCPSocket? stubSocket, BaseSocketWrapper? socket,
}) : socket = stubSocket ?? StubTCPSocket([]); }) : _socket = socket ?? StubTCPSocket([]);
final StubTCPSocket socket; final BaseSocketWrapper _socket;
final Map<String, XmppManagerBase> _managers = {}; final Map<String, XmppManagerBase> _managers = {};
/// A list of events that were triggered. // The amount of stanzas sent
final List<XmppEvent> sentEvents = List.empty(growable: true); int sentStanzas = 0;
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(
@ -32,40 +31,42 @@ class TestingManagerHolder {
password: 'abc123', password: 'abc123',
); );
Future<XMLNode?> _sendStanza(StanzaDetails details) async { Future<XMLNode> _sendStanza(
socket.write(details.stanza.toXml()); stanza, {
return null; bool addId = true,
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(List<XmppManagerBase> managers) async { Future<void> register(XmppManagerBase manager) async {
for (final manager in managers) { manager.register(
manager.register( XmppManagerAttributes(
XmppManagerAttributes( sendStanza: _sendStanza,
sendStanza: _sendStanza, getConnection: () => XmppConnection(
getConnection: () => XmppConnection( TestingReconnectionPolicy(),
TestingReconnectionPolicy(), AlwaysConnectedConnectivityManager(),
AlwaysConnectedConnectivityManager(), ClientToServerNegotiator(),
ClientToServerNegotiator(), _socket,
socket,
),
getConnectionSettings: () => settings,
sendNonza: (_) {},
sendEvent: sentEvents.add,
getSocket: () => socket,
getNegotiatorById: getNegotiatorNullStub,
getFullJID: () => jid,
getManagerById: _getManagerById,
), ),
); getConnectionSettings: () => settings,
_managers[manager.id] = manager; sendNonza: (_) {},
} sendEvent: (_) {},
getSocket: () => _socket,
getNegotiatorById: getNegotiatorNullStub,
getFullJID: () => jid,
getManagerById: _getManagerById,
),
);
for (final manager in managers) { await manager.postRegisterCallback();
await manager.postRegisterCallback(); _managers[manager.id] = manager;
}
} }
} }

View File

@ -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,

View File

@ -1,39 +0,0 @@
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);
});
}

View File

@ -120,7 +120,8 @@ void main() {
final ecm = EntityCapabilitiesManager(''); final ecm = EntityCapabilitiesManager('');
final dm = DiscoManager([]); final dm = DiscoManager([]);
await tm.register([dm, ecm]); await tm.register(dm);
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');
@ -139,7 +140,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.socket.getState(), 0); expect(tm.sentStanzas, 0);
}); });
}); });
} }

View File

@ -55,10 +55,8 @@ void main() {
() async { () async {
final manager = PubSubManager(); final manager = PubSubManager();
final tm = TestingManagerHolder(); final tm = TestingManagerHolder();
await tm.register([ await tm.register(StubbedDiscoManager(false));
StubbedDiscoManager(false), await tm.register(manager);
manager,
]);
final result = await manager.preprocessPublishOptions( final result = await manager.preprocessPublishOptions(
JID.fromString('pubsub.server.example.org'), JID.fromString('pubsub.server.example.org'),
@ -74,10 +72,8 @@ void main() {
() async { () async {
final manager = PubSubManager(); final manager = PubSubManager();
final tm = TestingManagerHolder(); final tm = TestingManagerHolder();
await tm.register([ await tm.register(StubbedDiscoManager(true));
StubbedDiscoManager(true), await tm.register(manager);
manager,
]);
final result = await manager.preprocessPublishOptions( final result = await manager.preprocessPublishOptions(
JID.fromString('pubsub.server.example.org'), JID.fromString('pubsub.server.example.org'),
@ -172,6 +168,7 @@ void main() {
PubSubManager(), PubSubManager(),
DiscoManager([]), DiscoManager([]),
PresenceManager(), PresenceManager(),
MessageManager(),
RosterManager(TestingRosterStateManager(null, [])), RosterManager(TestingRosterStateManager(null, [])),
]); ]);
await connection.registerFeatureNegotiators([ await connection.registerFeatureNegotiators([

View File

@ -319,28 +319,27 @@ void main() {
final tm = TestingManagerHolder(); final tm = TestingManagerHolder();
final manager = EntityCapabilitiesManager(''); final manager = EntityCapabilitiesManager('');
await tm.register([ await tm.register(StubbedDiscoManager());
StubbedDiscoManager(), await tm.register(manager);
manager,
]);
final stanza = Stanza.presence(
from: aliceJid.toString(),
children: [
XMLNode.xmlns(
tag: 'c',
xmlns: capsXmlns,
attributes: {
'hash': 'sha-1',
'node': 'http://example.org/client',
'ver': 'QgayPKawpkPSDYmwT/WM94uAlu0=',
},
),
],
);
await manager.onPresence( await manager.onPresence(
stanza, PresenceReceivedEvent(
StanzaHandlerData(false, false, stanza, TypedMap()), aliceJid,
Stanza.presence(
from: aliceJid.toString(),
children: [
XMLNode.xmlns(
tag: 'c',
xmlns: capsXmlns,
attributes: {
'hash': 'sha-1',
'node': 'http://example.org/client',
'ver': 'QgayPKawpkPSDYmwT/WM94uAlu0=',
},
),
],
),
),
); );
expect( expect(
@ -353,28 +352,27 @@ void main() {
final tm = TestingManagerHolder(); final tm = TestingManagerHolder();
final manager = EntityCapabilitiesManager(''); final manager = EntityCapabilitiesManager('');
await tm.register([ await tm.register(StubbedDiscoManager());
StubbedDiscoManager(), await tm.register(manager);
manager,
]);
final stanza = Stanza.presence(
from: aliceJid.toString(),
children: [
XMLNode.xmlns(
tag: 'c',
xmlns: capsXmlns,
attributes: {
'hash': 'sha-1',
'node': 'http://example.org/client',
'ver': 'QgayPKawpkPSDYmwT/WM94AAAAA=',
},
),
],
);
await manager.onPresence( await manager.onPresence(
stanza, PresenceReceivedEvent(
StanzaHandlerData(false, false, stanza, TypedMap()), aliceJid,
Stanza.presence(
from: aliceJid.toString(),
children: [
XMLNode.xmlns(
tag: 'c',
xmlns: capsXmlns,
attributes: {
'hash': 'sha-1',
'node': 'http://example.org/client',
'ver': 'QgayPKawpkPSDYmwT/WM94AAAAA=',
},
),
],
),
),
); );
expect( expect(
@ -387,28 +385,29 @@ 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,
]);
final stanza = Stanza.presence(
from: aliceJid.toString(),
children: [
XMLNode.xmlns(
tag: 'c',
xmlns: capsXmlns,
attributes: {
'hash': 'sha-1',
'node': 'http://example.org/client',
'ver': 'QgayPKawpkPSDYmwT/WM94uAlu0=',
},
),
],
); );
await tm.register(manager);
await manager.onPresence( await manager.onPresence(
stanza, PresenceReceivedEvent(
StanzaHandlerData(false, false, stanza, TypedMap()), aliceJid,
Stanza.presence(
from: aliceJid.toString(),
children: [
XMLNode.xmlns(
tag: 'c',
xmlns: capsXmlns,
attributes: {
'hash': 'sha-1',
'node': 'http://example.org/client',
'ver': 'QgayPKawpkPSDYmwT/WM94uAlu0=',
},
),
],
),
),
); );
expect( expect(
@ -421,28 +420,29 @@ 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,
]);
final stanza = Stanza.presence(
from: aliceJid.toString(),
children: [
XMLNode.xmlns(
tag: 'c',
xmlns: capsXmlns,
attributes: {
'hash': 'sha-1',
'node': 'http://example.org/client',
'ver': 'QgayPKawpkPSDYmwT/WM94uAlu0=',
},
),
],
); );
await tm.register(manager);
await manager.onPresence( await manager.onPresence(
stanza, PresenceReceivedEvent(
StanzaHandlerData(false, false, stanza, TypedMap()), aliceJid,
Stanza.presence(
from: aliceJid.toString(),
children: [
XMLNode.xmlns(
tag: 'c',
xmlns: capsXmlns,
attributes: {
'hash': 'sha-1',
'node': 'http://example.org/client',
'ver': 'QgayPKawpkPSDYmwT/WM94uAlu0=',
},
),
],
),
),
); );
expect( expect(
@ -455,28 +455,29 @@ 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,
]);
final stanza = Stanza.presence(
from: aliceJid.toString(),
children: [
XMLNode.xmlns(
tag: 'c',
xmlns: capsXmlns,
attributes: {
'hash': 'sha-1',
'node': 'http://example.org/client',
'ver': 'QgayPKawpkPSDYmwT/WM94uAlu0=',
},
),
],
); );
await tm.register(manager);
await manager.onPresence( await manager.onPresence(
stanza, PresenceReceivedEvent(
StanzaHandlerData(false, false, stanza, TypedMap()), aliceJid,
Stanza.presence(
from: aliceJid.toString(),
children: [
XMLNode.xmlns(
tag: 'c',
xmlns: capsXmlns,
attributes: {
'hash': 'sha-1',
'node': 'http://example.org/client',
'ver': 'QgayPKawpkPSDYmwT/WM94uAlu0=',
},
),
],
),
),
); );
expect( expect(
@ -489,28 +490,29 @@ 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,
]);
final stanza = Stanza.presence(
from: aliceJid.toString(),
children: [
XMLNode.xmlns(
tag: 'c',
xmlns: capsXmlns,
attributes: {
'hash': 'sha-1',
'node': 'http://example.org/client',
'ver': 'QgayPKawpkPSDYmwT/WM94uAlu0=',
},
),
],
); );
await tm.register(manager);
await manager.onPresence( await manager.onPresence(
stanza, PresenceReceivedEvent(
StanzaHandlerData(false, false, stanza, TypedMap()), aliceJid,
Stanza.presence(
from: aliceJid.toString(),
children: [
XMLNode.xmlns(
tag: 'c',
xmlns: capsXmlns,
attributes: {
'hash': 'sha-1',
'node': 'http://example.org/client',
'ver': 'QgayPKawpkPSDYmwT/WM94uAlu0=',
},
),
],
),
),
); );
final cachedItem = await manager.getCachedDiscoInfoFromJid(aliceJid); final cachedItem = await manager.getCachedDiscoInfoFromJid(aliceJid);
@ -525,28 +527,29 @@ 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,
]);
final stanza = Stanza.presence(
from: aliceJid.toString(),
children: [
XMLNode.xmlns(
tag: 'c',
xmlns: capsXmlns,
attributes: {
'hash': 'sha-1',
'node': 'http://example.org/client',
'ver': 'QgayPKawpkPSDYmwT/WM94uAlu0=',
},
),
],
); );
await tm.register(manager);
await manager.onPresence( await manager.onPresence(
stanza, PresenceReceivedEvent(
StanzaHandlerData(false, false, stanza, TypedMap()), aliceJid,
Stanza.presence(
from: aliceJid.toString(),
children: [
XMLNode.xmlns(
tag: 'c',
xmlns: capsXmlns,
attributes: {
'hash': 'sha-1',
'node': 'http://example.org/client',
'ver': 'QgayPKawpkPSDYmwT/WM94uAlu0=',
},
),
],
),
),
); );
final cachedItem = await manager.getCachedDiscoInfoFromJid(aliceJid); final cachedItem = await manager.getCachedDiscoInfoFromJid(aliceJid);
@ -561,28 +564,29 @@ 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,
]);
final stanza = Stanza.presence(
from: aliceJid.toString(),
children: [
XMLNode.xmlns(
tag: 'c',
xmlns: capsXmlns,
attributes: {
'hash': 'sha-1',
'node': 'http://example.org/client',
'ver': 'QgayPKawpkPSDYmwT/WM94uAlu0=',
},
),
],
); );
await tm.register(manager);
await manager.onPresence( await manager.onPresence(
stanza, PresenceReceivedEvent(
StanzaHandlerData(false, false, stanza, TypedMap()), aliceJid,
Stanza.presence(
from: aliceJid.toString(),
children: [
XMLNode.xmlns(
tag: 'c',
xmlns: capsXmlns,
attributes: {
'hash': 'sha-1',
'node': 'http://example.org/client',
'ver': 'QgayPKawpkPSDYmwT/WM94uAlu0=',
},
),
],
),
),
); );
expect( expect(

View File

@ -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(),
), ),
); );
} }

View File

@ -1,147 +0,0 @@
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,
]),
),
);
});
}

View File

@ -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='blake2b-256'>2AfMGH8O7UNPTvUVAM9aK13mpCY=</hash> <hash xmlns='urn:xmpp:hashes:2' algo='id-blake2b256'>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[HashFunction.sha3_256], sfs.metadata.hashes['sha3-256'],
'2XarmwTlNxDAMkvymloX3S5+VbylNrJt/l5QyPa+YoU=', '2XarmwTlNxDAMkvymloX3S5+VbylNrJt/l5QyPa+YoU=',
); );
expect( expect(
sfs.metadata.hashes[HashFunction.blake2b256], sfs.metadata.hashes['id-blake2b256'],
'2AfMGH8O7UNPTvUVAM9aK13mpCY=', '2AfMGH8O7UNPTvUVAM9aK13mpCY=',
); );
}); });

View File

@ -1,13 +1,7 @@
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('''
@ -231,186 +225,4 @@ 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,
);
});
} }

View File

@ -1,8 +1,6 @@
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!');
@ -22,250 +20,28 @@ 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!',
); );
expect(reply.withoutFallback, 'Hello right back!'); final bodyWithoutFallback = reply.removeFallback(body);
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!",
); );
expect(reply.withoutFallback, "I'm fine.\nThank you!"); final bodyWithoutFallback = reply.removeFallback(body);
}); 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);
}); });
} }

View File

@ -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,20 +793,4 @@ 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);
});
} }