From 358765b2bfae3068f2b7561852bcf14eeafea01b Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 14 Sep 2021 17:09:50 +0100 Subject: [PATCH] Add internal rate limiter for acknowledgements. Fix Ping format. Internally calculate ping. --- package.json | 2 +- src/Client.ts | 4 +++- src/api/routes.ts | 1 + src/config.ts | 4 +++- src/maps/Channels.ts | 24 ++++++++++++++++++++++-- src/tester.ts | 4 ++-- src/websocket/client.ts | 12 ++++++++++-- src/websocket/notifications.ts | 8 ++++---- 8 files changed, 46 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 68b2e9c7..6980536a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "revolt.js", - "version": "5.1.0-alpha.2", + "version": "5.1.0-alpha.3", "main": "dist/index.js", "repository": "https://gitlab.insrt.uk/revolt/revolt.js", "author": "Paul Makles ", diff --git a/src/Client.ts b/src/Client.ts index e45613b2..fd614698 100644 --- a/src/Client.ts +++ b/src/Client.ts @@ -30,6 +30,8 @@ export interface ClientOptions { heartbeat: number autoReconnect: boolean + + ackRateLimiter: boolean } export declare interface Client { @@ -63,9 +65,9 @@ export class Client extends EventEmitter { session?: Session | string; user?: User; + options: ClientOptions; websocket: WebSocketClient; private Axios: AxiosInstance; - private options: ClientOptions; configuration?: RevoltConfiguration; users: Users; diff --git a/src/api/routes.ts b/src/api/routes.ts index 8fe8e162..6e731007 100644 --- a/src/api/routes.ts +++ b/src/api/routes.ts @@ -497,6 +497,7 @@ type Routes = banner?: string, categories?: Category[], system_messages?: SystemMessageChannels, + nsfw?: boolean, remove?: RemoveServerField }, response: undefined diff --git a/src/config.ts b/src/config.ts index ab9b68e2..86c9c77c 100644 --- a/src/config.ts +++ b/src/config.ts @@ -5,5 +5,7 @@ export const defaultConfig = { autoReconnect: true, heartbeat: 30, debug: false, - cache: false + cache: false, + + ackRateLimiter: true, }; diff --git a/src/maps/Channels.ts b/src/maps/Channels.ts index cbbf5fb1..e31887b6 100644 --- a/src/maps/Channels.ts +++ b/src/maps/Channels.ts @@ -414,13 +414,33 @@ export class Channel { return await this.client.req('POST', `/channels/${this._id}/join_call` as '/channels/id/join_call'); } + private ackTimeout?: number; + private ackLimit?: number; + /** * Mark a channel as read * @param message Last read message or its ID */ async ack(message?: Message | string) { - const id = (typeof message === 'string' ? message : message?._id) ?? ulid(); - return await this.client.req('PUT', `/channels/${this._id}/ack/${id}` as '/channels/id/ack/id'); + const id = (typeof message === 'string' ? message : message?._id) ?? this.last_message_id ?? ulid(); + const performAck = () => { + delete this.ackLimit; + this.client.req('PUT', `/channels/${this._id}/ack/${id}` as '/channels/id/ack/id'); + } + + if (!this.client.options.ackRateLimiter) return performAck(); + + clearTimeout(this.ackTimeout); + if (this.ackLimit && + new Date() > this.ackLimit) { + performAck(); + } + + // We need to use setTimeout here for both Node.js and browser. + this.ackTimeout = setTimeout(performAck, 5000) as unknown as number; + + if (!this.ackLimit) { + this.ackLimit = + new Date() + 15e3; + } } /** diff --git a/src/tester.ts b/src/tester.ts index c531a084..03304514 100644 --- a/src/tester.ts +++ b/src/tester.ts @@ -40,7 +40,7 @@ function user() { } }); - // client.login({ email: process.env.EMAIL as string, password: process.env.PASSWORD as string }) + client.login({ email: process.env.EMAIL as string, password: process.env.PASSWORD as string }) } function bot() { @@ -58,7 +58,7 @@ function bot() { } }); - client.loginBot(process.env.BOT_TOKEN as string) + // client.loginBot(process.env.BOT_TOKEN as string) } user(); diff --git a/src/websocket/client.ts b/src/websocket/client.ts index a1d049da..f62bf0c6 100644 --- a/src/websocket/client.ts +++ b/src/websocket/client.ts @@ -16,6 +16,8 @@ export class WebSocketClient { connected: boolean; ready: boolean; + ping?: number; + constructor(client: Client) { this.client = client; @@ -136,8 +138,9 @@ export class WebSocketClient { resolve(); if (this.client.heartbeat > 0) { + this.send({ type: 'Ping', data: + new Date() }); this.heartbeat = setInterval( - () => this.send({ type: 'Ping', time: + new Date() }), + () => this.send({ type: 'Ping', data: + new Date() }), this.client.heartbeat * 1e3 ) as any; } @@ -351,7 +354,12 @@ export class WebSocketClient { case "ChannelAck": break; - default: console.warn(`Warning: Unhandled packet! ${packet.type}`); + case "Pong": { + this.ping = + new Date() - packet.data; + break; + } + + default: this.client.debug && console.warn(`Warning: Unhandled packet! ${packet.type}`); } } diff --git a/src/websocket/notifications.ts b/src/websocket/notifications.ts index 791434e8..c801bbb4 100644 --- a/src/websocket/notifications.ts +++ b/src/websocket/notifications.ts @@ -11,8 +11,8 @@ type WebSocketError = { }; export type ServerboundNotification = ( - { type: 'Ping', time: number } | - { type: 'Pong', time: number } | + { type: 'Ping', data: number } | + { type: 'Pong', data: number } | ({ type: 'Authenticate' } & Session) | ({ type: 'Authenticate', token: string }) | ({ type: 'BeginTyping', channel: string }) | @@ -28,8 +28,8 @@ export type ReadyPacket = { } export type ClientboundNotification = ( - { type: 'Ping', time: number } | - { type: 'Pong', time: number } | + { type: 'Ping', data: number } | + { type: 'Pong', data: number } | ({ type: 'Error' } & WebSocketError) | { type: 'Authenticated' } | ReadyPacket |