117 lines
3.9 KiB
JavaScript
117 lines
3.9 KiB
JavaScript
|
/*
|
||
|
* XFetch.js modified
|
||
|
* A extremely simple fetch extension inspired by sindresorhus/ky.
|
||
|
*/
|
||
|
const xf = (() => {
|
||
|
const METHODS = ['get', 'post', 'put', 'patch', 'delete', 'head']
|
||
|
class HTTPError extends Error {
|
||
|
constructor(res) {
|
||
|
super(res.statusText)
|
||
|
this.name = 'HTTPError'
|
||
|
this.response = res
|
||
|
}
|
||
|
}
|
||
|
class XResponsePromise extends Promise {}
|
||
|
for (const alias of ['arrayBuffer', 'blob', 'formData', 'json', 'text']) {
|
||
|
// alias for .json() .text() etc...
|
||
|
XResponsePromise.prototype[alias] = function(fn) {
|
||
|
return this.then(res => res[alias]()).then(fn || (x => x))
|
||
|
}
|
||
|
}
|
||
|
const { assign } = Object
|
||
|
function mergeDeep(target, source) {
|
||
|
const isObject = obj => obj && typeof obj === 'object'
|
||
|
|
||
|
if (!isObject(target) || !isObject(source)) {
|
||
|
return source
|
||
|
}
|
||
|
|
||
|
Object.keys(source).forEach(key => {
|
||
|
const targetValue = target[key]
|
||
|
const sourceValue = source[key]
|
||
|
|
||
|
if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
|
||
|
target[key] = targetValue.concat(sourceValue)
|
||
|
} else if (isObject(targetValue) && isObject(sourceValue)) {
|
||
|
target[key] = mergeDeep(Object.assign({}, targetValue), sourceValue)
|
||
|
} else {
|
||
|
target[key] = sourceValue
|
||
|
}
|
||
|
})
|
||
|
|
||
|
return target
|
||
|
}
|
||
|
const fromEntries = ent => ent.reduce((acc, [k, v]) => ((acc[k] = v), acc), {})
|
||
|
const typeis = (...types) => val =>
|
||
|
types.some(type => (typeof type === 'string' ? typeof val === type : val instanceof type))
|
||
|
const isstr = typeis('string')
|
||
|
const isobj = typeis('object')
|
||
|
const isstrorobj = v => isstr(v) || isobj(v)
|
||
|
const responseErrorThrower = res => {
|
||
|
if (!res.ok) throw new HTTPError(res)
|
||
|
return res
|
||
|
}
|
||
|
const extend = (defaultInit = {}) => {
|
||
|
const xfetch = (input, init = {}) => {
|
||
|
mergeDeep(init, defaultInit)
|
||
|
const createQueryString = o => new init.URLSearchParams(o).toString()
|
||
|
const parseQueryString = s => fromEntries([...new init.URLSearchParams(s).entries()])
|
||
|
const url = new init.URL(input, init.baseURI || undefined)
|
||
|
if (!init.headers) {
|
||
|
init.headers = {}
|
||
|
} else if (typeis(init.Headers)(init.headers)) {
|
||
|
// Transform into object if it is `Headers`
|
||
|
init.headers = fromEntries([...init.headers.entries()])
|
||
|
}
|
||
|
// Add json or form on body
|
||
|
if (init.json) {
|
||
|
init.body = JSON.stringify(init.json)
|
||
|
init.headers['Content-Type'] = 'application/json'
|
||
|
} else if (isstrorobj(init.urlencoded)) {
|
||
|
init.body = isstr(init.urlencoded) ? init.urlencoded : createQueryString(init.urlencoded)
|
||
|
init.headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
||
|
} else if (typeis(init.FormData, 'object')(init.formData)) {
|
||
|
// init.formData is data passed by user, init.FormData is FormData constructor
|
||
|
if (!typeis(init.FormData)(init.formData)) {
|
||
|
const fd = new init.FormData()
|
||
|
for (const [k, v] of Object.entries(init.formData)) {
|
||
|
fd.append(k, v)
|
||
|
}
|
||
|
init.formData = fd
|
||
|
}
|
||
|
init.body = init.formData
|
||
|
}
|
||
|
// Querystring
|
||
|
if (init.qs) {
|
||
|
if (isstr(init.qs)) init.qs = parseQueryString(init.qs)
|
||
|
url.search = createQueryString(assign(fromEntries([...url.searchParams.entries()]), init.qs))
|
||
|
}
|
||
|
return XResponsePromise.resolve(init.fetch(url, init).then(responseErrorThrower))
|
||
|
}
|
||
|
for (const method of METHODS) {
|
||
|
xfetch[method] = (input, init = {}) => {
|
||
|
init.method = method.toUpperCase()
|
||
|
return xfetch(input, init)
|
||
|
}
|
||
|
}
|
||
|
// Extra methods and classes
|
||
|
xfetch.extend = newDefaultInit => extend(assign({}, defaultInit, newDefaultInit))
|
||
|
xfetch.HTTPError = HTTPError
|
||
|
return xfetch
|
||
|
}
|
||
|
const isWindow = typeof document !== 'undefined'
|
||
|
const isBrowser = typeof self !== 'undefined' // works in both window & worker scope
|
||
|
return isBrowser
|
||
|
? extend({
|
||
|
fetch: fetch.bind(self),
|
||
|
URL,
|
||
|
Response,
|
||
|
URLSearchParams,
|
||
|
Headers,
|
||
|
FormData,
|
||
|
baseURI: isWindow ? document.baseURI : '' // since there is no document in webworkers
|
||
|
})
|
||
|
: extend()
|
||
|
})()
|
||
|
export default xf
|