mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-09 16:13:31 +01:00
feat: no-de-no-de, now with extra buns (#9683)
BREAKING CHANGE: The REST and RequestManager classes now extend AsyncEventEmitter from `@vladfrangu/async_event_emitter`, which aids in cross-compatibility between Node, Deno, Bun, CF Workers, Vercel Functions, etc. BREAKING CHANGE: DefaultUserAgentAppendix has been adapted to support multiple different platforms (previously mentioned Deno, Bun, CF Workers, etc) BREAKING CHANGE: the entry point for `@discordjs/rest` will now differ in non-node-like environments (CF Workers, etc.) Co-authored-by: Suneet Tipirneni <77477100+suneettipirneni@users.noreply.github.com> Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com> Co-authored-by: suneettipirneni <suneettipirneni@icloud.com>
This commit is contained in:
@@ -1,3 +1,11 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../.eslintrc.json"
|
"extends": "../../.eslintrc.json",
|
||||||
|
"rules": {
|
||||||
|
"n/prefer-global/url": 0,
|
||||||
|
"n/prefer-global/url-search-params": 0,
|
||||||
|
"n/prefer-global/buffer": 0,
|
||||||
|
"n/prefer-global/process": 0,
|
||||||
|
"no-restricted-globals": 0,
|
||||||
|
"unicorn/prefer-node-protocol": 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,4 @@
|
|||||||
module.exports = require('../../.lintstagedrc.json');
|
module.exports = {
|
||||||
|
...require('../../.lintstagedrc.json'),
|
||||||
|
'src/**.ts': 'vitest related --run --config ./vitest.config.ts',
|
||||||
|
};
|
||||||
|
|||||||
4
packages/rest/__tests__/setup.ts
Normal file
4
packages/rest/__tests__/setup.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { setDefaultStrategy } from '../src/environment.js';
|
||||||
|
import { makeRequest } from '../src/strategies/undiciRequest.js';
|
||||||
|
|
||||||
|
setDefaultStrategy(makeRequest);
|
||||||
@@ -14,14 +14,19 @@
|
|||||||
"changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/rest/*'",
|
"changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/rest/*'",
|
||||||
"release": "cliff-jumper"
|
"release": "cliff-jumper"
|
||||||
},
|
},
|
||||||
"main": "./dist/index.js",
|
"types": "./dist/index.d.ts",
|
||||||
"module": "./dist/index.mjs",
|
|
||||||
"typings": "./dist/index.d.ts",
|
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"types": "./dist/index.d.ts",
|
"node": {
|
||||||
"import": "./dist/index.mjs",
|
"types": "./dist/index.d.ts",
|
||||||
"require": "./dist/index.js"
|
"import": "./dist/index.mjs",
|
||||||
|
"require": "./dist/index.js"
|
||||||
|
},
|
||||||
|
"default": {
|
||||||
|
"types": "./dist/web.d.ts",
|
||||||
|
"import": "./dist/web.mjs",
|
||||||
|
"require": "./dist/web.js"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"./*": {
|
"./*": {
|
||||||
"types": "./dist/strategies/*.d.ts",
|
"types": "./dist/strategies/*.d.ts",
|
||||||
@@ -65,8 +70,9 @@
|
|||||||
"@discordjs/util": "workspace:^",
|
"@discordjs/util": "workspace:^",
|
||||||
"@sapphire/async-queue": "^1.5.0",
|
"@sapphire/async-queue": "^1.5.0",
|
||||||
"@sapphire/snowflake": "^3.5.1",
|
"@sapphire/snowflake": "^3.5.1",
|
||||||
|
"@vladfrangu/async_event_emitter": "^2.2.2",
|
||||||
"discord-api-types": "^0.37.45",
|
"discord-api-types": "^0.37.45",
|
||||||
"file-type": "^18.4.0",
|
"magic-bytes.js": "^1.0.14",
|
||||||
"tslib": "^2.5.2",
|
"tslib": "^2.5.2",
|
||||||
"undici": "^5.22.1"
|
"undici": "^5.22.1"
|
||||||
},
|
},
|
||||||
|
|||||||
11
packages/rest/src/environment.ts
Normal file
11
packages/rest/src/environment.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import type { RESTOptions } from './shared.js';
|
||||||
|
|
||||||
|
let defaultStrategy: RESTOptions['makeRequest'];
|
||||||
|
|
||||||
|
export function setDefaultStrategy(newStrategy: RESTOptions['makeRequest']) {
|
||||||
|
defaultStrategy = newStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDefaultStrategy() {
|
||||||
|
return defaultStrategy;
|
||||||
|
}
|
||||||
@@ -1,15 +1,7 @@
|
|||||||
export * from './lib/CDN.js';
|
import { shouldUseGlobalFetchAndWebSocket } from '@discordjs/util';
|
||||||
export * from './lib/errors/DiscordAPIError.js';
|
import { setDefaultStrategy } from './environment.js';
|
||||||
export * from './lib/errors/HTTPError.js';
|
import { makeRequest } from './strategies/undiciRequest.js';
|
||||||
export * from './lib/errors/RateLimitError.js';
|
|
||||||
export * from './lib/RequestManager.js';
|
|
||||||
export * from './lib/REST.js';
|
|
||||||
export * from './lib/utils/constants.js';
|
|
||||||
export { calculateUserDefaultAvatarIndex, makeURLSearchParams, parseResponse } from './lib/utils/utils.js';
|
|
||||||
|
|
||||||
/**
|
setDefaultStrategy(shouldUseGlobalFetchAndWebSocket() ? fetch : makeRequest);
|
||||||
* The {@link https://github.com/discordjs/discord.js/blob/main/packages/rest/#readme | @discordjs/rest} version
|
|
||||||
* that you are currently using.
|
export * from './shared.js';
|
||||||
*/
|
|
||||||
// This needs to explicitly be `string` so it is not typed as a "const string" that gets injected by esbuild
|
|
||||||
export const version = '[VI]{{inject}}[/VI]' as string;
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
/* eslint-disable jsdoc/check-param-names */
|
/* eslint-disable jsdoc/check-param-names */
|
||||||
|
|
||||||
import { URL } from 'node:url';
|
|
||||||
import {
|
import {
|
||||||
ALLOWED_EXTENSIONS,
|
ALLOWED_EXTENSIONS,
|
||||||
ALLOWED_SIZES,
|
ALLOWED_SIZES,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { EventEmitter } from 'node:events';
|
|
||||||
import type { Readable } from 'node:stream';
|
import type { Readable } from 'node:stream';
|
||||||
import type { ReadableStream } from 'node:stream/web';
|
import type { ReadableStream } from 'node:stream/web';
|
||||||
import type { Collection } from '@discordjs/collection';
|
import type { Collection } from '@discordjs/collection';
|
||||||
|
import { AsyncEventEmitter } from '@vladfrangu/async_event_emitter';
|
||||||
import type { Dispatcher, RequestInit, Response } from 'undici';
|
import type { Dispatcher, RequestInit, Response } from 'undici';
|
||||||
import { CDN } from './CDN.js';
|
import { CDN } from './CDN.js';
|
||||||
import {
|
import {
|
||||||
@@ -204,7 +204,7 @@ export interface APIRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ResponseLike
|
export interface ResponseLike
|
||||||
extends Pick<Response, 'arrayBuffer' | 'bodyUsed' | 'headers' | 'json' | 'ok' | 'status' | 'text'> {
|
extends Pick<Response, 'arrayBuffer' | 'bodyUsed' | 'headers' | 'json' | 'ok' | 'status' | 'statusText' | 'text'> {
|
||||||
body: Readable | ReadableStream | null;
|
body: Readable | ReadableStream | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,31 +223,16 @@ export interface RestEvents {
|
|||||||
handlerSweep: [sweptHandlers: Collection<string, IHandler>];
|
handlerSweep: [sweptHandlers: Collection<string, IHandler>];
|
||||||
hashSweep: [sweptHashes: Collection<string, HashData>];
|
hashSweep: [sweptHashes: Collection<string, HashData>];
|
||||||
invalidRequestWarning: [invalidRequestInfo: InvalidRequestWarningData];
|
invalidRequestWarning: [invalidRequestInfo: InvalidRequestWarningData];
|
||||||
newListener: [name: string, listener: (...args: any) => void];
|
|
||||||
rateLimited: [rateLimitInfo: RateLimitData];
|
rateLimited: [rateLimitInfo: RateLimitData];
|
||||||
removeListener: [name: string, listener: (...args: any) => void];
|
|
||||||
response: [request: APIRequest, response: ResponseLike];
|
response: [request: APIRequest, response: ResponseLike];
|
||||||
restDebug: [info: string];
|
restDebug: [info: string];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface REST {
|
export type RestEventsMap = {
|
||||||
emit: (<K extends keyof RestEvents>(event: K, ...args: RestEvents[K]) => boolean) &
|
[K in keyof RestEvents]: RestEvents[K];
|
||||||
(<S extends string | symbol>(event: Exclude<S, keyof RestEvents>, ...args: any[]) => boolean);
|
};
|
||||||
|
|
||||||
off: (<K extends keyof RestEvents>(event: K, listener: (...args: RestEvents[K]) => void) => this) &
|
export class REST extends AsyncEventEmitter<RestEventsMap> {
|
||||||
(<S extends string | symbol>(event: Exclude<S, keyof RestEvents>, listener: (...args: any[]) => void) => this);
|
|
||||||
|
|
||||||
on: (<K extends keyof RestEvents>(event: K, listener: (...args: RestEvents[K]) => void) => this) &
|
|
||||||
(<S extends string | symbol>(event: Exclude<S, keyof RestEvents>, listener: (...args: any[]) => void) => this);
|
|
||||||
|
|
||||||
once: (<K extends keyof RestEvents>(event: K, listener: (...args: RestEvents[K]) => void) => this) &
|
|
||||||
(<S extends string | symbol>(event: Exclude<S, keyof RestEvents>, listener: (...args: any[]) => void) => this);
|
|
||||||
|
|
||||||
removeAllListeners: (<K extends keyof RestEvents>(event?: K) => this) &
|
|
||||||
(<S extends string | symbol>(event?: Exclude<S, keyof RestEvents>) => this);
|
|
||||||
}
|
|
||||||
|
|
||||||
export class REST extends EventEmitter {
|
|
||||||
public readonly cdn: CDN;
|
public readonly cdn: CDN;
|
||||||
|
|
||||||
public readonly requestManager: RequestManager;
|
public readonly requestManager: RequestManager;
|
||||||
@@ -256,9 +241,13 @@ export class REST extends EventEmitter {
|
|||||||
super();
|
super();
|
||||||
this.cdn = new CDN(options.cdn ?? DefaultRestOptions.cdn);
|
this.cdn = new CDN(options.cdn ?? DefaultRestOptions.cdn);
|
||||||
this.requestManager = new RequestManager(options)
|
this.requestManager = new RequestManager(options)
|
||||||
|
// @ts-expect-error For some reason ts can't infer these types
|
||||||
.on(RESTEvents.Debug, this.emit.bind(this, RESTEvents.Debug))
|
.on(RESTEvents.Debug, this.emit.bind(this, RESTEvents.Debug))
|
||||||
|
// @ts-expect-error For some reason ts can't infer these types
|
||||||
.on(RESTEvents.RateLimited, this.emit.bind(this, RESTEvents.RateLimited))
|
.on(RESTEvents.RateLimited, this.emit.bind(this, RESTEvents.RateLimited))
|
||||||
|
// @ts-expect-error For some reason ts can't infer these types
|
||||||
.on(RESTEvents.InvalidRequestWarning, this.emit.bind(this, RESTEvents.InvalidRequestWarning))
|
.on(RESTEvents.InvalidRequestWarning, this.emit.bind(this, RESTEvents.InvalidRequestWarning))
|
||||||
|
// @ts-expect-error For some reason ts can't infer these types
|
||||||
.on(RESTEvents.HashSweep, this.emit.bind(this, RESTEvents.HashSweep));
|
.on(RESTEvents.HashSweep, this.emit.bind(this, RESTEvents.HashSweep));
|
||||||
|
|
||||||
this.on('newListener', (name, listener) => {
|
this.on('newListener', (name, listener) => {
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import { Blob, Buffer } from 'node:buffer';
|
|
||||||
import { EventEmitter } from 'node:events';
|
|
||||||
import { setInterval, clearInterval } from 'node:timers';
|
|
||||||
import type { URLSearchParams } from 'node:url';
|
|
||||||
import { Collection } from '@discordjs/collection';
|
import { Collection } from '@discordjs/collection';
|
||||||
import { lazy } from '@discordjs/util';
|
|
||||||
import { DiscordSnowflake } from '@sapphire/snowflake';
|
import { DiscordSnowflake } from '@sapphire/snowflake';
|
||||||
|
import { AsyncEventEmitter } from '@vladfrangu/async_event_emitter';
|
||||||
|
import { filetypeinfo } from 'magic-bytes.js';
|
||||||
import type { RequestInit, BodyInit, Dispatcher, Agent } from 'undici';
|
import type { RequestInit, BodyInit, Dispatcher, Agent } from 'undici';
|
||||||
import type { RESTOptions, ResponseLike, RestEvents } from './REST.js';
|
import type { RESTOptions, ResponseLike, RestEventsMap } from './REST.js';
|
||||||
import { BurstHandler } from './handlers/BurstHandler.js';
|
import { BurstHandler } from './handlers/BurstHandler.js';
|
||||||
import { SequentialHandler } from './handlers/SequentialHandler.js';
|
import { SequentialHandler } from './handlers/SequentialHandler.js';
|
||||||
import type { IHandler } from './interfaces/Handler.js';
|
import type { IHandler } from './interfaces/Handler.js';
|
||||||
@@ -17,9 +14,7 @@ import {
|
|||||||
OverwrittenMimeTypes,
|
OverwrittenMimeTypes,
|
||||||
RESTEvents,
|
RESTEvents,
|
||||||
} from './utils/constants.js';
|
} from './utils/constants.js';
|
||||||
|
import { isBufferLike } from './utils/utils.js';
|
||||||
// Make this a lazy dynamic import as file-type is a pure ESM package
|
|
||||||
const getFileType = lazy(async () => import('file-type'));
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a file to be added to the request
|
* Represents a file to be added to the request
|
||||||
@@ -32,7 +27,7 @@ export interface RawFile {
|
|||||||
/**
|
/**
|
||||||
* The actual data for the file
|
* The actual data for the file
|
||||||
*/
|
*/
|
||||||
data: Buffer | boolean | number | string;
|
data: Buffer | Uint8Array | boolean | number | string;
|
||||||
/**
|
/**
|
||||||
* An explicit key to use for key of the formdata field for this file.
|
* An explicit key to use for key of the formdata field for this file.
|
||||||
* When not provided, the index of the file in the files array is used in the form `files[${index}]`.
|
* When not provided, the index of the file in the files array is used in the form `files[${index}]`.
|
||||||
@@ -162,27 +157,10 @@ export interface HashData {
|
|||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RequestManager {
|
|
||||||
emit: (<K extends keyof RestEvents>(event: K, ...args: RestEvents[K]) => boolean) &
|
|
||||||
(<S extends string | symbol>(event: Exclude<S, keyof RestEvents>, ...args: any[]) => boolean);
|
|
||||||
|
|
||||||
off: (<K extends keyof RestEvents>(event: K, listener: (...args: RestEvents[K]) => void) => this) &
|
|
||||||
(<S extends string | symbol>(event: Exclude<S, keyof RestEvents>, listener: (...args: any[]) => void) => this);
|
|
||||||
|
|
||||||
on: (<K extends keyof RestEvents>(event: K, listener: (...args: RestEvents[K]) => void) => this) &
|
|
||||||
(<S extends string | symbol>(event: Exclude<S, keyof RestEvents>, listener: (...args: any[]) => void) => this);
|
|
||||||
|
|
||||||
once: (<K extends keyof RestEvents>(event: K, listener: (...args: RestEvents[K]) => void) => this) &
|
|
||||||
(<S extends string | symbol>(event: Exclude<S, keyof RestEvents>, listener: (...args: any[]) => void) => this);
|
|
||||||
|
|
||||||
removeAllListeners: (<K extends keyof RestEvents>(event?: K) => this) &
|
|
||||||
(<S extends string | symbol>(event?: Exclude<S, keyof RestEvents>) => this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the class that manages handlers for endpoints
|
* Represents the class that manages handlers for endpoints
|
||||||
*/
|
*/
|
||||||
export class RequestManager extends EventEmitter {
|
export class RequestManager extends AsyncEventEmitter<RestEventsMap> {
|
||||||
/**
|
/**
|
||||||
* The {@link https://undici.nodejs.org/#/docs/api/Agent | Agent} for all requests
|
* The {@link https://undici.nodejs.org/#/docs/api/Agent | Agent} for all requests
|
||||||
* performed by this manager.
|
* performed by this manager.
|
||||||
@@ -216,9 +194,9 @@ export class RequestManager extends EventEmitter {
|
|||||||
|
|
||||||
#token: string | null = null;
|
#token: string | null = null;
|
||||||
|
|
||||||
private hashTimer!: NodeJS.Timer;
|
private hashTimer!: NodeJS.Timer | number;
|
||||||
|
|
||||||
private handlerTimer!: NodeJS.Timer;
|
private handlerTimer!: NodeJS.Timer | number;
|
||||||
|
|
||||||
public readonly options: RESTOptions;
|
public readonly options: RESTOptions;
|
||||||
|
|
||||||
@@ -269,7 +247,9 @@ export class RequestManager extends EventEmitter {
|
|||||||
|
|
||||||
// Fire event
|
// Fire event
|
||||||
this.emit(RESTEvents.HashSweep, sweptHashes);
|
this.emit(RESTEvents.HashSweep, sweptHashes);
|
||||||
}, this.options.hashSweepInterval).unref();
|
}, this.options.hashSweepInterval);
|
||||||
|
|
||||||
|
this.hashTimer.unref?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.options.handlerSweepInterval !== 0 && this.options.handlerSweepInterval !== Number.POSITIVE_INFINITY) {
|
if (this.options.handlerSweepInterval !== 0 && this.options.handlerSweepInterval !== Number.POSITIVE_INFINITY) {
|
||||||
@@ -292,7 +272,9 @@ export class RequestManager extends EventEmitter {
|
|||||||
|
|
||||||
// Fire event
|
// Fire event
|
||||||
this.emit(RESTEvents.HandlerSweep, sweptHandlers);
|
this.emit(RESTEvents.HandlerSweep, sweptHandlers);
|
||||||
}, this.options.handlerSweepInterval).unref();
|
}, this.options.handlerSweepInterval);
|
||||||
|
|
||||||
|
this.handlerTimer.unref?.();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,14 +407,18 @@ export class RequestManager extends EventEmitter {
|
|||||||
// FormData.append only accepts a string or Blob.
|
// FormData.append only accepts a string or Blob.
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob#parameters
|
// https://developer.mozilla.org/en-US/docs/Web/API/Blob/Blob#parameters
|
||||||
// The Blob constructor accepts TypedArray/ArrayBuffer, strings, and Blobs.
|
// The Blob constructor accepts TypedArray/ArrayBuffer, strings, and Blobs.
|
||||||
if (Buffer.isBuffer(file.data)) {
|
if (isBufferLike(file.data)) {
|
||||||
// Try to infer the content type from the buffer if one isn't passed
|
// Try to infer the content type from the buffer if one isn't passed
|
||||||
const { fileTypeFromBuffer } = await getFileType();
|
|
||||||
let contentType = file.contentType;
|
let contentType = file.contentType;
|
||||||
|
|
||||||
if (!contentType) {
|
if (!contentType) {
|
||||||
const parsedType = (await fileTypeFromBuffer(file.data))?.mime;
|
const [parsedType] = filetypeinfo(file.data);
|
||||||
|
|
||||||
if (parsedType) {
|
if (parsedType) {
|
||||||
contentType = OverwrittenMimeTypes[parsedType as keyof typeof OverwrittenMimeTypes] ?? parsedType;
|
contentType =
|
||||||
|
OverwrittenMimeTypes[parsedType.mime as keyof typeof OverwrittenMimeTypes] ??
|
||||||
|
parsedType.mime ??
|
||||||
|
'application/octet-stream';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { STATUS_CODES } from 'node:http';
|
|
||||||
import type { InternalRequest } from '../RequestManager.js';
|
import type { InternalRequest } from '../RequestManager.js';
|
||||||
import type { RequestBody } from './DiscordAPIError.js';
|
import type { RequestBody } from './DiscordAPIError.js';
|
||||||
|
|
||||||
@@ -12,18 +11,19 @@ export class HTTPError extends Error {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param status - The status code of the response
|
* @param status - The status code of the response
|
||||||
|
* @param statusText - The status text of the response
|
||||||
* @param method - The method of the request that erred
|
* @param method - The method of the request that erred
|
||||||
* @param url - The url of the request that erred
|
* @param url - The url of the request that erred
|
||||||
* @param bodyData - The unparsed data for the request that errored
|
* @param bodyData - The unparsed data for the request that errored
|
||||||
*/
|
*/
|
||||||
public constructor(
|
public constructor(
|
||||||
public status: number,
|
public status: number,
|
||||||
|
statusText: string,
|
||||||
public method: string,
|
public method: string,
|
||||||
public url: string,
|
public url: string,
|
||||||
bodyData: Pick<InternalRequest, 'body' | 'files'>,
|
bodyData: Pick<InternalRequest, 'body' | 'files'>,
|
||||||
) {
|
) {
|
||||||
super(STATUS_CODES[status]);
|
super(statusText);
|
||||||
|
|
||||||
this.requestBody = { files: bodyData.files, json: bodyData.body };
|
this.requestBody = { files: bodyData.files, json: bodyData.body };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { setTimeout as sleep } from 'node:timers/promises';
|
|
||||||
import type { RequestInit } from 'undici';
|
import type { RequestInit } from 'undici';
|
||||||
import type { ResponseLike } from '../REST.js';
|
import type { ResponseLike } from '../REST.js';
|
||||||
import type { HandlerRequestData, RequestManager, RouteData } from '../RequestManager.js';
|
import type { HandlerRequestData, RequestManager, RouteData } from '../RequestManager.js';
|
||||||
import type { IHandler } from '../interfaces/Handler.js';
|
import type { IHandler } from '../interfaces/Handler.js';
|
||||||
import { RESTEvents } from '../utils/constants.js';
|
import { RESTEvents } from '../utils/constants.js';
|
||||||
import { onRateLimit } from '../utils/utils.js';
|
import { onRateLimit, sleep } from '../utils/utils.js';
|
||||||
import { handleErrors, incrementInvalidCount, makeNetworkRequest } from './Shared.js';
|
import { handleErrors, incrementInvalidCount, makeNetworkRequest } from './Shared.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { setTimeout as sleep } from 'node:timers/promises';
|
|
||||||
import { AsyncQueue } from '@sapphire/async-queue';
|
import { AsyncQueue } from '@sapphire/async-queue';
|
||||||
import type { RequestInit } from 'undici';
|
import type { RequestInit } from 'undici';
|
||||||
import type { RateLimitData, ResponseLike } from '../REST.js';
|
import type { RateLimitData, ResponseLike } from '../REST.js';
|
||||||
import type { HandlerRequestData, RequestManager, RouteData } from '../RequestManager.js';
|
import type { HandlerRequestData, RequestManager, RouteData } from '../RequestManager.js';
|
||||||
import type { IHandler } from '../interfaces/Handler.js';
|
import type { IHandler } from '../interfaces/Handler.js';
|
||||||
import { RESTEvents } from '../utils/constants.js';
|
import { RESTEvents } from '../utils/constants.js';
|
||||||
import { hasSublimit, onRateLimit } from '../utils/utils.js';
|
import { hasSublimit, onRateLimit, sleep } from '../utils/utils.js';
|
||||||
import { handleErrors, incrementInvalidCount, makeNetworkRequest } from './Shared.js';
|
import { handleErrors, incrementInvalidCount, makeNetworkRequest } from './Shared.js';
|
||||||
|
|
||||||
const enum QueueType {
|
const enum QueueType {
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { setTimeout, clearTimeout } from 'node:timers';
|
|
||||||
import { Response } from 'undici';
|
|
||||||
import type { RequestInit } from 'undici';
|
import type { RequestInit } from 'undici';
|
||||||
import type { ResponseLike } from '../REST.js';
|
import type { ResponseLike } from '../REST.js';
|
||||||
import type { HandlerRequestData, RequestManager, RouteData } from '../RequestManager.js';
|
import type { HandlerRequestData, RequestManager, RouteData } from '../RequestManager.js';
|
||||||
@@ -65,7 +63,7 @@ export async function makeNetworkRequest(
|
|||||||
retries: number,
|
retries: number,
|
||||||
) {
|
) {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeout = setTimeout(() => controller.abort(), manager.options.timeout).unref();
|
const timeout = setTimeout(() => controller.abort(), manager.options.timeout);
|
||||||
if (requestData.signal) {
|
if (requestData.signal) {
|
||||||
// If the user signal was aborted, abort the controller, else abort the local signal.
|
// If the user signal was aborted, abort the controller, else abort the local signal.
|
||||||
// The reason why we don't re-use the user's signal, is because users may use the same signal for multiple
|
// The reason why we don't re-use the user's signal, is because users may use the same signal for multiple
|
||||||
@@ -135,7 +133,7 @@ export async function handleErrors(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We are out of retries, throw an error
|
// We are out of retries, throw an error
|
||||||
throw new HTTPError(status, method, url, requestData);
|
throw new HTTPError(status, res.statusText, method, url, requestData);
|
||||||
} else {
|
} else {
|
||||||
// Handle possible malformed requests
|
// Handle possible malformed requests
|
||||||
if (status >= 400 && status < 500) {
|
if (status >= 400 && status < 500) {
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
import process from 'node:process';
|
import { getUserAgentAppendix } from '@discordjs/util';
|
||||||
import { lazy } from '@discordjs/util';
|
|
||||||
import { APIVersion } from 'discord-api-types/v10';
|
import { APIVersion } from 'discord-api-types/v10';
|
||||||
import type { RESTOptions } from '../REST.js';
|
import { getDefaultStrategy } from '../../environment.js';
|
||||||
|
import type { RESTOptions, ResponseLike } from '../REST.js';
|
||||||
const getUndiciRequest = lazy(async () => {
|
|
||||||
return import('../../strategies/undiciRequest.js');
|
|
||||||
});
|
|
||||||
|
|
||||||
export const DefaultUserAgent =
|
export const DefaultUserAgent =
|
||||||
`DiscordBot (https://discord.js.org, [VI]{{inject}}[/VI])` as `DiscordBot (https://discord.js.org, ${string})`;
|
`DiscordBot (https://discord.js.org, [VI]{{inject}}[/VI])` as `DiscordBot (https://discord.js.org, ${string})`;
|
||||||
@@ -13,7 +9,7 @@ export const DefaultUserAgent =
|
|||||||
/**
|
/**
|
||||||
* The default string to append onto the user agent.
|
* The default string to append onto the user agent.
|
||||||
*/
|
*/
|
||||||
export const DefaultUserAgentAppendix = process.release?.name === 'node' ? `Node.js/${process.version}` : '';
|
export const DefaultUserAgentAppendix = getUserAgentAppendix();
|
||||||
|
|
||||||
export const DefaultRestOptions = {
|
export const DefaultRestOptions = {
|
||||||
agent: null,
|
agent: null,
|
||||||
@@ -32,9 +28,8 @@ export const DefaultRestOptions = {
|
|||||||
hashSweepInterval: 14_400_000, // 4 Hours
|
hashSweepInterval: 14_400_000, // 4 Hours
|
||||||
hashLifetime: 86_400_000, // 24 Hours
|
hashLifetime: 86_400_000, // 24 Hours
|
||||||
handlerSweepInterval: 3_600_000, // 1 Hour
|
handlerSweepInterval: 3_600_000, // 1 Hour
|
||||||
async makeRequest(...args) {
|
async makeRequest(...args): Promise<ResponseLike> {
|
||||||
const strategy = await getUndiciRequest();
|
return getDefaultStrategy()(...args);
|
||||||
return strategy.makeRequest(...args);
|
|
||||||
},
|
},
|
||||||
} as const satisfies Required<RESTOptions>;
|
} as const satisfies Required<RESTOptions>;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { URLSearchParams } from 'node:url';
|
|
||||||
import type { RESTPatchAPIChannelJSONBody, Snowflake } from 'discord-api-types/v10';
|
import type { RESTPatchAPIChannelJSONBody, Snowflake } from 'discord-api-types/v10';
|
||||||
import type { RateLimitData, ResponseLike } from '../REST.js';
|
import type { RateLimitData, ResponseLike } from '../REST.js';
|
||||||
import { type RequestManager, RequestMethod } from '../RequestManager.js';
|
import { type RequestManager, RequestMethod } from '../RequestManager.js';
|
||||||
@@ -121,3 +120,23 @@ export async function onRateLimit(manager: RequestManager, rateLimitData: RateLi
|
|||||||
export function calculateUserDefaultAvatarIndex(userId: Snowflake) {
|
export function calculateUserDefaultAvatarIndex(userId: Snowflake) {
|
||||||
return Number(BigInt(userId) >> 22n) % 6;
|
return Number(BigInt(userId) >> 22n) % 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sleeps for a given amount of time.
|
||||||
|
*
|
||||||
|
* @param ms - The amount of time (in milliseconds) to sleep for
|
||||||
|
*/
|
||||||
|
export async function sleep(ms: number): Promise<void> {
|
||||||
|
return new Promise<void>((resolve) => {
|
||||||
|
setTimeout(() => resolve(), ms);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies that a value is a buffer-like object.
|
||||||
|
*
|
||||||
|
* @param value - The value to check
|
||||||
|
*/
|
||||||
|
export function isBufferLike(value: unknown): value is ArrayBuffer | Buffer | Uint8Array | Uint8ClampedArray {
|
||||||
|
return value instanceof ArrayBuffer || value instanceof Uint8Array || value instanceof Uint8ClampedArray;
|
||||||
|
}
|
||||||
|
|||||||
15
packages/rest/src/shared.ts
Normal file
15
packages/rest/src/shared.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
export * from './lib/CDN.js';
|
||||||
|
export * from './lib/errors/DiscordAPIError.js';
|
||||||
|
export * from './lib/errors/HTTPError.js';
|
||||||
|
export * from './lib/errors/RateLimitError.js';
|
||||||
|
export * from './lib/RequestManager.js';
|
||||||
|
export * from './lib/REST.js';
|
||||||
|
export * from './lib/utils/constants.js';
|
||||||
|
export { calculateUserDefaultAvatarIndex, makeURLSearchParams, parseResponse } from './lib/utils/utils.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link https://github.com/discordjs/discord.js/blob/main/packages/rest/#readme | @discordjs/rest} version
|
||||||
|
* that you are currently using.
|
||||||
|
*/
|
||||||
|
// This needs to explicitly be `string` so it is not typed as a "const string" that gets injected by esbuild
|
||||||
|
export const version = '[VI]{{inject}}[/VI]' as string;
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Buffer } from 'node:buffer';
|
import { STATUS_CODES } from 'node:http';
|
||||||
import { URLSearchParams } from 'node:url';
|
import { URLSearchParams } from 'node:url';
|
||||||
import { types } from 'node:util';
|
import { types } from 'node:util';
|
||||||
import { type RequestInit, request } from 'undici';
|
import { type RequestInit, request } from 'undici';
|
||||||
import type { ResponseLike } from '../index.js';
|
import type { ResponseLike } from '../shared.js';
|
||||||
|
|
||||||
export type RequestOptions = Exclude<Parameters<typeof request>[1], undefined>;
|
export type RequestOptions = Exclude<Parameters<typeof request>[1], undefined>;
|
||||||
|
|
||||||
@@ -30,6 +30,7 @@ export async function makeRequest(url: string, init: RequestInit): Promise<Respo
|
|||||||
},
|
},
|
||||||
headers: new Headers(res.headers as Record<string, string[] | string>),
|
headers: new Headers(res.headers as Record<string, string[] | string>),
|
||||||
status: res.statusCode,
|
status: res.statusCode,
|
||||||
|
statusText: STATUS_CODES[res.statusCode]!,
|
||||||
ok: res.statusCode >= 200 && res.statusCode < 300,
|
ok: res.statusCode >= 200 && res.statusCode < 300,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
5
packages/rest/src/web.ts
Normal file
5
packages/rest/src/web.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { setDefaultStrategy } from './environment.js';
|
||||||
|
|
||||||
|
setDefaultStrategy(fetch);
|
||||||
|
|
||||||
|
export * from './shared.js';
|
||||||
@@ -2,6 +2,6 @@ import { esbuildPluginVersionInjector } from 'esbuild-plugin-version-injector';
|
|||||||
import { createTsupConfig } from '../../tsup.config.js';
|
import { createTsupConfig } from '../../tsup.config.js';
|
||||||
|
|
||||||
export default createTsupConfig({
|
export default createTsupConfig({
|
||||||
entry: ['src/index.ts', 'src/strategies/*.ts'],
|
entry: ['src/index.ts', 'src/web.ts', 'src/strategies/*.ts'],
|
||||||
esbuildPlugins: [esbuildPluginVersionInjector()],
|
esbuildPlugins: [esbuildPluginVersionInjector()],
|
||||||
});
|
});
|
||||||
|
|||||||
11
packages/rest/vitest.config.ts
Normal file
11
packages/rest/vitest.config.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { defineProject, mergeConfig } from 'vitest/config';
|
||||||
|
import configShared from '../../vitest.config.js';
|
||||||
|
|
||||||
|
export default mergeConfig(
|
||||||
|
configShared,
|
||||||
|
defineProject({
|
||||||
|
test: {
|
||||||
|
setupFiles: ['./__tests__/setup.ts'],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
export * from './lazy.js';
|
export * from './lazy.js';
|
||||||
export * from './range.js';
|
export * from './range.js';
|
||||||
export * from './calculateShardId.js';
|
export * from './calculateShardId.js';
|
||||||
|
export * from './runtime.js';
|
||||||
|
export * from './userAgentAppendix.js';
|
||||||
|
|||||||
12
packages/util/src/functions/runtime.ts
Normal file
12
packages/util/src/functions/runtime.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
export function shouldUseGlobalFetchAndWebSocket() {
|
||||||
|
// Browser env and deno when ran directly
|
||||||
|
if (typeof globalThis.process === 'undefined') {
|
||||||
|
return 'fetch' in globalThis && 'WebSocket' in globalThis;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('versions' in globalThis.process) {
|
||||||
|
return 'deno' in globalThis.process.versions || 'bun' in globalThis.process.versions;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
52
packages/util/src/functions/userAgentAppendix.ts
Normal file
52
packages/util/src/functions/userAgentAppendix.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/* eslint-disable n/prefer-global/process */
|
||||||
|
/* eslint-disable no-restricted-globals */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the user agent appendix string for the current environment.
|
||||||
|
*/
|
||||||
|
export function getUserAgentAppendix(): string {
|
||||||
|
// https://vercel.com/docs/concepts/functions/edge-functions/edge-runtime#check-if-you're-running-on-the-edge-runtime
|
||||||
|
// @ts-expect-error Vercel Edge functions
|
||||||
|
if (typeof globalThis.EdgeRuntime !== 'undefined') {
|
||||||
|
return 'Vercel-Edge-Functions';
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error Cloudflare Workers
|
||||||
|
if (typeof globalThis.R2 !== 'undefined' && typeof globalThis.WebSocketPair !== 'undefined') {
|
||||||
|
// https://developers.cloudflare.com/workers/runtime-apis/web-standards/#navigatoruseragent
|
||||||
|
return 'Cloudflare-Workers';
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://docs.netlify.com/edge-functions/api/#netlify-global-object
|
||||||
|
// @ts-expect-error Netlify Edge functions
|
||||||
|
if (typeof globalThis.Netlify !== 'undefined') {
|
||||||
|
return 'Netlify-Edge-Functions';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Most (if not all) edge environments will have `process` defined. Within a web browser we'll extract it using `navigator.userAgent`.
|
||||||
|
if (typeof globalThis.process !== 'object') {
|
||||||
|
// @ts-expect-error web env
|
||||||
|
if (typeof globalThis.navigator === 'object') {
|
||||||
|
// @ts-expect-error web env
|
||||||
|
return globalThis.navigator.userAgent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'UnknownEnvironment';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('versions' in globalThis.process) {
|
||||||
|
if ('deno' in globalThis.process.versions) {
|
||||||
|
return `Deno/${globalThis.process.versions.deno}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('bun' in globalThis.process.versions) {
|
||||||
|
return `Bun/${globalThis.process.versions.bun}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('node' in globalThis.process.versions) {
|
||||||
|
return `Node.js/${globalThis.process.versions.node}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'UnknownEnvironment';
|
||||||
|
}
|
||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
type GatewayReceivePayload,
|
type GatewayReceivePayload,
|
||||||
type GatewaySendPayload,
|
type GatewaySendPayload,
|
||||||
} from 'discord-api-types/v10';
|
} from 'discord-api-types/v10';
|
||||||
import { WebSocket, type RawData } from 'ws';
|
import { WebSocket, type Data } from 'ws';
|
||||||
import type { Inflate } from 'zlib-sync';
|
import type { Inflate } from 'zlib-sync';
|
||||||
import type { IContextFetchingStrategy } from '../strategies/context/IContextFetchingStrategy.js';
|
import type { IContextFetchingStrategy } from '../strategies/context/IContextFetchingStrategy.js';
|
||||||
import { ImportantGatewayOpcodes, getInitialSendRateLimitState } from '../utils/constants.js';
|
import { ImportantGatewayOpcodes, getInitialSendRateLimitState } from '../utils/constants.js';
|
||||||
@@ -80,6 +80,12 @@ export interface SendRateLimitState {
|
|||||||
resetAt: number;
|
resetAt: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(vladfrangu): enable this once https://github.com/oven-sh/bun/issues/3392 is solved
|
||||||
|
// const WebSocketConstructor: typeof WebSocket = shouldUseGlobalFetchAndWebSocket()
|
||||||
|
// ? (globalThis as any).WebSocket
|
||||||
|
// : WebSocket;
|
||||||
|
const WebSocketConstructor: typeof WebSocket = WebSocket;
|
||||||
|
|
||||||
export class WebSocketShard extends AsyncEventEmitter<WebSocketShardEventsMap> {
|
export class WebSocketShard extends AsyncEventEmitter<WebSocketShardEventsMap> {
|
||||||
private connection: WebSocket | null = null;
|
private connection: WebSocket | null = null;
|
||||||
|
|
||||||
@@ -179,13 +185,27 @@ export class WebSocketShard extends AsyncEventEmitter<WebSocketShardEventsMap> {
|
|||||||
const session = await this.strategy.retrieveSessionInfo(this.id);
|
const session = await this.strategy.retrieveSessionInfo(this.id);
|
||||||
|
|
||||||
const url = `${session?.resumeURL ?? this.strategy.options.gatewayInformation.url}?${params.toString()}`;
|
const url = `${session?.resumeURL ?? this.strategy.options.gatewayInformation.url}?${params.toString()}`;
|
||||||
|
|
||||||
this.debug([`Connecting to ${url}`]);
|
this.debug([`Connecting to ${url}`]);
|
||||||
const connection = new WebSocket(url, { handshakeTimeout: this.strategy.options.handshakeTimeout ?? undefined })
|
|
||||||
.on('message', this.onMessage.bind(this))
|
const connection = new WebSocketConstructor(url, {
|
||||||
.on('error', this.onError.bind(this))
|
handshakeTimeout: this.strategy.options.handshakeTimeout ?? undefined,
|
||||||
.on('close', this.onClose.bind(this));
|
});
|
||||||
|
|
||||||
connection.binaryType = 'arraybuffer';
|
connection.binaryType = 'arraybuffer';
|
||||||
|
|
||||||
|
connection.onmessage = (event) => {
|
||||||
|
void this.onMessage(event.data, event.data instanceof ArrayBuffer);
|
||||||
|
};
|
||||||
|
|
||||||
|
connection.onerror = (event) => {
|
||||||
|
this.onError(event.error);
|
||||||
|
};
|
||||||
|
|
||||||
|
connection.onclose = (event) => {
|
||||||
|
void this.onClose(event.code);
|
||||||
|
};
|
||||||
|
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
|
|
||||||
this.#status = WebSocketShardStatus.Connecting;
|
this.#status = WebSocketShardStatus.Connecting;
|
||||||
@@ -249,9 +269,9 @@ export class WebSocketShard extends AsyncEventEmitter<WebSocketShardEventsMap> {
|
|||||||
|
|
||||||
if (this.connection) {
|
if (this.connection) {
|
||||||
// No longer need to listen to messages
|
// No longer need to listen to messages
|
||||||
this.connection.removeAllListeners('message');
|
this.connection.onmessage = null;
|
||||||
// Prevent a reconnection loop by unbinding the main close event
|
// Prevent a reconnection loop by unbinding the main close event
|
||||||
this.connection.removeAllListeners('close');
|
this.connection.onclose = null;
|
||||||
|
|
||||||
const shouldClose = this.connection.readyState === WebSocket.OPEN;
|
const shouldClose = this.connection.readyState === WebSocket.OPEN;
|
||||||
|
|
||||||
@@ -262,14 +282,22 @@ export class WebSocketShard extends AsyncEventEmitter<WebSocketShardEventsMap> {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
if (shouldClose) {
|
if (shouldClose) {
|
||||||
|
let outerResolve: () => void;
|
||||||
|
const promise = new Promise<void>((resolve) => {
|
||||||
|
outerResolve = resolve;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.connection.onclose = outerResolve!;
|
||||||
|
|
||||||
this.connection.close(options.code, options.reason);
|
this.connection.close(options.code, options.reason);
|
||||||
await once(this.connection, 'close');
|
|
||||||
|
await promise;
|
||||||
this.emit(WebSocketShardEvents.Closed, { code: options.code });
|
this.emit(WebSocketShardEvents.Closed, { code: options.code });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lastly, remove the error event.
|
// Lastly, remove the error event.
|
||||||
// Doing this earlier would cause a hard crash in case an error event fired on our `close` call
|
// Doing this earlier would cause a hard crash in case an error event fired on our `close` call
|
||||||
this.connection.removeAllListeners('error');
|
this.connection.onerror = null;
|
||||||
} else {
|
} else {
|
||||||
this.debug(['Destroying a shard that has no connection; please open an issue on GitHub']);
|
this.debug(['Destroying a shard that has no connection; please open an issue on GitHub']);
|
||||||
}
|
}
|
||||||
@@ -476,17 +504,23 @@ export class WebSocketShard extends AsyncEventEmitter<WebSocketShardEventsMap> {
|
|||||||
this.isAck = false;
|
this.isAck = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async unpackMessage(data: ArrayBuffer | Buffer, isBinary: boolean): Promise<GatewayReceivePayload | null> {
|
private async unpackMessage(data: Data, isBinary: boolean): Promise<GatewayReceivePayload | null> {
|
||||||
const decompressable = new Uint8Array(data);
|
|
||||||
|
|
||||||
// Deal with no compression
|
// Deal with no compression
|
||||||
if (!isBinary) {
|
if (!isBinary) {
|
||||||
return JSON.parse(this.textDecoder.decode(decompressable)) as GatewayReceivePayload;
|
try {
|
||||||
|
return JSON.parse(data as string) as GatewayReceivePayload;
|
||||||
|
} catch {
|
||||||
|
// This is a non-JSON payload / (at the time of writing this comment) emitted by bun wrongly interpreting custom close codes https://github.com/oven-sh/bun/issues/3392
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const decompressable = new Uint8Array(data as ArrayBuffer);
|
||||||
|
|
||||||
// Deal with identify compress
|
// Deal with identify compress
|
||||||
if (this.useIdentifyCompress) {
|
if (this.useIdentifyCompress) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
// eslint-disable-next-line promise/prefer-await-to-callbacks
|
||||||
inflate(decompressable, { chunkSize: 65_535 }, (err, result) => {
|
inflate(decompressable, { chunkSize: 65_535 }, (err, result) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
@@ -539,8 +573,8 @@ export class WebSocketShard extends AsyncEventEmitter<WebSocketShardEventsMap> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async onMessage(data: RawData, isBinary: boolean) {
|
private async onMessage(data: Data, isBinary: boolean) {
|
||||||
const payload = await this.unpackMessage(data as ArrayBuffer | Buffer, isBinary);
|
const payload = await this.unpackMessage(data, isBinary);
|
||||||
if (!payload) {
|
if (!payload) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
66
yarn.lock
66
yarn.lock
@@ -2318,13 +2318,14 @@ __metadata:
|
|||||||
"@sapphire/snowflake": ^3.5.1
|
"@sapphire/snowflake": ^3.5.1
|
||||||
"@types/node": 18.16.14
|
"@types/node": 18.16.14
|
||||||
"@vitest/coverage-c8": ^0.31.1
|
"@vitest/coverage-c8": ^0.31.1
|
||||||
|
"@vladfrangu/async_event_emitter": ^2.2.2
|
||||||
cross-env: ^7.0.3
|
cross-env: ^7.0.3
|
||||||
discord-api-types: ^0.37.45
|
discord-api-types: ^0.37.45
|
||||||
esbuild-plugin-version-injector: ^1.1.0
|
esbuild-plugin-version-injector: ^1.1.0
|
||||||
eslint: ^8.41.0
|
eslint: ^8.41.0
|
||||||
eslint-config-neon: ^0.1.47
|
eslint-config-neon: ^0.1.47
|
||||||
eslint-formatter-pretty: ^5.0.0
|
eslint-formatter-pretty: ^5.0.0
|
||||||
file-type: ^18.4.0
|
magic-bytes.js: ^1.0.14
|
||||||
prettier: ^2.8.8
|
prettier: ^2.8.8
|
||||||
tslib: ^2.5.2
|
tslib: ^2.5.2
|
||||||
tsup: ^6.7.0
|
tsup: ^6.7.0
|
||||||
@@ -6039,13 +6040,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@tokenizer/token@npm:^0.3.0":
|
|
||||||
version: 0.3.0
|
|
||||||
resolution: "@tokenizer/token@npm:0.3.0"
|
|
||||||
checksum: 1d575d02d2a9f0c5a4ca5180635ebd2ad59e0f18b42a65f3d04844148b49b3db35cf00b6012a1af2d59c2ab3caca59451c5689f747ba8667ee586ad717ee58e1
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@tootallnate/once@npm:1":
|
"@tootallnate/once@npm:1":
|
||||||
version: 1.1.2
|
version: 1.1.2
|
||||||
resolution: "@tootallnate/once@npm:1.1.2"
|
resolution: "@tootallnate/once@npm:1.1.2"
|
||||||
@@ -13540,17 +13534,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"file-type@npm:^18.4.0":
|
|
||||||
version: 18.4.0
|
|
||||||
resolution: "file-type@npm:18.4.0"
|
|
||||||
dependencies:
|
|
||||||
readable-web-to-node-stream: ^3.0.2
|
|
||||||
strtok3: ^7.0.0
|
|
||||||
token-types: ^5.0.1
|
|
||||||
checksum: 191aa44b662417d496efc51bfb061da4c51cddfe2e3f7467b580964c3d83dbd88f76662368ea231a84d489a7d8cfc0bc2df9fefc439b519c2e6ddc498122dae0
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"file-uri-to-path@npm:1.0.0":
|
"file-uri-to-path@npm:1.0.0":
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
resolution: "file-uri-to-path@npm:1.0.0"
|
resolution: "file-uri-to-path@npm:1.0.0"
|
||||||
@@ -15137,7 +15120,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"ieee754@npm:^1.1.13, ieee754@npm:^1.2.1":
|
"ieee754@npm:^1.1.13":
|
||||||
version: 1.2.1
|
version: 1.2.1
|
||||||
resolution: "ieee754@npm:1.2.1"
|
resolution: "ieee754@npm:1.2.1"
|
||||||
checksum: 5144c0c9815e54ada181d80a0b810221a253562422e7c6c3a60b1901154184f49326ec239d618c416c1c5945a2e197107aee8d986a3dd836b53dffefd99b5e7e
|
checksum: 5144c0c9815e54ada181d80a0b810221a253562422e7c6c3a60b1901154184f49326ec239d618c416c1c5945a2e197107aee8d986a3dd836b53dffefd99b5e7e
|
||||||
@@ -17760,6 +17743,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"magic-bytes.js@npm:^1.0.14":
|
||||||
|
version: 1.0.14
|
||||||
|
resolution: "magic-bytes.js@npm:1.0.14"
|
||||||
|
checksum: 5431948f5134ea27134a2e9c197ce5fdc89677682d365f275b0193a816a037cb9fc1c5eeeb541920d653e3c44f9022d007ef4b159ec4f1a814945be9f6be8abc
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"magic-string@npm:^0.27.0":
|
"magic-string@npm:^0.27.0":
|
||||||
version: 0.27.0
|
version: 0.27.0
|
||||||
resolution: "magic-string@npm:0.27.0"
|
resolution: "magic-string@npm:0.27.0"
|
||||||
@@ -20526,13 +20516,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"peek-readable@npm:^5.0.0":
|
|
||||||
version: 5.0.0
|
|
||||||
resolution: "peek-readable@npm:5.0.0"
|
|
||||||
checksum: bef5ceb50586eb42e14efba274ac57ffe97f0ed272df9239ce029f688f495d9bf74b2886fa27847c706a9db33acda4b7d23bbd09a2d21eb4c2a54da915117414
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"peek-stream@npm:^1.1.0":
|
"peek-stream@npm:^1.1.0":
|
||||||
version: 1.1.3
|
version: 1.1.3
|
||||||
resolution: "peek-stream@npm:1.1.3"
|
resolution: "peek-stream@npm:1.1.3"
|
||||||
@@ -21719,15 +21702,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"readable-web-to-node-stream@npm:^3.0.2":
|
|
||||||
version: 3.0.2
|
|
||||||
resolution: "readable-web-to-node-stream@npm:3.0.2"
|
|
||||||
dependencies:
|
|
||||||
readable-stream: ^3.6.0
|
|
||||||
checksum: 8c56cc62c68513425ddfa721954875b382768f83fa20e6b31e365ee00cbe7a3d6296f66f7f1107b16cd3416d33aa9f1680475376400d62a081a88f81f0ea7f9c
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"readdirp@npm:~3.6.0":
|
"readdirp@npm:~3.6.0":
|
||||||
version: 3.6.0
|
version: 3.6.0
|
||||||
resolution: "readdirp@npm:3.6.0"
|
resolution: "readdirp@npm:3.6.0"
|
||||||
@@ -23720,16 +23694,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"strtok3@npm:^7.0.0":
|
|
||||||
version: 7.0.0
|
|
||||||
resolution: "strtok3@npm:7.0.0"
|
|
||||||
dependencies:
|
|
||||||
"@tokenizer/token": ^0.3.0
|
|
||||||
peek-readable: ^5.0.0
|
|
||||||
checksum: 2ebe7ad8f2aea611dec6742cf6a42e82764892a362907f7ce493faf334501bf981ce21c828dcc300457e6d460dc9c34d644ededb3b01dcb9e37559203cf1748c
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"style-loader@npm:^3.3.2":
|
"style-loader@npm:^3.3.2":
|
||||||
version: 3.3.3
|
version: 3.3.3
|
||||||
resolution: "style-loader@npm:3.3.3"
|
resolution: "style-loader@npm:3.3.3"
|
||||||
@@ -24271,16 +24235,6 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"token-types@npm:^5.0.1":
|
|
||||||
version: 5.0.1
|
|
||||||
resolution: "token-types@npm:5.0.1"
|
|
||||||
dependencies:
|
|
||||||
"@tokenizer/token": ^0.3.0
|
|
||||||
ieee754: ^1.2.1
|
|
||||||
checksum: 32780123bc6ce8b6a2231d860445c994a02a720abf38df5583ea957aa6626873cd1c4dd8af62314da4cf16ede00c379a765707a3b06f04b8808c38efdae1c785
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"toml@npm:^3.0.0":
|
"toml@npm:^3.0.0":
|
||||||
version: 3.0.0
|
version: 3.0.0
|
||||||
resolution: "toml@npm:3.0.0"
|
resolution: "toml@npm:3.0.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user