diff --git a/examples_dart/bin/muc_client.dart b/examples_dart/bin/muc_client.dart index 3b874cd..4d2e45a 100644 --- a/examples_dart/bin/muc_client.dart +++ b/examples_dart/bin/muc_client.dart @@ -58,7 +58,11 @@ void main(List args) async { Logger.root.info('Connected.'); // Join room - await connection.getManagerById(mucManager)!.joinRoom(muc, nick); + await connection.getManagerById(mucManager)!.joinRoom( + muc, + nick, + maxHistoryStanzas: 0, + ); final repl = Repl(prompt: '> '); await for (final line in repl.runAsync()) { diff --git a/packages/moxxmpp/lib/src/xeps/xep_0045/types.dart b/packages/moxxmpp/lib/src/xeps/xep_0045/types.dart index 31e2c80..5c4a7fb 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0045/types.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0045/types.dart @@ -34,10 +34,25 @@ class RoomInformation { } class RoomState { - RoomState({ - required this.roomJid, - this.nick, - }); + RoomState({required this.roomJid, this.nick, required this.joined}); + + /// The JID of the room. final JID roomJid; + + /// The nick we're joined with. String? nick; + + /// Flag whether we're joined and can process messages + bool joined; + + RoomState copyWith({ + bool? joined, + String? nick, + }) { + return RoomState( + roomJid: roomJid, + joined: joined ?? this.joined, + nick: nick ?? this.nick, + ); + } } 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 e09505e..b84ac41 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0045/xep_0045.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0045/xep_0045.dart @@ -1,6 +1,8 @@ import 'package:moxlib/moxlib.dart'; import 'package:moxxmpp/src/jid.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/namespaces.dart'; import 'package:moxxmpp/src/stanza.dart'; @@ -9,6 +11,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/types.dart'; +import 'package:synchronized/extension.dart'; import 'package:synchronized/synchronized.dart'; class MUCManager extends XmppManagerBase { @@ -23,6 +26,16 @@ class MUCManager extends XmppManagerBase { /// Cache lock final Lock _cacheLock = Lock(); + @override + List getIncomingStanzaHandlers() => [ + StanzaHandler( + stanzaTag: 'message', + callback: _onMessage, + // Before the message handler + priority: -99, + ) + ]; + /// Queries the information of a Multi-User Chat room. /// /// Retrieves the information about the specified MUC room by performing a @@ -55,11 +68,23 @@ class MUCManager extends XmppManagerBase { /// if applicable. Future> joinRoom( JID roomJid, - String nick, - ) async { + String nick, { + int? maxHistoryStanzas, + }) async { if (nick.isEmpty) { return Result(NoNicknameSpecified()); } + + await _cacheLock.synchronized( + () { + _mucRoomCache[roomJid] = RoomState( + roomJid: roomJid, + nick: nick, + joined: false, + ); + }, + ); + await getAttributes().sendStanza( StanzaDetails( Stanza.presence( @@ -68,16 +93,20 @@ class MUCManager extends XmppManagerBase { XMLNode.xmlns( tag: 'x', xmlns: mucXmlns, - ) + children: [ + if (maxHistoryStanzas != null) + XMLNode( + tag: 'history', + attributes: { + 'maxstanzas': maxHistoryStanzas.toString(), + }, + ), + ], + ), ], ), ), ); - await _cacheLock.synchronized( - () { - _mucRoomCache[roomJid] = RoomState(roomJid: roomJid, nick: nick); - }, - ); return const Result(true); } @@ -108,4 +137,48 @@ class MUCManager extends XmppManagerBase { ); return const Result(true); } + + Future _onMessage( + Stanza message, + StanzaHandlerData state, + ) async { + final roomJid = JID.fromString(message.from!).toBare(); + return _mucRoomCache.synchronized(() { + final roomState = _mucRoomCache[roomJid]; + if (roomState == null) { + return state; + } + + if (message.type == 'groupchat' && message.firstTag('subject') != null) { + // The room subject marks the end of the join flow. + if (!roomState.joined) { + // Mark the room as joined. + _mucRoomCache[roomJid] = roomState.copyWith(joined: true); + logger.finest('$roomJid is now joined'); + } + + // TODO(Unknown): Signal the subject? + + return StanzaHandlerData( + true, + false, + message, + state.extensions, + ); + } else { + if (!roomState.joined) { + // Ignore the discussion history. + // TODO: Implement a copyWith method + return StanzaHandlerData( + true, + false, + message, + state.extensions, + ); + } + } + + return state; + }); + } }