package ezjs_fetch

  1. Overview
  2. Docs

Source file ezjs_fetch.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
module Stream = Stream
open Ezjs_min

let optdef f = function
  | Some x -> def (f x)
  | None -> undefined

let to_listf f a = List.map f @@ Array.to_list @@ to_array a

class type ['a] next = object
  method done_ : bool t prop
  method value : 'a optdef prop
end

class type ['a] iterator = object
  method next : 'a next t meth
end

class type ['a] array_from = object
  method from : 'a iterator t -> 'a js_array t meth
end

let array_from (it : 'a iterator t) =
  let arr : 'a array_from t = Unsafe.pure_js_expr "Array" in
  arr##from it

class type headers = object
  method append : js_string t -> js_string t -> unit meth
  method delete : js_string t -> unit meth
  method entries : js_string t js_array t iterator t meth
  method forEach : (js_string t -> js_string t -> unit) callback -> unit meth
  method get : js_string t -> js_string t opt meth
  method has : js_string t -> bool t meth
  method keys : js_string t iterator t meth
  method set : js_string t -> js_string t -> unit meth
  method values : js_string t iterator t meth
end

type 'a promise = 'a Promise.promise t

class type body = object
  method body : Stream.rstream t readonly_prop
  method bodyUsed : bool t readonly_prop
  method arrayBuffer : Typed_array.arrayBuffer t opt promise meth
  method blob : File.blob t opt promise meth
  method formData : Js_of_ocaml.Form.formData t opt promise meth
  method json : Unsafe.any opt promise meth
  method text : js_string t opt promise meth
end

class type request_init = object
  method cache : js_string t optdef prop
  method credentials : js_string t optdef prop
  method headers : headers t optdef prop
  method integrity : js_string t optdef prop
  method method_ : js_string t optdef prop
  method mode : js_string t optdef prop
  method redirect : js_string t optdef prop
  method referrer : js_string t optdef prop
  method body_blob : File.blob t optdef prop
  method body_string : js_string t optdef prop
  method body_buffer : Typed_array.arrayBuffer t optdef prop
  method body_formdata : Js_of_ocaml.Form.formData t optdef prop
  method body_urlparam : headers t optdef prop
end

class type fetch_init = object
  inherit request_init
  method referrerPolicy : js_string t optdef readonly_prop
  method keepalive : bool t optdef readonly_prop
  method signal : Stream.abort_signal t optdef readonly_prop
end

class type request = object
  inherit body
  method cache : js_string t readonly_prop
  method credentials : js_string t readonly_prop
  method destination : js_string t readonly_prop
  method headers : headers t readonly_prop
  method integrity : js_string t readonly_prop
  method method_ : js_string t readonly_prop
  method mode : js_string t readonly_prop
  method redirect : js_string t readonly_prop
  method referrer : js_string t readonly_prop
  method referrerPolicy : js_string t readonly_prop
  method url : js_string t readonly_prop
  method clone : request t meth
end

class type response_js = object
  inherit body
  method headers : headers t readonly_prop
  method ok : bool t readonly_prop
  method redirected : bool t readonly_prop
  method status : int readonly_prop
  method statusText : js_string t readonly_prop
  method trailers : headers t promise readonly_prop
  method type_ : js_string t readonly_prop
  method url : js_string t readonly_prop
  method clone : response_js t meth
  method error : response_js t meth
  method redirect : js_string t -> int optdef -> response_js t meth
end

class type global_scope = object
  method fetch : js_string t -> fetch_init t optdef -> response_js t promise meth
  method fetch_request : request t -> response_js t promise meth
end

let request_js : (js_string t -> request_init t optdef -> request t) constr =
  Unsafe.pure_js_expr "Request"
let header_js : headers t constr =
  Unsafe.pure_js_expr "Headers"

let global_scope : global_scope t = Unsafe.pure_js_expr "self"

let make_headers l =
  let h = new%js header_js in
  List.iter (fun (name, value) -> h##append (string name) (string value)) l;
  h

let get_headers (h : headers t)=
  let a = array_from h##entries in
  let l = to_listf (fun a -> match Array.to_list (to_array a) with
      | [ k; v] -> Some (to_string k, to_string v)
      | _ -> None) a in
  List.rev @@ List.fold_left
    (fun acc x -> match x with None -> acc | Some x -> x :: acc) [] l

type request_body =
  | RBlob of File.blob t
  | RString of string
  | RBuffer of Typed_array.arrayBuffer t
  | RFormData of Js_of_ocaml.Form.formData t
  | RUrlParam of (string * string) list

let request_init ?cache ?credentials ?headers ?integrity ?meth ?mode ?redirect
    ?referrer ?body () =
  match cache, credentials, headers, integrity, meth, mode, redirect, referrer, body with
  | None, None, None, None, None, None, None, None, None -> undefined
  | _ -> let r = Unsafe.obj [||] in
    r##.cache := optdef string cache;
    r##.credentials := optdef string credentials;
    r##.headers := optdef make_headers headers;
    r##.integrity := optdef string integrity;
    r##.method_ := optdef string meth;
    r##.mode := optdef string mode;
    r##.redirect := optdef string redirect;
    r##.referrer := optdef string referrer;
    (match body with
     | Some (RBlob b) -> r##.body_blob := def b
     | Some (RString s) -> r##.body_string := def (string s)
     | Some (RBuffer b) -> r##.body_buffer := def b
     | Some (RFormData f) -> r##.body_formdata := def f
     | Some (RUrlParam p) -> r##.body_urlparam := def (make_headers p)
     | _ -> ());
    def r

let request ?cache ?credentials ?headers ?integrity ?meth ?mode ?redirect
    ?referrer ?body url =
  let options = request_init ?cache ?credentials ?headers ?integrity ?meth
      ?mode ?redirect ?referrer ?body () in
  new%js request_js (string url) options

let fetch_init ?cache ?credentials ?headers ?integrity ?meth ?mode ?redirect
    ?referrer ?body ?referrerPolicy ?keepalive () : fetch_init t optdef =
  match Optdef.to_option (request_init ?cache ?credentials ?headers ?integrity ?meth ?mode ?redirect
                            ?referrer ?body ()), referrerPolicy, keepalive with
  | None, None, None -> undefined
  | r, _, _ ->
    let r = match r with
      | None -> Unsafe.obj [||]
      | Some r -> r in
    r##.referrerPolicy := optdef string referrerPolicy;
    r##.keepalive := optdef bool keepalive;
    def r

let fetch_base ?cache ?credentials ?headers ?integrity ?meth ?mode ?redirect ?referrer ?body
    ?referrerPolicy ?keepalive url =
  let options = fetch_init ?cache ?credentials ?headers ?integrity ?meth ?mode ?redirect
      ?referrer ?body ?referrerPolicy ?keepalive () in
  global_scope##fetch (string url) options

let fetch_request_base r = global_scope##fetch_request r

type 'a response = {
  headers : (string * string) list;
  ok : bool;
  redirected: bool;
  status: int;
  status_text: string;
  typ: string;
  url: string;
  body_used: bool;
  body: 'a;
}

let catch cb p =
  Promise.rthen p @@ function
  | Error e -> cb @@ Error e
  | Ok r -> match Opt.to_option r with
    | None -> cb @@ Error (error_of_string "Cannot parse response body")
    | Some x -> cb @@ Ok x

type 'a body_translate = (('a, error t) result -> unit) -> response_js t -> unit

let to_array_buffer : Typed_array.arrayBuffer t body_translate = fun cb b ->
  catch cb b##arrayBuffer
let to_blob : File.blob t body_translate = fun cb b ->
  catch cb b##blob
let to_form_data : Js_of_ocaml.Form.formData t body_translate = fun cb b ->
  catch cb b##formData
let to_js : 'a t body_translate = fun cb b ->
  catch (function Error e -> cb @@ Error e | Ok x -> cb @@ Ok (Unsafe.coerce x)) b##json
let to_text : string body_translate = fun cb b ->
  catch (function Error e -> cb @@ Error e | Ok x -> cb @@ Ok (to_string x)) b##text

let to_stream fold acc : 'a body_translate = fun cb b ->
  Stream.read ~source:(`stream (b##.body, None)) ~fold acc cb

let to_str_stream fold =
  to_stream (fun acc a -> fold acc (Typed_array.String.of_uint8Array a))

let to_response (tr : 'a body_translate) cb (r :response_js t) =
  tr (function
      | Error e -> cb @@ Error e
      | Ok body -> cb @@ Ok {
          headers = get_headers r##.headers;
          ok = to_bool r##.ok;
          redirected = to_bool r##.redirected;
          status = r##.status;
          status_text = to_string r##.statusText;
          typ = to_string r##.type_;
          url = to_string r##.url;
          body_used = to_bool r##.bodyUsed;
          body
        }) r

let fetch ?cache ?credentials ?headers ?integrity ?meth ?mode ?redirect ?referrer ?body
    ?referrerPolicy ?keepalive url tr cb =
  Promise.rthen
    (fetch_base ?cache ?credentials ?headers ?integrity ?meth ?mode ?redirect
       ?referrer ?body ?referrerPolicy ?keepalive url) @@ function
  | Error e -> cb (Error e)
  | Ok r -> to_response tr cb r

let fetch_request r tr cb =
  Promise.rthen (fetch_request_base r) @@ function
  | Error e -> cb (Error e)
  | Ok r -> to_response tr cb r
OCaml

Innovation. Community. Security.