UNPKG

@zikeji/hypixel

Version:

With IntelliSense support & test coverage, this is an unopinionated async/await API wrapper for Hypixel's Public API. It is developed in TypeScript complete with documentation, typed interfaces for all API responses, built-in rate-limit handling, flexible

147 lines (129 loc) 4.62 kB
import { request as httpsRequest } from "https"; import { GenericHTTPError } from "../errors/GenericHTTPError"; import { InvalidKeyError } from "../errors/InvalidKeyError"; import { RateLimitError } from "../errors/RateLimitError"; import type { RequestOptions } from "../Client"; import type { DefaultMeta } from "../types/DefaultMeta"; /** @internal */ const CACHE_CONTROL_REGEX = /s-maxage=(\d+)/; /** @internal */ export function request< T extends Record<string, unknown> & { cause?: string; } & { cloudflareCache?: DefaultMeta["cloudflareCache"] } >(options: RequestOptions): Promise<T> { return new Promise((resolve, reject) => { const clientRequest = httpsRequest( options.url, { method: "GET", timeout: options.timeout, headers: { "User-Agent": options.userAgent, Accept: "application/json", }, }, (incomingMessage) => { let responseBody = ""; incomingMessage.on("data", (chunk) => { responseBody += chunk; }); incomingMessage.on("end", () => { if (!options.noRateLimit) { options.getRateLimitHeaders( incomingMessage.headers as Record<string, string> ); } /* istanbul ignore next */ if ( typeof responseBody !== "string" || responseBody.trim().length === 0 ) { return reject(new Error(`No response body received.`)); } let responseObject: T | undefined; try { responseObject = JSON.parse(responseBody); } catch (_) { // noop } if (incomingMessage.statusCode !== 200) { /* istanbul ignore next */ if (incomingMessage.statusCode === 429) { return reject(new RateLimitError(`Hit key throttle.`)); } if (incomingMessage.statusCode === 403) { return reject(new InvalidKeyError("Invalid API Key")); } /* istanbul ignore else */ if ( /* istanbul ignore next */ responseObject?.cause && typeof incomingMessage.statusCode === "number" ) { return reject( new GenericHTTPError( incomingMessage.statusCode, responseObject.cause ) ); } /** * Generic catch all that probably should never be caught. */ /* istanbul ignore next */ return reject( new Error( `${incomingMessage.statusCode} ${incomingMessage.statusMessage}. Response: ${responseBody}` ) ); } /* istanbul ignore if */ if (typeof responseObject === "undefined") { return reject( new Error( `Invalid JSON response received. Response: ${responseBody}` ) ); } /* istanbul ignore else */ if (incomingMessage.headers["cf-cache-status"]) { const age = parseInt(incomingMessage.headers.age as string, 10); const maxAge = CACHE_CONTROL_REGEX.exec( incomingMessage.headers["cache-control"] as string ); responseObject.cloudflareCache = { status: incomingMessage.headers["cf-cache-status"] as never, ...(typeof age === "number" && !Number.isNaN(age) && { age }), ...(incomingMessage.headers["cf-cache-status"] === "HIT" && (typeof age !== "number" || Number.isNaN(age)) && /* istanbul ignore next */ { age: 0 }), ...(maxAge && typeof maxAge === "object" && maxAge.length === 2 && parseInt(maxAge[1], 10) > 0 && { maxAge: parseInt(maxAge[1], 10), }), }; } return resolve(responseObject); }); } ); let abortError: Error; /* istanbul ignore next */ clientRequest.once("abort", () => { abortError = abortError ?? new Error("Client aborted this request."); reject(abortError); }); /* istanbul ignore next */ clientRequest.once("error", (error) => { abortError = error; clientRequest.abort(); }); clientRequest.setTimeout(options.timeout, () => { abortError = new Error("Hit configured timeout."); clientRequest.abort(); }); clientRequest.end(); }); }