feat(gateway): wip: Connect to the Discord Gateway
This commit is contained in:
parent
1428b79ea0
commit
b1bdc8cc14
8 changed files with 90 additions and 30 deletions
|
@ -37,6 +37,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sapphire/async-queue": "^1.1.4",
|
"@sapphire/async-queue": "^1.1.4",
|
||||||
"abort-controller": "^3.0.0",
|
"abort-controller": "^3.0.0",
|
||||||
|
"fast-zlib": "^2.0.1",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"node-fetch": "^2.6.1",
|
"node-fetch": "^2.6.1",
|
||||||
|
@ -89,8 +90,7 @@
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"bufferutil": "^4.0.3",
|
"bufferutil": "^4.0.3",
|
||||||
"utf-8-validate": "^5.0.5",
|
"utf-8-validate": "^5.0.5"
|
||||||
"zlib-sync": "^0.1.7"
|
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"src/**/*": [
|
"src/**/*": [
|
||||||
|
|
|
@ -10,7 +10,6 @@ import type { Client } from '../client/client';
|
||||||
import type { IMakeRequestOptions, IRouteIdentifier } from '../utils/types';
|
import type { IMakeRequestOptions, IRouteIdentifier } from '../utils/types';
|
||||||
|
|
||||||
function calculateReset(reset: number, resetAfter: number, serverDate: number): number {
|
function calculateReset(reset: number, resetAfter: number, serverDate: number): number {
|
||||||
// Use direct reset time when available, server date becomes irrelevant in this case
|
|
||||||
if (resetAfter) {
|
if (resetAfter) {
|
||||||
return Date.now() + Number(resetAfter) * 1000;
|
return Date.now() + Number(resetAfter) * 1000;
|
||||||
}
|
}
|
||||||
|
@ -21,7 +20,6 @@ function getAPIOffset(serverDate: number): number {
|
||||||
return new Date(serverDate).getTime() - Date.now();
|
return new Date(serverDate).getTime() - Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class ApiHandler {
|
export class ApiHandler {
|
||||||
public client: Client;
|
public client: Client;
|
||||||
public hash: string;
|
public hash: string;
|
||||||
|
@ -35,7 +33,7 @@ export class ApiHandler {
|
||||||
|
|
||||||
private _token: string;
|
private _token: string;
|
||||||
|
|
||||||
public constructor(client: Client, manager: ApiManager, token: string, hash: string, majorParameter: string) {
|
public constructor(client: Client, token: string, manager: ApiManager, hash: string, majorParameter: string) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this._token = token;
|
this._token = token;
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ export class ApiManager {
|
||||||
let queue = this.queues.get(`${hash}:${routeId.majorParameter}`);
|
let queue = this.queues.get(`${hash}:${routeId.majorParameter}`);
|
||||||
|
|
||||||
if (!queue) {
|
if (!queue) {
|
||||||
const apiHandler = new ApiHandler(this.client, this, this._token, hash, routeId.majorParameter);
|
const apiHandler = new ApiHandler(this.client, this._token, this, hash, routeId.majorParameter);
|
||||||
this.queues.set(apiHandler.id, apiHandler);
|
this.queues.set(apiHandler.id, apiHandler);
|
||||||
|
|
||||||
queue = this.queues.get(`${hash}:${routeId.majorParameter}`);
|
queue = this.queues.get(`${hash}:${routeId.majorParameter}`);
|
||||||
|
|
|
@ -1,20 +1,30 @@
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import EventEmitter from 'events';
|
||||||
|
|
||||||
import { ApiHelper } from '../api/apiHelper';
|
import { ApiHelper } from '../api/apiHelper';
|
||||||
|
import { GatewayClient } from '../gateway/gatewayClient';
|
||||||
import { defaults } from '../utils/defaults';
|
import { defaults } from '../utils/defaults';
|
||||||
|
|
||||||
import type { DeepRequired, IClientOptions } from '../utils/types';
|
import type { DeepRequired, IClientOptions } from '../utils/types';
|
||||||
|
|
||||||
export class Client {
|
export class Client extends EventEmitter {
|
||||||
public api: ApiHelper;
|
public api: ApiHelper;
|
||||||
public options: DeepRequired<IClientOptions>;
|
public readonly options: DeepRequired<IClientOptions>;
|
||||||
|
public ws: GatewayClient;
|
||||||
|
|
||||||
private _token: string;
|
private _token: string;
|
||||||
|
|
||||||
public constructor(token: string, options: IClientOptions = {}) {
|
public constructor(token: string, options: IClientOptions = {}) {
|
||||||
|
super();
|
||||||
|
|
||||||
this.options = _.merge(defaults.clientOptions, options as DeepRequired<IClientOptions>);
|
this.options = _.merge(defaults.clientOptions, options as DeepRequired<IClientOptions>);
|
||||||
this._token = token;
|
this._token = token;
|
||||||
|
|
||||||
this.api = new ApiHelper(this, this._token);
|
this.api = new ApiHelper(this, this._token);
|
||||||
|
this.ws = new GatewayClient(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async login(): Promise<void> {
|
||||||
|
await this.ws.connect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
44
src/gateway/gatewayClient.ts
Normal file
44
src/gateway/gatewayClient.ts
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import zlib from 'fast-zlib';
|
||||||
|
import WebSocket from 'ws';
|
||||||
|
|
||||||
|
import type { Client } from '../client/client';
|
||||||
|
import type { IGatewayMessage } from '../utils/types';
|
||||||
|
|
||||||
|
export class GatewayClient {
|
||||||
|
public client: Client;
|
||||||
|
public connection: WebSocket | null;
|
||||||
|
public deflate: zlib.Deflate;
|
||||||
|
public inflate: zlib.Inflate;
|
||||||
|
|
||||||
|
private _sequence: number;
|
||||||
|
|
||||||
|
public constructor(client: Client) {
|
||||||
|
this.client = client;
|
||||||
|
|
||||||
|
this.connection = null;
|
||||||
|
this.deflate = new zlib.Deflate();
|
||||||
|
this.inflate = new zlib.Inflate();
|
||||||
|
|
||||||
|
this._sequence = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async close(): Promise<void> {
|
||||||
|
if (!this.connection) throw new Error('You are not connected to the Discord WebSocket Gateway!');
|
||||||
|
|
||||||
|
this.connection.close(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async connect(): Promise<void> {
|
||||||
|
if (this.connection) throw new Error('You are already connected to the Discord WebSocket Gateway!');
|
||||||
|
|
||||||
|
this.connection = new WebSocket(
|
||||||
|
`${this.client.options.ws.url}/?v=${this.client.options.ws.version}${this.client.options.ws.compression ? '&compress=zlib-stream' : ''}&encoding=${this.client.options.ws.encoding}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.connection.on('message', async (msg: Buffer | string) => {
|
||||||
|
let parsedMessage: IGatewayMessage<null>;
|
||||||
|
|
||||||
|
if (this.client.options.ws.compression) parsedMessage = this.inflate.process(msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,5 +10,11 @@ export const defaults: IDefaultOptions = {
|
||||||
url: 'https://discord.com/api',
|
url: 'https://discord.com/api',
|
||||||
version: 9,
|
version: 9,
|
||||||
},
|
},
|
||||||
|
ws: {
|
||||||
|
compression: true,
|
||||||
|
encoding: 'json',
|
||||||
|
url: 'wss://gateway.discord.gg',
|
||||||
|
version: 9,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,6 +10,7 @@ export interface IApiClientOptions {
|
||||||
|
|
||||||
export interface IClientOptions {
|
export interface IClientOptions {
|
||||||
api?: IApiClientOptions;
|
api?: IApiClientOptions;
|
||||||
|
ws?: IWebSocketClientOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IDefaultOptions {
|
export interface IDefaultOptions {
|
||||||
|
@ -55,6 +56,13 @@ export interface IRouteIdentifier {
|
||||||
route: string;
|
route: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IWebSocketClientOptions {
|
||||||
|
compression?: boolean;
|
||||||
|
encoding?: 'json';
|
||||||
|
url?: string;
|
||||||
|
version?: number;
|
||||||
|
}
|
||||||
|
|
||||||
// Api Rest Request Interfaces
|
// Api Rest Request Interfaces
|
||||||
// TODO: Add message components
|
// TODO: Add message components
|
||||||
export interface IApiCreateMessage {
|
export interface IApiCreateMessage {
|
||||||
|
@ -76,6 +84,14 @@ export interface IApiCreateMessage {
|
||||||
tts?: boolean;
|
tts?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WebSocket Gateway Message Interfaces
|
||||||
|
export interface IGatewayMessage<T> {
|
||||||
|
d?: T;
|
||||||
|
op: number;
|
||||||
|
s?: number;
|
||||||
|
t?: string;
|
||||||
|
}
|
||||||
|
|
||||||
// Enums
|
// Enums
|
||||||
|
|
||||||
// Type Aliases
|
// Type Aliases
|
||||||
|
|
30
yarn.lock
30
yarn.lock
|
@ -569,6 +569,7 @@ __metadata:
|
||||||
eslint-plugin-header: ^3.1.1
|
eslint-plugin-header: ^3.1.1
|
||||||
eslint-plugin-import: ^2.23.4
|
eslint-plugin-import: ^2.23.4
|
||||||
eslint-plugin-unicorn: ^34.0.1
|
eslint-plugin-unicorn: ^34.0.1
|
||||||
|
fast-zlib: ^2.0.1
|
||||||
form-data: ^4.0.0
|
form-data: ^4.0.0
|
||||||
husky: ^7.0.0
|
husky: ^7.0.0
|
||||||
inquirer: ^8.1.1
|
inquirer: ^8.1.1
|
||||||
|
@ -587,14 +588,11 @@ __metadata:
|
||||||
typescript: ^4.3.5
|
typescript: ^4.3.5
|
||||||
utf-8-validate: ^5.0.5
|
utf-8-validate: ^5.0.5
|
||||||
ws: ^7.5.2
|
ws: ^7.5.2
|
||||||
zlib-sync: ^0.1.7
|
|
||||||
dependenciesMeta:
|
dependenciesMeta:
|
||||||
bufferutil:
|
bufferutil:
|
||||||
optional: true
|
optional: true
|
||||||
utf-8-validate:
|
utf-8-validate:
|
||||||
optional: true
|
optional: true
|
||||||
zlib-sync:
|
|
||||||
optional: true
|
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
|
@ -3503,6 +3501,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"fast-zlib@npm:^2.0.1":
|
||||||
|
version: 2.0.1
|
||||||
|
resolution: "fast-zlib@npm:2.0.1"
|
||||||
|
checksum: 5019499b8aea5dea805117944cdcd4f7bf06b8d922690d4dca6384b5d6c0cc73b6e3c1488ab5c0b8add486617a2b6b2a0230f719be84362943a348c55f864a5a
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"fastq@npm:^1.6.0":
|
"fastq@npm:^1.6.0":
|
||||||
version: 1.11.1
|
version: 1.11.1
|
||||||
resolution: "fastq@npm:1.11.1"
|
resolution: "fastq@npm:1.11.1"
|
||||||
|
@ -5907,15 +5912,6 @@ fsevents@~2.3.2:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"nan@npm:^2.14.0":
|
|
||||||
version: 2.14.2
|
|
||||||
resolution: "nan@npm:2.14.2"
|
|
||||||
dependencies:
|
|
||||||
node-gyp: latest
|
|
||||||
checksum: 7a269139b66a7d37470effb7fb36a8de8cc3b5ffba6e40bb8e0545307911fe5ebf94797ec62f655ecde79c237d169899f8bd28256c66a32cbc8284faaf94c3f4
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"nanoid@npm:3.1.23":
|
"nanoid@npm:3.1.23":
|
||||||
version: 3.1.23
|
version: 3.1.23
|
||||||
resolution: "nanoid@npm:3.1.23"
|
resolution: "nanoid@npm:3.1.23"
|
||||||
|
@ -8802,13 +8798,3 @@ typescript@^4.3.5:
|
||||||
checksum: f77b3d8d00310def622123df93d4ee654fc6a0096182af8bd60679ddcdfb3474c56c6c7190817c84a2785648cdee9d721c0154eb45698c62176c322fb46fc700
|
checksum: f77b3d8d00310def622123df93d4ee654fc6a0096182af8bd60679ddcdfb3474c56c6c7190817c84a2785648cdee9d721c0154eb45698c62176c322fb46fc700
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"zlib-sync@npm:^0.1.7":
|
|
||||||
version: 0.1.7
|
|
||||||
resolution: "zlib-sync@npm:0.1.7"
|
|
||||||
dependencies:
|
|
||||||
nan: ^2.14.0
|
|
||||||
node-gyp: latest
|
|
||||||
checksum: 8f6ef7bf5726b41fefffa4509f5679872b425ef47227a59d8c787b32518db6220961262f78b0f6eb1dcd9b52cd71bc8213d4f061b8b56a9b7e82c345a548e6f4
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
Reference in a new issue