@signumjs/http
Version:
SignumJS Generic Http Module
125 lines • 4.46 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.HttpAdapterFetch = void 0;
const httpResponse_1 = require("./httpResponse");
const httpError_1 = require("./httpError");
const defaultValidateStatus = (status) => status >= 200 && status < 300;
/**
* Http implementation of {@link Http} using the platform's native `fetch`.
*
* Works in browsers, Node.js >= 18, Deno, Bun and Cloudflare Workers without
* any external dependency.
*
* Prefer {@link HttpClientFactory.createHttpClient} to create an instance.
*
* @module http
*/
class HttpAdapterFetch {
_baseURL;
_defaults;
constructor(baseURL, options = {}) {
this._baseURL = baseURL.replace(/\/+$/, '');
this._defaults = options;
}
async get(url, options) {
return this.request('GET', url, undefined, options);
}
async post(url, payload, options) {
return this.request('POST', url, payload, options);
}
async put(url, payload, options) {
return this.request('PUT', url, payload, options);
}
async delete(url, options) {
return this.request('DELETE', url, undefined, options);
}
buildUrl(url) {
if (/^https?:\/\//i.test(url)) {
return url;
}
if (!this._baseURL) {
return url;
}
return url.startsWith('/') ? `${this._baseURL}${url}` : `${this._baseURL}/${url}`;
}
resolveOptions(perCall) {
const merged = {
...this._defaults,
...perCall,
headers: {
...(this._defaults.headers || {}),
...(perCall?.headers || {})
},
fetchOptions: {
...(this._defaults.fetchOptions || {}),
...(perCall?.fetchOptions || {})
}
};
return {
...merged,
validateStatus: merged.validateStatus || defaultValidateStatus
};
}
async request(method, url, payload, perCall) {
const opts = this.resolveOptions(perCall);
const fullUrl = this.buildUrl(url);
const headers = {
Accept: 'application/json',
...(opts.headers || {})
};
let body;
if (payload !== undefined && payload !== null) {
if (typeof payload === 'string' || payload instanceof ArrayBuffer || payload instanceof Uint8Array) {
body = payload;
}
else if (typeof FormData !== 'undefined' && payload instanceof FormData) {
body = payload;
}
else {
body = JSON.stringify(payload);
if (!headers['Content-Type'] && !headers['content-type']) {
headers['Content-Type'] = 'application/json';
}
}
}
const init = {
...(opts.fetchOptions || {}),
method,
headers,
body
};
if (opts.timeout && opts.timeout > 0) {
init.signal = AbortSignal.timeout(opts.timeout);
}
let response;
try {
response = await fetch(fullUrl, init);
}
catch (error) {
if (error?.name === 'TimeoutError' || error?.name === 'AbortError') {
throw new httpError_1.HttpError(url, 0, 'Request timed out', error?.message || null);
}
throw new httpError_1.HttpError(url, 0, 'Request failed', error?.message || String(error));
}
const data = await this.parseBody(response);
if (!opts.validateStatus(response.status)) {
throw new httpError_1.HttpError(url, response.status, response.statusText || 'Request failed', data);
}
return new httpResponse_1.HttpResponse(response.status, data);
}
// eslint-disable-next-line class-methods-use-this
async parseBody(response) {
if (response.status === 204 || response.status === 205) {
return null;
}
const contentType = response.headers.get('content-type') || '';
if (contentType.includes('application/json')) {
const text = await response.text();
return text ? JSON.parse(text) : null;
}
// Fall back to text; callers that need blobs/streams can use fetchOptions
return response.text();
}
}
exports.HttpAdapterFetch = HttpAdapterFetch;
//# sourceMappingURL=httpAdapterFetch.js.map