Skip to content

Commit

Permalink
Rate limit web socket messages, close connection if limit exceeded
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jacobmischka committed Sep 6, 2022
1 parent 5d9491f commit 6e0dae3
Show file tree
Hide file tree
Showing 2 changed files with 17 additions and 3 deletions.
18 changes: 16 additions & 2 deletions src/classes/ISocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -46,6 +48,7 @@ export default class ISocket {
private pingTimeout: number
private isAuthenticated: boolean
private timeouts: Set<NodeJS.Timeout>
private isClosed = false
onMessage: Evt<string>
onOpen: Evt<void>
onError: Evt<Error>
Expand Down Expand Up @@ -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<void>((resolve, reject) => {
const id = v4()

Expand All @@ -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) {
Expand All @@ -148,13 +155,15 @@ export default class ISocket {
this.isAuthenticated = false

this.onClose.attach(() => {
this.isClosed = true
for (const timeout of this.timeouts) {
clearTimeout(timeout)
}
this.timeouts.clear()
})

this.ws.onopen = () => {
this.isClosed = false
this.onOpen.post()
}

Expand All @@ -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)

Expand Down Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion src/examples/basic/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
},
Expand Down

0 comments on commit 6e0dae3

Please sign in to comment.