92 lines
4.5 KiB
JavaScript
92 lines
4.5 KiB
JavaScript
import { Component, NewState } from '@apigo.cc/state'
|
|
|
|
export const HTTP = {
|
|
get: ({ url, ...opt }) => HTTP.request({ url, method: 'GET', ...opt }),
|
|
post: ({ url, data, ...opt }) => HTTP.request({ url, method: 'POST', data, ...opt }),
|
|
put: ({ url, data, ...opt }) => HTTP.request({ url, method: 'PUT', data, ...opt }),
|
|
delete: ({ url, ...opt }) => HTTP.request({ url, method: 'DELETE', ...opt }),
|
|
head: ({ url, ...opt }) => HTTP.request({ url, method: 'HEAD', ...opt }),
|
|
request: async ({ url, method = 'POST', data = undefined, headers = {}, responseType, timeout = 10000 }) => {
|
|
method = method.toUpperCase()
|
|
const options = { method, signal: AbortSignal.timeout?.(timeout) }
|
|
if (data !== undefined && method !== 'GET' && method !== 'HEAD') {
|
|
if (data instanceof HTMLFormElement) data = new FormData(data)
|
|
if (data && typeof data === 'object' && !(data instanceof FormData) && !(data instanceof ArrayBuffer || ArrayBuffer.isView(data)) && Object.values(data).some(v => v instanceof File || v instanceof Blob || v instanceof FileList || (Array.isArray(v) && v.some(i => i instanceof File || i instanceof Blob)))) {
|
|
const fd = new FormData()
|
|
for (const [k, v] of Object.entries(data)) {
|
|
if (v instanceof FileList || Array.isArray(v)) Array.from(v).forEach(item => fd.append(k, item))
|
|
else if (v !== undefined && v !== null) fd.append(k, v)
|
|
}
|
|
data = fd
|
|
}
|
|
if (data instanceof FormData) {
|
|
delete headers['Content-Type']
|
|
} else if (typeof data !== 'string' && !(data instanceof ArrayBuffer || ArrayBuffer.isView(data))) {
|
|
data = JSON.stringify(data)
|
|
if (!headers['Content-Type']) headers['Content-Type'] = 'application/json'
|
|
}
|
|
options.body = data
|
|
}
|
|
if (Object.keys(headers).length) options.headers = headers
|
|
const response = { error: null, ok: null, status: 0, headers: {}, responseType: '', result: null }
|
|
try {
|
|
const resp = await fetch(url, options)
|
|
Object.assign(response, { ok: resp.ok, status: resp.status, headers: Object.fromEntries(resp.headers.entries()) })
|
|
if (!responseType) {
|
|
const contentType = resp.headers.get('Content-Type') || ''
|
|
if (contentType.includes('application/json')) responseType = 'json'
|
|
else if (/image|video|audio|pdf|zip|octet-stream/.test(contentType)) responseType = 'binary'
|
|
else responseType = 'text'
|
|
response.responseType = responseType
|
|
}
|
|
if (response.ok === false) response.error = (response.statusText || 'HTTP ' + response.status + ' error') + ' for ' + url
|
|
if (responseType === 'json') response.result = await resp.json()
|
|
else response.result = (responseType === 'binary') ? await resp.arrayBuffer() : await resp.text()
|
|
} catch (err) {
|
|
Object.assign(response, { error: err.message || String(err), ok: false })
|
|
}
|
|
return response
|
|
}
|
|
}
|
|
|
|
// HTTP 和 API 组件
|
|
export const APIComponent = Component.register('API', container => {
|
|
container.request = NewState({ url: '', method: 'GET', headers: {}, data: null, timeout: 10000, responseType: '' })
|
|
container.response = NewState({ loading: false, ok: null, status: null, error: null, headers: {}, responseType: '', result: null })
|
|
container.result = NewState()
|
|
container.do = (opt = {}) => {
|
|
return new Promise((resolve, reject) => {
|
|
const req = { ...container.request, ...opt }
|
|
if (!req.url) throw new Error('.url is required')
|
|
req.headers = { ...container.request.headers, ...opt.headers }
|
|
container.response.loading = true
|
|
HTTP.request(req).then(resp => {
|
|
Object.keys(resp).forEach(k => { if (k !== 'result') container.response[k] = resp[k] })
|
|
if (resp.result && typeof resp.result === 'object' && container.result && typeof container.result === 'object') {
|
|
Object.assign(container.result, resp.result)
|
|
} else {
|
|
container.result = resp.result
|
|
}
|
|
container.response.loading = false
|
|
if (resp.ok === false) throw new Error(resp.error)
|
|
if (typeof resp.result === 'object' && resp.result.error) throw new Error(resp.result.error)
|
|
container.dispatchEvent(new CustomEvent('response', { detail: resp, bubbles: false }))
|
|
resolve(resp)
|
|
}).catch(err => {
|
|
if (!opt.noui && globalThis.UI?.toast) UI.toast(err.message, { type: 'danger' })
|
|
container.dispatchEvent(new CustomEvent('error', { detail: err, bubbles: true }))
|
|
reject(err)
|
|
})
|
|
})
|
|
}
|
|
let _autoTimer = null
|
|
container.request.__watch(null, () => {
|
|
if (!container.hasAttribute('auto') || !container.request.url) return
|
|
if (_autoTimer) return
|
|
_autoTimer = Promise.resolve().then(() => {
|
|
container.do()
|
|
_autoTimer = null
|
|
})
|
|
})
|
|
})
|