papatutuwawa.pages.polynom.me/posts/2023-06-17-XEP-0045-In-Moxxmpp.html
2024-01-01 01:10:28 +01:00

329 lines
19 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="/assets/css/index.css" rel="stylesheet" />
<link rel="shortcut icon" href="/assets/img/favicon.ico" sizes="32x32" />
<link href="/feed.xml" type="application/atom+xml" rel="alternate" title="Moxxy Blog" />
<!-- Begin Jekyll SEO tag v2.8.0 -->
<title>XEP-0045 implementation in Moxxmpp | Moxxy</title>
<meta name="generator" content="Jekyll v4.3.1" />
<meta property="og:title" content="XEP-0045 implementation in Moxxmpp" />
<meta name="author" content="Ikjot Singh Dhody" />
<meta property="og:locale" content="en_GB" />
<meta name="description" content="Hello readers!" />
<meta property="og:description" content="Hello readers!" />
<link rel="canonical" href="https://moxxy.org/posts/2023-06-17-XEP-0045-In-Moxxmpp.html" />
<meta property="og:url" content="https://moxxy.org/posts/2023-06-17-XEP-0045-In-Moxxmpp.html" />
<meta property="og:site_name" content="Moxxy" />
<meta property="og:type" content="article" />
<meta property="article:published_time" content="2023-06-17T00:00:00+02:00" />
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="XEP-0045 implementation in Moxxmpp" />
<script type="application/ld+json">
{"@context":"https://schema.org","@type":"BlogPosting","author":{"@type":"Person","name":"Ikjot Singh Dhody"},"dateModified":"2023-06-17T00:00:00+02:00","datePublished":"2023-06-17T00:00:00+02:00","description":"Hello readers!","headline":"XEP-0045 implementation in Moxxmpp","mainEntityOfPage":{"@type":"WebPage","@id":"https://moxxy.org/posts/2023-06-17-XEP-0045-In-Moxxmpp.html"},"publisher":{"@type":"Organization","logo":{"@type":"ImageObject","url":"https://moxxy.org/assets/img/logo.png"},"name":"Ikjot Singh Dhody"},"url":"https://moxxy.org/posts/2023-06-17-XEP-0045-In-Moxxmpp.html"}</script>
<!-- End Jekyll SEO tag -->
</head>
<body>
<div class="flex flex-col">
<div class="relative h-16 inset-x-0 inset-y-0 px-4 shadow-md grid place-items-center">
<div class="flex flex-row items-center w-full">
<!-- The Moxxy logo -->
<a href="/index.html">
<img src="/assets/img/logo.png" class="w-12 h-12 p-2" />
</a>
<a href="/index.html">
<span class="font-bold">Moxxy</span>
</a>
<!-- Spacer -->
<div class="grow"></div>
<a href="/posts.html" class="px-2 text-sky-400">Blog</a>
<a href="/developers.html" class="px-2 text-sky-400">Developers</a>
<a href="https://codeberg.org/moxxy/moxxy" class="px-2 text-sky-400">Source</a>
</div>
</div>
<div class="w-full md:max-w-prose mt-2 mx-auto">
<div class="px-8">
<h1 class="text-3xl">XEP-0045 implementation in Moxxmpp</h1>
<span>Posted by <span class="italic">Ikjot Singh Dhody</span> on 17.06.2023</span>
<article class="mt-5 prose lg:prose-lg">
<p>Hello readers!</p>
<p>Welcome back to the GSoC series of blogs. This one is about my implementation of the MUC XEP (XEP-0045) in Moxxmpp.</p>
<h2 id="introduction">Introduction</h2>
<p>Well, as you probably know, Moxxy is the frontend app that is being developed as a modern new XMPP client.</p>
<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.</p>
<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.</p>
<h2 id="what-is-moxxmpp">What is Moxxmpp?</h2>
<p>In the words of its developer, Moxxmpp is “a pure-Dart XMPP library”. Moxxmpp could be thought of as the backbone of Moxxy. The entire communication/networking part of Moxxy, i.e. how it (as a client) communicates with the users XMPP server is outsourced to this Dart library.</p>
<p>Not only this, used as per its rules - Moxxmpp could be used by any client to perform XMPP tasks that are currently supported by it. It is a re-usable and versatile XMPP library.</p>
<p>Listed below are some of its core responsibilities:</p>
<ol>
<li>Establishing the connection between the client and the XMPP server via the users JID and password.</li>
<li>Allowing Moxxy/any front-end client to register managers that provide support for a particular XEP/part of the XMPP flow. For example: the messaging service is handled by a MessageManager.
Similarly here are some other managers with their use:
<ol>
<li>PresenceManager: Handling the presence related stanzas/use.</li>
<li>PubSubManager: Provides support for XEP-0060.</li>
<li>DiscoManager: Provides support for the service discovery: XEP-0030.</li>
<li>RosterManager: Handling roster related tasks.</li>
<li>BlockingManager: For blocking related tasks.</li>
</ol>
<p>As of June 17 2023, there are 35 such managers and 35 XEPs supported in Moxxmpp.</p>
</li>
<li>Managing cache: Moxxmpp manages cache of common requests, such as service discovery requests. If a request is duplicated, the result is provided from the cache - saving bandwidth and time.</li>
</ol>
<p>As explained below, new XEP support is related to adding new managers to Moxxmpp and I will be doing the same for XEP-0045 support in Moxxy. My design plan is elaborated in the next section.</p>
<p>Here is my implementation plan for adding XEP-0045 support to Moxxmpp.</p>
<h2 id="gsoc-moxxmpp-implementation-design-plan">GSoC Moxxmpp Implementation Design Plan</h2>
<h3 id="directory-structure">Directory Structure</h3>
<ul>
<li>xep_0045
<ul>
<li>errors.dart</li>
<li>helpers.dart</li>
<li>types.dart</li>
<li>xep_0045.dart</li>
</ul>
</li>
<li>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.</li>
<li>The helpers.dart file will have some helper functions to maintain (DRY code avoiding repetition) and (modularized code).</li>
<li>types.dart will contain model classes for the events and other entities.</li>
<li>The xep_0045.dart is the main file which will contain the exposed routines as shown below, as well as the incoming stanza handlers.</li>
</ul>
<h3 id="phase-1---query-room-information">Phase 1 - Query Room Information</h3>
<h4 id="futureresultroominformationmucerror-queryroominformationjid-roomjid"><code class="language-plaintext highlighter-rouge">Future&lt;Result&lt;RoomInformation,MUCError&gt;&gt; queryRoomInformation(JID roomJID)</code></h4>
<p>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 servers address (for the to attribute) will be parsed from the latter half of the room JID (the part post the @).</p>
<p>The parameters of the RoomInformation class will be represented in the following way:</p>
<ol>
<li><code class="language-plaintext highlighter-rouge">JID roomJID</code></li>
<li><code class="language-plaintext highlighter-rouge">List&lt;String&gt; feature_list</code></li>
</ol>
<h3 id="phase-2---joinleave-roomhandle-errors">Phase 2 - Join/Leave Room(Handle errors)</h3>
<p>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.</p>
<h3 id="routines">Routines</h3>
<h4 id="futureresultbool-mucerror-joinroomjid-roomjid"><code class="language-plaintext highlighter-rouge">Future&lt;Result&gt;bool, MUCError&gt;&gt; joinRoom(JID roomJID)</code></h4>
<p>This routine will send a presence stanza to the MUC server asking it to join the server. It takes the <code class="language-plaintext highlighter-rouge">roomJID</code> 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).</p>
<p>Important to note is that the users 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 - <code class="language-plaintext highlighter-rouge">{local/MUC}@{domain/chat server}/{nickname}.</code></p>
<h4 id="futureresultbool-mucerror-leaveroomjid-roomjid"><code class="language-plaintext highlighter-rouge">Future&lt;Result&lt;bool, MUCError&gt;&gt; leaveRoom(JID roomJID)</code></h4>
<p>Similarly this is a routine to allow for leaving the room with a presence stanza.</p>
<p>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.</bool></p>
<h3 id="phase-3---refactor-sendmessage-to-handle-mucs">Phase 3 - Refactor sendMessage to handle MUCs</h3>
<p>To be able to send messages to groupchats/MUCs, the main requirement is to be able to change the <code class="language-plaintext highlighter-rouge">type</code> attribute in the message stanzas from <code class="language-plaintext highlighter-rouge">chat</code> to <code class="language-plaintext highlighter-rouge">groupchat</code>. This can be done in one of two ways. The first way will be to change the <code class="language-plaintext highlighter-rouge">sendMessage</code> routines description to include a <code class="language-plaintext highlighter-rouge">type</code> 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 <code class="language-plaintext highlighter-rouge">StanzaHandlerExtension</code> 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.</p>
<h4 id="solution-1">Solution 1</h4>
<p>The <code class="language-plaintext highlighter-rouge">sendMessage</code> routine will transform from</p>
<pre><code class="language-Dart">Future&lt;void&gt; sendMessage(
JID to,
TypedMap&lt;StanzaHandlerExtension&gt; extensions,
) async {
await getAttributes().sendStanza(
StanzaDetails(
Stanza.message(
to: to.toString(),
id: extensions.get&lt;MessageIdData&gt;()?.id,
type: 'chat',
children: _messageSendingCallbacks
.map((c) =&gt; c(extensions))
.flattened
.toList(),
),
awaitable: false,
),
);
}
</code></pre>
<p>to</p>
<pre><code class="language-Dart">Future&lt;void&gt; sendMessage(
JID to,
TypedMap&lt;StanzaHandlerExtension&gt; extensions,
String type,
) async {
await getAttributes().sendStanza(
StanzaDetails(
Stanza.message(
to: to.toString(),
id: extensions.get&lt;MessageIdData&gt;()?.id,
type: type,
children: _messageSendingCallbacks
.map((c) =&gt; c(extensions))
.flattened
.toList(),
),
awaitable: false,
),
);
}
</code></pre>
<h4 id="solution-2">Solution 2</h4>
<pre><code class="language-Dart">enum ConversationType {
chat, groupchat, groupchatprivate
}
</code></pre>
<p>The <code class="language-plaintext highlighter-rouge">ConversationTypeData</code> class essentially will act as a wrapper over the above enum.</p>
<p>Hence the new type attribute value will be:</p>
<pre><code class="language-Dart">extensions.get&lt;ConversationTypeData&gt;()?.conversationType ==
ConversationType.groupchat
? 'groupchat'
: 'chat',
</code></pre>
<p>Here, <code class="language-plaintext highlighter-rouge">ConversationTypeData</code> is a simple wrapper over <code class="language-plaintext highlighter-rouge">ConversationType</code> so that it can be recognized as a subclass of the <code class="language-plaintext highlighter-rouge">StanzaHandlerExtensions</code> class.</p>
<p>For private MUC messages to a single participant, the <code class="language-plaintext highlighter-rouge">type</code> attribute of the message stanza should be set to <code class="language-plaintext highlighter-rouge">chat</code>. This can be handled very easily in case of either solution implementation.</p>
<hr />
<p>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.</p>
<h3 id="phase-4---miscellaneous-requirements">Phase 4 - Miscellaneous requirements</h3>
<p>Some of the miscellaneous requirements from Moxxmpp could include viewing member list, group subject, change availability status, and more.
For now, I have identified the below as the most crucial requirements for the Moxxmpp feature additions for MUC.</p>
<p>Below are the use-cases and how they are planned to be handled:</p>
<ol>
<li>Get member list</li>
</ol>
<p>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 <code class="language-plaintext highlighter-rouge">MUCPresenceReceived</code> event to the UI.</p>
<p>This way, whenever a user joins a room, they can see the current member list, and on subsequent presence updates, also change that list.</p>
<ol>
<li>Handling the room subject</li>
</ol>
<p>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 <code class="language-plaintext highlighter-rouge">queryRoomInformation</code> routine will be triggered from the UI and the response will be sent to the same. This will be encapsulated in a simple <code class="language-plaintext highlighter-rouge">RoomInformation</code> class which is now updated as follows:</p>
<ol>
<li><code class="language-plaintext highlighter-rouge">JID roomJID</code></li>
<li><code class="language-plaintext highlighter-rouge">List&lt;String&gt; feature_list</code></li>
<li><code class="language-plaintext highlighter-rouge">String name</code></li>
</ol>
<p><strong>Note</strong>: 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.</p>
<h3 id="errors">Errors</h3>
<p>Frontend checks while joining a room to prevent avoidable errors:</p>
<ul>
<li>Nickname must be specified in accordance with the rules of the XEP - to prevent the no nickname specified error.</li>
<li>Validations for JID input field.</li>
<li>Validation for checking empty nickname.</li>
</ul>
<p>Some of the possible errors that can be faced by the user in an MUC workflow are:</p>
<ul>
<li>Invalid/Already in use nickname</li>
<li>Not allowed to register</li>
<li>Invalid stanza format</li>
<li>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.</li>
</ul>
<h2 id="challenges-noted">Challenges noted</h2>
<p>Some challenges/room for error were noted along the way. They are enumerated below:</p>
<ol>
<li>
<p>How will Moxxy/Moxxmpp handle messages from oneself?</p>
<p>When a user sends a message on the MUC, they receive the message themselves as well. How will moxxmpp/moxxy differentiate between other live messages and this echoed message?</p>
<p>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.</p>
</li>
<li>
<p>In anonymous MUCs, one can easily act as someone else by using their nickname once they leave. How will this be handled?</p>
<p>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.</p>
<p>This makes the implementation of reactions/retractions simpler.</p>
</li>
<li>
<p>How will discussion history (sent on joining an MUC) be handled when someone rejoins an MUC?</p>
<p>When a user joins a MUC, they are sent a set of stanzas in the following order:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 1. Errors (if any)
2. Self presence with status code
3. Room history
4. Room subject
5. Live message/presence updates
</code></pre></div> </div>
<p>Now, when a user gets disconnected from an MUC and joins again, then the room history will be sent again as well. To prevent this history from getting appended to the conversation in the UI, we will need to wait till we receive the room subject and only then start listening for message stanzas (since room subject is sent after room history).</p>
</li>
</ol>
<h2 id="conclusion-and-future-work">Conclusion and future work</h2>
<p>In conclusion, this blog post discussed the implementation plan for adding Multi-User Chat (MUC) support, specifically XEP-0045, to Moxxmpp. Moxxmpp serves as the backbone for the Moxxy XMPP client, handling the communication between the client and XMPP server. The blog outlined the directory structure, design plan, and key routines for implementing the MUC support in Moxxmpp. It also addressed some challenges and considerations related to handling MUC features.</p>
<p>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.</p>
</article>
</div>
</div>
</div>
<footer class="h-8 w-full flex flex-row justify-center">
<span class="text-sm text-center">
Made with &lt;3 by
<a class="text-sky-400" href="https://polynom.me" target="_blank" rel="noopener noreferrer">PapaTutuWawa</a>
using <a class="text-sky-400" href="https://tailwindcss.com/" target="_blank" rel="noopener noreferrer">TailwindCSS</a>,
<a class="text-sky-400" href="https://jekyllrb.com/" target="_blank" rel="noopener noreferrer">Jekyll</a> and
<a class="text-sky-400" href="https://heroicons.com/">heroicons</a>.
</span>
</footer>
</body>
</html>