this repo has no description
at main 5.6 kB view raw
1(*--------------------------------------------------------------------------- 2 Copyright (c) 2025 Anil Madhavapeddy. All rights reserved. 3 SPDX-License-Identifier: ISC 4 ---------------------------------------------------------------------------*) 5 6open Brr 7open Fut.Syntax 8 9type connection = { 10 session : Jmap.Proto.Session.t; 11 api_url : Jstr.t; 12 token : Jstr.t; 13} 14 15let session conn = conn.session 16let api_url conn = conn.api_url 17 18(* JSON logging callbacks *) 19let on_request : (string -> string -> unit) option ref = ref None 20let on_response : (string -> string -> unit) option ref = ref None 21 22let set_request_logger f = on_request := Some f 23let set_response_logger f = on_response := Some f 24 25let log_request label json = 26 match !on_request with 27 | Some f -> f label json 28 | None -> () 29 30let log_response label json = 31 match !on_response with 32 | Some f -> f label json 33 | None -> () 34 35(* JSON encoding/decoding using jsont.brr *) 36 37let encode_request req = 38 Jsont_brr.encode Jmap.Proto.Request.jsont req 39 40let encode_response resp = 41 Jsont_brr.encode Jmap.Proto.Response.jsont resp 42 43let encode_session session = 44 Jsont_brr.encode Jmap.Proto.Session.jsont session 45 46let decode_json s = 47 match Brr.Json.decode s with 48 | Ok jv -> Ok (Obj.magic jv : Jsont.json) (* Jv.t and Jsont.json are compatible *) 49 | Error e -> Error e 50 51let encode_json json = 52 Ok (Brr.Json.encode (Obj.magic json : Jv.t)) 53 54let pp_json ppf json = 55 match encode_json json with 56 | Ok s -> Format.pp_print_string ppf (Jstr.to_string s) 57 | Error _ -> Format.pp_print_string ppf "<json encoding error>" 58 59(* HTTP helpers *) 60 61let make_headers token = 62 Brr_io.Fetch.Headers.of_assoc [ 63 Jstr.v "Authorization", Jstr.(v "Bearer " + token); 64 Jstr.v "Content-Type", Jstr.v "application/json"; 65 Jstr.v "Accept", Jstr.v "application/json"; 66 ] 67 68let fetch_json ~url ~meth ~headers ?body () = 69 Console.(log [str ">>> Request:"; str (Jstr.to_string meth); str (Jstr.to_string url)]); 70 (match body with 71 | Some b -> Console.(log [str ">>> Body:"; b]) 72 | None -> Console.(log [str ">>> No body"])); 73 let init = Brr_io.Fetch.Request.init 74 ~method':meth 75 ~headers 76 ?body 77 () 78 in 79 let req = Brr_io.Fetch.Request.v ~init url in 80 let* response = Brr_io.Fetch.request req in 81 match response with 82 | Error e -> 83 Console.(error [str "<<< Fetch error:"; e]); 84 Fut.return (Error e) 85 | Ok resp -> 86 let status = Brr_io.Fetch.Response.status resp in 87 Console.(log [str "<<< Response status:"; str (Jstr.of_int status)]); 88 if not (Brr_io.Fetch.Response.ok resp) then begin 89 let msg = Jstr.(v "HTTP error: " + of_int status) in 90 (* Try to get response body for error details *) 91 let body = Brr_io.Fetch.Response.as_body resp in 92 let* text = Brr_io.Fetch.Body.text body in 93 (match text with 94 | Ok t -> Console.(error [str "<<< Error body:"; str (Jstr.to_string t)]) 95 | Error _ -> ()); 96 Fut.return (Error (Jv.Error.v msg)) 97 end else begin 98 let body = Brr_io.Fetch.Response.as_body resp in 99 let* text = Brr_io.Fetch.Body.text body in 100 match text with 101 | Error e -> 102 Console.(error [str "<<< Body read error:"; e]); 103 Fut.return (Error e) 104 | Ok text -> 105 Console.(log [str "<<< Response body:"; str (Jstr.to_string text)]); 106 Fut.return (Ok text) 107 end 108 109(* Session establishment *) 110 111let get_session ~url ~token = 112 Console.(log [str "get_session: token length ="; str (Jstr.of_int (Jstr.length token))]); 113 log_request "GET Session" (Printf.sprintf "{\"url\": \"%s\"}" (Jstr.to_string url)); 114 let headers = make_headers token in 115 let* result = fetch_json ~url ~meth:(Jstr.v "GET") ~headers () in 116 match result with 117 | Error e -> Fut.return (Error e) 118 | Ok text -> 119 log_response "Session" (Jstr.to_string text); 120 match Jsont_brr.decode Jmap.Proto.Session.jsont text with 121 | Error e -> Fut.return (Error e) 122 | Ok session -> 123 let api_url = Jstr.v (Jmap.Proto.Session.api_url session) in 124 Fut.return (Ok { session; api_url; token }) 125 126(* Making requests *) 127 128let request conn req = 129 let headers = make_headers conn.token in 130 match Jsont_brr.encode Jmap.Proto.Request.jsont req with 131 | Error e -> Fut.return (Error e) 132 | Ok body_str -> 133 log_request "JMAP Request" (Jstr.to_string body_str); 134 let body = Brr_io.Fetch.Body.of_jstr body_str in 135 let* result = fetch_json 136 ~url:conn.api_url 137 ~meth:(Jstr.v "POST") 138 ~headers 139 ~body 140 () 141 in 142 match result with 143 | Error e -> Fut.return (Error e) 144 | Ok text -> 145 log_response "JMAP Response" (Jstr.to_string text); 146 match Jsont_brr.decode Jmap.Proto.Response.jsont text with 147 | Error e -> Fut.return (Error e) 148 | Ok response -> Fut.return (Ok response) 149 150let request_json conn json = 151 let headers = make_headers conn.token in 152 match encode_json json with 153 | Error e -> Fut.return (Error e) 154 | Ok body_str -> 155 let body = Brr_io.Fetch.Body.of_jstr body_str in 156 let* result = fetch_json 157 ~url:conn.api_url 158 ~meth:(Jstr.v "POST") 159 ~headers 160 ~body 161 () 162 in 163 match result with 164 | Error e -> Fut.return (Error e) 165 | Ok text -> 166 match decode_json text with 167 | Error e -> Fut.return (Error e) 168 | Ok json -> Fut.return (Ok json) 169 170(* Toplevel support *) 171 172let install_printers () = 173 (* In browser context, printers are registered via the OCaml console *) 174 Console.(log [str "JMAP printers installed"])