UNPKG

musicbrainz-api

Version:

MusicBrainz API client for reading and submitting metadata

99 lines 3.86 kB
import Debug from "debug"; const debug = Debug('musicbrainz-api-node'); function isConnectionReset(err) { // Undici puts the OS error on .cause, with .code like 'ECONNRESET' const code = err?.cause?.code ?? err?.code; // Add other transient codes you consider safe to retry: return typeof code === "string" && code === 'ECONNRESET'; } export class HttpClient { constructor(httpOptions) { this.httpOptions = httpOptions; } get(path, options) { return this._fetch('get', path, options); } post(path, options) { return this._fetch('post', path, options); } postForm(path, formData, options) { const encodedFormData = new URLSearchParams(formData).toString(); return this._fetch('post', path, { ...options, body: encodedFormData, headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }); } postJson(path, json, options) { const encodedJson = JSON.stringify(json); return this._fetch('post', path, { ...options, body: encodedJson, headers: { 'Content-Type': 'application/json.' } }); } async _fetch(method, path, options) { if (!options) options = {}; let retryLimit = options.retryLimit && options.retryLimit > 1 ? options.retryLimit : 1; const retryTimeout = this.httpOptions.timeout ? this.httpOptions.timeout : 500; const url = this._buildUrl(path, options.query); const cookies = await this.getCookies(); const headers = new Headers(options.headers); headers.set('User-Agent', this.httpOptions.userAgent); if (cookies !== null) { headers.set('Cookie', cookies); } while (retryLimit > 0) { let response; try { response = await fetch(url, { method, ...options, headers, body: options.body, redirect: options.followRedirects === false ? 'manual' : 'follow' }); } catch (err) { if (isConnectionReset(err)) { // Retry on TCP connection resets await this._delay(retryTimeout); // wait 200ms before retry continue; } throw err; } if (response.status === 429 || response.status === 503) { debug(`Received status=${response.status}, assume reached rate limit, retry in ${retryTimeout} ms`); retryLimit--; if (retryLimit > 0) { await this._delay(retryTimeout); // wait 200ms before retry continue; } } await this.registerCookies(response); return response; } throw new Error(`Failed to fetch ${url} after retries`); } // Helper: Builds URL with query string _buildUrl(path, query) { let url = path.startsWith('/') ? `${this.httpOptions.baseUrl}${path}` : `${this.httpOptions.baseUrl}/${path}`; if (query) { const urlSearchParams = new URLSearchParams(); for (const key of Object.keys(query)) { const value = query[key]; (Array.isArray(value) ? value : [value]).forEach(v => { urlSearchParams.append(key, v); }); } url += `?${urlSearchParams.toString()}`; } return url; } // Helper: Delays execution _delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } registerCookies(_response) { return Promise.resolve(undefined); } async getCookies() { return Promise.resolve(null); } } //# sourceMappingURL=http-client.js.map