@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.
- package/LICENSE +21 -0
- package/README.MD +59 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +45 -0
- package/dist/runtime.d.ts +94 -0
- package/dist/runtime.js +654 -0
- package/index.js +3 -0
- package/package.json +42 -0
- package/src/__tests__/cookie.spec.js +89 -0
- package/src/__tests__/dom.spec.js +20 -0
- package/src/__tests__/setup.js +1 -0
- package/src/__tests__/utils.js +8 -0
- package/src/index.ts +52 -0
- package/src/runtime/Cookie.ts +312 -0
- package/src/runtime/XMLHttpRequest.ts +398 -0
- package/src/runtime/index.ts +35 -0
|
@@ -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 }
|