import { hasOwn } from 'uni-shared' import { publish, requireNativePlugin, base64ToArrayBuffer, arrayBufferToBase64 } from '../../bridge' import { invoke } from 'uni-core/service/bridge' let requestTaskId = 0 const requestTasks = {} const publishStateChange = res => { publish('onRequestTaskStateChange', res) delete requestTasks[requestTaskId] } const cookiesParse = header => { let cookiesStr = header['Set-Cookie'] || header['set-cookie'] let cookiesArr = [] if (!cookiesStr) { return [] } if (cookiesStr[0] === '[' && cookiesStr[cookiesStr.length - 1] === ']') { cookiesStr = cookiesStr.slice(1, -1) } const handleCookiesArr = cookiesStr.split(';') for (let i = 0; i < handleCookiesArr.length; i++) { if (handleCookiesArr[i].indexOf('Expires=') !== -1 || handleCookiesArr[i].indexOf('expires=') !== -1) { cookiesArr.push(handleCookiesArr[i].replace(',', '')) } else { cookiesArr.push(handleCookiesArr[i]) } } cookiesArr = cookiesArr.join(';').split(',') return cookiesArr } export function createRequestTaskById (requestTaskId, { url, data, header, method = 'GET', responseType, sslVerify = true, firstIpv4 = false, tls, timeout = (__uniConfig.networkTimeout && __uniConfig.networkTimeout.request) || 60 * 1000 } = {}) { const stream = requireNativePlugin('stream') const headers = {} let abortTimeout let aborted let hasContentType = false for (const name in header) { if (!hasContentType && name.toLowerCase() === 'content-type') { hasContentType = true headers['Content-Type'] = header[name] // TODO 需要重构 if (method !== 'GET' && header[name].indexOf('application/x-www-form-urlencoded') === 0 && typeof data !== 'string' && !(data instanceof ArrayBuffer)) { const bodyArray = [] for (const key in data) { if (hasOwn(data, key)) { bodyArray.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key])) } } data = bodyArray.join('&') } } else { headers[name] = header[name] } } if (!hasContentType && method === 'POST') { headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8' } if (timeout) { abortTimeout = setTimeout(() => { aborted = true publishStateChange({ requestTaskId, state: 'fail', statusCode: 0, errMsg: 'timeout' }) }, (timeout + 200)) // TODO +200 发消息到原生层有时间开销,以后考虑由原生层回调超时 } const options = { method, url: url.trim(), // weex 官方文档有误,headers 类型实际 object,用 string 类型会无响应 headers, type: responseType === 'arraybuffer' ? 'base64' : 'text', // weex 官方文档未说明实际支持 timeout,单位:ms timeout: timeout || 6e5, // 配置和weex模块内相反 sslVerify: !sslVerify, firstIpv4: firstIpv4, tls } let withArrayBuffer if (method !== 'GET') { if (toString.call(data) === '[object ArrayBuffer]') { withArrayBuffer = true } else { options.body = typeof data === 'string' ? data : JSON.stringify(data) } } const callback = ({ ok, status, data, headers, errorMsg }) => { if (aborted) { return } if (abortTimeout) { clearTimeout(abortTimeout) } const statusCode = status if (statusCode > 0) { publishStateChange({ requestTaskId, state: 'success', data: ok && responseType === 'arraybuffer' ? base64ToArrayBuffer(data) : data, statusCode, header: headers, cookies: cookiesParse(headers) }) } else { let errMsg = 'abort statusCode:' + statusCode if (errorMsg) { errMsg = errMsg + ' ' + errorMsg } publishStateChange({ requestTaskId, state: 'fail', statusCode, errMsg }) } } try { if (withArrayBuffer) { stream.fetchWithArrayBuffer({ '@type': 'binary', base64: arrayBufferToBase64(data) }, options, callback) } else { stream.fetch(options, callback) } requestTasks[requestTaskId] = { abort () { aborted = true if (abortTimeout) { clearTimeout(abortTimeout) } publishStateChange({ requestTaskId, state: 'fail', statusCode: 0, errMsg: 'abort' }) } } } catch (e) { return { requestTaskId, errMsg: 'createRequestTask:fail' } } return { requestTaskId, errMsg: 'createRequestTask:ok' } } export function createRequestTask (args) { return createRequestTaskById(++requestTaskId, args) } export function operateRequestTask ({ requestTaskId, operationType } = {}) { const requestTask = requestTasks[requestTaskId] if (requestTask && operationType === 'abort') { requestTask.abort() return { errMsg: 'operateRequestTask:ok' } } return { errMsg: 'operateRequestTask:fail' } } export function configMTLS ({ certificates }, callbackId) { const stream = requireNativePlugin('stream') stream.configMTLS(certificates, ({ type, code, message }) => { switch (type) { case 'success': invoke(callbackId, { errMsg: 'configMTLS:ok', code }) break case 'fail': invoke(callbackId, { errMsg: 'configMTLS:fail ' + message, code }) break } }) }