UNPKG

5.26 kBJavaScriptView Raw
1var qs = require('./querystring.js')
2
3var METHODS = ['put', 'head', 'patch', 'delete', 'post', 'get']
4var CONTENT_TYPE = 'Content-Type'
5var CONTENT_TYPE_FORM = 'application/x-www-form-urlencoded'
6var CONTENT_TYPE_JSON = 'application/json; charset=utf-8'
7
8function combineUrl (base, newurl) {
9 base = norm(base)
10 newurl = norm(newurl)
11
12 if (!newurl || !base) return base + newurl
13
14 if (
15 newurl.startsWith('http://') ||
16 newurl.startsWith('https://') ||
17 newurl.startsWith('//')
18 ) {
19 return newurl
20 }
21
22 if (!base.endsWith('/')) base += '/'
23 if (newurl.startsWith('/')) newurl = newurl.substring(1)
24 return base + newurl
25}
26
27function merge (req, obj) {
28 return Object.assign(req.clone(), obj)
29}
30
31function newRequest () {
32 var r = {
33 parser: '',
34 beforehooks: [],
35 afterhooks: [],
36 baseurl: '',
37 query: {},
38 meta: {},
39 }
40
41 r.clone = function () {
42 return Object.assign({}, this, {
43 query: Object.assign({}, this.query),
44 meta: Object.assign({}, this.meta),
45 })
46 }
47
48 r.addQuery = function (key, val) {
49 var req = this.clone()
50 req.query[key] = val
51 return req
52 }
53
54 r.removeQuery = function (key) {
55 var req = this.clone()
56 if (req.query[key] !== undefined) req.query[key] = undefined
57 return req
58 }
59
60 r.withCredentials = function (credential) {
61 return merge(this, { withCredentials: credential })
62 }
63
64 r.setQuery = function (query) {
65 return merge(this, { query: query })
66 }
67
68 r.clearHooks = function () {
69 return merge(this, { beforehooks: [], afterhooks: [] })
70 }
71
72 r.beforeHook = function (cb) {
73 var beforehooks = this.beforehooks.slice()
74 beforehooks.push(cb)
75 return merge(this, { beforehooks: beforehooks })
76 }
77
78 r.afterHook = function (cb) {
79 var afterhooks = this.afterhooks.slice()
80 afterhooks.push(cb)
81 return merge(this, { afterhooks: afterhooks })
82 }
83
84 r.setHeader = function (headers) {
85 headers = Object.assign({}, this.headers, headers)
86 headers[CONTENT_TYPE] = undefined
87 return merge(this, { headers: headers })
88 }
89
90 METHODS.map(function (method) {
91 r[method] = function (url, data, cb) {
92 return send(
93 merge(this, { method: method, baseurl: combineUrl(this.baseurl, url) }),
94 data,
95 cb
96 )
97 }
98 })
99
100 // pass // to clean
101 r.setBaseUrl = function (url) {
102 return merge(this, { baseurl: url })
103 }
104
105 r.contentTypeJson = function () {
106 return merge(this, { content_type: CONTENT_TYPE_JSON })
107 }
108
109 r.contentTypeForm = function () {
110 return merge(this, { content_type: CONTENT_TYPE_FORM })
111 }
112
113 r.setContentType = function (ty) {
114 return merge(this, { content_type: norm(ty) })
115 }
116
117 r.setParser = function (parser) {
118 return merge(this, { parser: norm(parser) })
119 }
120
121 r.setBody = function (body) {
122 return merge(this, { body: body })
123 }
124
125 r.setMeta = function (k, v) {
126 var req = this.clone()
127 req.meta[k] = v
128 return req
129 }
130
131 return r
132}
133
134function send (req, data, cb) {
135 cb = cb || function () {}
136 if (isFunc(data)) {
137 cb = data
138 data = undefined
139 }
140
141 var rs
142 var promise = new Promise(function (resolve) {
143 rs = function (res) {
144 try {
145 cb(res.error, res.body, res.code)
146 } catch (_) {}
147 resolve(res)
148 }
149 })
150
151 if (data) {
152 req = req.clone()
153 req.body = data
154 if (req.content_type === CONTENT_TYPE_JSON) req.body = env.Jsonify(data)
155 if (req.content_type === CONTENT_TYPE_FORM) {
156 req.body = qs.stringify(data)
157 }
158 }
159
160 waterfall(req.beforehooks.slice(), { request: req }, function (bp) {
161 if (bp.error) return rs({ body: undefined, code: 0, error: bp.error })
162 dosend(bp.request, function (err, body, code) {
163 waterfall(
164 req.afterhooks.slice(),
165 { request: req, code: code, body: body, err: err },
166 function (param) {
167 var body = param.body
168
169 if (req.parser == 'json' && param.body) {
170 try {
171 body = env.ParseJson(param.body)
172 } catch (e) {
173 param.err = param.err || 'invalid json'
174 }
175 }
176 var err = param.err
177 if (code < 200 || code > 299) err = 'not 200'
178 rs({ body: body, code: param.code, error: err })
179 }
180 )
181 })
182 })
183 return promise
184}
185
186var dosend = function (req, cb) {
187 var q = qs.stringify(req.query)
188 if (q) q = '?' + q
189
190 var request = new env.XMLHttpRequest()
191 request.onreadystatechange = function (e) {
192 if (request.readyState !== 4) return
193 if (request.status === 0) {
194 cb('network_error', request.responseText, request.status) // network error
195 } else {
196 cb(undefined, request.responseText, request.status)
197 }
198 }
199
200 request.open(req.method, req.baseurl + q)
201 request.withCredentials = req.withCredentials // for cloudflare to work correctly
202 for (var i in req.headers) request.setRequestHeader(i, req.headers[i])
203 if (req.content_type) {
204 request.setRequestHeader(CONTENT_TYPE, req.content_type)
205 }
206 request.send(req.body)
207}
208
209function norm (str) {
210 return (str || '').trim()
211}
212
213function isFunc (f) {
214 return f && {}.toString.call(f) === '[object Function]'
215}
216
217function waterfall (ps, param, cb) {
218 if (!ps || ps.length === 0) return cb(param)
219
220 var fp = ps.shift()
221 if (!isFunc(fp)) return waterfall(ps, param, cb)
222 fp(param, function (out) {
223 return out === false ? cb(param) : waterfall(ps, param, cb)
224 })
225}
226
227var ajax = newRequest()
228var env = { XMLHttpRequest: {}, Jsonify: JSON.stringify, ParseJson: JSON.parse }
229ajax.env = env
230ajax.waterfall = waterfall
231module.exports = ajax