From b1bdc8cc140c033b211ffa2d3e46ae1154081bee Mon Sep 17 00:00:00 2001 From: Daryl Ronningen Date: Mon, 12 Jul 2021 22:01:56 -0700 Subject: [PATCH] feat(gateway): wip: Connect to the Discord Gateway --- package.json | 4 ++-- src/api/apiHandler.ts | 4 +--- src/api/apiManager.ts | 2 +- src/client/client.ts | 14 ++++++++++-- src/gateway/gatewayClient.ts | 44 ++++++++++++++++++++++++++++++++++++ src/utils/defaults.ts | 6 +++++ src/utils/types.ts | 16 +++++++++++++ yarn.lock | 30 +++++++----------------- 8 files changed, 90 insertions(+), 30 deletions(-) create mode 100644 src/gateway/gatewayClient.ts diff --git a/package.json b/package.json index 4fb3223..ea55694 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "dependencies": { "@sapphire/async-queue": "^1.1.4", "abort-controller": "^3.0.0", + "fast-zlib": "^2.0.1", "form-data": "^4.0.0", "lodash": "^4.17.21", "node-fetch": "^2.6.1", @@ -89,8 +90,7 @@ }, "optionalDependencies": { "bufferutil": "^4.0.3", - "utf-8-validate": "^5.0.5", - "zlib-sync": "^0.1.7" + "utf-8-validate": "^5.0.5" }, "lint-staged": { "src/**/*": [ diff --git a/src/api/apiHandler.ts b/src/api/apiHandler.ts index a172942..e425a96 100644 --- a/src/api/apiHandler.ts +++ b/src/api/apiHandler.ts @@ -10,7 +10,6 @@ import type { Client } from '../client/client'; import type { IMakeRequestOptions, IRouteIdentifier } from '../utils/types'; function calculateReset(reset: number, resetAfter: number, serverDate: number): number { - // Use direct reset time when available, server date becomes irrelevant in this case if (resetAfter) { return Date.now() + Number(resetAfter) * 1000; } @@ -21,7 +20,6 @@ function getAPIOffset(serverDate: number): number { return new Date(serverDate).getTime() - Date.now(); } - export class ApiHandler { public client: Client; public hash: string; @@ -35,7 +33,7 @@ export class ApiHandler { 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._token = token; diff --git a/src/api/apiManager.ts b/src/api/apiManager.ts index cf01da7..495ea0e 100644 --- a/src/api/apiManager.ts +++ b/src/api/apiManager.ts @@ -32,7 +32,7 @@ export class ApiManager { let queue = this.queues.get(`${hash}:${routeId.majorParameter}`); 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); queue = this.queues.get(`${hash}:${routeId.majorParameter}`); diff --git a/src/client/client.ts b/src/client/client.ts index e04597a..ac1c2ac 100644 --- a/src/client/client.ts +++ b/src/client/client.ts @@ -1,20 +1,30 @@ import _ from 'lodash'; +import EventEmitter from 'events'; import { ApiHelper } from '../api/apiHelper'; +import { GatewayClient } from '../gateway/gatewayClient'; import { defaults } from '../utils/defaults'; import type { DeepRequired, IClientOptions } from '../utils/types'; -export class Client { +export class Client extends EventEmitter { public api: ApiHelper; - public options: DeepRequired; + public readonly options: DeepRequired; + public ws: GatewayClient; private _token: string; public constructor(token: string, options: IClientOptions = {}) { + super(); + this.options = _.merge(defaults.clientOptions, options as DeepRequired); this._token = token; this.api = new ApiHelper(this, this._token); + this.ws = new GatewayClient(this); + } + + public async login(): Promise { + await this.ws.connect(); } } diff --git a/src/gateway/gatewayClient.ts b/src/gateway/gatewayClient.ts new file mode 100644 index 0000000..986a792 --- /dev/null +++ b/src/gateway/gatewayClient.ts @@ -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 { + if (!this.connection) throw new Error('You are not connected to the Discord WebSocket Gateway!'); + + this.connection.close(1000); + } + + public async connect(): Promise { + 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; + + if (this.client.options.ws.compression) parsedMessage = this.inflate.process(msg); + }); + } +} diff --git a/src/utils/defaults.ts b/src/utils/defaults.ts index 979fcfa..8489f3d 100644 --- a/src/utils/defaults.ts +++ b/src/utils/defaults.ts @@ -10,5 +10,11 @@ export const defaults: IDefaultOptions = { url: 'https://discord.com/api', version: 9, }, + ws: { + compression: true, + encoding: 'json', + url: 'wss://gateway.discord.gg', + version: 9, + }, }, }; diff --git a/src/utils/types.ts b/src/utils/types.ts index d90c4aa..6b0fe11 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -10,6 +10,7 @@ export interface IApiClientOptions { export interface IClientOptions { api?: IApiClientOptions; + ws?: IWebSocketClientOptions; } export interface IDefaultOptions { @@ -55,6 +56,13 @@ export interface IRouteIdentifier { route: string; } +export interface IWebSocketClientOptions { + compression?: boolean; + encoding?: 'json'; + url?: string; + version?: number; +} + // Api Rest Request Interfaces // TODO: Add message components export interface IApiCreateMessage { @@ -76,6 +84,14 @@ export interface IApiCreateMessage { tts?: boolean; } +// WebSocket Gateway Message Interfaces +export interface IGatewayMessage { + d?: T; + op: number; + s?: number; + t?: string; +} + // Enums // Type Aliases diff --git a/yarn.lock b/yarn.lock index 4b5ded2..033a694 100644 --- a/yarn.lock +++ b/yarn.lock @@ -569,6 +569,7 @@ __metadata: eslint-plugin-header: ^3.1.1 eslint-plugin-import: ^2.23.4 eslint-plugin-unicorn: ^34.0.1 + fast-zlib: ^2.0.1 form-data: ^4.0.0 husky: ^7.0.0 inquirer: ^8.1.1 @@ -587,14 +588,11 @@ __metadata: typescript: ^4.3.5 utf-8-validate: ^5.0.5 ws: ^7.5.2 - zlib-sync: ^0.1.7 dependenciesMeta: bufferutil: optional: true utf-8-validate: optional: true - zlib-sync: - optional: true languageName: unknown linkType: soft @@ -3503,6 +3501,13 @@ __metadata: languageName: node 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": version: 1.11.1 resolution: "fastq@npm:1.11.1" @@ -5907,15 +5912,6 @@ fsevents@~2.3.2: languageName: node 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": version: 3.1.23 resolution: "nanoid@npm:3.1.23" @@ -8802,13 +8798,3 @@ typescript@^4.3.5: checksum: f77b3d8d00310def622123df93d4ee654fc6a0096182af8bd60679ddcdfb3474c56c6c7190817c84a2785648cdee9d721c0154eb45698c62176c322fb46fc700 languageName: node 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