Skip to content

Commit

Permalink
refactor(proxy): rely on auth header instead (#9422)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
4 people authored Apr 21, 2023
1 parent 3e01f91 commit a49ed0a
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 32 deletions.
5 changes: 4 additions & 1 deletion packages/proxy-container/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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])
Expand Down
6 changes: 1 addition & 5 deletions packages/proxy-container/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
42 changes: 17 additions & 25 deletions packages/proxy/src/handlers/proxyRequests.ts
Original file line number Diff line number Diff line change
@@ -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';

/**
Expand All @@ -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;
}
Expand Down
23 changes: 22 additions & 1 deletion packages/proxy/src/util/responseHelpers.ts
Original file line number Diff line number Diff line change
@@ -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';

/**
Expand Down Expand Up @@ -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;
}

0 comments on commit a49ed0a

Please sign in to comment.