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:
PapaTutuWawa 2023-06-02 22:00:44 +02:00
parent 1475cb542f
commit 9e0f38154e
12 changed files with 217 additions and 106 deletions

View File

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

View File

@ -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;
} }

View File

@ -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;
} }

View File

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

View File

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

View File

@ -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;
} }

View File

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

View File

@ -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');

View File

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

View File

@ -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);

View File

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

View File

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