-
Notifications
You must be signed in to change notification settings - Fork 12
/
fetch.cljs
138 lines (118 loc) · 5.32 KB
/
fetch.cljs
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
(ns lambdaisland.fetch
(:refer-clojure :exclude [get])
(:require [applied-science.js-interop :as j]
[clojure.core :as c]
[clojure.set :as set]
[clojure.string :as str]
[cognitect.transit :as transit]
[kitchen-async.promise :as p]
[lambdaisland.uri :as uri]
[lambdaisland.uri.normalize :as uri-normalize]))
;; fetch(url, {
;; method: 'POST', // *GET, POST, PUT, DELETE, etc.
;; mode: 'cors', // no-cors, *cors, same-origin
;; cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
;; credentials: 'same-origin', // include, *same-origin, omit
;; headers: {
;; 'Content-Type': 'application/json'
;; // 'Content-Type': 'application/x-www-form-urlencoded',
;; },
;; redirect: 'follow', // manual, *follow, error
;; referrerPolicy: 'no-referrer', // no-referrer, *client
;; body: JSON.stringify(data) // body data type must match "Content-Type" header
;; });
(def content-types
{:transit-json "application/transit+json"
:json "application/json"
:form-encoded "application/x-www-form-urlencoded"
:text "text/plain"
:html "text/html"
:edn "application/edn"})
(def transit-json-writer
(delay (transit/writer :json)))
(def transit-json-reader
(delay (transit/reader :json)))
(defmulti encode-body (fn [content-type body opts] content-type))
(defmethod encode-body :default [_ body opts]
body)
(defmethod encode-body :transit-json [_ body opts]
(transit/write (:transit-json-writer opts @transit-json-writer) body))
(defmethod encode-body :form-encoded [_ body opts]
(uri/map->query-string body))
(defmethod encode-body :json [_ body opts]
(js/JSON.stringify (clj->js body)))
(defmulti decode-body (fn [content-type bodyp opts] content-type))
(defmethod decode-body :default [_ response opts]
(j/call response :text))
(defmethod decode-body :transit-json [_ response opts]
(p/let [text (j/call response :text)]
(let [decoded (transit/read (:transit-json-reader opts @transit-json-reader) text)]
(if (satisfies? IWithMeta decoded)
(vary-meta decoded assoc ::raw text)
decoded))))
(defmethod decode-body :json [_ response opts]
(j/call response :json))
(defn fetch-opts [{:keys [method accept content-type
headers redirect mode cache signal
credentials referrer-policy]
:or {method :get
accept :transit-json
content-type :transit-json
redirect :follow
mode :cors
cache :default
credentials :same-origin
referrer-policy :client}}]
(let [fetch-headers #js {"Accept" (c/get content-types accept)
"Content-Type" (c/get content-types content-type)}]
(doseq [[k v] headers]
(j/assoc! fetch-headers k v))
#js {:method (str/upper-case (name method))
:headers fetch-headers
:redirect (name redirect)
:mode (name mode)
:cache (name cache)
:signal signal
:credentials (name credentials)
:referrer-policy (name referrer-policy)}))
(defn request [url & [{:keys [method accept content-type query-params body as]
:as opts
:or {accept :transit-json
content-type :transit-json}}]]
(let [url (-> (uri/uri url)
(uri/assoc-query* query-params)
str)
request (cond-> (fetch-opts opts)
body
(j/assoc! :body (if (string? body)
body
(encode-body content-type body opts))))]
(p/let [response (js/fetch url request)]
(p/try
(let [headers (j/get response :headers)
header-map (into {} (map vec) (es6-iterator-seq (j/call headers :entries)))
content-type-header (j/call headers :get "Content-Type")
content-type (if as
as
(when content-type-header
(c/get (set/map-invert content-types)
(str/replace content-type-header #";.*" ""))))]
(p/let [body (decode-body content-type response opts)]
^{::request (j/assoc! request :url url)
::response response}
{:status (j/get response :status)
:headers header-map
:body body}))
(p/catch :default e
^{::request (j/assoc! request :url url)
::response response}
{:error e})))))
(def get request)
(defn post [url & [opts]]
(request url (assoc opts :method :post)))
(defn put [url & [opts]]
(request url (assoc opts :method :put)))
(defn delete [url & [opts]]
(request url (assoc opts :method :delete)))
(defn head [url & [opts]]
(request url (assoc opts :method :head)))