-
-
Notifications
You must be signed in to change notification settings - Fork 94
/
core.cljs
149 lines (137 loc) · 5.47 KB
/
core.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
139
140
141
142
143
144
145
146
147
148
149
(ns cljs-http.core
(:import [goog.net EventType XhrIo]
[goog.net Jsonp])
(:require-macros [cljs.core.async.macros :refer [go]])
(:require [cljs-http.util :as util]
[cljs.core.async :as async]))
(def pending-requests (atom {}))
(defn abort!
"Attempt to close the given channel and abort the pending HTTP request
with which it is associated."
[channel]
(when-let [req (@pending-requests channel)]
(swap! pending-requests dissoc channel)
(async/close! channel)
(if (.hasOwnProperty req "abort")
(.abort req)
(.cancel (:jsonp req) (:request req)))))
(defn- aborted? [xhr]
(= (.getLastErrorCode xhr) goog.net.ErrorCode.ABORT))
(defn apply-default-headers!
"Takes an XhrIo object and applies the default-headers to it."
[xhr headers]
(let [formatted-h (zipmap (map util/camelize (keys headers)) (vals headers))]
(dorun
(map (fn [[k v]]
(.set (.-headers xhr) k v))
formatted-h))))
(defn apply-response-type!
"Takes an XhrIo object and sets response-type if not nil."
[xhr response-type]
(.setResponseType xhr
(case response-type
:array-buffer XhrIo.ResponseType.ARRAY_BUFFER
:blob XhrIo.ResponseType.BLOB
:document XhrIo.ResponseType.DOCUMENT
:text XhrIo.ResponseType.TEXT
:default XhrIo.ResponseType.DEFAULT
nil XhrIo.ResponseType.DEFAULT)))
(defn build-xhr
"Builds an XhrIo object from the request parameters."
[{:keys [with-credentials? default-headers response-type] :as request}]
(let [timeout (or (:timeout request) 0)
send-credentials (if (nil? with-credentials?)
true
with-credentials?)]
(doto (XhrIo.)
(apply-default-headers! default-headers)
(apply-response-type! response-type)
(.setTimeoutInterval timeout)
(.setWithCredentials send-credentials))))
;; goog.net.ErrorCode constants to CLJS keywords
(def error-kw
{0 :no-error
1 :access-denied
2 :file-not-found
3 :ff-silent-error
4 :custom-error
5 :exception
6 :http-error
7 :abort
8 :timeout
9 :offline})
(defn xhr
"Execute the HTTP request corresponding to the given Ring request
map and return a core.async channel."
[{:keys [request-method headers body cancel progress] :as request}]
(let [channel (async/chan)
request-url (util/build-url request)
method (name (or request-method :get))
headers (util/build-headers headers)
xhr (build-xhr request)]
(swap! pending-requests assoc channel xhr)
(.listen xhr EventType.COMPLETE
(fn [evt]
(let [target (.-target evt)
response {:status (.getStatus target)
:success (.isSuccess target)
:body (.getResponse target)
:headers (util/parse-headers (.getAllResponseHeaders target))
:trace-redirects [request-url (.getLastUri target)]
:error-code (error-kw (.getLastErrorCode target))
:error-text (.getLastError target)}]
(if-not (aborted? xhr)
(async/put! channel response))
(swap! pending-requests dissoc channel)
(when cancel (async/close! cancel))
(async/close! channel))))
(when progress
(let [listener (fn [direction evt]
(async/put! progress (merge {:direction direction :loaded (.-loaded evt)}
(when (.-lengthComputable evt) {:total (.-total evt)}))))]
(doto xhr
(.setProgressEventsEnabled true)
(.listen EventType.UPLOAD_PROGRESS (partial listener :upload))
(.listen EventType.DOWNLOAD_PROGRESS (partial listener :download)))))
(.send xhr request-url method body headers)
(when cancel
(go
(async/<! cancel)
(when (not (.isComplete xhr))
(.abort xhr))))
channel))
(defn jsonp
"Execute the JSONP request corresponding to the given Ring request
map and return a core.async channel."
[{:keys [timeout callback-name cancel keywordize-keys?]
:or {keywordize-keys? true}
:as request}]
(let [channel (async/chan)
jsonp (Jsonp. (util/build-url request) callback-name)]
(.setRequestTimeout jsonp timeout)
(let [req (.send jsonp nil
(fn success-callback [data]
(let [response {:status 200
:success true
:body (js->clj data :keywordize-keys keywordize-keys?)}]
(async/put! channel response)
(swap! pending-requests dissoc channel)
(when cancel (async/close! cancel))
(async/close! channel)))
(fn error-callback []
(swap! pending-requests dissoc channel)
(when cancel (async/close! cancel))
(async/close! channel)))]
(swap! pending-requests assoc channel {:jsonp jsonp :request req})
(when cancel
(go
(async/<! cancel)
(.cancel jsonp req))))
channel))
(defn request
"Execute the HTTP request corresponding to the given Ring request
map and return a core.async channel."
[{:keys [request-method] :as request}]
(if (= request-method :jsonp)
(jsonp request)
(xhr request)))