[ANN] GeoPub - A XMPP web client


I’d like to announce an initial, proof-of-concept release of GeoPub - an XMPP web client. Unlike many XMPP clients the focus is not on instant messaging but on creating, displaying and managing things such as events, maps, information on local organizations and other local knowledge (see the openEngiadina project for the context).

This initial release is not really anything useful but a proof-of-concept how such an application can be developed using XMPP and OCaml. There are many rough edges and broken hacks that need fixing. I’d be very grateful for your feedback, thoughts and ideas.

The source code of the app is on codeberg and a pre-built hosted version is available here.

The application consists of some parts and ideas that I’d like to illustrate separately:


ocaml-xmpp is a XMPP client library for OCaml (documentation available online.


ocaml-xmpp is reactive in the sense that the XMPP connection is abstracted as a React event of Stanzas (small pieces of information that flow over XMPP):

val stanzas : t -> Stanza.t React.event

This React event can be filtered for messages in a specific conversation, for example.


XMPP works with different transport mechanisms and ocaml-xmpp supports this. Currently ocaml-xmpp can be used from Unix with a TCP/SSL connection to a XMPP server and from web browsers with a WebSocket connection. This is implemented by abstracting the XMPP transport:

module type TRANSPORT = sig
  (** {2 Connection} *)

  type options
  (** Additional options that may be passed to the transport *)

  type t
  (** Type of an instantiated connection to an XMPP server *)

  val connect : host:string -> options -> t Lwt.t

  val close : t -> unit Lwt.t

  val closed : t -> unit Lwt.t

  (** {2 XML Stream} *)

  type stream

  val open_stream : t -> to':string -> stream Lwt.t

  val stream_id : stream -> string Lwt.t

  val send_xml : stream -> Xmlc.t -> unit Lwt.t

  val signals : stream -> Xmlc.signal Lwt_stream.t

  val stop_stream : stream -> unit Lwt.t

A transport establishes the underlying connection to a server and can create XML streams (in XMPP a connection is by multiple XML streams sequentially). For technical reasons XML parsing is also handled by the transport and a stream of XML signals (element start, data, element end) is returned. This is due to the fact that XML parsing in XMPP needs to be done slightly differently when using TCP (a single XML document over the entire stream) or WebSockets (every WebSocket frame is a parse-able XML document).

The Unix/TCP/SSL transport uses Markup.ml and whereas the WebSocket transport uses Xmlm (and Brrr).

Parser combinators for XML

For parsing streams of XML signals to OCaml types ocaml-xmpp contains a parser combinator helper library: Xmlc. This allows parser for XML such as this:

<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><jid>w4iu4ckn3kjbqvcd@demo.openengiadina.net/z8Pkzfa8</jid></bind>

to be parses like this:

  element (Ns.bind "bind") (fun _ ->
    element (Ns.bind "jid") (fun _ ->
      text >>| String.concat "" >>= fun jid_s ->
      match Jid.of_string jid_s with
      | Some jid -> return jid
      | None -> fail_with "invalid JID")))

XMPP extensions

Inspiration for the scope of the core library is taken from the Strophe XMPP libraries - everything that does not have directly to do with XMPP transport, authentication or stream management is kept outside of the core library.

There are already some “extension” libraries outside of the core for useful XMPP features (e.g. Roster management, PubSub and pinging).

One thing that I do want to add to the core library is stream management according to XEP-0198. I expect this addition to change the core library API - the API is not stable yet!

Much inspiration was taken from Jackline - an OCaml XMPP client - and in particular this post on Jackline. Many thanks to @hannes.


GeoPub uses Brr. I had some trouble figuring out a suitable “architecture” for managing complex logic and ended up hacking an Elm inspired helper library: reactor.mli. State updates for the entire application are then handled in a single update function.

I’m not yet very happy with this machinery and I’m pretty sure I’m using react in wrong and dangerous ways. I’d be very grateful for ideas on how to improve this. THis might be related to this discussion: Structuring FRP (specifically Note) applications - #17 by dbuenzli.

The reason for using React over Note is because ocaml-xmpp uses a lot of Lwt and Lwt_react provides nice bindings for working with both. I guess something similar could be created for Note (e.g. Lwt_note) and I’m open to using Note (also in ocaml-xmpp).


GeoPub displays a map using the Leaflet.js JavaScript library. GeoPub contains OCaml bindings to Leaflet using Brr: leaflet.mli. Writing this was very straightforward and pleasant (I like Brr!).

One issue I have is that the Leaflet map needs to be manipulated very imperatively, whereas the rest of the application is much more functional. This causes some mismatches. I guess one needs to find a way of hiding the impressiveness of Leaflet (e.g. like react-leaflet).

Guix for build and development environments

I use Guix for providing a build and development environment. With guix installed one can run guix shell in the GeoPub repository to get a reproducible build environment. All dependencies are fetched and made available by Guix in this environment (e.g. ocaml-xmpp or the OCaml compiler).

I will publish ocaml-xmpp on OPAM once the API is more stable and an initial release can be made.

Thank you!


Wow, I love these: codeberg, guix, leaflet!!!

I’m not yet very happy with this machinery and I’m pretty sure I’m using react in wrong and dangerous ways.

Added World Map to Academic Excellence Page by shreyaswikriti · Pull Request #513 · ocaml/v3.ocaml.org · GitHub - this may have some useful bits, if you take interest in rescript-react later on.


Do you plan to publish your leaflet bindings into a separate library ? I’m sure it would be helpful to other - or me at least. :slight_smile:


the name sounds a bit odd for that purpose - how comes you stuck with it when fleshing the client out?

(side note: I’m having a geoXY name at codeberg as well: mro/geohash: ♊️ Mirror of https://code.mro.name/mro/geohash | #🌐 🐫 - geohash - Codeberg.org)

Update: uh, stupid me, I see it’s special purpose, makes sense.

1 Like

FYI, virtual postgis day is streaming today. PostGIS Day 2021