UNPKG

@fnlb-project/fnbr

Version:

A library to interact with Epic Games' Fortnite HTTP and XMPP services

189 lines 8.99 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const Base_1 = tslib_1.__importDefault(require("../Base")); const AuthenticationMissingError_1 = tslib_1.__importDefault(require("../exceptions/AuthenticationMissingError")); const constants_1 = require("../../resources/constants"); const EpicgamesAPIError_1 = tslib_1.__importDefault(require("../exceptions/EpicgamesAPIError")); /** * Represents the client's HTTP manager * @private */ class HTTP extends Base_1.default { /** * @param client The main client */ constructor(client) { super(client); } /** * Sends an HTTP request * @param config The request config * @param retries How many times this request has been retried (5xx errors) */ async request(config, retries = 0) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m; const reqStartTime = Date.now(); const { url, method = 'GET', headers = {}, data, responseType } = config; let body; const finalHeaders = { 'Accept-Language': this.client.config.language, ...this.client.config.defaultHeaders, ...headers, }; if (data) { if (headers['Content-Type'] === 'application/x-www-form-urlencoded') { if (typeof data === 'object' && !(data instanceof URLSearchParams)) { body = new URLSearchParams(data).toString(); } else { body = data; } finalHeaders['Content-Type'] = 'application/x-www-form-urlencoded'; } else if (headers['Content-Type'] === 'application/json' || (typeof data === 'object' && !(data instanceof ArrayBuffer))) { body = JSON.stringify(data); finalHeaders['Content-Type'] = 'application/json'; } else { body = data; } } try { const response = await ((_a = this.client.config.restFetchAdapter) !== null && _a !== void 0 ? _a : fetch)(this.client.config.restTransformURL ? this.client.config.restTransformURL(url) : url, { method, headers: finalHeaders, body, referrerPolicy: 'no-referrer', }); const reqDuration = ((Date.now() - reqStartTime) / 1000); this.client.debug(`${method.toUpperCase()} ${url} (${reqDuration.toFixed(2)}s): ` + `${response.status} ${response.statusText}`, 'http'); if (!response.ok) { let errorData = null; let responseBody = ''; try { if ((_b = response.headers.get('content-type')) === null || _b === void 0 ? void 0 : _b.includes('application/json')) { errorData = await response.json(); } else if ((_c = response.headers.get('content-type')) === null || _c === void 0 ? void 0 : _c.includes('text')) { responseBody = await response.text(); } else { responseBody = await response.text(); } } catch { } const errorMessage = `HTTP ${response.status}: ${method} ${url}: ${responseBody}`; const error = new Error(errorMessage); Object.assign(error, { response, errorData, responseBody, }); throw error; } const contentType = response.headers.get("Content-Type") || ""; if (responseType === 'arraybuffer') { return await response.arrayBuffer(); } else if (responseType === 'blob' || contentType.includes("application/octet-stream")) { return await response.blob(); } else if (responseType === 'json' || contentType.includes("application/json")) { const text = await response.text(); try { return JSON.parse(text); } catch (err) { this.client.debug(`Failed to parse JSON response (Status ${response.status}): ${text.slice(0, 500)}`, 'http'); throw err; } } else if (responseType === 'formdata' || contentType.includes("multipart/form-data")) { return await response.formData(); } else if (contentType.includes("image/")) { const blob = await response.blob(); return URL.createObjectURL(blob); } else if (contentType.includes("application/x-www-form-urlencoded")) { const text = await response.text(); return new URLSearchParams(text); } else { return await response.text(); } } catch (err) { const reqDuration = ((Date.now() - reqStartTime) / 1000); this.client.debug(`${method.toUpperCase()} ${url} (${reqDuration.toFixed(2)}s): ${err.name} - ${err.message}`, 'http'); if (((_e = (_d = err.response) === null || _d === void 0 ? void 0 : _d.status) === null || _e === void 0 ? void 0 : _e.toString().startsWith('5')) && retries < this.client.config.restRetryLimit) { return this.request(config, retries + 1); } if (((_f = err.response) === null || _f === void 0 ? void 0 : _f.status) === 429 || ((_g = err.errorData) === null || _g === void 0 ? void 0 : _g.errorCode) === 'errors.com.epicgames.common.throttled') { const match = (_j = (_h = err.errorData) === null || _h === void 0 ? void 0 : _h.errorMessage) === null || _j === void 0 ? void 0 : _j.match(/in (\d+) second/); const retryAfter = parseInt(((_k = err.response) === null || _k === void 0 ? void 0 : _k.headers.get('Retry-After')) || ((_m = (_l = err.errorData) === null || _l === void 0 ? void 0 : _l.messageVars) === null || _m === void 0 ? void 0 : _m[0]) || (match === null || match === void 0 ? void 0 : match[1]) || '0', 10); if (!Number.isNaN(retryAfter)) { const sleepTimeout = retryAfter * 1000 + 500; this.client.debug(`Ratelimited: Retrying ${method} ${url} in ${sleepTimeout / 1000}s`, 'http'); await new Promise((res) => setTimeout(res, sleepTimeout)); return this.request(config, retries); } } throw err; } } /** * Sends an HTTP request to the Fortnite API * @param config The request config * @param includeAuthentication Whether to include authentication * @throws {EpicgamesAPIError} */ async epicgamesRequest(config, auth) { var _a, _b, _c; if (auth) { const authSession = this.client.auth.sessions.get(auth); if (!authSession) throw new AuthenticationMissingError_1.default(auth); if (authSession.refreshLock.isLocked) { this.client.debug(`Waiting for ${auth} refresh lock...`, 'http'); await authSession.refreshLock.wait(); this.client.debug(`${auth} refresh lock released, proceeding with request.`, 'http'); } } try { const currentAuthSession = auth ? this.client.auth.sessions.get(auth) : undefined; return await this.request({ ...config, headers: { ...config.headers, ...(currentAuthSession && { Authorization: `Bearer ${currentAuthSession.accessToken}`, }), }, }); } catch (err) { if (auth && constants_1.invalidTokenCodes.includes((_a = err.errorData) === null || _a === void 0 ? void 0 : _a.errorCode)) { const sessionToRefresh = this.client.auth.sessions.get(auth); if (sessionToRefresh) { this.client.debug(`Token expired for ${auth}, refreshing...`, 'http'); await sessionToRefresh.refresh(); return this.epicgamesRequest(config, auth); } } if (typeof ((_b = err.errorData) === null || _b === void 0 ? void 0 : _b.errorCode) === 'string') { throw new EpicgamesAPIError_1.default(err.errorData, config, ((_c = err.response) === null || _c === void 0 ? void 0 : _c.status) || 500); } throw err; } } } exports.default = HTTP; //# sourceMappingURL=HTTP.js.map