mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-16 11:33:30 +01:00
100 lines
3.0 KiB
TypeScript
100 lines
3.0 KiB
TypeScript
import { Buffer } from 'node:buffer';
|
|
import { STATUS_CODES } from 'node:http';
|
|
import { types } from 'node:util';
|
|
import { type RequestInit, request, Headers, FormData as UndiciFormData, Agent } from 'undici';
|
|
import type { HeaderRecord } from 'undici/types/header.js';
|
|
import type { ResponseLike } from '../shared.js';
|
|
|
|
export type RequestOptions = Exclude<Parameters<typeof request>[1], undefined>;
|
|
|
|
let localAgent: Agent | null = null;
|
|
|
|
export async function makeRequest(url: string, init: RequestInit): Promise<ResponseLike> {
|
|
// The cast is necessary because `headers` and `method` are narrower types in `undici.request`
|
|
// our request path guarantees they are of acceptable type for `undici.request`
|
|
const options = {
|
|
...init,
|
|
body: await resolveBody(init.body),
|
|
} as RequestOptions;
|
|
|
|
// Mismatched dispatchers from the Node.js-bundled undici and package-installed undici breaks file uploads.
|
|
// So we ensure that we always pass an Agent to request()
|
|
// https://github.com/nodejs/node/issues/59012
|
|
if (!options.dispatcher) {
|
|
localAgent ??= new Agent();
|
|
options.dispatcher = localAgent;
|
|
}
|
|
|
|
const res = await request(url, options);
|
|
return {
|
|
body: res.body,
|
|
async arrayBuffer() {
|
|
return res.body.arrayBuffer();
|
|
},
|
|
async json() {
|
|
return res.body.json();
|
|
},
|
|
async text() {
|
|
return res.body.text();
|
|
},
|
|
get bodyUsed() {
|
|
return res.body.bodyUsed;
|
|
},
|
|
headers: new Headers(res.headers as HeaderRecord),
|
|
status: res.statusCode,
|
|
statusText: STATUS_CODES[res.statusCode]!,
|
|
ok: res.statusCode >= 200 && res.statusCode < 300,
|
|
};
|
|
}
|
|
|
|
export async function resolveBody(body: RequestInit['body']): Promise<Exclude<RequestOptions['body'], undefined>> {
|
|
// eslint-disable-next-line no-eq-null, eqeqeq
|
|
if (body == null) {
|
|
return null;
|
|
} else if (typeof body === 'string') {
|
|
return body;
|
|
} else if (types.isUint8Array(body)) {
|
|
return body;
|
|
} else if (types.isArrayBuffer(body)) {
|
|
return new Uint8Array(body);
|
|
} else if (body instanceof URLSearchParams) {
|
|
return body.toString();
|
|
} else if (body instanceof DataView) {
|
|
return new Uint8Array(body.buffer);
|
|
} else if (body instanceof Blob) {
|
|
return new Uint8Array(await body.arrayBuffer());
|
|
} else if (body instanceof UndiciFormData) {
|
|
return body;
|
|
} else if (body instanceof FormData) {
|
|
return globalToUndiciFormData(body);
|
|
} else if ((body as Iterable<Uint8Array>)[Symbol.iterator]) {
|
|
const chunks = [...(body as Iterable<Uint8Array>)];
|
|
|
|
return Buffer.concat(chunks);
|
|
} else if ((body as AsyncIterable<Uint8Array>)[Symbol.asyncIterator]) {
|
|
const chunks: Uint8Array[] = [];
|
|
|
|
for await (const chunk of body as AsyncIterable<Uint8Array>) {
|
|
chunks.push(chunk);
|
|
}
|
|
|
|
return Buffer.concat(chunks);
|
|
}
|
|
|
|
throw new TypeError(`Unable to resolve body.`);
|
|
}
|
|
|
|
function globalToUndiciFormData(fd: globalThis.FormData): UndiciFormData {
|
|
const clone = new UndiciFormData();
|
|
|
|
for (const [name, value] of fd.entries()) {
|
|
if (typeof value === 'string') {
|
|
clone.append(name, value);
|
|
} else {
|
|
clone.append(name, value, value.name);
|
|
}
|
|
}
|
|
|
|
return clone;
|
|
}
|