@dvsa/appdev-api-common
Version:
Utils library for common API functionality
155 lines (154 loc) • 5.84 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.HTTP = exports.HTTPError = void 0;
class HTTPError extends Error {
response;
constructor(message, response) {
super(message);
this.response = response;
this.name = "HTTPError";
this.response = response;
}
}
exports.HTTPError = HTTPError;
// biome-ignore lint/complexity/noStaticOnlyClass: makes sense for an HTTP utility to encompass all methods
class HTTP {
/**
* Performs an HTTP GET request.
* Note: This method will throw an HTTPError if the response is not ok (status code 200-299) to emulate Axios behaviour.
* @param url
* @param options
*/
static async get(url, options) {
const response = await fetch(url, { method: "GET", ...options });
const serialisedResponse = await HTTP.serialise(response);
if (!response.ok) {
throw new HTTPError(`HTTP GET request failed with status ${response.status}`, serialisedResponse);
}
return serialisedResponse;
}
/**
* Performs an HTTP POST request.
* Note: This method will throw an HTTPError if the response is not ok (status code 200-299) to emulate Axios behaviour.
* @param url
* @param body
* @param options
*/
static async post(url, body, options) {
const { contentType, processedBody } = HTTP.prepareBody(body);
const response = await fetch(url, {
...options,
method: "POST",
headers: {
...(contentType && { "Content-Type": contentType }),
...options?.headers,
},
body: processedBody,
});
const serialisedResponse = await HTTP.serialise(response);
if (!response.ok) {
throw new HTTPError(`HTTP POST request failed with status ${response.status}`, serialisedResponse);
}
return serialisedResponse;
}
/**
* Performs an HTTP PUT request.
* Note: This method will throw an HTTPError if the response is not ok (status code 200-299) to emulate Axios behaviour.
* @param url
* @param body
* @param options
*/
static async put(url, body, options) {
const { contentType, processedBody } = HTTP.prepareBody(body);
const response = await fetch(url, {
...options,
method: "PUT",
headers: {
...(contentType && { "Content-Type": contentType }),
...options?.headers,
},
body: processedBody,
});
const serialisedResponse = await HTTP.serialise(response);
if (!response.ok) {
throw new HTTPError(`HTTP PUT request failed with status ${response.status}`, serialisedResponse);
}
return serialisedResponse;
}
/**
* Performs an HTTP DELETE request.
* Note: This method will throw an HTTPError if the response is not ok (status code 200-299) to emulate Axios behaviour.
* @param url
* @param options
*/
static async delete(url, options) {
const response = await fetch(url, { method: "DELETE", ...options });
const serialisedResponse = await HTTP.serialise(response);
if (!response.ok) {
throw new HTTPError(`HTTP DELETE request failed with status ${response.status}`, serialisedResponse);
}
return serialisedResponse;
}
static async serialise(response) {
let body;
try {
// Clone the response so we don't consume the original body
const clonedResponse = response.clone();
// Extract the content-type header so we know how to serialise it
const contentType = response.headers.get("content-type") || "";
// Check if the JSON header is present
if (contentType.includes("application/json")) {
// parse to text first
const text = await clonedResponse.text();
// if there is an empty body e.g. 204, calling `.json()` would cause an error to be thrown, therefore check length
body = text.trim().length ? JSON.parse(text) : null;
}
// Check if the body is a buffer
else if (contentType.includes("application/pdf") ||
contentType.includes("image/") ||
contentType.includes("application/octet-stream")) {
const buffer = await clonedResponse.arrayBuffer();
body = Buffer.from(buffer).toString("base64");
}
// Otherwise attempt to serialise as text
else {
body = await clonedResponse.text();
}
}
catch (error) {
console.error("Serialisation error:", error);
body = null;
}
return {
url: response.url,
status: response.status,
statusText: response.statusText,
headers: Object.fromEntries(response.headers.entries()),
redirected: response.redirected,
type: response.type,
body,
};
}
static prepareBody(body) {
if (body instanceof FormData) {
return { contentType: undefined, processedBody: body };
}
if (body instanceof URLSearchParams) {
return {
contentType: "application/x-www-form-urlencoded",
processedBody: body,
};
}
if (typeof body === "string") {
return {
contentType: "text/plain",
processedBody: body,
};
}
return {
contentType: "application/json",
processedBody: JSON.stringify(body),
};
}
}
exports.HTTP = HTTP;