Archived
0
0
Fork 0

feat(gateway): wip: Connect to the Discord Gateway

This commit is contained in:
Daryl Ronningen 2021-07-12 22:01:56 -07:00
parent 1428b79ea0
commit b1bdc8cc14
Signed by: Daryl Ronningen
GPG key ID: FD23F0C934A5EC6B
8 changed files with 90 additions and 30 deletions

View file

@ -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/**/*": [

View file

@ -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;

View file

@ -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}`);

View file

@ -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();
} }
} }

View 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);
});
}
}

View file

@ -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,
},
}, },
}; };

View file

@ -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

View file

@ -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