From 93e9d6ca220b16934730559259f882bb2229e8f2 Mon Sep 17 00:00:00 2001 From: "Alexander \"PapaTutuWawa" Date: Sun, 1 Oct 2023 20:44:47 +0200 Subject: [PATCH] feat(xep): Handle a user changing their nickname --- .../moxxmpp/lib/src/xeps/xep_0045/events.dart | 14 ++ .../lib/src/xeps/xep_0045/status_codes.dart | 2 + .../lib/src/xeps/xep_0045/xep_0045.dart | 59 ++++-- packages/moxxmpp/test/xeps/xep_0045_test.dart | 184 +++++++++++++++++- 4 files changed, 244 insertions(+), 15 deletions(-) create mode 100644 packages/moxxmpp/lib/src/xeps/xep_0045/status_codes.dart diff --git a/packages/moxxmpp/lib/src/xeps/xep_0045/events.dart b/packages/moxxmpp/lib/src/xeps/xep_0045/events.dart index 2ce352f..f3f795d 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0045/events.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0045/events.dart @@ -56,3 +56,17 @@ class MemberLeftEvent extends XmppEvent { /// The nick of the user who left. final String nick; } + +/// Triggered when an entity changes their nick. +class MemberChangedNickEvent extends XmppEvent { + MemberChangedNickEvent(this.roomJid, this.oldNick, this.newNick); + + /// The JID of the room. + final JID roomJid; + + /// The original nick. + final String oldNick; + + /// The new nick. + final String newNick; +} diff --git a/packages/moxxmpp/lib/src/xeps/xep_0045/status_codes.dart b/packages/moxxmpp/lib/src/xeps/xep_0045/status_codes.dart new file mode 100644 index 0000000..734029a --- /dev/null +++ b/packages/moxxmpp/lib/src/xeps/xep_0045/status_codes.dart @@ -0,0 +1,2 @@ +const selfPresenceStatus = '110'; +const nicknameChangedStatus = '303'; diff --git a/packages/moxxmpp/lib/src/xeps/xep_0045/xep_0045.dart b/packages/moxxmpp/lib/src/xeps/xep_0045/xep_0045.dart index dc68132..60b180b 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0045/xep_0045.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0045/xep_0045.dart @@ -14,6 +14,7 @@ import 'package:moxxmpp/src/xeps/xep_0030/types.dart'; import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart'; import 'package:moxxmpp/src/xeps/xep_0045/errors.dart'; import 'package:moxxmpp/src/xeps/xep_0045/events.dart'; +import 'package:moxxmpp/src/xeps/xep_0045/status_codes.dart'; import 'package:moxxmpp/src/xeps/xep_0045/types.dart'; import 'package:moxxmpp/src/xeps/xep_0359.dart'; import 'package:synchronized/synchronized.dart'; @@ -304,7 +305,7 @@ class MUCManager extends XmppManagerBase { item.attributes['affiliation']! as String, ); - if (statuses.contains('110')) { + if (statuses.contains(selfPresenceStatus)) { if (room.joined) { if (room.nick != from.resource || room.affiliation != affiliation || @@ -335,19 +336,49 @@ class MUCManager extends XmppManagerBase { ); } - if (presence.attributes['type'] == 'unavailable' && role == Role.none) { - // Cannot happen while joining, so we assume we are joined - assert( - room.joined, - 'Should not receive unavailable with role="none" while joining', - ); - room.members.remove(from.resource); - getAttributes().sendEvent( - MemberLeftEvent( - bareFrom, - from.resource, - ), - ); + if (presence.attributes['type'] == 'unavailable') { + if (role == Role.none) { + // Cannot happen while joining, so we assume we are joined + assert( + room.joined, + 'Should not receive unavailable with role="none" while joining', + ); + room.members.remove(from.resource); + getAttributes().sendEvent( + MemberLeftEvent( + bareFrom, + from.resource, + ), + ); + } else if (statuses.contains(nicknameChangedStatus)) { + assert( + room.joined, + 'Should not receive nick change while joining', + ); + final newNick = item.attributes['nick']! as String; + final member = RoomMember( + newNick, + Affiliation.fromString( + item.attributes['affiliation']! as String, + ), + role, + ); + + // Remove the old member. + room.members.remove(from.resource); + + // Add the "new" member". + room.members[newNick] = member; + + // Trigger an event. + getAttributes().sendEvent( + MemberChangedNickEvent( + bareFrom, + from.resource, + newNick, + ), + ); + } } else { final member = RoomMember( from.resource, diff --git a/packages/moxxmpp/test/xeps/xep_0045_test.dart b/packages/moxxmpp/test/xeps/xep_0045_test.dart index de83d51..d857552 100644 --- a/packages/moxxmpp/test/xeps/xep_0045_test.dart +++ b/packages/moxxmpp/test/xeps/xep_0045_test.dart @@ -658,7 +658,7 @@ void main() { 2, ); - // Now a new user joins the room. + // Now a user leaves the room. MemberLeftEvent? event; conn.asBroadcastStream().listen((e) { if (e is MemberLeftEvent) { @@ -689,4 +689,186 @@ void main() { expect(roomAfterLeave.members.length, 1); }, ); + + test( + 'Test a user changing their nick name', + () async { + final fakeSocket = StubTCPSocket([ + StringExpectation( + "", + ''' + + + + PLAIN + + + + + ''', + ), + StringExpectation( + "AHBvbHlub21kaXZpc2lvbgBhYWFh", + '', + ), + StringExpectation( + "", + ''' + + + + + + + + + + + +''', + ), + StanzaExpectation( + '', + 'polynomdivision@test.server/MU29eEZn', + ignoreId: true, + ), + StanzaExpectation( + '', + '', + ignoreId: true, + ), + ]); + final conn = XmppConnection( + TestingSleepReconnectionPolicy(1), + AlwaysConnectedConnectivityManager(), + ClientToServerNegotiator(), + fakeSocket, + ) + ..connectionSettings = ConnectionSettings( + jid: JID.fromString('polynomdivision@test.server'), + password: 'aaaa', + ) + ..setResource('test-resource', triggerEvent: false); + await conn.registerManagers([ + DiscoManager([]), + MUCManager(), + ]); + + await conn.registerFeatureNegotiators([ + SaslPlainNegotiator(), + ResourceBindingNegotiator(), + ]); + + await conn.connect( + waitUntilLogin: true, + shouldReconnect: false, + ); + + // Join a groupchat + final roomJid = JID.fromString('channel@muc.example.org'); + final joinResult = conn.getManagerById(mucManager)!.joinRoom( + roomJid, + 'test', + maxHistoryStanzas: 0, + ); + await Future.delayed(const Duration(seconds: 1)); + + fakeSocket + ..injectRawXml( + ''' + + + + + + ''', + ) + ..injectRawXml( + ''' + + + + + + ''', + ) + ..injectRawXml( + ''' + + + + + + + ''', + ) + ..injectRawXml( + ''' + + + + ''', + ); + + await joinResult; + final room = (await conn + .getManagerById(mucManager)! + .getRoomState(roomJid))!; + expect(room.joined, true); + expect( + room.members.length, + 2, + ); + + // Now a new user changes their nick. + MemberChangedNickEvent? event; + conn.asBroadcastStream().listen((e) { + if (e is MemberChangedNickEvent) { + event = e; + } + }); + + fakeSocket.injectRawXml( + ''' + + + + + + + ''', + ); + + await Future.delayed(const Duration(seconds: 2)); + expect(event != null, true); + expect(event!.oldNick, 'firstwitch'); + expect(event!.newNick, 'papatutuwawa'); + + final roomAfterChange = (await conn + .getManagerById(mucManager)! + .getRoomState(roomJid))!; + expect(roomAfterChange.members.length, 2); + expect(roomAfterChange.members['firstwitch'], null); + expect(roomAfterChange.members['papatutuwawa'] != null, true); + }, + ); }