@tkrotoff/fetch
Version:
Fetch wrapper
138 lines (137 loc) • 5.79 kB
JavaScript
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');
}