feat(all): Various changes
- Fix unavailable presence being sent *after* connecting - Migrate more APIs to the JID class - Advertise +notify for user avatar metadata
This commit is contained in:
parent
1475cb542f
commit
9e0f38154e
@ -405,8 +405,7 @@ class XmppConnection {
|
|||||||
|
|
||||||
/// Returns true if we can send data through the socket.
|
/// Returns true if we can send data through the socket.
|
||||||
Future<bool> _canSendData() async {
|
Future<bool> _canSendData() async {
|
||||||
return [XmppConnectionState.connected, XmppConnectionState.connecting]
|
return await getConnectionState() == XmppConnectionState.connected;
|
||||||
.contains(await getConnectionState());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends a stanza described by [details] to the server. Until sent, the stanza is
|
/// Sends a stanza described by [details] to the server. Until sent, the stanza is
|
||||||
@ -424,13 +423,17 @@ class XmppConnection {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final completer = details.awaitable ? Completer<XMLNode>() : null;
|
final completer = details.awaitable ? Completer<XMLNode>() : null;
|
||||||
await _stanzaQueue.enqueueStanza(
|
final entry = StanzaQueueEntry(
|
||||||
StanzaQueueEntry(
|
details,
|
||||||
details,
|
completer,
|
||||||
completer,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (details.bypassQueue) {
|
||||||
|
await _sendStanzaImpl(entry);
|
||||||
|
} else {
|
||||||
|
await _stanzaQueue.enqueueStanza(entry);
|
||||||
|
}
|
||||||
|
|
||||||
return completer?.future;
|
return completer?.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -523,7 +526,7 @@ class XmppConnection {
|
|||||||
if (await _canSendData()) {
|
if (await _canSendData()) {
|
||||||
_socket.write(data.stanza.toXml());
|
_socket.write(data.stanza.toXml());
|
||||||
} else {
|
} else {
|
||||||
_log.fine('Not sending dat as _canSendData() returned false.');
|
_log.fine('Not sending data as _canSendData() returned false.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run post-send handlers
|
// Run post-send handlers
|
||||||
@ -535,6 +538,7 @@ class XmppConnection {
|
|||||||
false,
|
false,
|
||||||
null,
|
null,
|
||||||
newStanza,
|
newStanza,
|
||||||
|
excludeFromStreamManagement: details.excludeFromStreamManagement,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
_log.fine('Done');
|
_log.fine('Done');
|
||||||
@ -835,7 +839,7 @@ class XmppConnection {
|
|||||||
await _reconnectionPolicy.setShouldReconnect(false);
|
await _reconnectionPolicy.setShouldReconnect(false);
|
||||||
|
|
||||||
if (triggeredByUser) {
|
if (triggeredByUser) {
|
||||||
getPresenceManager()?.sendUnavailablePresence();
|
await getPresenceManager()?.sendUnavailablePresence();
|
||||||
}
|
}
|
||||||
|
|
||||||
_socket.prepareDisconnect();
|
_socket.prepareDisconnect();
|
||||||
|
@ -7,6 +7,7 @@ import 'package:moxxmpp/src/stanza.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_0066.dart';
|
||||||
|
import 'package:moxxmpp/src/xeps/xep_0084.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0085.dart';
|
import 'package:moxxmpp/src/xeps/xep_0085.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0334.dart';
|
import 'package:moxxmpp/src/xeps/xep_0334.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0359.dart';
|
import 'package:moxxmpp/src/xeps/xep_0359.dart';
|
||||||
@ -192,15 +193,35 @@ class SubscriptionRequestReceivedEvent extends XmppEvent {
|
|||||||
final JID from;
|
final JID from;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Triggered when we receive a new or updated avatar
|
/// Triggered when we receive a new or updated avatar via XEP-0084
|
||||||
class AvatarUpdatedEvent extends XmppEvent {
|
class UserAvatarUpdatedEvent extends XmppEvent {
|
||||||
AvatarUpdatedEvent({
|
UserAvatarUpdatedEvent(
|
||||||
required this.jid,
|
this.jid,
|
||||||
required this.base64,
|
this.metadata,
|
||||||
required this.hash,
|
);
|
||||||
});
|
|
||||||
final String jid;
|
/// The JID of the user updating their avatar.
|
||||||
|
final JID jid;
|
||||||
|
|
||||||
|
/// The metadata of the avatar.
|
||||||
|
final List<UserAvatarMetadata> metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Triggered when we receive a new or updated avatar via XEP-0054
|
||||||
|
class VCardAvatarUpdatedEvent extends XmppEvent {
|
||||||
|
VCardAvatarUpdatedEvent(
|
||||||
|
this.jid,
|
||||||
|
this.base64,
|
||||||
|
this.hash,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// The JID of the entity that updated their avatar.
|
||||||
|
final JID jid;
|
||||||
|
|
||||||
|
/// The base64-encoded avatar data.
|
||||||
final String base64;
|
final String base64;
|
||||||
|
|
||||||
|
/// The SHA-1 hash of the avatar.
|
||||||
final String hash;
|
final String hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,5 +75,8 @@ class StanzaHandlerData with _$StanzaHandlerData {
|
|||||||
MessageReactions? messageReactions,
|
MessageReactions? messageReactions,
|
||||||
// The Id of the sticker pack this sticker belongs to
|
// The Id of the sticker pack this sticker belongs to
|
||||||
String? stickerPackId,
|
String? stickerPackId,
|
||||||
|
// Flag indicating whether the stanza should be excluded from stream management's
|
||||||
|
// resending behaviour
|
||||||
|
@Default(false) bool excludeFromStreamManagement,
|
||||||
}) = _StanzaHandlerData;
|
}) = _StanzaHandlerData;
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,10 @@ mixin _$StanzaHandlerData {
|
|||||||
throw _privateConstructorUsedError; // Reactions data
|
throw _privateConstructorUsedError; // Reactions data
|
||||||
MessageReactions? get messageReactions =>
|
MessageReactions? get messageReactions =>
|
||||||
throw _privateConstructorUsedError; // The Id of the sticker pack this sticker belongs to
|
throw _privateConstructorUsedError; // The Id of the sticker pack this sticker belongs to
|
||||||
String? get stickerPackId => throw _privateConstructorUsedError;
|
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)
|
@JsonKey(ignore: true)
|
||||||
$StanzaHandlerDataCopyWith<StanzaHandlerData> get copyWith =>
|
$StanzaHandlerDataCopyWith<StanzaHandlerData> get copyWith =>
|
||||||
@ -111,7 +114,8 @@ abstract class $StanzaHandlerDataCopyWith<$Res> {
|
|||||||
MessageRetractionData? messageRetraction,
|
MessageRetractionData? messageRetraction,
|
||||||
String? lastMessageCorrectionSid,
|
String? lastMessageCorrectionSid,
|
||||||
MessageReactions? messageReactions,
|
MessageReactions? messageReactions,
|
||||||
String? stickerPackId});
|
String? stickerPackId,
|
||||||
|
bool excludeFromStreamManagement});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -154,6 +158,7 @@ class _$StanzaHandlerDataCopyWithImpl<$Res, $Val extends StanzaHandlerData>
|
|||||||
Object? lastMessageCorrectionSid = freezed,
|
Object? lastMessageCorrectionSid = freezed,
|
||||||
Object? messageReactions = freezed,
|
Object? messageReactions = freezed,
|
||||||
Object? stickerPackId = freezed,
|
Object? stickerPackId = freezed,
|
||||||
|
Object? excludeFromStreamManagement = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_value.copyWith(
|
return _then(_value.copyWith(
|
||||||
done: null == done
|
done: null == done
|
||||||
@ -264,6 +269,10 @@ class _$StanzaHandlerDataCopyWithImpl<$Res, $Val extends StanzaHandlerData>
|
|||||||
? _value.stickerPackId
|
? _value.stickerPackId
|
||||||
: stickerPackId // ignore: cast_nullable_to_non_nullable
|
: stickerPackId // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,
|
as String?,
|
||||||
|
excludeFromStreamManagement: null == excludeFromStreamManagement
|
||||||
|
? _value.excludeFromStreamManagement
|
||||||
|
: excludeFromStreamManagement // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
) as $Val);
|
) as $Val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -303,7 +312,8 @@ abstract class _$$_StanzaHandlerDataCopyWith<$Res>
|
|||||||
MessageRetractionData? messageRetraction,
|
MessageRetractionData? messageRetraction,
|
||||||
String? lastMessageCorrectionSid,
|
String? lastMessageCorrectionSid,
|
||||||
MessageReactions? messageReactions,
|
MessageReactions? messageReactions,
|
||||||
String? stickerPackId});
|
String? stickerPackId,
|
||||||
|
bool excludeFromStreamManagement});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@ -344,6 +354,7 @@ class __$$_StanzaHandlerDataCopyWithImpl<$Res>
|
|||||||
Object? lastMessageCorrectionSid = freezed,
|
Object? lastMessageCorrectionSid = freezed,
|
||||||
Object? messageReactions = freezed,
|
Object? messageReactions = freezed,
|
||||||
Object? stickerPackId = freezed,
|
Object? stickerPackId = freezed,
|
||||||
|
Object? excludeFromStreamManagement = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_$_StanzaHandlerData(
|
return _then(_$_StanzaHandlerData(
|
||||||
null == done
|
null == done
|
||||||
@ -454,6 +465,10 @@ class __$$_StanzaHandlerDataCopyWithImpl<$Res>
|
|||||||
? _value.stickerPackId
|
? _value.stickerPackId
|
||||||
: stickerPackId // ignore: cast_nullable_to_non_nullable
|
: stickerPackId // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,
|
as String?,
|
||||||
|
excludeFromStreamManagement: null == excludeFromStreamManagement
|
||||||
|
? _value.excludeFromStreamManagement
|
||||||
|
: excludeFromStreamManagement // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -484,7 +499,8 @@ class _$_StanzaHandlerData implements _StanzaHandlerData {
|
|||||||
this.messageRetraction,
|
this.messageRetraction,
|
||||||
this.lastMessageCorrectionSid,
|
this.lastMessageCorrectionSid,
|
||||||
this.messageReactions,
|
this.messageReactions,
|
||||||
this.stickerPackId})
|
this.stickerPackId,
|
||||||
|
this.excludeFromStreamManagement = false})
|
||||||
: _stanzaIds = stanzaIds,
|
: _stanzaIds = stanzaIds,
|
||||||
_other = other;
|
_other = other;
|
||||||
|
|
||||||
@ -595,10 +611,15 @@ class _$_StanzaHandlerData implements _StanzaHandlerData {
|
|||||||
// The Id of the sticker pack this sticker belongs to
|
// The Id of the sticker pack this sticker belongs to
|
||||||
@override
|
@override
|
||||||
final String? stickerPackId;
|
final String? stickerPackId;
|
||||||
|
// Flag indicating whether the stanza should be excluded from stream management's
|
||||||
|
// resending behaviour
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final bool excludeFromStreamManagement;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
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)';
|
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
|
@override
|
||||||
@ -652,7 +673,11 @@ class _$_StanzaHandlerData implements _StanzaHandlerData {
|
|||||||
(identical(other.messageReactions, messageReactions) ||
|
(identical(other.messageReactions, messageReactions) ||
|
||||||
other.messageReactions == messageReactions) &&
|
other.messageReactions == messageReactions) &&
|
||||||
(identical(other.stickerPackId, stickerPackId) ||
|
(identical(other.stickerPackId, stickerPackId) ||
|
||||||
other.stickerPackId == stickerPackId));
|
other.stickerPackId == stickerPackId) &&
|
||||||
|
(identical(other.excludeFromStreamManagement,
|
||||||
|
excludeFromStreamManagement) ||
|
||||||
|
other.excludeFromStreamManagement ==
|
||||||
|
excludeFromStreamManagement));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -684,7 +709,8 @@ class _$_StanzaHandlerData implements _StanzaHandlerData {
|
|||||||
messageRetraction,
|
messageRetraction,
|
||||||
lastMessageCorrectionSid,
|
lastMessageCorrectionSid,
|
||||||
messageReactions,
|
messageReactions,
|
||||||
stickerPackId
|
stickerPackId,
|
||||||
|
excludeFromStreamManagement
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
@ -720,7 +746,8 @@ abstract class _StanzaHandlerData implements StanzaHandlerData {
|
|||||||
final MessageRetractionData? messageRetraction,
|
final MessageRetractionData? messageRetraction,
|
||||||
final String? lastMessageCorrectionSid,
|
final String? lastMessageCorrectionSid,
|
||||||
final MessageReactions? messageReactions,
|
final MessageReactions? messageReactions,
|
||||||
final String? stickerPackId}) = _$_StanzaHandlerData;
|
final String? stickerPackId,
|
||||||
|
final bool excludeFromStreamManagement}) = _$_StanzaHandlerData;
|
||||||
|
|
||||||
@override // Indicates to the runner that processing is now done. This means that all
|
@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.
|
// pre-processing is done and no other handlers should be consulted.
|
||||||
@ -786,6 +813,9 @@ abstract class _StanzaHandlerData implements StanzaHandlerData {
|
|||||||
MessageReactions? get messageReactions;
|
MessageReactions? get messageReactions;
|
||||||
@override // The Id of the sticker pack this sticker belongs to
|
@override // The Id of the sticker pack this sticker belongs to
|
||||||
String? get stickerPackId;
|
String? get stickerPackId;
|
||||||
|
@override // Flag indicating whether the stanza should be excluded from stream management's
|
||||||
|
// resending behaviour
|
||||||
|
bool get excludeFromStreamManagement;
|
||||||
@override
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$_StanzaHandlerDataCopyWith<_$_StanzaHandlerData> get copyWith =>
|
_$$_StanzaHandlerDataCopyWith<_$_StanzaHandlerData> get copyWith =>
|
||||||
|
@ -112,24 +112,48 @@ class PresenceManager extends XmppManagerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Send an unavailable presence with no 'to' attribute.
|
/// Send an unavailable presence with no 'to' attribute.
|
||||||
void sendUnavailablePresence() {
|
Future<void> sendUnavailablePresence() async {
|
||||||
getAttributes().sendStanza(
|
// Bypass the queue so that this get's sent immediately.
|
||||||
|
// If we do it like this, we can also block the disconnection
|
||||||
|
// until we're actually ready.
|
||||||
|
await getAttributes().sendStanza(
|
||||||
StanzaDetails(
|
StanzaDetails(
|
||||||
Stanza.presence(
|
Stanza.presence(
|
||||||
type: 'unavailable',
|
type: 'unavailable',
|
||||||
),
|
),
|
||||||
awaitable: false,
|
awaitable: false,
|
||||||
|
bypassQueue: true,
|
||||||
|
excludeFromStreamManagement: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends a subscription request to [to].
|
||||||
|
// TODO(PapaTutuWawa): Check if we're allowed to pre-approve
|
||||||
|
Future<void> requestSubscription(JID to, {bool preApprove = false}) async {
|
||||||
|
await getAttributes().sendStanza(
|
||||||
|
StanzaDetails(
|
||||||
|
Stanza.presence(
|
||||||
|
type: preApprove ? 'subscribed' : 'subscribe',
|
||||||
|
to: to.toString(),
|
||||||
|
),
|
||||||
|
awaitable: false,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends a subscription request to [to].
|
/// Accept a subscription request from [to].
|
||||||
void sendSubscriptionRequest(String to) {
|
Future<void> acceptSubscriptionRequest(JID to) async {
|
||||||
getAttributes().sendStanza(
|
await requestSubscription(to, preApprove: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a subscription request rejection to [to].
|
||||||
|
Future<void> rejectSubscriptionRequest(JID to) async {
|
||||||
|
await getAttributes().sendStanza(
|
||||||
StanzaDetails(
|
StanzaDetails(
|
||||||
Stanza.presence(
|
Stanza.presence(
|
||||||
type: 'subscribe',
|
type: 'unsubscribed',
|
||||||
to: to,
|
to: to.toString(),
|
||||||
),
|
),
|
||||||
awaitable: false,
|
awaitable: false,
|
||||||
),
|
),
|
||||||
@ -137,38 +161,12 @@ class PresenceManager extends XmppManagerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Sends an unsubscription request to [to].
|
/// Sends an unsubscription request to [to].
|
||||||
void sendUnsubscriptionRequest(String to) {
|
Future<void> unsubscribe(JID to) async {
|
||||||
getAttributes().sendStanza(
|
await getAttributes().sendStanza(
|
||||||
StanzaDetails(
|
StanzaDetails(
|
||||||
Stanza.presence(
|
Stanza.presence(
|
||||||
type: 'unsubscribe',
|
type: 'unsubscribe',
|
||||||
to: to,
|
to: to.toString(),
|
||||||
),
|
|
||||||
awaitable: false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Accept a presence subscription request for [to].
|
|
||||||
void sendSubscriptionRequestApproval(String to) {
|
|
||||||
getAttributes().sendStanza(
|
|
||||||
StanzaDetails(
|
|
||||||
Stanza.presence(
|
|
||||||
type: 'subscribed',
|
|
||||||
to: to,
|
|
||||||
),
|
|
||||||
awaitable: false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reject a presence subscription request for [to].
|
|
||||||
void sendSubscriptionRequestRejection(String to) {
|
|
||||||
getAttributes().sendStanza(
|
|
||||||
StanzaDetails(
|
|
||||||
Stanza.presence(
|
|
||||||
type: 'unsubscribed',
|
|
||||||
to: to,
|
|
||||||
),
|
),
|
||||||
awaitable: false,
|
awaitable: false,
|
||||||
),
|
),
|
||||||
|
@ -223,15 +223,21 @@ class RosterManager extends XmppManagerBase {
|
|||||||
return Result(result);
|
return Result(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Requests the roster following RFC 6121.
|
/// Requests the roster following RFC 6121. If [useRosterVersion] is set to false, then
|
||||||
Future<Result<RosterRequestResult, RosterError>> requestRoster() async {
|
/// roster versioning will not be used, even if the server supports it and we have a last
|
||||||
|
/// known roster version.
|
||||||
|
Future<Result<RosterRequestResult, RosterError>> requestRoster({
|
||||||
|
bool useRosterVersion = true,
|
||||||
|
}) async {
|
||||||
final attrs = getAttributes();
|
final attrs = getAttributes();
|
||||||
final query = XMLNode.xmlns(
|
final query = XMLNode.xmlns(
|
||||||
tag: 'query',
|
tag: 'query',
|
||||||
xmlns: rosterXmlns,
|
xmlns: rosterXmlns,
|
||||||
);
|
);
|
||||||
final rosterVersion = await _stateManager.getRosterVersion();
|
final rosterVersion = await _stateManager.getRosterVersion();
|
||||||
if (rosterVersion != null && rosterVersioningAvailable()) {
|
if (rosterVersion != null &&
|
||||||
|
rosterVersioningAvailable() &&
|
||||||
|
useRosterVersion) {
|
||||||
query.attributes['ver'] = rosterVersion;
|
query.attributes['ver'] = rosterVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,8 @@ class StanzaDetails {
|
|||||||
this.awaitable = true,
|
this.awaitable = true,
|
||||||
this.encrypted = false,
|
this.encrypted = false,
|
||||||
this.forceEncryption = false,
|
this.forceEncryption = false,
|
||||||
|
this.bypassQueue = false,
|
||||||
|
this.excludeFromStreamManagement = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The stanza to send.
|
/// The stanza to send.
|
||||||
@ -23,6 +25,16 @@ class StanzaDetails {
|
|||||||
final bool encrypted;
|
final bool encrypted;
|
||||||
|
|
||||||
final bool forceEncryption;
|
final bool forceEncryption;
|
||||||
|
|
||||||
|
/// Bypasses being put into the queue. Useful for sending stanzas that must go out
|
||||||
|
/// now, where it's okay if it does not get sent.
|
||||||
|
/// This should never have to be set to true.
|
||||||
|
final bool bypassQueue;
|
||||||
|
|
||||||
|
/// This makes the Stream Management implementation, when available, ignore the stanza,
|
||||||
|
/// meaning that it gets counted but excluded from resending.
|
||||||
|
/// This should never have to be set to true.
|
||||||
|
final bool excludeFromStreamManagement;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A simple description of the <error /> element that may be inside a stanza
|
/// A simple description of the <error /> element that may be inside a stanza
|
||||||
|
@ -66,11 +66,7 @@ class VCardManager extends XmppManagerBase {
|
|||||||
final binval = vcardResult.get<VCard>().photo?.binval;
|
final binval = vcardResult.get<VCard>().photo?.binval;
|
||||||
if (binval != null) {
|
if (binval != null) {
|
||||||
getAttributes().sendEvent(
|
getAttributes().sendEvent(
|
||||||
AvatarUpdatedEvent(
|
VCardAvatarUpdatedEvent(JID.fromString(from), binval, hash),
|
||||||
jid: from,
|
|
||||||
base64: binval,
|
|
||||||
hash: hash,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
logger.warning('No avatar data found');
|
logger.warning('No avatar data found');
|
||||||
|
@ -179,13 +179,13 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Result<PubSubError, bool>> subscribe(String jid, String node) async {
|
Future<Result<PubSubError, bool>> subscribe(JID jid, String node) async {
|
||||||
final attrs = getAttributes();
|
final attrs = getAttributes();
|
||||||
final result = (await attrs.sendStanza(
|
final result = (await attrs.sendStanza(
|
||||||
StanzaDetails(
|
StanzaDetails(
|
||||||
Stanza.iq(
|
Stanza.iq(
|
||||||
type: 'set',
|
type: 'set',
|
||||||
to: jid,
|
to: jid.toString(),
|
||||||
children: [
|
children: [
|
||||||
XMLNode.xmlns(
|
XMLNode.xmlns(
|
||||||
tag: 'pubsub',
|
tag: 'pubsub',
|
||||||
@ -222,13 +222,13 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
return Result(subscription.attributes['subscription'] == 'subscribed');
|
return Result(subscription.attributes['subscription'] == 'subscribed');
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Result<PubSubError, bool>> unsubscribe(String jid, String node) async {
|
Future<Result<PubSubError, bool>> unsubscribe(JID jid, String node) async {
|
||||||
final attrs = getAttributes();
|
final attrs = getAttributes();
|
||||||
final result = (await attrs.sendStanza(
|
final result = (await attrs.sendStanza(
|
||||||
StanzaDetails(
|
StanzaDetails(
|
||||||
Stanza.iq(
|
Stanza.iq(
|
||||||
type: 'set',
|
type: 'set',
|
||||||
to: jid,
|
to: jid.toString(),
|
||||||
children: [
|
children: [
|
||||||
XMLNode.xmlns(
|
XMLNode.xmlns(
|
||||||
tag: 'pubsub',
|
tag: 'pubsub',
|
||||||
@ -398,14 +398,14 @@ class PubSubManager extends XmppManagerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<Result<PubSubError, List<PubSubItem>>> getItems(
|
Future<Result<PubSubError, List<PubSubItem>>> getItems(
|
||||||
String jid,
|
JID jid,
|
||||||
String node,
|
String node,
|
||||||
) async {
|
) async {
|
||||||
final result = (await getAttributes().sendStanza(
|
final result = (await getAttributes().sendStanza(
|
||||||
StanzaDetails(
|
StanzaDetails(
|
||||||
Stanza.iq(
|
Stanza.iq(
|
||||||
type: 'get',
|
type: 'get',
|
||||||
to: jid,
|
to: jid.toString(),
|
||||||
children: [
|
children: [
|
||||||
XMLNode.xmlns(
|
XMLNode.xmlns(
|
||||||
tag: 'pubsub',
|
tag: 'pubsub',
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:convert';
|
||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/events.dart';
|
||||||
import 'package:moxxmpp/src/jid.dart';
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
import 'package:moxxmpp/src/managers/base.dart';
|
import 'package:moxxmpp/src/managers/base.dart';
|
||||||
@ -15,10 +16,17 @@ abstract class AvatarError {}
|
|||||||
|
|
||||||
class UnknownAvatarError extends AvatarError {}
|
class UnknownAvatarError extends AvatarError {}
|
||||||
|
|
||||||
class UserAvatar {
|
class UserAvatarData {
|
||||||
const UserAvatar({required this.base64, required this.hash});
|
const UserAvatarData(this.base64, this.hash);
|
||||||
|
|
||||||
|
/// The base64-encoded avatar data.
|
||||||
final String base64;
|
final String base64;
|
||||||
|
|
||||||
|
/// The SHA-1 hash of the raw avatar data.
|
||||||
final String hash;
|
final String hash;
|
||||||
|
|
||||||
|
/// The raw avatar data.
|
||||||
|
List<int> get data => base64Decode(base64);
|
||||||
}
|
}
|
||||||
|
|
||||||
class UserAvatarMetadata {
|
class UserAvatarMetadata {
|
||||||
@ -27,21 +35,44 @@ class UserAvatarMetadata {
|
|||||||
this.length,
|
this.length,
|
||||||
this.width,
|
this.width,
|
||||||
this.height,
|
this.height,
|
||||||
this.mime,
|
this.type,
|
||||||
|
this.url,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// The amount of bytes in the file
|
factory UserAvatarMetadata.fromXML(XMLNode node) {
|
||||||
|
assert(
|
||||||
|
node.tag == 'metadata' &&
|
||||||
|
node.attributes['xmlns'] == userAvatarMetadataXmlns,
|
||||||
|
'<metadata /> element required',
|
||||||
|
);
|
||||||
|
|
||||||
|
final width = node.attributes['width'] as String?;
|
||||||
|
final height = node.attributes['height'] as String?;
|
||||||
|
return UserAvatarMetadata(
|
||||||
|
node.attributes['id']! as String,
|
||||||
|
int.parse(node.attributes['bytes']! as String),
|
||||||
|
width != null ? int.parse(width) : null,
|
||||||
|
height != null ? int.parse(height) : null,
|
||||||
|
node.attributes['type']! as String,
|
||||||
|
node.attributes['url'] as String?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The amount of bytes in the file.
|
||||||
final int length;
|
final int length;
|
||||||
|
|
||||||
/// The identifier of the avatar
|
/// The identifier of the avatar.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
/// Image proportions
|
/// Image proportions.
|
||||||
final int width;
|
final int? width;
|
||||||
final int height;
|
final int? height;
|
||||||
|
|
||||||
/// The MIME type of the avatar
|
/// The URL where the avatar can be found.
|
||||||
final String mime;
|
final String? url;
|
||||||
|
|
||||||
|
/// The MIME type of the avatar.
|
||||||
|
final String type;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// NOTE: This class requires a PubSubManager
|
/// NOTE: This class requires a PubSubManager
|
||||||
@ -51,13 +82,18 @@ class UserAvatarManager extends XmppManagerBase {
|
|||||||
PubSubManager _getPubSubManager() =>
|
PubSubManager _getPubSubManager() =>
|
||||||
getAttributes().getManagerById(pubsubManager)! as PubSubManager;
|
getAttributes().getManagerById(pubsubManager)! as PubSubManager;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<String> getDiscoFeatures() => [
|
||||||
|
'$userAvatarMetadataXmlns+notify',
|
||||||
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> onXmppEvent(XmppEvent event) async {
|
Future<void> onXmppEvent(XmppEvent event) async {
|
||||||
if (event is PubSubNotificationEvent) {
|
if (event is PubSubNotificationEvent) {
|
||||||
if (event.item.node != userAvatarDataXmlns) return;
|
if (event.item.node != userAvatarMetadataXmlns) return;
|
||||||
|
|
||||||
if (event.item.payload.tag != 'data' ||
|
if (event.item.payload.tag != 'metadata' ||
|
||||||
event.item.payload.attributes['xmlns'] != userAvatarDataXmlns) {
|
event.item.payload.attributes['xmlns'] != userAvatarMetadataXmlns) {
|
||||||
logger.warning(
|
logger.warning(
|
||||||
'Received avatar update from ${event.from} but the payload is invalid. Ignoring...',
|
'Received avatar update from ${event.from} but the payload is invalid. Ignoring...',
|
||||||
);
|
);
|
||||||
@ -65,10 +101,12 @@ class UserAvatarManager extends XmppManagerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getAttributes().sendEvent(
|
getAttributes().sendEvent(
|
||||||
AvatarUpdatedEvent(
|
UserAvatarUpdatedEvent(
|
||||||
jid: event.from,
|
JID.fromString(event.from),
|
||||||
base64: event.item.payload.innerText(),
|
event.item.payload
|
||||||
hash: event.item.id,
|
.findTags('metadata', xmlns: userAvatarMetadataXmlns)
|
||||||
|
.map(UserAvatarMetadata.fromXML)
|
||||||
|
.toList(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -80,7 +118,7 @@ class UserAvatarManager extends XmppManagerBase {
|
|||||||
|
|
||||||
/// Requests the avatar from [jid]. Returns the avatar data if the request was
|
/// Requests the avatar from [jid]. Returns the avatar data if the request was
|
||||||
/// successful. Null otherwise
|
/// successful. Null otherwise
|
||||||
Future<Result<AvatarError, UserAvatar>> getUserAvatar(String jid) async {
|
Future<Result<AvatarError, UserAvatarData>> getUserAvatar(JID jid) async {
|
||||||
final pubsub = _getPubSubManager();
|
final pubsub = _getPubSubManager();
|
||||||
final resultsRaw = await pubsub.getItems(jid, userAvatarDataXmlns);
|
final resultsRaw = await pubsub.getItems(jid, userAvatarDataXmlns);
|
||||||
if (resultsRaw.isType<PubSubError>()) return Result(UnknownAvatarError());
|
if (resultsRaw.isType<PubSubError>()) return Result(UnknownAvatarError());
|
||||||
@ -90,9 +128,9 @@ class UserAvatarManager extends XmppManagerBase {
|
|||||||
|
|
||||||
final item = results[0];
|
final item = results[0];
|
||||||
return Result(
|
return Result(
|
||||||
UserAvatar(
|
UserAvatarData(
|
||||||
base64: item.payload.innerText(),
|
item.payload.innerText(),
|
||||||
hash: item.id,
|
item.id,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -146,7 +184,7 @@ class UserAvatarManager extends XmppManagerBase {
|
|||||||
'bytes': metadata.length.toString(),
|
'bytes': metadata.length.toString(),
|
||||||
'height': metadata.height.toString(),
|
'height': metadata.height.toString(),
|
||||||
'width': metadata.width.toString(),
|
'width': metadata.width.toString(),
|
||||||
'type': metadata.mime,
|
'type': metadata.type,
|
||||||
'id': metadata.id,
|
'id': metadata.id,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -163,14 +201,14 @@ class UserAvatarManager extends XmppManagerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Subscribe the data and metadata node of [jid].
|
/// Subscribe the data and metadata node of [jid].
|
||||||
Future<Result<AvatarError, bool>> subscribe(String jid) async {
|
Future<Result<AvatarError, bool>> subscribe(JID jid) async {
|
||||||
await _getPubSubManager().subscribe(jid, userAvatarMetadataXmlns);
|
await _getPubSubManager().subscribe(jid, userAvatarMetadataXmlns);
|
||||||
|
|
||||||
return const Result(true);
|
return const Result(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unsubscribe the data and metadata node of [jid].
|
/// Unsubscribe the data and metadata node of [jid].
|
||||||
Future<Result<AvatarError, bool>> unsubscribe(String jid) async {
|
Future<Result<AvatarError, bool>> unsubscribe(JID jid) async {
|
||||||
await _getPubSubManager().subscribe(jid, userAvatarMetadataXmlns);
|
await _getPubSubManager().subscribe(jid, userAvatarMetadataXmlns);
|
||||||
|
|
||||||
return const Result(true);
|
return const Result(true);
|
||||||
|
@ -399,10 +399,12 @@ class StreamManagementManager extends XmppManagerBase {
|
|||||||
Stanza stanza,
|
Stanza stanza,
|
||||||
StanzaHandlerData state,
|
StanzaHandlerData state,
|
||||||
) async {
|
) async {
|
||||||
await _incrementC2S();
|
|
||||||
_unackedStanzas[_state.c2s] = stanza;
|
|
||||||
|
|
||||||
if (isStreamManagementEnabled()) {
|
if (isStreamManagementEnabled()) {
|
||||||
|
await _incrementC2S();
|
||||||
|
|
||||||
|
if (state.excludeFromStreamManagement) return state;
|
||||||
|
|
||||||
|
_unackedStanzas[_state.c2s] = stanza;
|
||||||
await _sendAckRequest();
|
await _sendAckRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -414,6 +416,8 @@ class StreamManagementManager extends XmppManagerBase {
|
|||||||
_unackedStanzas.clear();
|
_unackedStanzas.clear();
|
||||||
|
|
||||||
for (final stanza in stanzas) {
|
for (final stanza in stanzas) {
|
||||||
|
logger
|
||||||
|
.finest('Resending ${stanza.tag} with id ${stanza.attributes["id"]}');
|
||||||
await getAttributes().sendStanza(
|
await getAttributes().sendStanza(
|
||||||
StanzaDetails(
|
StanzaDetails(
|
||||||
stanza,
|
stanza,
|
||||||
|
@ -516,8 +516,7 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
|||||||
JID jid,
|
JID jid,
|
||||||
) async {
|
) async {
|
||||||
final pm = getAttributes().getManagerById<PubSubManager>(pubsubManager)!;
|
final pm = getAttributes().getManagerById<PubSubManager>(pubsubManager)!;
|
||||||
final result =
|
final result = await pm.getItems(jid.toBare(), omemoDevicesXmlns);
|
||||||
await pm.getItems(jid.toBare().toString(), omemoDevicesXmlns);
|
|
||||||
if (result.isType<PubSubError>()) return Result(UnknownOmemoError());
|
if (result.isType<PubSubError>()) return Result(UnknownOmemoError());
|
||||||
return Result(result.get<List<PubSubItem>>().first.payload);
|
return Result(result.get<List<PubSubItem>>().first.payload);
|
||||||
}
|
}
|
||||||
@ -543,7 +542,7 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
|||||||
) async {
|
) async {
|
||||||
// TODO(Unknown): Should we query the device list first?
|
// TODO(Unknown): Should we query the device list first?
|
||||||
final pm = getAttributes().getManagerById<PubSubManager>(pubsubManager)!;
|
final pm = getAttributes().getManagerById<PubSubManager>(pubsubManager)!;
|
||||||
final bundlesRaw = await pm.getItems(jid.toString(), omemoBundlesXmlns);
|
final bundlesRaw = await pm.getItems(jid, omemoBundlesXmlns);
|
||||||
if (bundlesRaw.isType<PubSubError>()) return Result(UnknownOmemoError());
|
if (bundlesRaw.isType<PubSubError>()) return Result(UnknownOmemoError());
|
||||||
|
|
||||||
final bundles = bundlesRaw
|
final bundles = bundlesRaw
|
||||||
@ -639,7 +638,7 @@ abstract class BaseOmemoManager extends XmppManagerBase {
|
|||||||
/// Subscribes to the device list PubSub node of [jid].
|
/// Subscribes to the device list PubSub node of [jid].
|
||||||
Future<void> subscribeToDeviceListImpl(String jid) async {
|
Future<void> subscribeToDeviceListImpl(String jid) async {
|
||||||
final pm = getAttributes().getManagerById<PubSubManager>(pubsubManager)!;
|
final pm = getAttributes().getManagerById<PubSubManager>(pubsubManager)!;
|
||||||
await pm.subscribe(jid, omemoDevicesXmlns);
|
await pm.subscribe(JID.fromString(jid), omemoDevicesXmlns);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attempts to find out if [jid] supports omemo:2.
|
/// Attempts to find out if [jid] supports omemo:2.
|
||||||
|
Loading…
Reference in New Issue
Block a user