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:
DD
2023-04-21 23:36:15 +03:00
committed by GitHub
parent 3e01f91bbb
commit a49ed0a2d5
4 changed files with 44 additions and 32 deletions

View File

@@ -23,7 +23,7 @@
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:
@@ -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
- [Website][website] ([source][website-source])

View File

@@ -3,12 +3,8 @@ import process from 'node:process';
import { proxyRequests } from '@discordjs/proxy';
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
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 port = Number.parseInt(process.env.PORT ?? '8080', 10);

View File

@@ -1,18 +1,6 @@
import { URL } from 'node:url';
import {
DiscordAPIError,
HTTPError,
RateLimitError,
type RequestMethod,
type REST,
type RouteLike,
} from '@discordjs/rest';
import {
populateAbortErrorResponse,
populateGeneralErrorResponse,
populateSuccessfulResponse,
populateRatelimitErrorResponse,
} from '../util/responseHelpers.js';
import type { RequestMethod, REST, RouteLike } from '@discordjs/rest';
import { populateSuccessfulResponse, populateErrorResponse } from '../util/responseHelpers.js';
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
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 {
const discordResponse = await rest.raw({
body: req,
fullRoute,
// This type cast is technically incorrect, but we want Discord to throw Method Not Allowed for us
method: method as RequestMethod,
// We forward the auth header anyway
auth: false,
passThroughBody: true,
query: parsedUrl.searchParams,
headers: {
'Content-Type': req.headers['content-type']!,
},
headers,
});
await populateSuccessfulResponse(res, discordResponse);
} catch (error) {
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 {
// 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
const knownError = populateErrorResponse(res, error);
if (!knownError) {
// 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
throw error;
}

View File

@@ -1,6 +1,6 @@
import type { ServerResponse } from 'node:http';
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';
/**
@@ -59,3 +59,24 @@ export function populateAbortErrorResponse(res: ServerResponse): void {
res.statusCode = 504;
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;
}