import { stringify } from 'query-string' import debug from 'debug' const debugStart = debug('yfetch:start') const debugRaw = debug('yfetch:raw') const debugResult = debug('yfetch:result') const debugError = debug('yfetch:error') export class YfetchError extends Error {} export const JSON_HEADER = { Accept: 'application/json', 'Content-Type': 'application/json' } // Support simple opts.query , opts.base and opts.url logic // Support opts.json headers // return fetch() arguments export const transformFetchOptions = ({ query, headers, base = '', url = '', ...opts }) => { const queryString = stringify(query) const urlString = base + url + (queryString ? `?${queryString}` : '') const H = opts.json ? JSON_HEADER : {} return [urlString, { headers: { ...H, ...headers }, ...opts }] } // Support opts.json, send debug yfetch:result export const transformFetchResult = ({ fetchArgs, ...response }) => { if (fetchArgs[1].json) { try { response.body = JSON.parse(response.body) response.parsed = true } catch (E) { if (!fetchArgs[1].ignoreJsonError) { throw new Error(`Can not JSON parse response body: "${response.body}"`) } } } debugResult('url: %s - status: %s - body: %O', response.url, response.status, response.body) return { ...response, fetchArgs } } // Support opts.error export const transformFetchStatusError = ({ fetchArgs, ...response }) => { if (fetchArgs[1].error && fetchArgs[1].error.indexOf && fetchArgs[1].error.indexOf(response.status) > -1) { throw new YfetchError(`Response Code ${response.status} means Error (includes: ${fetchArgs[1].error.join(',')})`) } return { ...response, fetchArgs } } // handle response.text() promise, make response simple, keep response.fetchArgs // send debug yfetch:raw const transformForContext = fetchArgs => (response = {}) => { const { url, status, statusText, headers = [], ok, size } = response const H = {} headers.forEach((v, k) => { H[k] = v }) const job = response.text ? response.text() : (fetchArgs[1].jsonp ? response.json() : Promise.resolve()) return job .then((body) => { debugRaw('url: %s - status: %s - size: %s - header: %o - body: %s', url, status, size, H, body) return { url, status, statusText, ok, size, body, fetchArgs, headers: H } }) } // keep error.fetchArgs and error.response , send debug yfetch:error // const transformFetchError = (fetchArgs, { response = {} }) => (error) => { const transformFetchError = (fetchArgs, R) => (error) => { const response = R.response || {} debugError('url: %s - status: %s - size: %s - body: %s - %O', fetchArgs[0], response.status, response.size, response.body, error) error.fetchArgs = fetchArgs error.response = response throw error } // The main yfetch function export const yfetch = (opts = {}) => { const fetchArgs = transformFetchOptions(opts) const R = {} const storeResponse = (response) => { R.response = response return response } debugStart('url: %s - args: %O', fetchArgs[0], fetchArgs[1]) return ((global.fetchJsonp && fetchArgs[1].jsonp) ? global.fetchJsonp(...fetchArgs) : global.fetch(...fetchArgs)) .then(transformForContext(fetchArgs)) .then(storeResponse) .then(transformFetchResult) .then(storeResponse) .then(transformFetchStatusError) .catch(transformFetchError(fetchArgs, R)) } // as default export default yfetch