blog: Fixes for the moxxmpp blog

Signed-off-by: Ikjot Singh Dhody <ikjotsd@gmail.com>
This commit is contained in:
Ikjot Singh Dhody 2023-06-18 01:04:44 +05:30
parent 407f1a2017
commit 5ef69c62af

View File

@ -1,4 +1,8 @@
# XEP 0045 implementation in Moxxmpp ---
layout: post
title: XEP 0045 implementation in Moxxmpp
author: Ikjot Singh Dhody
---
Hello readers! Hello readers!
@ -8,9 +12,9 @@ Welcome back to the GSoC series of blogs. This one is about my implementation of
Well, as you probably know, Moxxy is the frontend app that is being developed as a modern new XMPP client. Well, as you probably know, Moxxy is the frontend app that is being developed as a modern new XMPP client.
<p>Moxxmpp is a Dart package that is used by Moxxy for multiple XMPP services. Essentially, the conversation between Moxxy and the XMPP server is handled by Moxxmpp. It is like the middleman that helps ease the communication between the two, keeping Moxxy code clean and free from the intricacies it handles. Moxxmpp is a Dart package that is used by Moxxy for multiple XMPP services. Essentially, the conversation between Moxxy and the XMPP server is handled by Moxxmpp. It is like the middleman that helps ease the communication between the two, keeping Moxxy code clean and free from the intricacies it handles.
<p>Since my GSoC project is all about adding MUC support to Moxxy, it was essential to first add the required support in Moxxmpp before proceeding to Moxxy. This blog will give you an idea of what the responsibilities of Moxxmpp are, and why it was important to separate it from Moxxy. Since my GSoC project is all about adding MUC support to Moxxy, it was essential to first add the required support in Moxxmpp before proceeding to Moxxy. This blog will give you an idea of what the responsibilities of Moxxmpp are, and why it was important to separate it from Moxxy.
## What is Moxxmpp? ## What is Moxxmpp?
@ -39,81 +43,101 @@ Here is my implementation plan for adding XEP-0045 support to Moxxmpp.
## GSoC Moxxmpp Implementation Design Plan ## GSoC Moxxmpp Implementation Design Plan
### Directory Structure ### Directory Structure
* xep_0045
* errors.dart
* helpers.dart
* types.dart
* `xep_0045` * xep_0045.dart
* `errors.dart` * Here, the errors.dart file will contain the abstract classes for different errors that one could get whilst going through all the lifecycle use-cases of the MUC feature.
* `helpers.dart` * The helpers.dart file will have some helper functions to maintain (DRY code avoiding repetition) and (modularized code).
* `types.dart` * types.dart will contain model classes for the events and other entities.
* `xep_0045.dart` * The xep_0045.dart is the main file which will contain the exposed routines as shown below, as well as the incoming stanza handlers.
* `Here, the errors.dart file will contain the abstract classes for different errors that one could get whilst going through all the lifecycle use-cases of the MUC feature.`
* `The helpers.dart file will have some helper functions to maintain (DRY code avoiding repetition) and (modularized code).`
* `types.dart will contain model classes for the events and other entities.`
* `The xep_0045.dart is the main file which will contain the exposed routines as shown below, as well as the incoming stanza handlers.`
### Phase 1 - Query Room Information ### Phase 1 - Query Room Information
#### `Future<Result<RoomInformation,MUCError>> queryRoomInformation(JID roomJID)` #### `Future<Result<RoomInformation,MUCError>> queryRoomInformation(JID roomJID)`
This routine will allow the users to view the features of the room. These will include whether the room is open, moderated, password protected, etc. All this will be stored in a RoomInformation object and sent back as a Result object. It is important to note that the MUC server's address (for the 'to' attribute) will be parsed from the latter half of the room JID (the part post the '@'). This routine will allow the users to view the features of the room. These will include whether the room is open, moderated, password protected, etc. All this will be stored in a RoomInformation object and sent back as a Result object. It is important to note that the MUC server's address (for the 'to' attribute) will be parsed from the latter half of the room JID (the part post the '@').
The parameters of the RoomInformation class will be represented in the following way: The parameters of the RoomInformation class will be represented in the following way:
1. `JID roomJID` 1. `JID roomJID`
2. `List<String> feature_list` 2. `List<String> feature_list`
### Phase 2 - Join/Leave Room(Handle errors) ### Phase 2 - Join/Leave Room(Handle errors)
To join/leave a room we can send an appropriate presence stanza that will handle the same for us. So we will have two simple routines for the same as well. To join/leave a room we can send an appropriate presence stanza that will handle the same for us. So we will have two simple routines for the same as well.
### Routines ### Routines
#### `Future<Result>bool, MUCError>> joinRoom(JID roomJID)` #### `Future<Result>bool, MUCError>> joinRoom(JID roomJID)`
This routine will send a presence stanza to the MUC server asking it to join the server. It takes the `roomJID` as a parameter that will essentially contain the bare JID as well as the nickname (prompted from the user) as the resource. This will be preceded by the queryRoomInformation() routine call to obtain necessary information about the room, for example: if the room is password protected (in which case the user will see a not-implemented error dialog till a time it is supported). This routine will send a presence stanza to the MUC server asking it to join the server. It takes the `roomJID` as a parameter that will essentially contain the bare JID as well as the nickname (prompted from the user) as the resource. This will be preceded by the queryRoomInformation() routine call to obtain necessary information about the room, for example: if the room is password protected (in which case the user will see a not-implemented error dialog till a time it is supported).
Important to note is that the user's nickname will be used as the 'resource' in the roomJID sent by the frontend. This is because the 'to' attribute of a presence stanza to join a room looks as such - `{local/MUC}@{domain/chat server}/{nickname}.` Important to note is that the user's nickname will be used as the 'resource' in the roomJID sent by the frontend. This is because the 'to' attribute of a presence stanza to join a room looks as such - `{local/MUC}@{domain/chat server}/{nickname}.`
#### `Future<Result<bool, MUCError>> leaveRoom(String roomJID)` #### `Future<Result<bool, MUCError>> leaveRoom(String roomJID)`
Similarly this is a routine to allow for leaving the room with a presence stanza. Similarly this is a routine to allow for leaving the room with a presence stanza.
The boolean return type is just to allow for a simple return object with the Result type. We could move forward with a simple Future<bool> as well, but for the sake of consistency - maybe sticking to the former is better. The boolean return type is just to allow for a simple return object with the Result type. We could move forward with a simple Future<bool> as well, but for the sake of consistency - maybe sticking to the former is better.
### Phase 3 - Refactor sendMessage to handle MUCs ### Phase 3 - Refactor sendMessage to handle MUCs
To be able to send MUC messages, the main change will be to change the 'type' attribute in the stanza as below. The reason why this is needed is that message stanzas meant for MUC are of the type 'groupchat' and the current implementation of the sendMessage stanza hardcodes the same to 'chat'. An enum for the same will be as shown below: To be able to send messages to groupchats/MUCs, the main requirement is to be able to change the `type` attribute in the message stanzas from `chat` to `groupchat`. This can be done in one of two ways. The first way will be to change the `sendMessage` routine's description to include a `type` parameter that will simply transfer over to the stanza that will be sent from moxxmpp as a string. Another was is described below, where a new extension called ConversationTypeData will be added to the `StanzaHandlerExtension` object whilst calling the sendMessage routine. The former solution is simpler and will most probably be implemented. The design for both these possibilities is listed below.
#### Solution 1
The `sendMessage` routine will transform from
```Dart
Future<void> sendMessage(
JID to,
TypedMap<StanzaHandlerExtension> extensions,
) async {
await getAttributes().sendStanza(
StanzaDetails(
Stanza.message(
to: to.toString(),
id: extensions.get<MessageIdData>()?.id,
type: 'chat',
children: _messageSendingCallbacks
.map((c) => c(extensions))
.flattened
.toList(),
),
awaitable: false,
),
);
}
```
to
```Dart
Future<void> sendMessage(
JID to,
TypedMap<StanzaHandlerExtension> extensions,
String type,
) async {
await getAttributes().sendStanza(
StanzaDetails(
Stanza.message(
to: to.toString(),
id: extensions.get<MessageIdData>()?.id,
type: type,
children: _messageSendingCallbacks
.map((c) => c(extensions))
.flattened
.toList(),
),
awaitable: false,
),
);
}
```
#### Solution 2
```Dart ```Dart
enum ConversationType { enum ConversationType {
@ -121,6 +145,8 @@ enum ConversationType {
} }
``` ```
The `ConversationTypeData` class essentially will act as a wrapper over the above enum.
Hence the new type attribute value will be: Hence the new type attribute value will be:
```Dart ```Dart
extensions.get<ConversationTypeData>()?.conversationType == extensions.get<ConversationTypeData>()?.conversationType ==
@ -128,22 +154,15 @@ extensions.get<ConversationTypeData>()?.conversationType ==
? 'groupchat' ? 'groupchat'
: 'chat', : 'chat',
``` ```
Here, `ConversationTypeData` is a simple wrapper over `ConversationType` so that it can be recognized as a subclass of the `StanzaHandlerExtensions` class.
Here, `ConversationTypeData` is a simple wrapper over `ConversationType` so that it can be recognized as a subclass of the `StanzaHandlerExtensions` class.
The described change will be to allow for MUC messages, but doesn't describe how the private MUC messages will be handled. This has been elaborated below: The described change will be to allow for MUC messages, but doesn't describe how the private MUC messages will be handled. This has been elaborated below:
### Changes/Example ### Changes/Example
* `For normal MUC messages:` * `For normal MUC messages:`
The call from Moxxy will send an extensions object as a parameter which will contain the following: The call from Moxxy will send an extensions object as a parameter which will contain the following:
```Dart ```Dart
TypedMap<StanzaHandlerExtension>.fromList([ TypedMap<StanzaHandlerExtension>.fromList([
@ -152,21 +171,18 @@ TypedMap<StanzaHandlerExtension>.fromList([
]) ])
``` ```
* `For private MUC messages:` * `For private MUC messages:`
```Dart ```Dart
TypedMap<StanzaHandlerExtension>.fromList([ TypedMap<StanzaHandlerExtension>.fromList([
... ...
(ConversationType.groupchatprivate) (ConversationType.groupchatprivate)
]) ])
``` ```
---
For MUC messages with a stanza reflecting a chat server (type: 'groupchat'), the type in the MessageEvent will be set to 'groupchat' automatically. The rest will be handled similarly to 1:1 chats.
---
For MUC messages with a stanza reflecting a chat server (type: 'groupchat'), the type in the MessageEvent will be set to 'groupchat' automatically. The rest will be handled similarly to 1:1 chats.
### Phase 4 - Miscellaneous requirements ### Phase 4 - Miscellaneous requirements
Some of the miscellaneous requirements from Moxxmpp could include viewing member list, group subject, change availability status, and more. Some of the miscellaneous requirements from Moxxmpp could include viewing member list, group subject, change availability status, and more.
@ -174,56 +190,36 @@ For now, I have identified the below as the most crucial requirements for the Mo
Below are the use-cases and how they are planned to be handled: Below are the use-cases and how they are planned to be handled:
1. #### `Get member list` 1. #### `Get member list`
After one joins a room, the user receives a presence from all the connected users to the chat. The best way to get the member list is to capture/handle the incoming presence stanzas and send an appropriate `MUCPresenceReceived` event to the UI. After one joins a room, the user receives a presence from all the connected users to the chat. The best way to get the member list is to capture/handle the incoming presence stanzas and send an appropriate `MUCPresenceReceived` event to the UI.
This way, whenever a user joins a room, they can see the current member list, and on subsequent presence updates, also change that list. This way, whenever a user joins a room, they can see the current member list, and on subsequent presence updates, also change that list.
2. #### `Handling the room subject ` 2. #### `Handling the room subject `
This is a very simple usecase. When a disco info query about the MUC goes out to the XMPP server, the response contains an identity node containing the chat name. This would be the best way to extract the rom subject. As of now, the `queryRoomInformation` routine will be triggered from the UI and the response will be sent to the same. This will be encapsulated in a simple `RoomInformation` class which is now updated as follows: This is a very simple usecase. When a disco info query about the MUC goes out to the XMPP server, the response contains an identity node containing the chat name. This would be the best way to extract the rom subject. As of now, the `queryRoomInformation` routine will be triggered from the UI and the response will be sent to the same. This will be encapsulated in a simple `RoomInformation` class which is now updated as follows:
1. `JID roomJID` 1. `JID roomJID`
2. `List<String> feature_list` 2. `List<String> feature_list`
3. `String name` 3. `String name`
**Note**: Changing your status seemed like a task that would be slighlty out of scope for the project duration, so that is kept as a stretch goal for now.
<b>Note</b>: Changing your status seemed like a task that would be slighlty out of scope for the project duration, so that is kept as a stretch goal for now.
### Errors ### Errors
Frontend checks while joining a room to prevent avoidable errors: Frontend checks while joining a room to prevent avoidable errors:
* Nickname must be specified in accordance with the rules of the XEP - to prevent the no nickname specified error.
* Validations for JID input field.
* `Nickname must be specified in accordance with the rules of the XEP - to prevent the no nickname specified error.` * Validation for checking empty nickname.
* `Validations for JID input field`
* `Validation for checking empty nickname`
Some of the possible errors that can be faced by the user in an MUC workflow are: Some of the possible errors that can be faced by the user in an MUC workflow are:
* Invalid/Already in use nickname
* Not allowed to register
* Invalid stanza format
* Invalid Disco Info Response: In case of some rare edge cases (not handled currently) where the response does not contain some information like the room name, or the features of the room.
* `Invalid/Already in use nickname`
* `Not allowed to register`
* `Invalid stanza format`
* `Invalid Disco Info Response`: In case of some rare edge cases (not handled currently) where the response does not contain some information like the room name, or the features of the room.
## Challenges noted ## Challenges noted
Some challenges/room for error were noted along the way. They are enumerated below: Some challenges/room for error were noted along the way. They are enumerated below:
@ -234,14 +230,12 @@ Some challenges/room for error were noted along the way. They are enumerated bel
The solution to this problem is to use the origin-id of the message. If the origin-id of the response matches with the sent origin-id, then the message is to be ignored and not shown in the UI. The solution to this problem is to use the origin-id of the message. If the origin-id of the response matches with the sent origin-id, then the message is to be ignored and not shown in the UI.
2. In anonymous MUCs, one can easily act as someone else by using their nickname once they leave. How will this be handled? 2. In anonymous MUCs, one can easily act as someone else by using their nickname once they leave. How will this be handled?
This is fixed by using the occupant-id property as described in XEP-0421. A user (with their unique JID) when joins the group, is assigned a unique occupant-id, which can be used to actually track which message is from which user. This is fixed by using the occupant-id property as described in XEP-0421. A user (with their unique JID) when joins the group, is assigned a unique occupant-id, which can be used to actually track which message is from which user.
This makes the implementation of reactions/retractions simpler. This makes the implementation of reactions/retractions simpler.
3. How will discussion history (sent on joining an MUC) be handled when someone rejoins an MUC? 3. How will discussion history (sent on joining an MUC) be handled when someone rejoins an MUC?
When a user joins a MUC, they are sent a set of stanzas in the following order: When a user joins a MUC, they are sent a set of stanzas in the following order:
@ -256,8 +250,6 @@ Some challenges/room for error were noted along the way. They are enumerated bel
## Conclusion and future work ## Conclusion and future work
Implementation for this has begun, and a <a href= 'https://codeberg.org/moxxy/moxxmpp/pulls/46'>PR</a> is already underway. Once this PR is completed, then work will begin on the front-end and the changes will be integrated into Moxxy. Implementation for this has begun, and a [PR](https://codeberg.org/moxxy/moxxmpp/pulls/46) is already underway. Once this PR is completed, then work will begin on the front-end and the changes will be integrated into Moxxy.
The design plan for that will be listed in a separate document.
--- ---