diff --git a/package.json b/package.json index 337d1240..6d82fea7 100644 --- a/package.json +++ b/package.json @@ -12,13 +12,15 @@ "eventemitter3": "^4.0.7", "exponential-backoff": "^3.1.0", "isomorphic-ws": "^4.0.1", + "lodash.defaultsdeep": "^4.6.1", "tsc-watch": "^4.1.0", "ulid": "^2.3.0", "ws": "^7.2.1" }, "devDependencies": { "@types/events": "^3.0.0", - "@types/lodash": "^4.14.149", + "@types/lodash": "^4.14.168", + "@types/lodash.defaultsdeep": "^4.6.6", "@types/node": "^14.14.31", "@types/ws": "^7.2.1", "dotenv": "^8.2.0", diff --git a/src/Client.ts b/src/Client.ts index 7f3eb288..8a4e4b29 100644 --- a/src/Client.ts +++ b/src/Client.ts @@ -1,6 +1,6 @@ import Axios, { AxiosInstance, AxiosRequestConfig } from 'axios'; +import defaultsDeep from 'lodash.defaultsdeep'; import { EventEmitter } from 'eventemitter3'; -import { defaultsDeep } from 'lodash'; import { defaultConfig } from '.'; import { WebSocketClient } from './websocket/client'; @@ -15,6 +15,7 @@ import { ClientboundNotification } from './websocket/notifications'; export interface ClientOptions { apiURL: string autoReconnect: boolean + heartbeat: number debug: boolean db: Db } @@ -35,6 +36,7 @@ export declare interface Client { export class Client extends EventEmitter { private db?: Db; + heartbeat: number; user?: Readonly; session?: Auth.Session; websocket: WebSocketClient; @@ -52,6 +54,7 @@ export class Client extends EventEmitter { this.options = defaultsDeep(options, defaultConfig); this.Axios = Axios.create({ baseURL: this.apiURL }); this.websocket = new WebSocketClient(this); + this.heartbeat = this.options.heartbeat; if (options.db) { this.db = options.db; @@ -224,7 +227,6 @@ export class Client extends EventEmitter { this.channels.clear(); this.users = new Users(this); this.channels = new Channels(this); - // this.messages = new Map(); } register(apiURL: string, data: Route<'POST', '/auth/create'>["data"]) { diff --git a/src/index.ts b/src/index.ts index 799e53b9..b9e8868d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,5 +6,6 @@ export const LIBRARY_VERSION = '4.0.0-alpha.7'; export const defaultConfig = { apiURL: 'https://api.revolt.chat', autoReconnect: true, - debug: false + heartbeat: 10, + debug: false, }; diff --git a/src/websocket/client.ts b/src/websocket/client.ts index 35ed137f..becfa668 100644 --- a/src/websocket/client.ts +++ b/src/websocket/client.ts @@ -9,6 +9,7 @@ export class WebSocketClient { client: Client; ws?: WebSocket; + heartbeat?: number; connected: boolean; ready: boolean; @@ -20,6 +21,7 @@ export class WebSocketClient { } disconnect() { + clearInterval(this.heartbeat); this.connected = false; this.ready = false; @@ -29,7 +31,7 @@ export class WebSocketClient { } send(notification: ServerboundNotification) { - if (!this.ws) return; + if (typeof this.ws === 'undefined' || this.ws.readyState !== WebSocket.OPEN) return; let data = JSON.stringify(notification); if (this.client.debug) console.debug('[<] PACKET', data); @@ -100,6 +102,14 @@ export class WebSocketClient { this.client.emit('ready'); this.ready = true; resolve(); + + if (this.client.heartbeat > 0) { + this.heartbeat = setInterval( + () => this.send({ type: 'Ping', time: + new Date() }), + this.client.heartbeat * 1e3 + ) as any; + } + break; } diff --git a/src/websocket/notifications.ts b/src/websocket/notifications.ts index 617b6dd4..8f21c0dd 100644 --- a/src/websocket/notifications.ts +++ b/src/websocket/notifications.ts @@ -5,12 +5,16 @@ type WebSocketError = { }; export type ServerboundNotification = ( + { type: 'Ping', time: number } | + { type: 'Pong', time: number } | ({ type: 'Authenticate' } & Auth.Session) | ({ type: 'BeginTyping', channel: string }) | ({ type: 'EndTyping', channel: string }) ); export type ClientboundNotification = ( + { type: 'Ping', time: number } | + { type: 'Pong', time: number } | ({ type: 'Error' } & WebSocketError) | { type: 'Authenticated' } | { type: 'Ready', users: Users.User[], channels: Channels.Channel[] } | diff --git a/yarn.lock b/yarn.lock index 2630057d..ef47f0d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -38,10 +38,17 @@ resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== -"@types/lodash@^4.14.149": - version "4.14.149" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.149.tgz#1342d63d948c6062838fbf961012f74d4e638440" - integrity sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ== +"@types/lodash.defaultsdeep@^4.6.6": + version "4.6.6" + resolved "https://registry.yarnpkg.com/@types/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.6.tgz#d2e87c07ec8d0361e4b79aa000815732b210be04" + integrity sha512-k3bXTg1/54Obm6uFEtSwvDm2vCyK9jSROv0V9X3gFFNPu7eKmvqqadPSXx0SkVVixSilR30BxhFlnIj8OavXOA== + dependencies: + "@types/lodash" "*" + +"@types/lodash@*", "@types/lodash@^4.14.168": + version "4.14.168" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.168.tgz#fe24632e79b7ade3f132891afff86caa5e5ce008" + integrity sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q== "@types/node@*": version "13.7.0" @@ -295,6 +302,11 @@ isomorphic-ws@^4.0.1: resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== +lodash.defaultsdeep@^4.6.1: + version "4.6.1" + resolved "https://registry.yarnpkg.com/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz#512e9bd721d272d94e3d3a63653fa17516741ca6" + integrity sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA== + lodash.isequal@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"