62 lines
2.9 KiB
OCaml
62 lines
2.9 KiB
OCaml
open Lwt.Syntax
|
|
open Js_of_ocaml
|
|
let jss = Js.string
|
|
let sjs = Js.to_string
|
|
|
|
(* sic. XEP-0156: "host-meta files MUST be fetched only over HTTPS". I don't make the rules. *)
|
|
let well_known_of (domain : string) = "https://" ^ domain ^ "/.well-known/host-meta"
|
|
|
|
let ws_endpoint (domain : string) =
|
|
(** [ws_endpoint domain] is a promise containing the XMPP websocket endpoint associated with [domain], by using the
|
|
domain's Web-host Metadata.
|
|
|
|
This function uses XMLHttpRequest, so while it should work fine in the browser, in environments that don't provide
|
|
this constructor (Node.js), there should be some sort of polyfill.
|
|
|
|
Lastly, if [domain] doesn't provide a well-formed Web-host Metadata file, the function throws an exception. *)
|
|
|
|
let+ host_meta = Js_of_ocaml_lwt.XmlHttpRequest.perform_raw_url (well_known_of domain)
|
|
in let i = Xmlm.make_input (`String (0, host_meta.content))
|
|
(* This ugly function extracts the href element from a Link tag's attributes if it's a websocket. *)
|
|
and link_websocket = function
|
|
| ((_, "rel"), "urn:xmpp:alt-connections:websocket") :: ((_, "href"), href) :: _
|
|
| ((_, "href"), href) :: ((_, "rel"), "urn:xmpp:alt-connections:websocket") :: _ -> Some href
|
|
| _ -> None
|
|
in let parse_xrd = (* Parse a single XRD tree. *)
|
|
Xmlm.input_tree
|
|
~el:(fun tag children
|
|
-> match tag with
|
|
| ((_, "Link"), attributes) -> link_websocket attributes
|
|
| ((_, "XRD"), _) -> List.find_map (fun x -> x) children
|
|
| _ -> None)
|
|
(* The XRD tree doesn't hold any relevant XML data. *)
|
|
~data:(fun _ -> None)
|
|
in ignore (Xmlm.input i); (* DTD stuff *)
|
|
match parse_xrd i with
|
|
| Some uri -> uri
|
|
| None -> failwith (domain ^ "doesn't advertise a WebSocket endpoint via Web-host Metadata.")
|
|
|
|
let ws_stream (url : string) =
|
|
(** [ws_stream url] returns a stream (and its push function) that talk with the websocket located at [url].
|
|
|
|
Pushing [None] closes the websocket.
|
|
|
|
If the websocket is closed server-side, it's still up to the caller to close the stream. *)
|
|
let open Lwt_stream in
|
|
let handle ws incoming () =
|
|
let+ _ = iter (fun msg -> ws##send (jss msg)) incoming
|
|
in (ws##close)
|
|
in let stream, message = create () (* websocket -> user *)
|
|
and incoming, push = create () (* user -> websocket *)
|
|
in let (ws : WebSockets.webSocket Js.t) = new%js WebSockets.webSocket_withProtocol (jss url) (jss "xmpp")
|
|
in ws##.onmessage :=
|
|
Dom.handler (fun x -> Some (sjs x##.data) |> message; Js._false);
|
|
ws##.onopen :=
|
|
Dom.handler (fun _ -> Lwt.async @@ handle ws incoming; Js._false);
|
|
ws##.onclose :=
|
|
Dom.handler (fun _ -> message None; Js._true);
|
|
stream, push
|
|
|
|
(* let connect domain = *)
|
|
(* let+ url = server_ws domain *)
|
|
(* in ws_stream url *)
|