From 6e0dae37c6457eea423399eef9457e6f5ddbf8c1 Mon Sep 17 00:00:00 2001 From: Jacob Mischka Date: Tue, 6 Sep 2022 08:05:06 -0500 Subject: [PATCH 1/2] Rate limit web socket messages, close connection if limit exceeded The host will reconnect afterward, so future legitimate uses shouldn't be compromised, but the misbehaving action or loop should be terminated by the thrown error which should alert the users to look into their code. --- src/classes/ISocket.ts | 18 ++++++++++++++++-- src/examples/basic/index.ts | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/classes/ISocket.ts b/src/classes/ISocket.ts index 55ee72c..922f4c1 100644 --- a/src/classes/ISocket.ts +++ b/src/classes/ISocket.ts @@ -11,6 +11,8 @@ const MESSAGE_META = z.object({ export class TimeoutError extends Error {} +export class NotConnectedError extends Error {} + interface PendingMessage { data: string onAckReceived: () => void @@ -46,6 +48,7 @@ export default class ISocket { private pingTimeout: number private isAuthenticated: boolean private timeouts: Set + private isClosed = false onMessage: Evt onOpen: Evt onError: Evt @@ -95,6 +98,8 @@ export default class ISocket { * throwing an error if `ACK` is not received within `sendTimeout`. */ async send(data: string) { + if (this.isClosed) throw new NotConnectedError() + return new Promise((resolve, reject) => { const id = v4() @@ -121,8 +126,10 @@ export default class ISocket { * Close the underlying WebSocket connection, and this ISocket * connection. */ - close() { - return this.ws.close() + close(code?: number, reason?: string) { + this.isClosed = true + this.onMessage.detach() + return this.ws.close(code, reason) } constructor(ws: WebSocket | NodeWebSocket, config?: ISocketConfig) { @@ -148,6 +155,7 @@ export default class ISocket { this.isAuthenticated = false this.onClose.attach(() => { + this.isClosed = true for (const timeout of this.timeouts) { clearTimeout(timeout) } @@ -155,6 +163,7 @@ export default class ISocket { }) this.ws.onopen = () => { + this.isClosed = false this.onOpen.post() } @@ -172,6 +181,9 @@ export default class ISocket { if (evt.stopPropagation) { evt.stopPropagation() } + + if (this.isClosed) return + const data = JSON.parse(evt.data.toString()) const meta = MESSAGE_META.parse(data) @@ -218,6 +230,8 @@ export default class ISocket { * `pong` is not received within `pingTimeout`. */ async ping() { + if (this.isClosed) throw new NotConnectedError() + if (!('ping' in this.ws)) { // Not supported in web client WebSocket throw new Error( diff --git a/src/examples/basic/index.ts b/src/examples/basic/index.ts index 6917ede..d038f0d 100644 --- a/src/examples/basic/index.ts +++ b/src/examples/basic/index.ts @@ -689,7 +689,7 @@ const interval = new Interval({ } }, log_dos: async () => { - for (let i = 0; i < 100_000; i++) { + for (let i = 0; i < 1000; i++) { await ctx.log(i) } }, From a89b8c67737a96b392a93f4fa5d7f1beb45f27a1 Mon Sep 17 00:00:00 2001 From: Jacob Mischka Date: Wed, 7 Sep 2022 13:50:13 -0500 Subject: [PATCH 2/2] Add "[Interval] " prefix to non-production Logger logs too --- src/classes/Logger.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/classes/Logger.ts b/src/classes/Logger.ts index 09c3e97..300d34f 100644 --- a/src/classes/Logger.ts +++ b/src/classes/Logger.ts @@ -23,16 +23,16 @@ export default class Logger { } warn(...args: any[]) { - console.warn(...args) + console.warn('[Interval] ', ...args) } error(...args: any[]) { - console.error(...args) + console.error('[Interval] ', ...args) } debug(...args: any[]) { if (this.logLevel === 'debug') { - console.debug(...args) + console.debug('[Interval] ', ...args) } }