WebSocket libraries for JSOO?

Hi,

I’m trying to use WebSockets to communicate between an application using OCaml (Dream) on the server side and JSOO on the browser side. I have problems finding a library for this with JSOO.

  • brr - looks promising, but it looks like it does not implement all required functions.

  • vdom - seems to have WebSocket bindings.

Are there other libraries or solution for supporting WebSockets in JSOO?

At a more general level, I’m looking for a way for easy communication between the server and the browser, that also support push from the server. Something like Meteor pubsub or GraphQl. What is the best option for this in OCaml/JSOO?

Regards,

Hans Ole Rafaelsen

Eliom does all this automatically without having to think about it. You would save a lot of time

2 Likes

Which required functions are missing ?

ocaml-websocket works.

@zoggy that’s server side.

1 Like

Sorry. I looked at some old code which uses the WebSockets module in JSOO. This works well with OCaml-websocket on server side.

I’ve used Brr client-side for websocket and I did not miss any function!

However, I was bit by this issue: Receiving websocket payloads larger than ~4K yields garbled data · Issue #214 · aantron/dream · GitHub

Bascially, the client and server (dream) are discussing through websockets. When the server sends a long string, everything works well. When the client sends a long string, the string received by the server is garbage character at some point.
So if your app might send lots of data in a single message, be aware of this!

1 Like

It’s probably me, not understanding how to use it properly. In JavaScript I would do something like:

    var socket = new WebSocket("ws://" + window.location.host + "/ws");

    socket.onopen = function () {
      socket.send("start");
    };

    socket.onmessage = function (e) {
        ...
    };

I’m able to connect to the server with Brr_io.Websocket.create, but I’m not able to use it. I was hoping to find similar functions as onopen and onmessage. At least the “addEventListener” method.

Is the “as_target” function used for setting up handlers? Do you have examples on how to use this, or a full example that shows how to set up and use the socket?

Js_of_ocaml supports websockets, you don’t need anything else.

3 Likes

Sorry no sample code but yes you need to turn the websocket value into an event target and then use the DOM event API to setup your handlers (here’s a random example which you can accomodate with the right events).

The websocket specific events are here a few generic ones are in the DOM Event API here and the message event is here.

The websocket API predates JavaScript promises so it’s a bit inconvenient to use.

I have been using Eliom for some of my applications. I have 3 main issues with it that I don’t know how to solve.

Deployment is my biggest problem. I have tried solving it before.

https://sympa.inria.fr/sympa/arc/ocsigen/2018-03/msg00005.html

https://sympa.inria.fr/sympa/arc/ocsigen/2018-06/msg00000.html

My current solution is to copy the required subset of my development environment (opam installed packages) over to the target nodes, in addition to the application. That was one of my main motivations for trying out Dream. It creates a single binary that can be copied and run. It would be a lot of help if the demo in ocsigen-start also could be built as a single static binary.

The second issue was that I had some problem with the application consuming memory.

https://sympa.inria.fr/sympa/arc/ocsigen/2018-02/msg00003.html

I’m not sure if it is a bug, or I’m not using it correctly. However, for my current usage, it is not a big issue.

The last issue is editor support. When working with these modules, getting type information and suggestions is a lot of help. I see there is some improvement lately, so it might be partly solved. I have not tested the latest setup.

Thanks for the pointers. I’ll give it a try :smiley:

I saw these definitions, but could not figure out how to use them. I thought they are interface definitions, and that they requires an implementation. Do you know where I might find examples on how to use them?

You can have a look at the code @zoggy mentioned. That being said I personally don’t find jsoo’s way of interacting with JavaScript very usable (more on this here).

Maybe this helps to get started. Beware, that’s not production level code.

let rec connect_websocket token ws_url is_debug () = begin
  Log.info "Connecting WebSocket." ;
  Log.info (sprintf "Debugging: %b." is_debug) ;
  Log.info (sprintf "WS-URL: %s" ws_url) ;
  (* let host = Js.to_string (Dom_html.window##.location##.host) in *)
  let ws = new%js WebSockets.webSocket (Js.string ws_url) in
  let pp_intv_opt =
    if ws##.readyState = WebSockets.CONNECTING && is_debug then begin
      show_message (sprintf "Ping interval: %i ms." ping_interval_ms) ;
      Some (Jsoohelpers.Timers.set_interval ~timeout:ping_interval_ms
              (fun () -> ws##send (Js.string "PING") ; Log.debug "-> PING"))
    end
    else None
  in
  (* Window event listener: unload *)
  let ev_unl_id =
    Dom_html.addEventListener Dom_html.window Dom_html.Event.unload
      (Dom_html.handler (fun _ev ->
           (Log.warn "Window event: unload!" ; ws##close ; Js._true)))
    Js._true
  in
  (* WebSocket handler: onopen *)
  ws##.onopen := Dom.handler (fun _ev -> begin
        ws##send token ;
        Log.info "WebSocket connected." ;
        if is_debug then show_message "WebSocket connected." ;
        Js._true
      end) ;
  (* WebSocket handler: onclose *)
  ws##.onclose := Dom.handler (fun _ev -> begin
        Log.error "WebSocket connection closed!" ;
        (match pp_intv_opt with
         | Some intv -> begin
             Jsoohelpers.Timers.clear_interval intv ;
             Log.debug "Pingpong interval cleared."
           end
         | None -> ()) ;
        Dom_html.removeEventListener ev_unl_id ;
        ignore (Dom_html.setTimeout (connect_websocket token ws_url is_debug)
                  reconnect_timeout_ms) ;
        Js._true
      end) ;
  (* WebSocket handler: onerror *)
  ws##.onerror := Dom.handler (fun _ev -> begin
        Log.error "WebSocket error!" ;
        Js._true
      end) ;
  (* WebSocket handler: onmessage *)
  ws##.onmessage := Dom.handler (fun ev -> begin
        let msg = Js.to_string ev##.data in
        add_message msg ;
        (* if is_debug then show_message msg ; *)
        Js._true
      end)
end

If you are not strongly tied to JSOO, maybe htmx with the WebSocket extension? </> htmx ~ The websockets Extension

I have an HTML generation library for Dream that supports htmx out of the box so you can write HTML directly in your Dream server code. Adapting the first snippet from the htmx page:

open Dream_html
open HTML

let chatroom = div [Hx.ext "ws"; Hx.ws_connect "/chatroom"] [
  div [id "notifications"] [];
  div [id "chat_room"] [
    ...
  ];
  form [id "form"; Hx.ws_send] [
    input [name "chat_message"];
  ];
]

So you render all the pages on the server side and htmx takes care of the network interactions on the client side ie setting up the WS, sending and receiving messages, reconnecting on disconnect. So in practice you have to write little or no JavaScript code.

I have just seen def/cuite: OCaml bindings to Qt toolkit. - cuite - Gitea: Git with a cup of tea a Qt5 binding, but not tested yet.

(It is based of C++ widgets, not the Qml language).