mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-09 16:13:31 +01:00
refactor(WebSocketManager)!: remove deprecated rest option (#10998)
BREAKING CHANGE: The `rest` option in the `WebSocketManager` constructor has been removed. Pass a `fetchGatewayInformation` function instead.
This commit is contained in:
@@ -37,9 +37,16 @@ pnpm add @discordjs/core
|
|||||||
These examples use [ES modules](https://nodejs.org/api/esm.html#enabling).
|
These examples use [ES modules](https://nodejs.org/api/esm.html#enabling).
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
|
import {
|
||||||
|
Client,
|
||||||
|
GatewayDispatchEvents,
|
||||||
|
GatewayIntentBits,
|
||||||
|
InteractionType,
|
||||||
|
MessageFlags,
|
||||||
|
type RESTGetAPIGatewayBotResult,
|
||||||
|
} from '@discordjs/core';
|
||||||
import { REST } from '@discordjs/rest';
|
import { REST } from '@discordjs/rest';
|
||||||
import { WebSocketManager } from '@discordjs/ws';
|
import { WebSocketManager } from '@discordjs/ws';
|
||||||
import { GatewayDispatchEvents, GatewayIntentBits, InteractionType, MessageFlags, Client } from '@discordjs/core';
|
|
||||||
|
|
||||||
// Create REST and WebSocket managers directly
|
// Create REST and WebSocket managers directly
|
||||||
const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN);
|
const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN);
|
||||||
@@ -47,7 +54,7 @@ const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN);
|
|||||||
const gateway = new WebSocketManager({
|
const gateway = new WebSocketManager({
|
||||||
token: process.env.DISCORD_TOKEN,
|
token: process.env.DISCORD_TOKEN,
|
||||||
intents: GatewayIntentBits.GuildMessages | GatewayIntentBits.MessageContent,
|
intents: GatewayIntentBits.GuildMessages | GatewayIntentBits.MessageContent,
|
||||||
rest,
|
fetchGatewayInformation: () => rest.get('/gateway/bot') as Promise<RESTGetAPIGatewayBotResult>,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create a client to emit relevant events.
|
// Create a client to emit relevant events.
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ class Client extends BaseClient {
|
|||||||
const wsOptions = {
|
const wsOptions = {
|
||||||
...this.options.ws,
|
...this.options.ws,
|
||||||
intents: this.options.intents.bitfield,
|
intents: this.options.intents.bitfield,
|
||||||
rest: this.rest,
|
fetchGatewayInformation: () => this.rest.get(Routes.gatewayBot()),
|
||||||
// Explicitly nulled to always be set using `setToken` in `login`
|
// Explicitly nulled to always be set using `setToken` in `login`
|
||||||
token: null,
|
token: null,
|
||||||
};
|
};
|
||||||
|
|||||||
12
packages/ws/__tests__/gateway.mock.ts
Normal file
12
packages/ws/__tests__/gateway.mock.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import type { RESTGetAPIGatewayBotResult } from 'discord-api-types/v10';
|
||||||
|
|
||||||
|
export const mockGatewayInformation: RESTGetAPIGatewayBotResult = {
|
||||||
|
shards: 1,
|
||||||
|
session_start_limit: {
|
||||||
|
max_concurrency: 3,
|
||||||
|
reset_after: 60,
|
||||||
|
remaining: 3,
|
||||||
|
total: 3,
|
||||||
|
},
|
||||||
|
url: 'wss://gateway.discord.gg',
|
||||||
|
};
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
/* eslint-disable @typescript-eslint/consistent-type-imports */
|
/* eslint-disable @typescript-eslint/consistent-type-imports */
|
||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { REST } from '@discordjs/rest';
|
|
||||||
import { MockAgent, type Interceptable } from 'undici';
|
|
||||||
import { beforeEach, test, vi, expect } from 'vitest';
|
import { beforeEach, test, vi, expect } from 'vitest';
|
||||||
import {
|
import {
|
||||||
managerToFetchingStrategyOptions,
|
managerToFetchingStrategyOptions,
|
||||||
@@ -12,15 +10,7 @@ import {
|
|||||||
type WorkerReceivePayload,
|
type WorkerReceivePayload,
|
||||||
type WorkerSendPayload,
|
type WorkerSendPayload,
|
||||||
} from '../../src/index.js';
|
} from '../../src/index.js';
|
||||||
|
import { mockGatewayInformation } from '../gateway.mock.js';
|
||||||
let mockAgent: MockAgent;
|
|
||||||
let mockPool: Interceptable;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
mockAgent = new MockAgent();
|
|
||||||
mockAgent.disableNetConnect();
|
|
||||||
mockPool = mockAgent.get('https://discord.com');
|
|
||||||
});
|
|
||||||
|
|
||||||
const session = {
|
const session = {
|
||||||
shardId: 0,
|
shardId: 0,
|
||||||
@@ -52,32 +42,13 @@ vi.mock('node:worker_threads', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('session info', async () => {
|
test('session info', async () => {
|
||||||
const rest = new REST().setAgent(mockAgent).setToken('A-Very-Fake-Token');
|
const manager = new WebSocketManager({
|
||||||
const manager = new WebSocketManager({ token: 'A-Very-Fake-Token', intents: 0, rest });
|
token: 'A-Very-Fake-Token',
|
||||||
|
intents: 0,
|
||||||
mockPool
|
async fetchGatewayInformation() {
|
||||||
.intercept({
|
return mockGatewayInformation;
|
||||||
path: '/api/v10/gateway/bot',
|
},
|
||||||
method: 'GET',
|
});
|
||||||
})
|
|
||||||
.reply(() => ({
|
|
||||||
data: {
|
|
||||||
shards: 1,
|
|
||||||
session_start_limit: {
|
|
||||||
max_concurrency: 3,
|
|
||||||
reset_after: 60,
|
|
||||||
remaining: 3,
|
|
||||||
total: 3,
|
|
||||||
},
|
|
||||||
url: 'wss://gateway.discord.gg',
|
|
||||||
},
|
|
||||||
statusCode: 200,
|
|
||||||
responseOptions: {
|
|
||||||
headers: {
|
|
||||||
'content-type': 'application/json',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const strategy = new WorkerContextFetchingStrategy(await managerToFetchingStrategyOptions(manager));
|
const strategy = new WorkerContextFetchingStrategy(await managerToFetchingStrategyOptions(manager));
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
/* eslint-disable id-length */
|
/* eslint-disable id-length */
|
||||||
import { setImmediate } from 'node:timers';
|
import { setImmediate } from 'node:timers';
|
||||||
import { REST } from '@discordjs/rest';
|
import type { GatewayDispatchPayload, GatewaySendPayload } from 'discord-api-types/v10';
|
||||||
import type { RESTGetAPIGatewayBotResult, GatewayDispatchPayload, GatewaySendPayload } from 'discord-api-types/v10';
|
import { GatewayDispatchEvents, GatewayOpcodes } from 'discord-api-types/v10';
|
||||||
import { GatewayDispatchEvents, GatewayOpcodes, Routes } from 'discord-api-types/v10';
|
import { test, vi, expect, afterEach } from 'vitest';
|
||||||
import { MockAgent, type Interceptable } from 'undici';
|
|
||||||
import { beforeEach, test, vi, expect, afterEach } from 'vitest';
|
|
||||||
import {
|
import {
|
||||||
WebSocketManager,
|
WebSocketManager,
|
||||||
WorkerSendPayloadOp,
|
WorkerSendPayloadOp,
|
||||||
@@ -15,9 +13,7 @@ import {
|
|||||||
type WorkerSendPayload,
|
type WorkerSendPayload,
|
||||||
type SessionInfo,
|
type SessionInfo,
|
||||||
} from '../../src/index.js';
|
} from '../../src/index.js';
|
||||||
|
import { mockGatewayInformation } from '../gateway.mock.js';
|
||||||
let mockAgent: MockAgent;
|
|
||||||
let mockPool: Interceptable;
|
|
||||||
|
|
||||||
const mockConstructor = vi.fn();
|
const mockConstructor = vi.fn();
|
||||||
const mockSend = vi.fn();
|
const mockSend = vi.fn();
|
||||||
@@ -135,12 +131,6 @@ vi.mock('node:worker_threads', async () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
mockAgent = new MockAgent();
|
|
||||||
mockAgent.disableNetConnect();
|
|
||||||
mockPool = mockAgent.get('https://discord.com');
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
mockConstructor.mockClear();
|
mockConstructor.mockClear();
|
||||||
mockSend.mockClear();
|
mockSend.mockClear();
|
||||||
@@ -148,15 +138,13 @@ afterEach(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('spawn, connect, send a message, session info, and destroy', async () => {
|
test('spawn, connect, send a message, session info, and destroy', async () => {
|
||||||
const rest = new REST().setAgent(mockAgent).setToken('A-Very-Fake-Token');
|
|
||||||
|
|
||||||
const mockRetrieveSessionInfo = vi.fn();
|
const mockRetrieveSessionInfo = vi.fn();
|
||||||
const mockUpdateSessionInfo = vi.fn();
|
const mockUpdateSessionInfo = vi.fn();
|
||||||
const manager = new WebSocketManager({
|
const manager = new WebSocketManager({
|
||||||
token: 'A-Very-Fake-Token',
|
token: 'A-Very-Fake-Token',
|
||||||
intents: 0,
|
intents: 0,
|
||||||
async fetchGatewayInformation() {
|
async fetchGatewayInformation() {
|
||||||
return rest.get(Routes.gatewayBot()) as Promise<RESTGetAPIGatewayBotResult>;
|
return mockGatewayInformation;
|
||||||
},
|
},
|
||||||
shardIds: [0, 1],
|
shardIds: [0, 1],
|
||||||
retrieveSessionInfo: mockRetrieveSessionInfo,
|
retrieveSessionInfo: mockRetrieveSessionInfo,
|
||||||
@@ -166,30 +154,6 @@ test('spawn, connect, send a message, session info, and destroy', async () => {
|
|||||||
|
|
||||||
const managerEmitSpy = vi.spyOn(manager, 'emit');
|
const managerEmitSpy = vi.spyOn(manager, 'emit');
|
||||||
|
|
||||||
mockPool
|
|
||||||
.intercept({
|
|
||||||
path: '/api/v10/gateway/bot',
|
|
||||||
method: 'GET',
|
|
||||||
})
|
|
||||||
.reply(() => ({
|
|
||||||
data: {
|
|
||||||
shards: 1,
|
|
||||||
session_start_limit: {
|
|
||||||
max_concurrency: 3,
|
|
||||||
reset_after: 60,
|
|
||||||
remaining: 3,
|
|
||||||
total: 3,
|
|
||||||
},
|
|
||||||
url: 'wss://gateway.discord.gg',
|
|
||||||
},
|
|
||||||
statusCode: 200,
|
|
||||||
responseOptions: {
|
|
||||||
headers: {
|
|
||||||
'content-type': 'application/json',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
await manager.connect();
|
await manager.connect();
|
||||||
expect(mockConstructor).toHaveBeenCalledWith(
|
expect(mockConstructor).toHaveBeenCalledWith(
|
||||||
expect.stringContaining('defaultWorker.js'),
|
expect.stringContaining('defaultWorker.js'),
|
||||||
|
|||||||
@@ -1,107 +1,55 @@
|
|||||||
import { REST } from '@discordjs/rest';
|
import type { GatewaySendPayload } from 'discord-api-types/v10';
|
||||||
import type { RESTGetAPIGatewayBotResult, APIGatewayBotInfo, GatewaySendPayload } from 'discord-api-types/v10';
|
import { GatewayOpcodes } from 'discord-api-types/v10';
|
||||||
import { GatewayOpcodes, Routes } from 'discord-api-types/v10';
|
import { describe, expect, test, vi } from 'vitest';
|
||||||
import { MockAgent, type Interceptable } from 'undici';
|
|
||||||
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
|
||||||
import { WebSocketManager, type IShardingStrategy } from '../../src/index.js';
|
import { WebSocketManager, type IShardingStrategy } from '../../src/index.js';
|
||||||
|
import { mockGatewayInformation } from '../gateway.mock.js';
|
||||||
|
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
|
|
||||||
let mockAgent: MockAgent;
|
|
||||||
let mockPool: Interceptable;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
mockAgent = new MockAgent();
|
|
||||||
mockAgent.disableNetConnect();
|
|
||||||
mockPool = mockAgent.get('https://discord.com');
|
|
||||||
});
|
|
||||||
|
|
||||||
const NOW = vi.fn().mockReturnValue(Date.now());
|
const NOW = vi.fn().mockReturnValue(Date.now());
|
||||||
global.Date.now = NOW;
|
global.Date.now = NOW;
|
||||||
|
|
||||||
test('fetch gateway information', async () => {
|
test('fetch gateway information', async () => {
|
||||||
const rest = new REST().setAgent(mockAgent).setToken('A-Very-Fake-Token');
|
const fetchGatewayInformation = vi.fn(async () => mockGatewayInformation);
|
||||||
|
|
||||||
const manager = new WebSocketManager({
|
const manager = new WebSocketManager({
|
||||||
token: 'A-Very-Fake-Token',
|
token: 'A-Very-Fake-Token',
|
||||||
intents: 0,
|
intents: 0,
|
||||||
async fetchGatewayInformation() {
|
fetchGatewayInformation,
|
||||||
return rest.get(Routes.gatewayBot()) as Promise<RESTGetAPIGatewayBotResult>;
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const data: APIGatewayBotInfo = {
|
|
||||||
shards: 1,
|
|
||||||
session_start_limit: {
|
|
||||||
max_concurrency: 3,
|
|
||||||
reset_after: 60,
|
|
||||||
remaining: 3,
|
|
||||||
total: 3,
|
|
||||||
},
|
|
||||||
url: 'wss://gateway.discord.gg',
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetch = vi.fn(() => ({
|
|
||||||
data,
|
|
||||||
statusCode: 200,
|
|
||||||
responseOptions: {
|
|
||||||
headers: {
|
|
||||||
'content-type': 'application/json',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
mockPool
|
|
||||||
.intercept({
|
|
||||||
path: '/api/v10/gateway/bot',
|
|
||||||
method: 'GET',
|
|
||||||
})
|
|
||||||
.reply(fetch);
|
|
||||||
|
|
||||||
const initial = await manager.fetchGatewayInformation();
|
const initial = await manager.fetchGatewayInformation();
|
||||||
expect(initial).toEqual(data);
|
expect(initial).toEqual(mockGatewayInformation);
|
||||||
expect(fetch).toHaveBeenCalledOnce();
|
expect(fetchGatewayInformation).toHaveBeenCalledOnce();
|
||||||
|
|
||||||
fetch.mockClear();
|
fetchGatewayInformation.mockClear();
|
||||||
|
|
||||||
const cached = await manager.fetchGatewayInformation();
|
const cached = await manager.fetchGatewayInformation();
|
||||||
expect(cached).toEqual(data);
|
expect(cached).toEqual(mockGatewayInformation);
|
||||||
expect(fetch).not.toHaveBeenCalled();
|
expect(fetchGatewayInformation).not.toHaveBeenCalled();
|
||||||
|
|
||||||
fetch.mockClear();
|
fetchGatewayInformation.mockClear();
|
||||||
mockPool
|
|
||||||
.intercept({
|
|
||||||
path: '/api/v10/gateway/bot',
|
|
||||||
method: 'GET',
|
|
||||||
})
|
|
||||||
.reply(fetch);
|
|
||||||
|
|
||||||
const forced = await manager.fetchGatewayInformation(true);
|
const forced = await manager.fetchGatewayInformation(true);
|
||||||
expect(forced).toEqual(data);
|
expect(forced).toEqual(mockGatewayInformation);
|
||||||
expect(fetch).toHaveBeenCalledOnce();
|
expect(fetchGatewayInformation).toHaveBeenCalledOnce();
|
||||||
|
|
||||||
fetch.mockClear();
|
fetchGatewayInformation.mockClear();
|
||||||
mockPool
|
|
||||||
.intercept({
|
|
||||||
path: '/api/v10/gateway/bot',
|
|
||||||
method: 'GET',
|
|
||||||
})
|
|
||||||
.reply(fetch);
|
|
||||||
|
|
||||||
NOW.mockReturnValue(Number.POSITIVE_INFINITY);
|
NOW.mockReturnValue(Number.POSITIVE_INFINITY);
|
||||||
const cacheExpired = await manager.fetchGatewayInformation();
|
const cacheExpired = await manager.fetchGatewayInformation();
|
||||||
expect(cacheExpired).toEqual(data);
|
expect(cacheExpired).toEqual(mockGatewayInformation);
|
||||||
expect(fetch).toHaveBeenCalledOnce();
|
expect(fetchGatewayInformation).toHaveBeenCalledOnce();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('get shard count', () => {
|
describe('get shard count', () => {
|
||||||
test('with shard count', async () => {
|
test('with shard count', async () => {
|
||||||
const rest = new REST().setAgent(mockAgent).setToken('A-Very-Fake-Token');
|
|
||||||
const manager = new WebSocketManager({
|
const manager = new WebSocketManager({
|
||||||
token: 'A-Very-Fake-Token',
|
token: 'A-Very-Fake-Token',
|
||||||
intents: 0,
|
intents: 0,
|
||||||
shardCount: 2,
|
shardCount: 2,
|
||||||
async fetchGatewayInformation() {
|
async fetchGatewayInformation() {
|
||||||
return rest.get(Routes.gatewayBot()) as Promise<RESTGetAPIGatewayBotResult>;
|
return mockGatewayInformation;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -109,14 +57,13 @@ describe('get shard count', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('with shard ids array', async () => {
|
test('with shard ids array', async () => {
|
||||||
const rest = new REST().setAgent(mockAgent).setToken('A-Very-Fake-Token');
|
|
||||||
const shardIds = [5, 9];
|
const shardIds = [5, 9];
|
||||||
const manager = new WebSocketManager({
|
const manager = new WebSocketManager({
|
||||||
token: 'A-Very-Fake-Token',
|
token: 'A-Very-Fake-Token',
|
||||||
intents: 0,
|
intents: 0,
|
||||||
shardIds,
|
shardIds,
|
||||||
async fetchGatewayInformation() {
|
async fetchGatewayInformation() {
|
||||||
return rest.get(Routes.gatewayBot()) as Promise<RESTGetAPIGatewayBotResult>;
|
return mockGatewayInformation;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -124,14 +71,13 @@ describe('get shard count', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('with shard id range', async () => {
|
test('with shard id range', async () => {
|
||||||
const rest = new REST().setAgent(mockAgent).setToken('A-Very-Fake-Token');
|
|
||||||
const shardIds = { start: 5, end: 9 };
|
const shardIds = { start: 5, end: 9 };
|
||||||
const manager = new WebSocketManager({
|
const manager = new WebSocketManager({
|
||||||
token: 'A-Very-Fake-Token',
|
token: 'A-Very-Fake-Token',
|
||||||
intents: 0,
|
intents: 0,
|
||||||
shardIds,
|
shardIds,
|
||||||
async fetchGatewayInformation() {
|
async fetchGatewayInformation() {
|
||||||
return rest.get(Routes.gatewayBot()) as Promise<RESTGetAPIGatewayBotResult>;
|
return mockGatewayInformation;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -140,62 +86,26 @@ describe('get shard count', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('update shard count', async () => {
|
test('update shard count', async () => {
|
||||||
const rest = new REST().setAgent(mockAgent).setToken('A-Very-Fake-Token');
|
const fetchGatewayInformation = vi.fn(async () => mockGatewayInformation);
|
||||||
|
|
||||||
const manager = new WebSocketManager({
|
const manager = new WebSocketManager({
|
||||||
token: 'A-Very-Fake-Token',
|
token: 'A-Very-Fake-Token',
|
||||||
intents: 0,
|
intents: 0,
|
||||||
shardCount: 2,
|
shardCount: 2,
|
||||||
async fetchGatewayInformation() {
|
fetchGatewayInformation,
|
||||||
return rest.get(Routes.gatewayBot()) as Promise<RESTGetAPIGatewayBotResult>;
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const data: APIGatewayBotInfo = {
|
|
||||||
shards: 1,
|
|
||||||
session_start_limit: {
|
|
||||||
max_concurrency: 3,
|
|
||||||
reset_after: 60,
|
|
||||||
remaining: 3,
|
|
||||||
total: 3,
|
|
||||||
},
|
|
||||||
url: 'wss://gateway.discord.gg',
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetch = vi.fn(() => ({
|
|
||||||
data,
|
|
||||||
statusCode: 200,
|
|
||||||
responseOptions: {
|
|
||||||
headers: {
|
|
||||||
'content-type': 'application/json',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
mockPool
|
|
||||||
.intercept({
|
|
||||||
path: '/api/v10/gateway/bot',
|
|
||||||
method: 'GET',
|
|
||||||
})
|
|
||||||
.reply(fetch);
|
|
||||||
|
|
||||||
expect(await manager.getShardCount()).toBe(2);
|
expect(await manager.getShardCount()).toBe(2);
|
||||||
expect(fetch).not.toHaveBeenCalled();
|
expect(fetchGatewayInformation).not.toHaveBeenCalled();
|
||||||
|
|
||||||
fetch.mockClear();
|
fetchGatewayInformation.mockClear();
|
||||||
mockPool
|
|
||||||
.intercept({
|
|
||||||
path: '/api/v10/gateway/bot',
|
|
||||||
method: 'GET',
|
|
||||||
})
|
|
||||||
.reply(fetch);
|
|
||||||
|
|
||||||
await manager.updateShardCount(3);
|
await manager.updateShardCount(3);
|
||||||
expect(await manager.getShardCount()).toBe(3);
|
expect(await manager.getShardCount()).toBe(3);
|
||||||
expect(fetch).toHaveBeenCalled();
|
expect(fetchGatewayInformation).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it handles passing in both shardIds and shardCount', async () => {
|
test('it handles passing in both shardIds and shardCount', async () => {
|
||||||
const rest = new REST().setAgent(mockAgent).setToken('A-Very-Fake-Token');
|
|
||||||
const shardIds = { start: 2, end: 3 };
|
const shardIds = { start: 2, end: 3 };
|
||||||
const manager = new WebSocketManager({
|
const manager = new WebSocketManager({
|
||||||
token: 'A-Very-Fake-Token',
|
token: 'A-Very-Fake-Token',
|
||||||
@@ -203,7 +113,7 @@ test('it handles passing in both shardIds and shardCount', async () => {
|
|||||||
shardIds,
|
shardIds,
|
||||||
shardCount: 4,
|
shardCount: 4,
|
||||||
async fetchGatewayInformation() {
|
async fetchGatewayInformation() {
|
||||||
return rest.get(Routes.gatewayBot()) as Promise<RESTGetAPIGatewayBotResult>;
|
return mockGatewayInformation;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -226,44 +136,18 @@ test('strategies', async () => {
|
|||||||
|
|
||||||
const strategy = new MockStrategy();
|
const strategy = new MockStrategy();
|
||||||
|
|
||||||
const rest = new REST().setAgent(mockAgent).setToken('A-Very-Fake-Token');
|
|
||||||
const shardIds = [0, 1, 2];
|
const shardIds = [0, 1, 2];
|
||||||
|
|
||||||
const manager = new WebSocketManager({
|
const manager = new WebSocketManager({
|
||||||
token: 'A-Very-Fake-Token',
|
token: 'A-Very-Fake-Token',
|
||||||
intents: 0,
|
intents: 0,
|
||||||
rest,
|
|
||||||
shardIds,
|
shardIds,
|
||||||
|
async fetchGatewayInformation() {
|
||||||
|
return mockGatewayInformation;
|
||||||
|
},
|
||||||
buildStrategy: () => strategy,
|
buildStrategy: () => strategy,
|
||||||
});
|
});
|
||||||
|
|
||||||
const data: APIGatewayBotInfo = {
|
|
||||||
shards: 1,
|
|
||||||
session_start_limit: {
|
|
||||||
max_concurrency: 3,
|
|
||||||
reset_after: 60,
|
|
||||||
remaining: 3,
|
|
||||||
total: 3,
|
|
||||||
},
|
|
||||||
url: 'wss://gateway.discord.gg',
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetch = vi.fn(() => ({
|
|
||||||
data,
|
|
||||||
statusCode: 200,
|
|
||||||
responseOptions: {
|
|
||||||
headers: {
|
|
||||||
'content-type': 'application/json',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
mockPool
|
|
||||||
.intercept({
|
|
||||||
path: '/api/v10/gateway/bot',
|
|
||||||
method: 'GET',
|
|
||||||
})
|
|
||||||
.reply(fetch);
|
|
||||||
|
|
||||||
await manager.connect();
|
await manager.connect();
|
||||||
expect(strategy.spawn).toHaveBeenCalledWith(shardIds);
|
expect(strategy.spawn).toHaveBeenCalledWith(shardIds);
|
||||||
expect(strategy.connect).toHaveBeenCalled();
|
expect(strategy.connect).toHaveBeenCalled();
|
||||||
|
|||||||
@@ -74,7 +74,6 @@
|
|||||||
"funding": "https://github.com/discordjs/discord.js?sponsor",
|
"funding": "https://github.com/discordjs/discord.js?sponsor",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@discordjs/collection": "workspace:^",
|
"@discordjs/collection": "workspace:^",
|
||||||
"@discordjs/rest": "workspace:^",
|
|
||||||
"@discordjs/util": "workspace:^",
|
"@discordjs/util": "workspace:^",
|
||||||
"@sapphire/async-queue": "^1.5.5",
|
"@sapphire/async-queue": "^1.5.5",
|
||||||
"@types/ws": "^8.18.1",
|
"@types/ws": "^8.18.1",
|
||||||
@@ -100,7 +99,6 @@
|
|||||||
"tsup": "^8.5.0",
|
"tsup": "^8.5.0",
|
||||||
"turbo": "^2.5.4",
|
"turbo": "^2.5.4",
|
||||||
"typescript": "~5.8.3",
|
"typescript": "~5.8.3",
|
||||||
"undici": "7.11.0",
|
|
||||||
"vitest": "^3.2.4",
|
"vitest": "^3.2.4",
|
||||||
"zlib-sync": "^0.1.10"
|
"zlib-sync": "^0.1.10"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
import type { Collection } from '@discordjs/collection';
|
import type { Collection } from '@discordjs/collection';
|
||||||
import type { REST } from '@discordjs/rest';
|
|
||||||
import { range, type Awaitable } from '@discordjs/util';
|
import { range, type Awaitable } from '@discordjs/util';
|
||||||
import { AsyncEventEmitter } from '@vladfrangu/async_event_emitter';
|
import { AsyncEventEmitter } from '@vladfrangu/async_event_emitter';
|
||||||
import {
|
import type {
|
||||||
Routes,
|
APIGatewayBotInfo,
|
||||||
type APIGatewayBotInfo,
|
GatewayIdentifyProperties,
|
||||||
type GatewayIdentifyProperties,
|
GatewayPresenceUpdateData,
|
||||||
type GatewayPresenceUpdateData,
|
RESTGetAPIGatewayBotResult,
|
||||||
type RESTGetAPIGatewayBotResult,
|
GatewayIntentBits,
|
||||||
type GatewayIntentBits,
|
GatewaySendPayload,
|
||||||
type GatewaySendPayload,
|
GatewayDispatchPayload,
|
||||||
type GatewayDispatchPayload,
|
GatewayReadyDispatchData,
|
||||||
type GatewayReadyDispatchData,
|
|
||||||
} from 'discord-api-types/v10';
|
} from 'discord-api-types/v10';
|
||||||
import type { IShardingStrategy } from '../strategies/sharding/IShardingStrategy.js';
|
import type { IShardingStrategy } from '../strategies/sharding/IShardingStrategy.js';
|
||||||
import type { IIdentifyThrottler } from '../throttling/IIdentifyThrottler.js';
|
import type { IIdentifyThrottler } from '../throttling/IIdentifyThrottler.js';
|
||||||
@@ -56,6 +54,22 @@ export interface SessionInfo {
|
|||||||
* Required options for the WebSocketManager
|
* Required options for the WebSocketManager
|
||||||
*/
|
*/
|
||||||
export interface RequiredWebSocketManagerOptions {
|
export interface RequiredWebSocketManagerOptions {
|
||||||
|
/**
|
||||||
|
* Function for retrieving the information returned by the `/gateway/bot` endpoint.
|
||||||
|
* We recommend using a REST client that respects Discord's rate limits, such as `@discordjs/rest`.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const rest = new REST().setToken(process.env.DISCORD_TOKEN);
|
||||||
|
* const manager = new WebSocketManager({
|
||||||
|
* token: process.env.DISCORD_TOKEN,
|
||||||
|
* fetchGatewayInformation() {
|
||||||
|
* return rest.get(Routes.gatewayBot()) as Promise<RESTGetAPIGatewayBotResult>;
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
fetchGatewayInformation(): Awaitable<RESTGetAPIGatewayBotResult>;
|
||||||
/**
|
/**
|
||||||
* The intents to request
|
* The intents to request
|
||||||
*/
|
*/
|
||||||
@@ -75,10 +89,13 @@ export interface OptionalWebSocketManagerOptions {
|
|||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* ```ts
|
* ```ts
|
||||||
|
* const rest = new REST().setToken(process.env.DISCORD_TOKEN);
|
||||||
* const manager = new WebSocketManager({
|
* const manager = new WebSocketManager({
|
||||||
* token: process.env.DISCORD_TOKEN,
|
* token: process.env.DISCORD_TOKEN,
|
||||||
* intents: 0, // for no intents
|
* intents: 0, // for no intents
|
||||||
* rest,
|
* fetchGatewayInformation() {
|
||||||
|
* return rest.get(Routes.gatewayBot()) as Promise<RESTGetAPIGatewayBotResult>;
|
||||||
|
* },
|
||||||
* buildStrategy: (manager) => new WorkerShardingStrategy(manager, { shardsPerWorker: 2 }),
|
* buildStrategy: (manager) => new WorkerShardingStrategy(manager, { shardsPerWorker: 2 }),
|
||||||
* });
|
* });
|
||||||
* ```
|
* ```
|
||||||
@@ -96,21 +113,6 @@ export interface OptionalWebSocketManagerOptions {
|
|||||||
* @defaultValue `'json'`
|
* @defaultValue `'json'`
|
||||||
*/
|
*/
|
||||||
encoding: Encoding;
|
encoding: Encoding;
|
||||||
/**
|
|
||||||
* Fetches the initial gateway URL used to connect to Discord. When missing, this will default to the gateway URL
|
|
||||||
* that Discord returns from the `/gateway/bot` route.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* ```ts
|
|
||||||
* const manager = new WebSocketManager({
|
|
||||||
* token: process.env.DISCORD_TOKEN,
|
|
||||||
* fetchGatewayInformation() {
|
|
||||||
* return rest.get(Routes.gatewayBot());
|
|
||||||
* },
|
|
||||||
* })
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
fetchGatewayInformation(): Awaitable<RESTGetAPIGatewayBotResult>;
|
|
||||||
/**
|
/**
|
||||||
* How long to wait for a shard to connect before giving up
|
* How long to wait for a shard to connect before giving up
|
||||||
*/
|
*/
|
||||||
@@ -135,12 +137,6 @@ export interface OptionalWebSocketManagerOptions {
|
|||||||
* How long to wait for a shard's READY packet before giving up
|
* How long to wait for a shard's READY packet before giving up
|
||||||
*/
|
*/
|
||||||
readyTimeout: number | null;
|
readyTimeout: number | null;
|
||||||
/**
|
|
||||||
* The REST instance to use for fetching gateway information
|
|
||||||
*
|
|
||||||
* @deprecated Providing a REST instance is deprecated. Provide the `fetchGatewayInformation` function instead.
|
|
||||||
*/
|
|
||||||
rest?: REST;
|
|
||||||
/**
|
/**
|
||||||
* Function used to retrieve session information (and attempt to resume) for a given shard
|
* Function used to retrieve session information (and attempt to resume) for a given shard
|
||||||
*
|
*
|
||||||
@@ -271,22 +267,13 @@ export class WebSocketManager extends AsyncEventEmitter<ManagerShardEventsMap> i
|
|||||||
}
|
}
|
||||||
|
|
||||||
public constructor(options: CreateWebSocketManagerOptions) {
|
public constructor(options: CreateWebSocketManagerOptions) {
|
||||||
if (!options.rest && !options.fetchGatewayInformation) {
|
if (typeof options.fetchGatewayInformation !== 'function') {
|
||||||
throw new RangeError('Either a REST instance or a fetchGatewayInformation function must be provided');
|
throw new TypeError('fetchGatewayInformation is required');
|
||||||
}
|
}
|
||||||
|
|
||||||
super();
|
super();
|
||||||
this.options = {
|
this.options = {
|
||||||
...DefaultWebSocketManagerOptions,
|
...DefaultWebSocketManagerOptions,
|
||||||
fetchGatewayInformation:
|
|
||||||
options.fetchGatewayInformation ??
|
|
||||||
(async () => {
|
|
||||||
if (!options.rest) {
|
|
||||||
throw new RangeError('A REST instance must be provided if no fetchGatewayInformation function is provided');
|
|
||||||
}
|
|
||||||
|
|
||||||
return options.rest.get(Routes.gatewayBot()) as Promise<RESTGetAPIGatewayBotResult>;
|
|
||||||
}),
|
|
||||||
...options,
|
...options,
|
||||||
};
|
};
|
||||||
this.strategy = this.options.buildStrategy(this);
|
this.strategy = this.options.buildStrategy(this);
|
||||||
|
|||||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
@@ -1861,9 +1861,6 @@ importers:
|
|||||||
'@discordjs/collection':
|
'@discordjs/collection':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../collection
|
version: link:../collection
|
||||||
'@discordjs/rest':
|
|
||||||
specifier: workspace:^
|
|
||||||
version: link:../rest
|
|
||||||
'@discordjs/util':
|
'@discordjs/util':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../util
|
version: link:../util
|
||||||
@@ -1934,9 +1931,6 @@ importers:
|
|||||||
typescript:
|
typescript:
|
||||||
specifier: ~5.8.3
|
specifier: ~5.8.3
|
||||||
version: 5.8.3
|
version: 5.8.3
|
||||||
undici:
|
|
||||||
specifier: 7.11.0
|
|
||||||
version: 7.11.0
|
|
||||||
vitest:
|
vitest:
|
||||||
specifier: ^3.2.4
|
specifier: ^3.2.4
|
||||||
version: 3.2.4(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.0)
|
version: 3.2.4(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.0)
|
||||||
|
|||||||
Reference in New Issue
Block a user