UNPKG

@tkrotoff/fetch

Version:
138 lines (137 loc) 5.79 kB
import { HttpError } from './HttpError'; import { wait } from './wait'; const ARRAYBUFFER_MIME_TYPE = '*/*'; const BLOB_MIME_TYPE = '*/*'; const FORMDATA_MIME_TYPE = 'multipart/form-data'; export const JSON_MIME_TYPE = 'application/json'; const TEXT_MIME_TYPE = 'text/*'; export function isJSONResponse(response) { var _a; const contentType = (_a = response.headers.get('content-type')) !== null && _a !== void 0 ? _a : ''; return contentType.includes(JSON_MIME_TYPE); } function extendResponsePromiseWithBodyMethods(responsePromise, headers) { /* eslint-disable no-param-reassign */ function setAcceptHeader(mimeType) { var _a; headers.set('accept', (_a = headers.get('accept')) !== null && _a !== void 0 ? _a : mimeType); } responsePromise.arrayBuffer = async () => { setAcceptHeader(ARRAYBUFFER_MIME_TYPE); const response = await responsePromise; return response.arrayBuffer(); }; responsePromise.blob = async () => { setAcceptHeader(BLOB_MIME_TYPE); const response = await responsePromise; return response.blob(); }; responsePromise.formData = async () => { setAcceptHeader(FORMDATA_MIME_TYPE); const response = await responsePromise; return response.formData(); }; responsePromise.json = async () => { setAcceptHeader(JSON_MIME_TYPE); const response = await responsePromise; if (isJSONResponse(response)) { return response.json(); } return response.text(); }; responsePromise.text = async () => { setAcceptHeader(TEXT_MIME_TYPE); const response = await responsePromise; return response.text(); }; /* eslint-enable no-param-reassign */ } export const defaults = { init: { // https://github.com/github/fetch/blob/v3.0.0/README.md#sending-cookies // TODO Remove when old browsers are not supported anymore credentials: 'same-origin' } }; // Can throw: // - HttpError if !response.ok // - TypeError if request blocked (DevTools or CORS) or network timeout (net::ERR_TIMED_OUT): // - Firefox 68: "TypeError: "NetworkError when attempting to fetch resource."" // - Chrome 76: "TypeError: Failed to fetch" // - DOMException if request aborted function request(input, headers, init, method, body) { async function _fetch() { // Have to wait for headers to be modified inside extendResponsePromiseWithBodyMethods await wait(1); const response = await fetch(input, { ...defaults.init, ...init, headers, method, body }); if (!response.ok) throw new HttpError(response); return response; } const responsePromise = _fetch(); extendResponsePromiseWithBodyMethods(responsePromise, headers); return responsePromise; } // https://gist.github.com/userpixel/fedfe80d59aa1c096267600595ba423e export function entriesToObject(object) { return Object.fromEntries(object.entries()); } // FIXME Remove when support for [EdgeHTML](https://en.wikipedia.org/wiki/EdgeHTML) will be dropped function newHeaders(init) { // Why "?? {}"? Microsoft Edge <= 18 (EdgeHTML) throws "Invalid argument" with "new Headers(undefined)" and "new Headers(null)" return new Headers(init !== null && init !== void 0 ? init : {}); } function getHeaders(init) { // We don't know if defaults.init.headers and init.headers are JSON or Headers instances // thus we have to make the conversion const defaultInitHeaders = entriesToObject(newHeaders(defaults.init.headers)); const initHeaders = entriesToObject(newHeaders(init === null || init === void 0 ? void 0 : init.headers)); return newHeaders({ ...defaultInitHeaders, ...initHeaders }); } function getJSONHeaders(init) { const headers = getHeaders(init); headers.set('content-type', JSON_MIME_TYPE); return headers; } export function get(input, init) { return request(input, getHeaders(init), init, 'GET'); } // Should be named postJson or postJSON? // - JS uses JSON.parse(), JSON.stringify(), toJSON() // - Deno uses [JSON](https://github.com/denoland/deno/blob/v1.5.3/cli/dts/lib.webworker.d.ts#L387) and [Json](https://github.com/denoland/deno/blob/v1.5.3/cli/dts/lib.webworker.d.ts#L260) // // Record<string, unknown> is compatible with "type" not with "interface": "Index signature is missing in type 'MyInterface'" // Best alternative is object, why? https://stackoverflow.com/a/58143592 // eslint-disable-next-line @typescript-eslint/ban-types export function postJSON(input, body, init) { return request(input, getJSONHeaders(init), init, 'POST', JSON.stringify(body)); } // No need to have postFormData() and friends: the browser already sets the proper request content type // Something like "content-type: multipart/form-data; boundary=----WebKitFormBoundaryl8VQ0sfwUpJEWna3" export function post(input, body, init) { return request(input, getHeaders(init), init, 'POST', body); } // eslint-disable-next-line @typescript-eslint/ban-types export function putJSON(input, body, init) { return request(input, getJSONHeaders(init), init, 'PUT', JSON.stringify(body)); } export function put(input, body, init) { return request(input, getHeaders(init), init, 'PUT', body); } // eslint-disable-next-line @typescript-eslint/ban-types export function patchJSON(input, body, init) { return request(input, getJSONHeaders(init), init, 'PATCH', JSON.stringify(body)); } export function patch(input, body, init) { return request(input, getHeaders(init), init, 'PATCH', body); } // Cannot be named delete :-/ export function del(input, init) { return request(input, getHeaders(init), init, 'DELETE'); }