mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-16 03:23:29 +01:00
refactor(proxy): rely on auth header instead (#9422)
* refactor(proxy): rely on auth header instead * chore: typo * chore: language Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com> * chore: more language Co-authored-by: Aura Román <kyradiscord@gmail.com> * chore: more language nitpicks Co-authored-by: ckohen <chaikohen@gmail.com> * fix: unnecessary async --------- Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com> Co-authored-by: Aura Román <kyradiscord@gmail.com> Co-authored-by: ckohen <chaikohen@gmail.com>
This commit is contained in:
@@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
Quickly spin up an instance:
|
Quickly spin up an instance:
|
||||||
|
|
||||||
`docker run -d --restart unless-stopped --name proxy -p 127.0.0.1:8080:8080 -e DISCORD_TOKEN=abc discordjs/proxy`
|
`docker run -d --restart unless-stopped --name proxy -p 127.0.0.1:8080:8080 discordjs/proxy`
|
||||||
|
|
||||||
Use it:
|
Use it:
|
||||||
|
|
||||||
@@ -48,6 +48,9 @@ const rest = new REST({
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Do note that you should not use the same proxy with multiple bots. We cannot guarantee you won't hit rate limits.
|
||||||
|
Webhooks with tokens or other requests that don't include the Authorization header are okay, though!**
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
- [Website][website] ([source][website-source])
|
- [Website][website] ([source][website-source])
|
||||||
|
|||||||
@@ -3,12 +3,8 @@ import process from 'node:process';
|
|||||||
import { proxyRequests } from '@discordjs/proxy';
|
import { proxyRequests } from '@discordjs/proxy';
|
||||||
import { REST } from '@discordjs/rest';
|
import { REST } from '@discordjs/rest';
|
||||||
|
|
||||||
if (!process.env.DISCORD_TOKEN) {
|
|
||||||
throw new Error('A DISCORD_TOKEN env var is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
// We want to let upstream handle retrying
|
// We want to let upstream handle retrying
|
||||||
const api = new REST({ rejectOnRateLimit: () => true, retries: 0 }).setToken(process.env.DISCORD_TOKEN);
|
const api = new REST({ rejectOnRateLimit: () => true, retries: 0 });
|
||||||
const server = createServer(proxyRequests(api));
|
const server = createServer(proxyRequests(api));
|
||||||
|
|
||||||
const port = Number.parseInt(process.env.PORT ?? '8080', 10);
|
const port = Number.parseInt(process.env.PORT ?? '8080', 10);
|
||||||
|
|||||||
@@ -1,18 +1,6 @@
|
|||||||
import { URL } from 'node:url';
|
import { URL } from 'node:url';
|
||||||
import {
|
import type { RequestMethod, REST, RouteLike } from '@discordjs/rest';
|
||||||
DiscordAPIError,
|
import { populateSuccessfulResponse, populateErrorResponse } from '../util/responseHelpers.js';
|
||||||
HTTPError,
|
|
||||||
RateLimitError,
|
|
||||||
type RequestMethod,
|
|
||||||
type REST,
|
|
||||||
type RouteLike,
|
|
||||||
} from '@discordjs/rest';
|
|
||||||
import {
|
|
||||||
populateAbortErrorResponse,
|
|
||||||
populateGeneralErrorResponse,
|
|
||||||
populateSuccessfulResponse,
|
|
||||||
populateRatelimitErrorResponse,
|
|
||||||
} from '../util/responseHelpers.js';
|
|
||||||
import type { RequestHandler } from '../util/util';
|
import type { RequestHandler } from '../util/util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,29 +24,33 @@ export function proxyRequests(rest: REST): RequestHandler {
|
|||||||
// eslint-disable-next-line unicorn/no-unsafe-regex, prefer-named-capture-group
|
// eslint-disable-next-line unicorn/no-unsafe-regex, prefer-named-capture-group
|
||||||
const fullRoute = parsedUrl.pathname.replace(/^\/api(\/v\d+)?/, '') as RouteLike;
|
const fullRoute = parsedUrl.pathname.replace(/^\/api(\/v\d+)?/, '') as RouteLike;
|
||||||
|
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
'Content-Type': req.headers['content-type']!,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (req.headers.authorization) {
|
||||||
|
headers.authorization = req.headers.authorization;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const discordResponse = await rest.raw({
|
const discordResponse = await rest.raw({
|
||||||
body: req,
|
body: req,
|
||||||
fullRoute,
|
fullRoute,
|
||||||
// This type cast is technically incorrect, but we want Discord to throw Method Not Allowed for us
|
// This type cast is technically incorrect, but we want Discord to throw Method Not Allowed for us
|
||||||
method: method as RequestMethod,
|
method: method as RequestMethod,
|
||||||
|
// We forward the auth header anyway
|
||||||
|
auth: false,
|
||||||
passThroughBody: true,
|
passThroughBody: true,
|
||||||
query: parsedUrl.searchParams,
|
query: parsedUrl.searchParams,
|
||||||
headers: {
|
headers,
|
||||||
'Content-Type': req.headers['content-type']!,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await populateSuccessfulResponse(res, discordResponse);
|
await populateSuccessfulResponse(res, discordResponse);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof DiscordAPIError || error instanceof HTTPError) {
|
const knownError = populateErrorResponse(res, error);
|
||||||
populateGeneralErrorResponse(res, error);
|
if (!knownError) {
|
||||||
} else if (error instanceof RateLimitError) {
|
// Unclear if there's better course of action here for unknown errors.
|
||||||
populateRatelimitErrorResponse(res, error);
|
// Any web framework allows to pass in an error handler for something like this
|
||||||
} else if (error instanceof Error && error.name === 'AbortError') {
|
|
||||||
populateAbortErrorResponse(res);
|
|
||||||
} else {
|
|
||||||
// Unclear if there's better course of action here for unknown errors. Any web framework allows to pass in an error handler for something like this
|
|
||||||
// at which point the user could dictate what to do with the error - otherwise we could just 500
|
// at which point the user could dictate what to do with the error - otherwise we could just 500
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { ServerResponse } from 'node:http';
|
import type { ServerResponse } from 'node:http';
|
||||||
import { pipeline } from 'node:stream/promises';
|
import { pipeline } from 'node:stream/promises';
|
||||||
import type { DiscordAPIError, HTTPError, RateLimitError } from '@discordjs/rest';
|
import { DiscordAPIError, HTTPError, RateLimitError } from '@discordjs/rest';
|
||||||
import type { Dispatcher } from 'undici';
|
import type { Dispatcher } from 'undici';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,3 +59,24 @@ export function populateAbortErrorResponse(res: ServerResponse): void {
|
|||||||
res.statusCode = 504;
|
res.statusCode = 504;
|
||||||
res.statusMessage = 'Upstream timed out';
|
res.statusMessage = 'Upstream timed out';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to populate a server response from an error object
|
||||||
|
*
|
||||||
|
* @param res - The server response to populate
|
||||||
|
* @param error - The error to check and use
|
||||||
|
* @returns - True if the error is known and the response object was populated, otherwise false
|
||||||
|
*/
|
||||||
|
export function populateErrorResponse(res: ServerResponse, error: unknown): boolean {
|
||||||
|
if (error instanceof DiscordAPIError || error instanceof HTTPError) {
|
||||||
|
populateGeneralErrorResponse(res, error);
|
||||||
|
} else if (error instanceof RateLimitError) {
|
||||||
|
populateRatelimitErrorResponse(res, error);
|
||||||
|
} else if (error instanceof Error && error.name === 'AbortError') {
|
||||||
|
populateAbortErrorResponse(res);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user