Easy PubSub with XMPP in Elixir
Personal Eventing Protocol (PEP) is the simplified version of full PubSub protocol of XMPP. It is a solution similar to federated data exchange such as ActivityPub. While I do not know enough to comment of pros and cons between PEP/XMPP and ActivityPub, I can look at the minimal implement of PEP in Elixir.
XEP-0163 clearly defines the steps to implement PEP. To begin with, I use Prosody IM as server. Version 0.11 is required to support PEP properly. Module for PEP is automatically included and activated in Prosody IM by default and PubSub module is not required unless advanced features are implemented.
Romeo and XMPP are used. To implement XMPP, there are a few steps to do:
- Both publisher and subscriber needs to see presence of each other. In another word, one needs to be able to see online status of the other. It can be easily configured by XMPP client.
- Subscriber need to declare the interest to receive PEP message from publisher. It is declared in <presence>
- Publisher need to publish according to PEP protocol.
First, connect to a XMPP in Elixir with Romeo:
opts = [jid: "publisher@localhost/home", password: "password"]
{:ok, conn} = Romeo.Connection.start_link(opts)
Publisher can just send a presence if he doesn’t care to receive PEP message
Romeo.Connection.send(conn, Romeo.Stanza.presence)
Subscriber need to declare the interest of receiving PEP message. I use Miranda NG as client of subscriber, which supports some default notification type
<feature var="http://jabber.org/protocol/mood+notify" />
<feature var="http://jabber.org/protocol/tune+notify" />
<feature var="http://jabber.org/protocol/activity+notify" />
For example, it will receive notification about tune, mood and activity. Thus, publisher can post message to these nodes.
To declare interests in Elixir, subscriber can add Entity Capabilities to his presence like this:
caps = Romeo.Stanza.caps("localhost/home", "zHyEOgxTrkpSdGcQKH8EFPLsriY")
presence = Romeo.Stanza.presence(:undefined, :available, [caps: caps])
Romeo.Connection.send(conn, presence)
It will send a xml like this:
<presence xmlns='jabber:client'><c ver='zHyEOgxTrkpSdGcQKH8EFPLsriY' node='localhost/home' hash='sha-1' xmlns='http://jabber.org/protocol/caps'/></presence>
The hashed number “zHyEOgxTrkpSdGcQKH8EFPLsriY” (directly copied from XEP-0163 document) indicates its entity capabilities. XMPP server will immediately ask about the exact capabilities represented by this hashed value
<iq id='disco' to='publisher@localhost/vdM3EJ1f' from='publisher@localhost' type='get'><query xmlns='http://jabber.org/protocol/disco#info' node='localhost/xmppchat#zHyEOgxTrkpSdGcQKH8EFPLsriY'/></iq>
Then subscribe can return the capabilities like this
identity = {:identity, "client", "pc", "", "xmpp client 0.1"}
features = [
"http://jabber.org/protocol/disco#info",
"http://jabber.org/protocol/disco#items",
"http://jabber.org/protocol/geoloc",
"http://jabber.org/protocol/geoloc+notify",
"http://jabber.org/protocol/tune",
"http://jabber.org/protocol/tune+notify"
]
disco_info = {:disco_info, "", [identity], features, []}
iq = {:iq, "123", :result, "", :undefined, :undefined, [disco_info], {}}
Romeo.Connection.send(conn, :xmpp.encode(iq))
Again, if you only work as publisher, it is not necessary to declare entity capabilities.
Once subscriber can receive notification for certain types, publisher can send a message like this to all subscribers
title = {:xmlel, "title", [], [{:xmlcdata, "Music"}]}
tune = {:xmlel, "tune", [{"xmlns", Romeo.XMLNS.ns_user_tune}], [title]}
item = {:xmlel, "item", [], [tune]}
publish = {:xmlel, "publish", [{"node", Romeo.XMLNS.ns_user_tune}], [item]}
pubsub = {:xmlel, "pubsub", [{"xmlns", Romeo.XMLNS.ns_pubsub}], [publish]}
iq = Romeo.Stanza.iq("set", pubsub)
Romeo.Connection.send(conn, iq)
And on subscriber side, it would receive something like this
<message type="headline" to="subscriber@localhost/Miranda" from="publisher@localhost">
<event xmlns="http://jabber.org/protocol/pubsub#event">
<items node="http://jabber.org/protocol/tune">
<item id="28a4f80d-5bd0-453c-b99d-c51a1ce99733">
<tune xmlns="http://jabber.org/protocol/tune">
<title>Music</title>
</tune>
</item>
</items>
</event>
</message>
This basically follow XEP-0163 with minimal implementation in Elixir. Once it works, you can expand it to a full pubsub application.