Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(websocket): use proxy for websocket connections #10149

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ class WebSocketManager extends EventEmitter {
}
if (!this._ws) {
const wsOptions = {
proxyAgentOptions: ws.proxyAgentOptions,
intents: intents.bitfield,
rest: this.client.rest,
token: this.client.token,
Expand Down
8 changes: 8 additions & 0 deletions packages/discord.js/src/util/Options.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const { DefaultRestOptions, DefaultUserAgentAppendix } = require('@discordjs/rest');
const { toSnakeCase } = require('./Transformers');
const { version } = require('../../package.json');
const { HttpsProxyAgentOptions } = require('https-proxy-agent');

// TODO(ckohen): switch order of params so full manager is first and "type" is optional
/**
Expand All @@ -13,6 +14,12 @@ const { version } = require('../../package.json');
* @returns {Collection} A Collection used to store the cache of the manager.
*/

/**
* @typedef {Object} ProxyAgentOptions
* @property {string|URL} [proxy] The proxy URL
* @property {HttpsProxyAgentOptions} [httpsProxyAgentOptions] The options for the proxy agent
*/

/**
* Options for a client.
* @typedef {Object} ClientOptions
Expand Down Expand Up @@ -85,6 +92,7 @@ const { version } = require('../../package.json');
/**
* WebSocket options (these are left as snake_case to match the API)
* @typedef {Object} WebsocketOptions
* @property {ProxyAgentOptions} [proxyAgentOptions] The options for the proxy agent
* @property {number} [large_threshold=50] Number of members in a guild after which offline users will no longer be
* sent in the initial guild member list, must be between 50 and 250
* @property {number} [version=10] The Discord gateway version to use <warn>Changing this can break the library;
Expand Down
2 changes: 2 additions & 0 deletions packages/discord.js/typings/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
IShardingStrategy,
IIdentifyThrottler,
SessionInfo,
ProxyAgentOptions,
} from '@discordjs/ws';
import {
APIActionRowComponent,
Expand Down Expand Up @@ -6777,6 +6778,7 @@ export interface WebhookMessageCreateOptions extends Omit<MessageCreateOptions,
}

export interface WebSocketOptions {
proxyAgentOptions: ProxyAgentOptions;
large_threshold?: number;
version?: number;
buildStrategy?(manager: WSWebSocketManager): IShardingStrategy;
Expand Down
1 change: 1 addition & 0 deletions packages/ws/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
"@types/ws": "^8.5.10",
"@vladfrangu/async_event_emitter": "^2.2.4",
"discord-api-types": "0.37.61",
"https-proxy-agent": "^7.0.4",
"tslib": "^2.6.2",
"ws": "^8.16.0"
},
Expand Down
6 changes: 6 additions & 0 deletions packages/ws/src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import process from 'node:process';
import { Collection } from '@discordjs/collection';
import { lazy } from '@discordjs/util';
import { APIVersion, GatewayOpcodes } from 'discord-api-types/v10';
import type { HttpsProxyAgentOptions } from 'https-proxy-agent';
import { SimpleShardingStrategy } from '../strategies/sharding/SimpleShardingStrategy.js';
import { SimpleIdentifyThrottler } from '../throttling/SimpleIdentifyThrottler.js';
import type { SessionInfo, OptionalWebSocketManagerOptions, WebSocketManager } from '../ws/WebSocketManager.js';
Expand All @@ -25,6 +26,11 @@ export const DefaultDeviceProperty = `@discordjs/ws [VI]{{inject}}[/VI]` as `@di

const getDefaultSessionStore = lazy(() => new Collection<number, SessionInfo | null>());

export interface ProxyAgentOptions {
httpsProxyAgentOptions: HttpsProxyAgentOptions<string>;
proxy: URL | string;
}

/**
* Default options used by the manager
*/
Expand Down
7 changes: 6 additions & 1 deletion packages/ws/src/ws/WebSocketManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import {
} from 'discord-api-types/v10';
import type { IShardingStrategy } from '../strategies/sharding/IShardingStrategy.js';
import type { IIdentifyThrottler } from '../throttling/IIdentifyThrottler.js';
import { DefaultWebSocketManagerOptions, type CompressionMethod, type Encoding } from '../utils/constants.js';
import { DefaultWebSocketManagerOptions } from '../utils/constants.js';
import type { ProxyAgentOptions, CompressionMethod, Encoding } from '../utils/constants.js';
import type { WebSocketShardDestroyOptions, WebSocketShardEvents } from './WebSocketShard.js';

// We put this here because in index.ts WebSocketManager seems to be outputted before polyfillDispose() is called from tsup.
Expand Down Expand Up @@ -127,6 +128,10 @@ export interface OptionalWebSocketManagerOptions {
* Value between 50 and 250, total number of members where the gateway will stop sending offline members in the guild member list
*/
largeThreshold: number | null;
/**
* The proxy agent to use for the WebSocket connections
*/
proxyAgentOptions?: ProxyAgentOptions;
/**
* How long to wait for a shard's READY packet before giving up
*/
Expand Down
7 changes: 7 additions & 0 deletions packages/ws/src/ws/WebSocketShard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
type GatewayReceivePayload,
type GatewaySendPayload,
} from 'discord-api-types/v10';
import { HttpsProxyAgent } from 'https-proxy-agent';
import { WebSocket, type Data } from 'ws';
import type { Inflate } from 'zlib-sync';
import type { IContextFetchingStrategy } from '../strategies/context/IContextFetchingStrategy.js';
Expand Down Expand Up @@ -185,8 +186,14 @@ export class WebSocketShard extends AsyncEventEmitter<WebSocketShardEventsMap> {

this.debug([`Connecting to ${url}`]);

const { proxy, httpsProxyAgentOptions } = this.strategy.options.proxyAgentOptions ?? {
proxy: undefined,
httpsProxyAgentOptions: undefined,
};
const agent = proxy ? new HttpsProxyAgent(proxy, httpsProxyAgentOptions) : undefined;
Comment on lines +189 to +193
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const { proxy, httpsProxyAgentOptions } = this.strategy.options.proxyAgentOptions ?? {
proxy: undefined,
httpsProxyAgentOptions: undefined,
};
const agent = proxy ? new HttpsProxyAgent(proxy, httpsProxyAgentOptions) : undefined;
const { proxy, httpsProxyAgentOptions } = this.strategy.options.proxyAgentOptions ?? {
proxy: undefined,
httpsProxyAgentOptions: undefined,
};
const agent = proxy && new HttpsProxyAgent(proxy, httpsProxyAgentOptions);

const connection = new WebSocketConstructor(url, {
handshakeTimeout: this.strategy.options.handshakeTimeout ?? undefined,
agent,
});

connection.binaryType = 'arraybuffer';
Expand Down
13 changes: 13 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.