diff --git a/packages/moxxmpp/lib/src/roster/roster.dart b/packages/moxxmpp/lib/src/roster/roster.dart
index 7a51efc..ff4c640 100644
--- a/packages/moxxmpp/lib/src/roster/roster.dart
+++ b/packages/moxxmpp/lib/src/roster/roster.dart
@@ -182,8 +182,18 @@ class RosterManager extends XmppManagerBase {
/// Shared code between requesting rosters without and with roster versioning, if
/// the server deems a regular roster response more efficient than n roster pushes.
+ ///
+ /// [query] is the child of the iq, if available.
+ ///
+ /// If roster versioning was used, then [requestedRosterVersion] is the version
+ /// we requested the roster with.
+ ///
+ /// Note that if roster versioning is used and the server returns us an empty iq,
+ /// it means that the roster did not change since the last version. In that case,
+ /// we do nothing and just return. The roster state manager will not be notified.
Future> _handleRosterResponse(
XMLNode? query,
+ String? requestedRosterVersion,
) async {
final List items;
String? rosterVersion;
@@ -204,6 +214,14 @@ class RosterManager extends XmppManagerBase {
.toList();
rosterVersion = query.attributes['ver'] as String?;
+ } else if (requestedRosterVersion != null) {
+ // Skip the handleRosterFetch call since nothing changed.
+ return Result(
+ RosterRequestResult(
+ [],
+ requestedRosterVersion,
+ ),
+ );
} else {
logger.warning(
'Server response to roster request without roster versioning does not contain a element, while the type is not error. This violates RFC6121',
@@ -258,7 +276,7 @@ class RosterManager extends XmppManagerBase {
}
final responseQuery = response.firstTag('query', xmlns: rosterXmlns);
- return _handleRosterResponse(responseQuery);
+ return _handleRosterResponse(responseQuery, rosterVersion);
}
/// Requests a series of roster pushes according to RFC6121. Requires that the server
@@ -266,6 +284,7 @@ class RosterManager extends XmppManagerBase {
Future>
requestRosterPushes() async {
final attrs = getAttributes();
+ final rosterVersion = await _stateManager.getRosterVersion();
final result = (await attrs.sendStanza(
StanzaDetails(
Stanza.iq(
@@ -275,7 +294,7 @@ class RosterManager extends XmppManagerBase {
tag: 'query',
xmlns: rosterXmlns,
attributes: {
- 'ver': await _stateManager.getRosterVersion() ?? '',
+ 'ver': rosterVersion ?? '',
},
),
],
@@ -289,7 +308,7 @@ class RosterManager extends XmppManagerBase {
}
final query = result.firstTag('query', xmlns: rosterXmlns);
- return _handleRosterResponse(query);
+ return _handleRosterResponse(query, rosterVersion);
}
bool rosterVersioningAvailable() {
diff --git a/packages/moxxmpp/test/helpers/xmpp.dart b/packages/moxxmpp/test/helpers/xmpp.dart
index 4d25475..9d2dc41 100644
--- a/packages/moxxmpp/test/helpers/xmpp.dart
+++ b/packages/moxxmpp/test/helpers/xmpp.dart
@@ -89,6 +89,7 @@ List buildAuthenticatedPlay(ConnectionSettings settings) {
+
''',
),
diff --git a/packages/moxxmpp/test/roster_test.dart b/packages/moxxmpp/test/roster_test.dart
new file mode 100644
index 0000000..a76b7b2
--- /dev/null
+++ b/packages/moxxmpp/test/roster_test.dart
@@ -0,0 +1,53 @@
+import 'package:moxxmpp/moxxmpp.dart';
+import 'package:test/test.dart';
+import 'helpers/logging.dart';
+import 'helpers/xmpp.dart';
+
+void main() {
+ initLogger();
+
+ test('Test a versioned roster fetch returning an empty iq', () async {
+ final sm = TestingRosterStateManager('ver14', []);
+ final rm = RosterManager(sm);
+ final cs = ConnectionSettings(
+ jid: JID.fromString('user@example.org'),
+ password: 'abc123',
+ );
+ final socket = StubTCPSocket([
+ ...buildAuthenticatedPlay(cs),
+ StanzaExpectation(
+ '',
+ '',
+ ignoreId: true,
+ adjustId: true,
+ ),
+ ]);
+ final conn = XmppConnection(
+ TestingReconnectionPolicy(),
+ AlwaysConnectedConnectivityManager(),
+ ClientToServerNegotiator(),
+ socket,
+ )..connectionSettings = cs;
+ await conn.registerManagers([
+ rm,
+ PresenceManager(),
+ ]);
+ await conn.registerFeatureNegotiators([
+ SaslPlainNegotiator(),
+ ResourceBindingNegotiator(),
+ RosterFeatureNegotiator(),
+ ]);
+
+ // Connect
+ await conn.connect(
+ shouldReconnect: false,
+ waitUntilLogin: true,
+ );
+
+ // Request the roster
+ final rawResult = await rm.requestRoster();
+ expect(rawResult.isType(), true);
+ final result = rawResult.get();
+ expect(result.items.isEmpty, true);
+ });
+}