@tarojs/plugin-http 3.6.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,398 @@
1
+ import { Events, parseUrl, window } from '@tarojs/runtime'
2
+ import { isFunction, isString } from '@tarojs/shared'
3
+ import { request } from '@tarojs/taro'
4
+
5
+ declare const ENABLE_COOKIE: boolean
6
+
7
+ const SUPPORT_METHOD = ['OPTIONS', 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'TRACE', 'CONNECT']
8
+ const STATUS_TEXT_MAP = {
9
+ 100: 'Continue',
10
+ 101: 'Switching protocols',
11
+
12
+ 200: 'OK',
13
+ 201: 'Created',
14
+ 202: 'Accepted',
15
+ 203: 'Non-Authoritative Information',
16
+ 204: 'No Content',
17
+ 205: 'Reset Content',
18
+ 206: 'Partial Content',
19
+
20
+ 300: 'Multiple Choices',
21
+ 301: 'Moved Permanently',
22
+ 302: 'Found',
23
+ 303: 'See Other',
24
+ 304: 'Not Modified',
25
+ 305: 'Use Proxy',
26
+ 307: 'Temporary Redirect',
27
+
28
+ 400: 'Bad Request',
29
+ 401: 'Unauthorized',
30
+ 402: 'Payment Required',
31
+ 403: 'Forbidden',
32
+ 404: 'Not Found',
33
+ 405: 'Method Not Allowed',
34
+ 406: 'Not Acceptable',
35
+ 407: 'Proxy Authentication Required',
36
+ 408: 'Request Timeout',
37
+ 409: 'Conflict',
38
+ 410: 'Gone',
39
+ 411: 'Length Required',
40
+ 412: 'Precondition Failed',
41
+ 413: 'Request Entity Too Large',
42
+ 414: 'Request-URI Too Long',
43
+ 415: 'Unsupported Media Type',
44
+ 416: 'Requested Range Not Suitable',
45
+ 417: 'Expectation Failed',
46
+
47
+ 500: 'Internal Server Error',
48
+ 501: 'Not Implemented',
49
+ 502: 'Bad Gateway',
50
+ 503: 'Service Unavailable',
51
+ 504: 'Gateway Timeout',
52
+ 505: 'HTTP Version Not Supported',
53
+ }
54
+ // https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest
55
+ export class XMLHttpRequest extends Events {
56
+ static readonly UNSENT = 0
57
+ static readonly OPENED = 1
58
+ static readonly HEADERS_RECEIVED = 2
59
+ static readonly LOADING = 3
60
+ static readonly DONE = 4
61
+
62
+ // 欺骗一些库让其认为是原生的xhr
63
+ static toString () {
64
+ return 'function XMLHttpRequest() { [native code] }'
65
+ }
66
+
67
+ toString () {
68
+ return '[object XMLHttpRequest]'
69
+ }
70
+
71
+ #method: string
72
+ #url: string
73
+ #data: null
74
+ #status: number
75
+ #statusText: string
76
+ #readyState: number
77
+ #header: Record<string, any>
78
+ #responseType: string
79
+ #resHeader: null | Record<string, any>
80
+ #response: null
81
+ #timeout: number
82
+ #withCredentials: boolean
83
+ #requestTask: null | Taro.RequestTask<any>
84
+
85
+ // 事件
86
+
87
+ /** 当 request 被停止时触发,例如当程序调用 XMLHttpRequest.abort() 时 */
88
+ onabort: (() => void) | null = null
89
+
90
+ /** 当 request 遭遇错误时触发 */
91
+ onerror: ((err: any) => void) | null = null
92
+
93
+ /** 接收到响应数据时触发 */
94
+ onloadstart: (() => void) | null = null
95
+
96
+ /** 请求成功完成时触发 */
97
+ onload: (() => void) | null = null
98
+
99
+ /** 当请求结束时触发,无论请求成功 ( load) 还是失败 (abort 或 error)。 */
100
+ onloadend: (() => void) | null = null
101
+
102
+ /** 在预设时间内没有接收到响应时触发 */
103
+ ontimeout: (() => void) | null = null
104
+
105
+ /** 当 readyState 属性发生变化时,调用的事件处理器 */
106
+ onreadystatechange: (() => void) | null = null
107
+
108
+ constructor () {
109
+ super()
110
+
111
+ this.#method = ''
112
+ this.#url = ''
113
+ this.#data = null
114
+ this.#status = 0
115
+ this.#statusText = ''
116
+ this.#readyState = XMLHttpRequest.UNSENT
117
+ this.#header = {
118
+ Accept: '*/*',
119
+ }
120
+ this.#responseType = ''
121
+ this.#resHeader = null
122
+ this.#response = null
123
+ this.#timeout = 0
124
+ /** 向前兼容,默认为 true */
125
+ this.#withCredentials = true
126
+
127
+ this.#requestTask = null
128
+ }
129
+
130
+ addEventListener (event: string, callback: (arg: any) => void) {
131
+ if (!isString(event)) return
132
+ this.on(event, callback, null)
133
+ }
134
+
135
+ removeEventListener (event: string, callback: (arg: any) => void) {
136
+ if (!isString(event)) return
137
+ this.off(event, callback, null)
138
+ }
139
+
140
+ /**
141
+ * readyState 变化
142
+ */
143
+ #callReadyStateChange (readyState) {
144
+ const hasChange = readyState !== this.#readyState
145
+ this.#readyState = readyState
146
+
147
+ if (hasChange) {
148
+ this.trigger('readystatechange')
149
+ isFunction(this.onreadystatechange) && this.onreadystatechange()
150
+ }
151
+ }
152
+
153
+ /**
154
+ * 执行请求
155
+ */
156
+ #callRequest () {
157
+ if (!window || !window.document) {
158
+ console.warn('this page has been unloaded, so this request will be canceled.')
159
+ return
160
+ }
161
+
162
+ if (this.#timeout) {
163
+ setTimeout(() => {
164
+ if (!this.#status && this.#readyState !== XMLHttpRequest.DONE) {
165
+ // 超时
166
+ if (this.#requestTask) this.#requestTask.abort()
167
+ this.#callReadyStateChange(XMLHttpRequest.DONE)
168
+ this.trigger('timeout')
169
+ isFunction(this.ontimeout) && this.ontimeout()
170
+ }
171
+ }, this.#timeout)
172
+ }
173
+
174
+ // 重置各种状态
175
+ this.#status = 0
176
+ this.#statusText = ''
177
+ this.#readyState = XMLHttpRequest.OPENED
178
+ this.#resHeader = null
179
+ this.#response = null
180
+
181
+ // 补完 url
182
+ let url = this.#url
183
+ url = url.indexOf('//') === -1 ? window.location.origin + url : url
184
+
185
+ // 头信息
186
+ const header = Object.assign({}, this.#header)
187
+ header.cookie = window.document.cookie
188
+ if (!this.withCredentials) {
189
+ // 不同源,要求 withCredentials 为 true 才携带 cookie
190
+ const { origin } = parseUrl(url)
191
+ if (origin !== window.location.origin) delete header.cookie
192
+ }
193
+ this.#requestTask = request({
194
+ url,
195
+ data: this.#data || {},
196
+ header,
197
+ // @ts-ignore
198
+ method: this.#method,
199
+ dataType: this.#responseType === 'json' ? 'json' : 'text',
200
+ responseType: this.#responseType === 'arraybuffer' ? 'arraybuffer' : 'text',
201
+ success: this.#requestSuccess.bind(this),
202
+ fail: this.#requestFail.bind(this),
203
+ complete: this.#requestComplete.bind(this),
204
+ })
205
+ }
206
+
207
+ /**
208
+ * 请求成功
209
+ */
210
+ #requestSuccess ({ data, statusCode, header }) {
211
+ if (!window || !window.document) {
212
+ console.warn('this page has been unloaded, so this request will be canceled.')
213
+ return
214
+ }
215
+
216
+ this.#status = statusCode
217
+ this.#resHeader = header
218
+
219
+ this.#callReadyStateChange(XMLHttpRequest.HEADERS_RECEIVED)
220
+
221
+ if (ENABLE_COOKIE) {
222
+ // 处理 set-cookie
223
+ const setCookieStr = header['Set-Cookie']
224
+
225
+ if (setCookieStr && typeof setCookieStr === 'string') {
226
+ let start = 0
227
+ let startSplit = 0
228
+ let nextSplit = setCookieStr.indexOf(',', startSplit)
229
+ const cookies: string[] = []
230
+
231
+ while (nextSplit >= 0) {
232
+ const lastSplitStr = setCookieStr.substring(start, nextSplit)
233
+ const splitStr = setCookieStr.substr(nextSplit)
234
+
235
+ // eslint-disable-next-line no-control-regex
236
+ if (/^,\s*([^,=;\x00-\x1F]+)=([^;\n\r\0\x00-\x1F]*).*/.test(splitStr)) {
237
+ // 分割成功,则上一片是完整 cookie
238
+ cookies.push(lastSplitStr)
239
+ start = nextSplit + 1
240
+ }
241
+
242
+ startSplit = nextSplit + 1
243
+ nextSplit = setCookieStr.indexOf(',', startSplit)
244
+ }
245
+
246
+ // 塞入最后一片 cookie
247
+ cookies.push(setCookieStr.substr(start))
248
+
249
+ cookies.forEach((cookie) => {
250
+ window.document.cookie = cookie
251
+ })
252
+ }
253
+ }
254
+
255
+ // 处理返回数据
256
+ if (data) {
257
+ this.#callReadyStateChange(XMLHttpRequest.LOADING)
258
+ this.trigger('loadstart')
259
+ isFunction(this.onloadstart) && this.onloadstart()
260
+ this.#response = data
261
+ this.trigger('load')
262
+ isFunction(this.onload) && this.onload()
263
+ }
264
+ }
265
+
266
+ /**
267
+ * 请求失败
268
+ */
269
+ #requestFail (err) {
270
+ this.#status = 0
271
+ this.#statusText = err.errMsg
272
+ this.trigger('error')
273
+ isFunction(this.onerror) && this.onerror(err)
274
+ }
275
+
276
+ /**
277
+ * 请求完成
278
+ */
279
+ #requestComplete () {
280
+ this.#requestTask = null
281
+ this.#callReadyStateChange(XMLHttpRequest.DONE)
282
+
283
+ if (this.#status) {
284
+ this.trigger('loadend')
285
+ isFunction(this.onloadend) && this.onloadend()
286
+ }
287
+ }
288
+
289
+ /**
290
+ * 对外属性和方法
291
+ */
292
+ get timeout () {
293
+ return this.#timeout
294
+ }
295
+
296
+ set timeout (timeout) {
297
+ if (typeof timeout !== 'number' || !isFinite(timeout) || timeout <= 0) return
298
+
299
+ this.#timeout = timeout
300
+ }
301
+
302
+ get status () {
303
+ return this.#status
304
+ }
305
+
306
+ get statusText () {
307
+ if (this.#readyState === XMLHttpRequest.UNSENT || this.#readyState === XMLHttpRequest.OPENED) return ''
308
+
309
+ return STATUS_TEXT_MAP[this.#status + ''] || this.#statusText || ''
310
+ }
311
+
312
+ get readyState () {
313
+ return this.#readyState
314
+ }
315
+
316
+ get responseType () {
317
+ return this.#responseType
318
+ }
319
+
320
+ set responseType (value) {
321
+ if (typeof value !== 'string') return
322
+
323
+ this.#responseType = value
324
+ }
325
+
326
+ get responseText () {
327
+ if (!this.#responseType || this.#responseType === 'text') {
328
+ return this.#response
329
+ }
330
+
331
+ return null
332
+ }
333
+
334
+ get response () {
335
+ return this.#response
336
+ }
337
+
338
+ get withCredentials () {
339
+ return this.#withCredentials
340
+ }
341
+
342
+ set withCredentials (value) {
343
+ this.#withCredentials = !!value
344
+ }
345
+
346
+ abort () {
347
+ if (this.#requestTask) {
348
+ this.#requestTask.abort()
349
+ this.trigger('abort')
350
+ isFunction(this.onabort) && this.onabort()
351
+ }
352
+ }
353
+
354
+ getAllResponseHeaders () {
355
+ if (this.#readyState === XMLHttpRequest.UNSENT || this.#readyState === XMLHttpRequest.OPENED || !this.#resHeader)
356
+ return ''
357
+
358
+ return Object.keys(this.#resHeader)
359
+ .map((key) => `${key}: ${this.#resHeader![key]}`)
360
+ .join('\r\n')
361
+ }
362
+
363
+ getResponseHeader (name) {
364
+ if (this.#readyState === XMLHttpRequest.UNSENT || this.#readyState === XMLHttpRequest.OPENED || !this.#resHeader)
365
+ return null
366
+
367
+ // 处理大小写不敏感
368
+ const key = Object.keys(this.#resHeader).find((item) => item.toLowerCase() === name.toLowerCase())
369
+ const value = key ? this.#resHeader[key] : null
370
+
371
+ return typeof value === 'string' ? value : null
372
+ }
373
+
374
+ open (method, url) {
375
+ if (typeof method === 'string') method = method.toUpperCase()
376
+
377
+ if (SUPPORT_METHOD.indexOf(method) < 0) return
378
+ if (!url || typeof url !== 'string') return
379
+
380
+ this.#method = method
381
+ this.#url = url
382
+
383
+ this.#callReadyStateChange(XMLHttpRequest.OPENED)
384
+ }
385
+
386
+ setRequestHeader (header, value) {
387
+ if (typeof header === 'string' && typeof value === 'string') {
388
+ this.#header[header] = value
389
+ }
390
+ }
391
+
392
+ send (data) {
393
+ if (this.#readyState !== XMLHttpRequest.OPENED) return
394
+
395
+ this.#data = data
396
+ this.#callRequest()
397
+ }
398
+ }
@@ -0,0 +1,35 @@
1
+ import { document, window } from '@tarojs/runtime'
2
+
3
+ import { Cookie, createCookieInstance } from './Cookie'
4
+ import { XMLHttpRequest } from './XMLHttpRequest'
5
+
6
+ declare const ENABLE_COOKIE: boolean
7
+
8
+ if (process.env.TARO_ENV !== 'h5') {
9
+
10
+ if (ENABLE_COOKIE) {
11
+
12
+ const _cookie = createCookieInstance()
13
+
14
+ Object.defineProperties(document, {
15
+ URL: {
16
+ get () {
17
+ if (this.defaultView) return this.defaultView.location.href
18
+ return ''
19
+ },
20
+ },
21
+ cookie: {
22
+ get () {
23
+ return _cookie.getCookie(this.URL)
24
+ },
25
+ set (value: string) {
26
+ if (!value || typeof value !== 'string') return
27
+ _cookie.setCookie(value, this.URL)
28
+ },
29
+ },
30
+ })
31
+ }
32
+
33
+ window.XMLHttpRequest = XMLHttpRequest
34
+ }
35
+ export { Cookie, document, XMLHttpRequest }