feat: @discordjs/proxy (#7925)

Co-authored-by: Parbez <imranbarbhuiya.fsd@gmail.com>
This commit is contained in:
DD
2022-06-04 14:26:25 +03:00
committed by GitHub
parent e518c8a137
commit 1ba2d2a898
26 changed files with 925 additions and 3 deletions

View File

@@ -15,6 +15,7 @@ body:
- builders
- collection
- rest
- proxy
- voice
validations:
required: true

View File

@@ -17,6 +17,7 @@ body:
- builders
- collection
- rest
- proxy
- voice
validations:
required: true

4
.github/labeler.yml vendored
View File

@@ -14,6 +14,10 @@ chore:
- packages/discord.js/*
- packages/discord.js/**/*
'packages:proxy':
- packages/proxy/*
- packages/proxy/**/*
'packages:rest':
- packages/rest/*
- packages/rest/**/*

2
.github/labels.yml vendored
View File

@@ -50,6 +50,8 @@
color: 'fbca04'
- name: 'packages:discord.js'
color: 'fbca04'
- name: 'packages:proxy'
color: 'fbca04'
- name: 'packages:rest'
color: 'fbca04'
- name: 'packages:voice'

View File

@@ -62,7 +62,7 @@ jobs:
max-parallel: 1
fail-fast: false
matrix:
package: ['builders', 'collection', 'discord.js', 'rest', 'voice']
package: ['builders', 'collection', 'discord.js', 'proxy', 'rest', 'voice']
runs-on: ubuntu-latest
env:
BRANCH_NAME: ${{ needs.build.outputs.BRANCH_NAME }}

View File

@@ -16,6 +16,8 @@ jobs:
folder: 'collection'
- package: 'discord.js'
folder: 'discord.js'
- package: '@discordjs/proxy'
folder: 'proxy'
- package: '@discordjs/rest'
folder: 'rest'
- package: '@discordjs/voice'

View File

@@ -0,0 +1,11 @@
{
"root": true,
"extends": "marine/prettier/node",
"parserOptions": {
"project": "./tsconfig.eslint.json"
},
"ignorePatterns": ["**/dist/*"],
"env": {
"jest": true
}
}

32
packages/proxy/.gitignore vendored Normal file
View File

@@ -0,0 +1,32 @@
# Packages
node_modules/
# Log files
logs/
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Env
.env
# Dist
dist/
typings/
docs/**/*
!docs/index.yml
!docs/README.md
# Miscellaneous
.tmp/
coverage/
tsconfig.tsbuildinfo
.turbo
# Yarn files
.yarn/install-state.gz
.yarn/build-state.yml

View File

@@ -0,0 +1,8 @@
{
"printWidth": 120,
"useTabs": true,
"singleQuote": true,
"quoteProps": "as-needed",
"trailingComma": "all",
"endOfLine": "lf"
}

191
packages/proxy/LICENSE Normal file
View File

@@ -0,0 +1,191 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2022 Noel Buechler
Copyright 2022 Charlotte Cristea
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

46
packages/proxy/README.md Normal file
View File

@@ -0,0 +1,46 @@
<div align="center">
<br />
<p>
<a href="https://discord.js.org"><img src="https://discord.js.org/static/logo.svg" width="546" alt="discord.js" /></a>
</p>
<br />
<p>
<a href="https://discord.gg/djs"><img src="https://img.shields.io/discord/222078108977594368?color=5865F2&logo=discord&logoColor=white" alt="Discord server" /></a>
<a href="https://www.npmjs.com/package/@discordjs/proxy"><img src="https://img.shields.io/npm/v/@discordjs/proxy.svg?maxAge=3600" alt="npm version" /></a>
<a href="https://www.npmjs.com/package/@discordjs/proxy"><img src="https://img.shields.io/npm/dt/@discordjs/proxy.svg?maxAge=3600" alt="npm downloads" /></a>
<a href="https://github.com/discordjs/discord.js/actions"><img src="https://github.com/discordjs/discord.js/actions/workflows/test.yml/badge.svg" alt="Build status" /></a>
</p>
</div>
## About
`@discordjs/proxy` is a powerful wrapper around `@discordjs/rest` for running an HTTP proxy in front of Discord's API
## Installation
**Node.js 16.9.0 or newer is required.**
```sh-session
npm install @discordjs/proxy
yarn add @discordjs/proxy
pnpm add @discordjs/proxy
```
## Links
- [Website](https://discord.js.org/) ([source](https://github.com/discordjs/website))
- [Documentation](https://discord.js.org/#/docs/proxy)
- [discord.js Discord server](https://discord.gg/djs)
- [GitHub](https://github.com/discordjs/discord.js/tree/main/packages/proxy)
- [npm](https://www.npmjs.com/package/@discordjs/proxy)
## Contributing
Before creating an issue, please ensure that it hasn't already been reported/suggested, and double-check the
[documentation](https://discord.js.org/#/docs/proxy).
See [the contribution guide](https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md) if you'd like to submit a PR.
## Help
If you don't understand something in the documentation, you are experiencing problems, or you just need a gentle
nudge in the right direction, please don't hesitate to join our official [discord.js Server](https://discord.gg/djs).

View File

@@ -0,0 +1,83 @@
import { createServer } from 'node:http';
import { REST } from '@discordjs/rest';
import supertest from 'supertest';
import { MockAgent, Interceptable, setGlobalDispatcher } from 'undici';
import type { MockInterceptor } from 'undici/types/mock-interceptor';
import { proxyRequests } from '../src';
let mockAgent: MockAgent;
let mockPool: Interceptable;
const responseOptions: MockInterceptor.MockResponseOptions = {
headers: {
'content-type': 'application/json',
},
};
const api = new REST().setToken('A-Very-Fake-Token');
// eslint-disable-next-line @typescript-eslint/no-misused-promises
const server = createServer(proxyRequests(api));
beforeEach(() => {
mockAgent = new MockAgent();
mockAgent.disableNetConnect(); // prevent actual requests to Discord
setGlobalDispatcher(mockAgent); // enabled the mock client to intercept requests
mockPool = mockAgent.get('https://discord.com');
});
afterEach(async () => {
await mockAgent.close();
});
afterAll(() => {
server.close();
});
test('simple GET', async () => {
mockPool
.intercept({
path: '/api/v10/simpleGet',
method: 'GET',
})
.reply(() => ({
data: { test: true },
statusCode: 200,
responseOptions: {
...responseOptions,
headers: {
...responseOptions.headers,
'x-ratelimit-limit': '10',
},
},
}));
const res = await supertest(server).get('/api/v10/simpleGet');
const headers = res.headers as Record<string, string>;
expect(headers['content-type']).toEqual(expect.stringMatching(/^application\/json/));
// Ratelimit headers should be dropped
expect(headers).not.toHaveProperty('x-ratelimit-limit');
expect(res.statusCode).toBe(200);
expect(res.body).toStrictEqual({ test: true });
});
test('failed request', async () => {
mockPool
.intercept({
path: '/api/v10/simpleGet',
method: 'GET',
})
.reply(() => ({
data: { code: 404, message: 'Not Found' },
statusCode: 404,
responseOptions,
}));
const res = await supertest(server).get('/api/v10/simpleGet');
const headers = res.headers as Record<string, string>;
expect(headers['content-type']).toEqual(expect.stringMatching(/^application\/json/));
expect(res.statusCode).toBe(404);
expect(res.body).toStrictEqual({ code: 404, message: 'Not Found' });
});

View File

@@ -0,0 +1,17 @@
/**
* @type {import('@babel/core').TransformOptions}
*/
module.exports = {
parserOpts: { strictMode: true },
sourceMaps: 'inline',
presets: [
[
'@babel/preset-env',
{
targets: { node: 'current' },
modules: 'commonjs',
},
],
'@babel/preset-typescript',
],
};

65
packages/proxy/cliff.toml Normal file
View File

@@ -0,0 +1,65 @@
[changelog]
header = """
# Changelog
All notable changes to this project will be documented in this file.\n
"""
body = """
{% if version %}\
# [{{ version | trim_start_matches(pat="v") }}]\
{% if previous %}\
{% if previous.version %}\
(https://github.com/discordjs/discord.js/compare/{{ previous.version }}...{{ version }})\
{% else %}
(https://github.com/discordjs/discord.js/tree/{{ version }})\
{% endif %}\
{% endif %} \
- ({{ timestamp | date(format="%Y-%m-%d") }})
{% else %}\
# [unreleased]
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
## {{ group | upper_first }}
{% for commit in commits %}
- {% if commit.scope %}\
**{{commit.scope}}:** \
{% endif %}\
{{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}](https://github.com/discordjs/discord.js/commit/{{ commit.id }}))\
{% if commit.breaking %}\
\n\n {% raw %} {% endraw %} ### Breaking Changes:\n \
{% for breakingChange in commit.footers %}\
{% raw %} {% endraw %} - {{ breakingChange }}\n\
{% endfor %}\
{% endif %}\
{% endfor %}
{% endfor %}\n
"""
trim = true
footer = ""
[git]
conventional_commits = true
filter_unconventional = true
commit_parsers = [
{ message = "^feat", group = "Features"},
{ message = "^fix", group = "Bug Fixes"},
{ message = "^docs", group = "Documentation"},
{ message = "^perf", group = "Performance"},
{ message = "^refactor", group = "Refactor"},
{ message = "^typings", group = "Typings"},
{ message = "^types", group = "Typings"},
{ message = ".*deprecated", body = ".*deprecated", group = "Deprecation"},
{ message = "^revert", skip = true},
{ message = "^style", group = "Styling"},
{ message = "^test", group = "Testing"},
{ message = "^chore", skip = true},
{ message = "^ci", skip = true},
{ message = "^build", skip = true},
{ body = ".*security", group = "Security"},
]
filter_commits = true
tag_pattern = "@discordjs\\/proxy@.*"
skip_tags = "v[0-9]*|11|12"
ignore_tags = ""
topo_order = false
sort_commits = "newest"

View File

@@ -0,0 +1 @@
## [View the documentation here.](https://discord.js.org/#/docs/proxy)

View File

@@ -0,0 +1,5 @@
- name: General
files:
- name: Welcome
id: welcome
path: ../../README.md

View File

@@ -0,0 +1,11 @@
/**
* @type {import('@jest/types').Config.InitialOptions}
*/
module.exports = {
testMatch: ['**/+(*.)+(spec|test).+(ts|js)?(x)'],
testEnvironment: 'node',
collectCoverage: true,
collectCoverageFrom: ['src/**/*.ts'],
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov', 'clover'],
};

View File

@@ -0,0 +1,89 @@
{
"name": "@discordjs/proxy",
"version": "0.1.0-dev",
"description": "Tools for running an HTTP proxy for Discord's API",
"scripts": {
"build": "tsup && tsc --emitDeclarationOnly --incremental",
"test": "jest --pass-with-no-tests --collect-coverage",
"lint": "prettier --check . && eslint src __tests__ --ext mjs,js,ts",
"format": "prettier --write . && eslint src __tests__ --ext mjs,js,ts --fix",
"docs": "typedoc --json docs/typedoc-out.json src/index.ts && node scripts/docs.mjs",
"prepublishOnly": "yarn build && yarn lint && yarn test",
"changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/proxy/*'"
},
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"typings": "./dist/index.d.ts",
"exports": {
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"directories": {
"lib": "src",
"test": "__tests__"
},
"files": [
"dist"
],
"contributors": [
"Crawl <icrawltogo@gmail.com>",
"Amish Shah <amishshah.2k@gmail.com>",
"SpaceEEC <spaceeec@yahoo.com>",
"Vlad Frangu <kingdgrizzle@gmail.com>",
"Antonio Roman <kyradiscord@gmail.com>",
"DD <didinele.dev@gmail.com>"
],
"license": "Apache-2.0",
"keywords": [
"discord",
"api",
"rest",
"proxy",
"discordapp",
"discordjs"
],
"repository": {
"type": "git",
"url": "git+https://github.com/discordjs/discord.js.git"
},
"bugs": {
"url": "https://github.com/discordjs/discord.js/issues"
},
"homepage": "https://discord.js.org",
"dependencies": {
"@discordjs/rest": "workspace:^",
"tslib": "^2.4.0",
"undici": "^5.4.0"
},
"devDependencies": {
"@babel/core": "^7.18.2",
"@babel/plugin-proposal-decorators": "^7.18.2",
"@babel/preset-env": "^7.18.2",
"@babel/preset-typescript": "^7.17.12",
"@discordjs/ts-docgen": "^0.4.1",
"@types/jest": "^28.1.0",
"@types/node": "^16.11.38",
"@types/supertest": "^2.0.12",
"@typescript-eslint/eslint-plugin": "^5.27.0",
"@typescript-eslint/parser": "^5.27.0",
"babel-plugin-const-enum": "^1.2.0",
"babel-plugin-transform-typescript-metadata": "^0.3.2",
"eslint": "^8.17.0",
"eslint-config-marine": "^9.4.1",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"jest": "^28.1.0",
"prettier": "^2.6.2",
"supertest": "^6.2.3",
"tsup": "^6.0.1",
"typedoc": "^0.22.17",
"typescript": "^4.7.3"
},
"engines": {
"node": ">=16.9.0"
},
"publishConfig": {
"access": "public"
}
}

View File

@@ -0,0 +1,55 @@
import { URL } from 'node:url';
import { DiscordAPIError, HTTPError, RateLimitError, RequestMethod, REST, RouteLike } from '@discordjs/rest';
import {
populateAbortErrorResponse,
populateGeneralErrorResponse,
populateSuccessfulResponse,
populateRatelimitErrorResponse,
} from '../util/responseHelpers';
import type { RequestHandler } from '../util/util';
/**
* Creates an HTTP handler used to forward requests to Discord
* @param rest REST instance to use for the requests
*/
export function proxyRequests(rest: REST): RequestHandler {
return async (req, res) => {
const { method, url } = req;
if (!method || !url) {
throw new TypeError(
'Invalid request. Missing method and/or url, implying that this is not a Server IncomingMesage',
);
}
// The 2nd parameter is here so the URL constructor doesn't complain about an "invalid url" when the origin is missing
// we don't actually care about the origin and the value passed is irrelevant
const fullRoute = new URL(url, 'http://noop').pathname.replace(/^\/api(\/v\d+)?/, '') as RouteLike;
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,
passThroughBody: true,
});
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 erorrs. 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;
}
} finally {
res.end();
}
};
}

View File

@@ -0,0 +1,3 @@
export * from './handlers/proxyRequests';
export * from './util/responseHelpers';
export { RequestHandler } from './util/util';

View File

@@ -0,0 +1,54 @@
import type { ServerResponse } from 'node:http';
import { pipeline } from 'node:stream/promises';
import type { DiscordAPIError, HTTPError, RateLimitError } from '@discordjs/rest';
import type { Dispatcher } from 'undici';
/**
* Populates a server response with the data from a Discord 2xx REST response
* @param res The server response to populate
* @param data The data to populate the response with
*/
export async function populateSuccessfulResponse(res: ServerResponse, data: Dispatcher.ResponseData): Promise<void> {
res.statusCode = data.statusCode;
for (const header of Object.keys(data.headers)) {
// Strip ratelimit headers
if (header.startsWith('x-ratelimit')) {
continue;
}
res.setHeader(header, data.headers[header]!);
}
await pipeline(data.body, res);
}
/**
* Populates a server response with the data from a Discord non-2xx REST response that is NOT a 429
* @param res The server response to populate
* @param error The error to populate the response with
*/
export function populateGeneralErrorResponse(res: ServerResponse, error: DiscordAPIError | HTTPError): void {
res.statusCode = error.status;
res.statusMessage = error.message;
if ('rawError' in error) {
res.setHeader('Content-Type', 'application/json');
res.write(JSON.stringify(error.rawError));
}
}
/**
* Populates a server response with the data from a Discord 429 REST response
* @param res The server response to populate
* @param error The error to populate the response with
*/
export function populateRatelimitErrorResponse(res: ServerResponse, error: RateLimitError): void {
res.statusCode = 429;
res.setHeader('Retry-After', error.timeToReset / 1000);
}
export function populateAbortErrorResponse(res: ServerResponse): void {
res.statusCode = 504;
res.statusMessage = 'Upstream timed out';
}

View File

@@ -0,0 +1,10 @@
import type { IncomingMessage, ServerResponse } from 'node:http';
/**
* Represents a potentially awaitable value
*/
export type Awaitable<T> = T | PromiseLike<T>;
/**
* Represents a simple HTTP request handler
*/
export type RequestHandler = (req: IncomingMessage, res: ServerResponse) => Awaitable<void>;

View File

@@ -0,0 +1,20 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"allowJs": true
},
"include": [
"**/*.ts",
"**/*.tsx",
"**/*.js",
"**/*.mjs",
"**/*.jsx",
"**/*.test.ts",
"**/*.test.js",
"**/*.test.mjs",
"**/*.spec.ts",
"**/*.spec.js",
"**/*.spec.mjs"
],
"exclude": []
}

View File

@@ -0,0 +1,4 @@
{
"extends": "../../tsconfig.json",
"include": ["src/**/*.ts"]
}

View File

@@ -0,0 +1,20 @@
import { defineConfig } from 'tsup';
export default defineConfig({
clean: true,
dts: true,
entryPoints: ['src/index.ts'],
format: ['esm', 'cjs'],
minify: false,
keepNames: true,
skipNodeModulesBundle: true,
sourcemap: true,
target: 'es2021',
esbuildOptions: (options, context) => {
if (context.format === 'cjs') {
options.banner = {
js: '"use strict";',
};
}
},
});

191
yarn.lock
View File

@@ -1947,6 +1947,38 @@ __metadata:
languageName: node
linkType: hard
"@discordjs/proxy@workspace:packages/proxy":
version: 0.0.0-use.local
resolution: "@discordjs/proxy@workspace:packages/proxy"
dependencies:
"@babel/core": ^7.18.2
"@babel/plugin-proposal-decorators": ^7.18.2
"@babel/preset-env": ^7.18.2
"@babel/preset-typescript": ^7.17.12
"@discordjs/rest": "workspace:^"
"@discordjs/ts-docgen": ^0.4.1
"@types/jest": ^28.1.0
"@types/node": ^16.11.38
"@types/supertest": ^2.0.12
"@typescript-eslint/eslint-plugin": ^5.27.0
"@typescript-eslint/parser": ^5.27.0
babel-plugin-const-enum: ^1.2.0
babel-plugin-transform-typescript-metadata: ^0.3.2
eslint: ^8.17.0
eslint-config-marine: ^9.4.1
eslint-config-prettier: ^8.5.0
eslint-plugin-import: ^2.26.0
jest: ^28.1.0
prettier: ^2.6.2
supertest: ^6.2.3
tslib: ^2.4.0
tsup: ^6.0.1
typedoc: ^0.22.17
typescript: ^4.7.3
undici: ^5.4.0
languageName: unknown
linkType: soft
"@discordjs/rest@workspace:^, @discordjs/rest@workspace:packages/rest":
version: 0.0.0-use.local
resolution: "@discordjs/rest@workspace:packages/rest"
@@ -2640,6 +2672,13 @@ __metadata:
languageName: node
linkType: hard
"@types/cookiejar@npm:*":
version: 2.1.2
resolution: "@types/cookiejar@npm:2.1.2"
checksum: f6e1903454007f86edd6c3520cbb4d553e1d4e17eaf1f77f6f75e3270f48cc828d74397a113a36942f5fe52f9fa71067bcfa738f53ad468fcca0bc52cb1cbd28
languageName: node
linkType: hard
"@types/eslint@npm:^7.2.13":
version: 7.29.0
resolution: "@types/eslint@npm:7.29.0"
@@ -2785,6 +2824,25 @@ __metadata:
languageName: node
linkType: hard
"@types/superagent@npm:*":
version: 4.1.15
resolution: "@types/superagent@npm:4.1.15"
dependencies:
"@types/cookiejar": "*"
"@types/node": "*"
checksum: 347cd74ef0a29e6b9c6d32253c3fb0dd39a31618b50752f84d36b6a9246237bb6b68c9b436c1f94adabc2df89d9f1939e4782f4c850f98b9c2fe431ad4e565a4
languageName: node
linkType: hard
"@types/supertest@npm:^2.0.12":
version: 2.0.12
resolution: "@types/supertest@npm:2.0.12"
dependencies:
"@types/superagent": "*"
checksum: f0e2b44f86bec2f708d6a3d0cb209055b487922040773049b0f8c6b557af52d4b5fa904e17dfaa4ce6e610172206bbec7b62420d158fa57b6ffc2de37b1730d3
languageName: node
linkType: hard
"@types/ws@npm:^8.5.3":
version: 8.5.3
resolution: "@types/ws@npm:8.5.3"
@@ -3269,6 +3327,13 @@ __metadata:
languageName: node
linkType: hard
"asap@npm:^2.0.0":
version: 2.0.6
resolution: "asap@npm:2.0.6"
checksum: b296c92c4b969e973260e47523207cd5769abd27c245a68c26dc7a0fe8053c55bb04360237cb51cab1df52be939da77150ace99ad331fb7fb13b3423ed73ff3d
languageName: node
linkType: hard
"asn1@npm:~0.2.3":
version: 0.2.6
resolution: "asn1@npm:0.2.6"
@@ -3988,7 +4053,7 @@ __metadata:
languageName: node
linkType: hard
"combined-stream@npm:^1.0.6, combined-stream@npm:~1.0.6":
"combined-stream@npm:^1.0.6, combined-stream@npm:^1.0.8, combined-stream@npm:~1.0.6":
version: 1.0.8
resolution: "combined-stream@npm:1.0.8"
dependencies:
@@ -4079,6 +4144,13 @@ __metadata:
languageName: node
linkType: hard
"component-emitter@npm:^1.3.0":
version: 1.3.0
resolution: "component-emitter@npm:1.3.0"
checksum: b3c46de38ffd35c57d1c02488355be9f218e582aec72d72d1b8bbec95a3ac1b38c96cd6e03ff015577e68f550fbb361a3bfdbd9bb248be9390b7b3745691be6b
languageName: node
linkType: hard
"concat-map@npm:0.0.1":
version: 0.0.1
resolution: "concat-map@npm:0.0.1"
@@ -4316,6 +4388,13 @@ __metadata:
languageName: node
linkType: hard
"cookiejar@npm:^2.1.3":
version: 2.1.3
resolution: "cookiejar@npm:2.1.3"
checksum: 88259983ebc52ceb23cdacfa48762b6a518a57872eff1c7ed01d214fff5cf492e2660d7d5c04700a28f1787a76811df39e8639f8e17670b3cf94ecd86e161f07
languageName: node
linkType: hard
"core-js-compat@npm:^3.20.0":
version: 3.20.2
resolution: "core-js-compat@npm:3.20.2"
@@ -4564,6 +4643,16 @@ __metadata:
languageName: node
linkType: hard
"dezalgo@npm:1.0.3":
version: 1.0.3
resolution: "dezalgo@npm:1.0.3"
dependencies:
asap: ^2.0.0
wrappy: 1
checksum: 8b26238db91423b2702a7a6d9629d0019c37c415e7b6e75d4b3e8d27e9464e21cac3618dd145f4d4ee96c70cc6ff034227b5b8a0e9c09015a8bdbe6dace3cfb9
languageName: node
linkType: hard
"diff-sequences@npm:^27.4.0":
version: 27.4.0
resolution: "diff-sequences@npm:27.4.0"
@@ -5558,6 +5647,13 @@ dts-critic@latest:
languageName: node
linkType: hard
"fast-safe-stringify@npm:^2.1.1":
version: 2.1.1
resolution: "fast-safe-stringify@npm:2.1.1"
checksum: a851cbddc451745662f8f00ddb622d6766f9bd97642dabfd9a405fb0d646d69fc0b9a1243cbf67f5f18a39f40f6fa821737651ff1bceeba06c9992ca2dc5bd3d
languageName: node
linkType: hard
"fastq@npm:^1.6.0":
version: 1.13.0
resolution: "fastq@npm:1.13.0"
@@ -5721,6 +5817,17 @@ dts-critic@latest:
languageName: node
linkType: hard
"form-data@npm:^4.0.0":
version: 4.0.0
resolution: "form-data@npm:4.0.0"
dependencies:
asynckit: ^0.4.0
combined-stream: ^1.0.8
mime-types: ^2.1.12
checksum: 01135bf8675f9d5c61ff18e2e2932f719ca4de964e3be90ef4c36aacfc7b9cb2fceb5eca0b7e0190e3383fe51c5b37f4cb80b62ca06a99aaabfcfd6ac7c9328c
languageName: node
linkType: hard
"form-data@npm:~2.3.2":
version: 2.3.3
resolution: "form-data@npm:2.3.3"
@@ -5732,6 +5839,18 @@ dts-critic@latest:
languageName: node
linkType: hard
"formidable@npm:^2.0.1":
version: 2.0.1
resolution: "formidable@npm:2.0.1"
dependencies:
dezalgo: 1.0.3
hexoid: 1.0.0
once: 1.4.0
qs: 6.9.3
checksum: b35445444e7b6f6f3cacbadd5e6fadd6b5b2e83162e7c41fa22586df584cc515bbd1ee0dc2b701ce031fcb000d71769bc77bd0958db8a89a0ceb8b2227bdc695
languageName: node
linkType: hard
"fs-constants@npm:^1.0.0":
version: 1.0.0
resolution: "fs-constants@npm:1.0.0"
@@ -6230,6 +6349,13 @@ dts-critic@latest:
languageName: node
linkType: hard
"hexoid@npm:1.0.0":
version: 1.0.0
resolution: "hexoid@npm:1.0.0"
checksum: 27a148ca76a2358287f40445870116baaff4a0ed0acc99900bf167f0f708ffd82e044ff55e9949c71963852b580fc024146d3ac6d5d76b508b78d927fa48ae2d
languageName: node
linkType: hard
"hosted-git-info@npm:^2.1.4":
version: 2.8.9
resolution: "hosted-git-info@npm:2.8.9"
@@ -7983,6 +8109,13 @@ dts-critic@latest:
languageName: node
linkType: hard
"methods@npm:^1.1.2":
version: 1.1.2
resolution: "methods@npm:1.1.2"
checksum: 0917ff4041fa8e2f2fda5425a955fe16ca411591fbd123c0d722fcf02b73971ed6f764d85f0a6f547ce49ee0221ce2c19a5fa692157931cecb422984f1dcd13a
languageName: node
linkType: hard
"micromatch@npm:^4.0.4":
version: 4.0.4
resolution: "micromatch@npm:4.0.4"
@@ -8009,6 +8142,15 @@ dts-critic@latest:
languageName: node
linkType: hard
"mime@npm:^2.5.0":
version: 2.6.0
resolution: "mime@npm:2.6.0"
bin:
mime: cli.js
checksum: 1497ba7b9f6960694268a557eae24b743fd2923da46ec392b042469f4b901721ba0adcf8b0d3c2677839d0e243b209d76e5edcbd09cfdeffa2dfb6bb4df4b862
languageName: node
linkType: hard
"mimic-fn@npm:^2.1.0":
version: 2.1.0
resolution: "mimic-fn@npm:2.1.0"
@@ -8499,7 +8641,7 @@ dts-critic@latest:
languageName: node
linkType: hard
"once@npm:^1.3.0, once@npm:^1.4.0":
"once@npm:1.4.0, once@npm:^1.3.0, once@npm:^1.4.0":
version: 1.4.0
resolution: "once@npm:1.4.0"
dependencies:
@@ -8959,6 +9101,22 @@ dts-critic@latest:
languageName: node
linkType: hard
"qs@npm:6.9.3":
version: 6.9.3
resolution: "qs@npm:6.9.3"
checksum: 89cd1b5e521c19a7e0a7a056ddc261c5c30889664608cf9ce6085f9f25606fc48568cf6a6249e641b4b5c04dac7889e3b82133142523abf397228eb4f488fc38
languageName: node
linkType: hard
"qs@npm:^6.10.3":
version: 6.10.3
resolution: "qs@npm:6.10.3"
dependencies:
side-channel: ^1.0.4
checksum: 0fac5e6c7191d0295a96d0e83c851aeb015df7e990e4d3b093897d3ac6c94e555dbd0a599739c84d7fa46d7fee282d94ba76943983935cf33bba6769539b8019
languageName: node
linkType: hard
"qs@npm:~6.5.2":
version: 6.5.2
resolution: "qs@npm:6.5.2"
@@ -10005,6 +10163,35 @@ dts-critic@latest:
languageName: node
linkType: hard
"superagent@npm:^7.1.3":
version: 7.1.3
resolution: "superagent@npm:7.1.3"
dependencies:
component-emitter: ^1.3.0
cookiejar: ^2.1.3
debug: ^4.3.4
fast-safe-stringify: ^2.1.1
form-data: ^4.0.0
formidable: ^2.0.1
methods: ^1.1.2
mime: ^2.5.0
qs: ^6.10.3
readable-stream: ^3.6.0
semver: ^7.3.7
checksum: 436045d555d35c282de7bcba85102b1421470bdc80781c9a0b7ab7c639675b4eca026a71301974935f3de0d33782a0392274e24f3915335b81a78a04b48eeee5
languageName: node
linkType: hard
"supertest@npm:^6.2.3":
version: 6.2.3
resolution: "supertest@npm:6.2.3"
dependencies:
methods: ^1.1.2
superagent: ^7.1.3
checksum: c1bed86c31723a4bc461153a58176fd80d675deb7d23ab7bd170213040673b35c38e3cbeab9a4eb8a325cf736176c08c6f6522e42f0293314f183e192a6681fa
languageName: node
linkType: hard
"supports-color@npm:^2.0.0":
version: 2.0.0
resolution: "supports-color@npm:2.0.0"