mirror of
https://github.com/LukeHagar/plexjs.git
synced 2025-12-06 04:20:46 +00:00
216 lines
5.7 KiB
TypeScript
216 lines
5.7 KiB
TypeScript
/*
|
|
* Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT.
|
|
*/
|
|
|
|
export type Fetcher = (
|
|
input: RequestInfo | URL,
|
|
init?: RequestInit,
|
|
) => Promise<Response>;
|
|
|
|
export type Awaitable<T> = T | Promise<T>;
|
|
|
|
const DEFAULT_FETCHER: Fetcher = (input, init) => fetch(input, init);
|
|
|
|
export interface HTTPClientOptions {
|
|
fetcher?: Fetcher;
|
|
}
|
|
|
|
export type BeforeRequestHook = (req: Request) => Awaitable<Request | void>;
|
|
export type RequestErrorHook = (err: unknown, req: Request) => Awaitable<void>;
|
|
export type ResponseHook = (res: Response, req: Request) => Awaitable<void>;
|
|
|
|
export class HTTPClient {
|
|
private fetcher: Fetcher;
|
|
private requestHooks: BeforeRequestHook[] = [];
|
|
private requestErrorHooks: RequestErrorHook[] = [];
|
|
private responseHooks: ResponseHook[] = [];
|
|
|
|
constructor(private options: HTTPClientOptions = {}) {
|
|
this.fetcher = options.fetcher || DEFAULT_FETCHER;
|
|
}
|
|
|
|
async request(request: Request): Promise<Response> {
|
|
let req = request;
|
|
for (const hook of this.requestHooks) {
|
|
const nextRequest = await hook(req);
|
|
if (nextRequest) {
|
|
req = nextRequest;
|
|
}
|
|
}
|
|
|
|
try {
|
|
const res = await this.fetcher(req);
|
|
|
|
for (const hook of this.responseHooks) {
|
|
await hook(res, req);
|
|
}
|
|
|
|
return res;
|
|
} catch (err) {
|
|
for (const hook of this.requestErrorHooks) {
|
|
await hook(err, req);
|
|
}
|
|
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Registers a hook that is called before a request is made. The hook function
|
|
* can mutate the request or return a new request. This may be useful to add
|
|
* additional information to request such as request IDs and tracing headers.
|
|
*/
|
|
addHook(hook: "beforeRequest", fn: BeforeRequestHook): this;
|
|
/**
|
|
* Registers a hook that is called when a request cannot be made due to a
|
|
* network error.
|
|
*/
|
|
addHook(hook: "requestError", fn: RequestErrorHook): this;
|
|
/**
|
|
* Registers a hook that is called when a response has been received from the
|
|
* server.
|
|
*/
|
|
addHook(hook: "response", fn: ResponseHook): this;
|
|
addHook(
|
|
...args:
|
|
| [hook: "beforeRequest", fn: BeforeRequestHook]
|
|
| [hook: "requestError", fn: RequestErrorHook]
|
|
| [hook: "response", fn: ResponseHook]
|
|
) {
|
|
if (args[0] === "beforeRequest") {
|
|
this.requestHooks.push(args[1]);
|
|
} else if (args[0] === "requestError") {
|
|
this.requestErrorHooks.push(args[1]);
|
|
} else if (args[0] === "response") {
|
|
this.responseHooks.push(args[1]);
|
|
} else {
|
|
throw new Error(`Invalid hook type: ${args[0]}`);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/** Removes a hook that was previously registered with `addHook`. */
|
|
removeHook(hook: "beforeRequest", fn: BeforeRequestHook): this;
|
|
/** Removes a hook that was previously registered with `addHook`. */
|
|
removeHook(hook: "requestError", fn: RequestErrorHook): this;
|
|
/** Removes a hook that was previously registered with `addHook`. */
|
|
removeHook(hook: "response", fn: ResponseHook): this;
|
|
removeHook(
|
|
...args:
|
|
| [hook: "beforeRequest", fn: BeforeRequestHook]
|
|
| [hook: "requestError", fn: RequestErrorHook]
|
|
| [hook: "response", fn: ResponseHook]
|
|
): this {
|
|
let target: unknown[];
|
|
if (args[0] === "beforeRequest") {
|
|
target = this.requestHooks;
|
|
} else if (args[0] === "requestError") {
|
|
target = this.requestErrorHooks;
|
|
} else if (args[0] === "response") {
|
|
target = this.responseHooks;
|
|
} else {
|
|
throw new Error(`Invalid hook type: ${args[0]}`);
|
|
}
|
|
|
|
const index = target.findIndex((v) => v === args[1]);
|
|
if (index >= 0) {
|
|
target.splice(index, 1);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
clone(): HTTPClient {
|
|
const child = new HTTPClient(this.options);
|
|
child.requestHooks = this.requestHooks.slice();
|
|
child.requestErrorHooks = this.requestErrorHooks.slice();
|
|
child.responseHooks = this.responseHooks.slice();
|
|
|
|
return child;
|
|
}
|
|
}
|
|
|
|
export function matchContentType(response: Response, pattern: string): boolean {
|
|
if (pattern === "*" || pattern === "*/*") {
|
|
return true;
|
|
}
|
|
|
|
const contentType =
|
|
response.headers.get("content-type") ?? "application/octet-stream";
|
|
|
|
const idx = contentType.split(";").findIndex((raw) => {
|
|
const ctype = raw.trim();
|
|
if (ctype === pattern) {
|
|
return true;
|
|
}
|
|
|
|
const parts = ctype.split("/");
|
|
if (parts.length !== 2) {
|
|
return false;
|
|
}
|
|
|
|
return `${parts[0]}/*` === pattern || `*/${parts[1]}` === pattern;
|
|
});
|
|
|
|
return idx >= 0;
|
|
}
|
|
|
|
const codeRangeRE = new RegExp("^[0-9]xx$", "i");
|
|
|
|
export function matchStatusCode(
|
|
response: Response,
|
|
codes: number | string | (number | string)[],
|
|
): boolean {
|
|
const actual = `${response.status}`;
|
|
const expectedCodes = Array.isArray(codes) ? codes : [codes];
|
|
if (!expectedCodes.length) {
|
|
return false;
|
|
}
|
|
|
|
return expectedCodes.some((ec) => {
|
|
const code = `${ec}`;
|
|
|
|
if (code === "default") {
|
|
return true;
|
|
}
|
|
|
|
if (!codeRangeRE.test(`${code}`)) {
|
|
return code === actual;
|
|
}
|
|
|
|
const expectFamily = code.charAt(0);
|
|
if (!expectFamily) {
|
|
throw new Error("Invalid status code range");
|
|
}
|
|
|
|
const actualFamily = actual.charAt(0);
|
|
if (!actualFamily) {
|
|
throw new Error(`Invalid response status code: ${actual}`);
|
|
}
|
|
|
|
return actualFamily === expectFamily;
|
|
});
|
|
}
|
|
|
|
export function matchResponse(
|
|
response: Response,
|
|
code: number | string | (number | string)[],
|
|
contentTypePattern: string,
|
|
): boolean {
|
|
return (
|
|
matchStatusCode(response, code) &&
|
|
matchContentType(response, contentTypePattern)
|
|
);
|
|
}
|
|
|
|
const headerValRE = /, */;
|
|
export function unpackHeaders(headers: Headers): Record<string, string[]> {
|
|
const out: Record<string, string[]> = {};
|
|
|
|
for (const [k, v] of headers.entries()) {
|
|
out[k] = v.split(headerValRE);
|
|
}
|
|
|
|
return out;
|
|
}
|