angelia.io 5.0.0 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/index.js +21 -2
- package/client/transport.js +51 -12
- package/package.json +1 -1
- package/readme.md +16 -5
- package/server/Server.js +9 -57
- package/server/Socket.js +4 -8
- package/server/constants.js +0 -3
- package/shared/protocol.js +3 -0
- package/types/client/transport.d.ts +1 -1
- package/types/server/Server.d.ts +0 -7
- package/types/server/Socket.d.ts +0 -1
- package/types/server/constants.d.ts +0 -2
- package/types/shared/protocol.d.ts +2 -0
package/client/index.js
CHANGED
|
@@ -47,6 +47,18 @@ const settle = reply => reply
|
|
|
47
47
|
|
|
48
48
|
const noop = () => {}
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* A reply that can no longer arrive isn't an error of the app's: only a caller
|
|
52
|
+
* reading the promise hears of it. A throwing callback still surfaces as unhandled.
|
|
53
|
+
*
|
|
54
|
+
* @template T
|
|
55
|
+
* @param {Promise<T>} promise
|
|
56
|
+
*/
|
|
57
|
+
const handled = promise => {
|
|
58
|
+
promise.catch(noop)
|
|
59
|
+
return promise
|
|
60
|
+
}
|
|
61
|
+
|
|
50
62
|
const newConnid = () =>
|
|
51
63
|
crypto.getRandomValues(new BigUint64Array(1))[0].toString(36)
|
|
52
64
|
|
|
@@ -104,6 +116,7 @@ export default class Client {
|
|
|
104
116
|
#isReconnect = false
|
|
105
117
|
#reconnect = true
|
|
106
118
|
#packFailed = false
|
|
119
|
+
#online = () => this.connect()
|
|
107
120
|
|
|
108
121
|
/** @param {ClientOptions} options */
|
|
109
122
|
constructor(options) {
|
|
@@ -135,6 +148,8 @@ export default class Client {
|
|
|
135
148
|
/** Open the socket; no-op while open or after `disconnect(true)`. */
|
|
136
149
|
connect() {
|
|
137
150
|
if (this.#io || !this.#reconnect) return
|
|
151
|
+
// The same function again is no second listener.
|
|
152
|
+
addEventListener('online', this.#online)
|
|
138
153
|
|
|
139
154
|
const url = new URL(this.options.url)
|
|
140
155
|
if (url.pathname === '/') url.pathname = DEFAULT_PATH
|
|
@@ -294,7 +309,7 @@ export default class Client {
|
|
|
294
309
|
}
|
|
295
310
|
// After `disconnect(true)` nothing will be sent: don't queue it.
|
|
296
311
|
if (!this.#reconnect) {
|
|
297
|
-
return callback ? Promise.reject(new Error(DISCONNECTED)) : undefined
|
|
312
|
+
return callback ? handled(Promise.reject(new Error(DISCONNECTED))) : undefined
|
|
298
313
|
}
|
|
299
314
|
|
|
300
315
|
if (callback) {
|
|
@@ -419,6 +434,7 @@ export default class Client {
|
|
|
419
434
|
|
|
420
435
|
/** After `disconnect(true)`: nothing will be sent or answered again. */
|
|
421
436
|
#shutdown() {
|
|
437
|
+
removeEventListener('online', this.#online)
|
|
422
438
|
this.#transport.terminate()
|
|
423
439
|
this.#messages = []
|
|
424
440
|
this.#cancelPending(DISCONNECTED)
|
|
@@ -463,7 +479,10 @@ export default class Client {
|
|
|
463
479
|
reject(err)
|
|
464
480
|
}
|
|
465
481
|
},
|
|
466
|
-
cancel:
|
|
482
|
+
cancel: err => {
|
|
483
|
+
handled(promise)
|
|
484
|
+
reject(err)
|
|
485
|
+
},
|
|
467
486
|
})
|
|
468
487
|
return [id, promise]
|
|
469
488
|
}
|
package/client/transport.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The Client's socket lives in a Web Worker, so the browser's own
|
|
3
|
-
* send/receive work (expensive in Chromium) and the heartbeat
|
|
3
|
+
* send/receive work (expensive in Chromium) and the heartbeat stay
|
|
4
4
|
* off the main thread (see `bench/browser`). Only strings cross
|
|
5
5
|
* `postMessage`; everything else stays in Client.
|
|
6
6
|
*
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
* }} Transport
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
+
import { TIMEOUT_MS } from '../shared/protocol.js'
|
|
26
|
+
|
|
25
27
|
/**
|
|
26
28
|
* The Client only opens a connection after the previous one closed, so
|
|
27
29
|
* the worker holds at most one socket and no ids are needed. Data frames
|
|
@@ -71,7 +73,7 @@ export function workerTransport() {
|
|
|
71
73
|
open(url, handlers) {
|
|
72
74
|
const live = (worker ??= spawn())
|
|
73
75
|
current = handlers
|
|
74
|
-
live.postMessage(['connect', url.toString()])
|
|
76
|
+
live.postMessage(['connect', url.toString(), TIMEOUT_MS])
|
|
75
77
|
return {
|
|
76
78
|
send: text => live.postMessage(text),
|
|
77
79
|
close: (code, reason) => live.postMessage(['close', code, reason]),
|
|
@@ -97,26 +99,63 @@ export function workerTransport() {
|
|
|
97
99
|
function workerMain(scope) {
|
|
98
100
|
/** @type {WebSocket | null} */
|
|
99
101
|
let ws = null
|
|
102
|
+
/** @type {ReturnType<typeof setInterval> | undefined} */
|
|
103
|
+
let heartbeat
|
|
104
|
+
let received = 0
|
|
105
|
+
let sent = 0
|
|
106
|
+
|
|
107
|
+
/** @param {number} code */
|
|
108
|
+
const closed = code => {
|
|
109
|
+
ws = null
|
|
110
|
+
clearInterval(heartbeat)
|
|
111
|
+
scope.postMessage(['close', code])
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** @param {number} timeout */
|
|
115
|
+
const watch = timeout => {
|
|
116
|
+
heartbeat = setInterval(() => {
|
|
117
|
+
const socket = /** @type {WebSocket} */ (ws)
|
|
118
|
+
const now = Date.now()
|
|
119
|
+
if (now - received > timeout) {
|
|
120
|
+
// A dead connection never finishes the closing handshake: don't wait for it.
|
|
121
|
+
socket.onmessage = socket.onclose = null
|
|
122
|
+
closed(1006)
|
|
123
|
+
socket.close()
|
|
124
|
+
} else if (now - Math.min(received, sent) > timeout / 2 - 5000) {
|
|
125
|
+
// Either way: the server times out a client that only listens.
|
|
126
|
+
sent = now
|
|
127
|
+
socket.send('')
|
|
128
|
+
}
|
|
129
|
+
}, timeout / 2)
|
|
130
|
+
}
|
|
100
131
|
|
|
101
132
|
scope.onmessage = ({ data }) => {
|
|
102
133
|
if (typeof data === 'string') {
|
|
103
|
-
if (ws?.readyState === WebSocket.OPEN)
|
|
134
|
+
if (ws?.readyState === WebSocket.OPEN) {
|
|
135
|
+
sent = Date.now()
|
|
136
|
+
ws.send(data)
|
|
137
|
+
}
|
|
104
138
|
return
|
|
105
139
|
}
|
|
106
|
-
const [type
|
|
140
|
+
const [type] = data
|
|
107
141
|
if (type === 'connect') {
|
|
108
|
-
const
|
|
142
|
+
const [, url, timeout] = data
|
|
143
|
+
const socket = new WebSocket(url)
|
|
109
144
|
ws = socket
|
|
110
|
-
socket.onopen = () =>
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
scope.postMessage(['
|
|
145
|
+
socket.onopen = () => {
|
|
146
|
+
received = sent = Date.now()
|
|
147
|
+
watch(timeout)
|
|
148
|
+
scope.postMessage(['open'])
|
|
149
|
+
}
|
|
150
|
+
socket.onclose = e => closed(e.code)
|
|
151
|
+
socket.onmessage = e => {
|
|
152
|
+
received = Date.now()
|
|
153
|
+
if (e.data !== '') scope.postMessage(e.data)
|
|
114
154
|
}
|
|
115
|
-
socket.onmessage = e =>
|
|
116
|
-
e.data === '' ? socket.send('') : scope.postMessage(e.data)
|
|
117
155
|
} else if (type === 'close') {
|
|
156
|
+
const [, code, reason] = data
|
|
118
157
|
try {
|
|
119
|
-
ws?.close(
|
|
158
|
+
ws?.close(code, reason)
|
|
120
159
|
} catch (err) {
|
|
121
160
|
scope.postMessage(['error', String(err)])
|
|
122
161
|
}
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -74,7 +74,6 @@ socket.on('gotIt', message => {
|
|
|
74
74
|
| `hostname` | String | `127.0.0.1` | the hostname (interface) to bind to |
|
|
75
75
|
| `port` | Number | 3001 | the TCP port to listen on; `0` lets the OS pick one, read it from `Server.port` after `listen()` |
|
|
76
76
|
| `path` | String | `/angelia` | the WebSocket path; clients connect to it by putting it in their `url` |
|
|
77
|
-
| `timeout` | Number | 60000 | time in milliseconds after a socket is considered gone, minimum value is 10000 |
|
|
78
77
|
| `maxMessageSize` | Number | 5 | max size of a single incoming message, in megabytes |
|
|
79
78
|
| `skipUTF8Validation` | Boolean | true | skip UTF-8 validation of incoming frames; pass `false` to close connections that send invalid UTF-8 |
|
|
80
79
|
| `cert` | String | undefined | path to a PEM-encoded TLS certificate file. With `privKey`, the server speaks `wss://`; otherwise `ws://` |
|
|
@@ -115,7 +114,6 @@ them with the exported class: `import { Server } from
|
|
|
115
114
|
| `port` | Number | port used by this server |
|
|
116
115
|
| `hostname` | String | the interface the server listens on |
|
|
117
116
|
| `path` | String | the WebSocket path clients connect to |
|
|
118
|
-
| `timeout` | Number | after how long the socket is considered gone, in ms |
|
|
119
117
|
| `served` | Number | count of sockets ever connected |
|
|
120
118
|
| `bytesReceived` | Number | sum of bytes the server has ever received |
|
|
121
119
|
| `bytesSent` | Number | sum of bytes sent by the server |
|
|
@@ -149,7 +147,6 @@ The `socket` object is given to you by a listener
|
|
|
149
147
|
| `params` | Object | the params sent via the client constructor (parsed from the WebSocket URL query); includes `connid`, a per-client id stable across reconnects. The reserved `m` param (URL-packed messages) is parsed off and not exposed here |
|
|
150
148
|
| `since` | Number | timestamp of first seen |
|
|
151
149
|
| `seen` | Number | timestamp of last received message |
|
|
152
|
-
| `ping` | Number | delay with the socket in milliseconds (full round trip) |
|
|
153
150
|
| `closed` | Boolean | whether the socket has disconnected |
|
|
154
151
|
| `timedout` | Boolean | whether we lost connection with this socket |
|
|
155
152
|
| `bytesSent` | Number | sum of bytes sent to this socket |
|
|
@@ -236,7 +233,6 @@ the right one by name.
|
|
|
236
233
|
| `listen()` | when the server has started listening |
|
|
237
234
|
| `connect(socket, request)` | when a socket connects; `request` is `{ headers, url }` from the upgrade — read cookies / auth / custom headers here, it's not kept on the socket |
|
|
238
235
|
| `disconnect(socket, code, reason)` | when a socket gets disconnected, with the WebSocket close code and reason (1006 and `''` when the connection just dropped) |
|
|
239
|
-
| `ping(socket)` | when we got an update of the ping for a socket |
|
|
240
236
|
| `timeout(socket, delay)` | when we are about to disconnect a timedout socket, gives the delay in milliseconds |
|
|
241
237
|
| `garbage(socket, data)` | when the client sends a message with no listener, a malformed one, or one named after a predefined event |
|
|
242
238
|
| `incoming(socket, messages)` | for debugging: ref to array of incoming messages received before dispatching to listeners |
|
|
@@ -267,7 +263,7 @@ to `/angelia`, the server's default; include the path
|
|
|
267
263
|
|
|
268
264
|
The WebSocket runs in a Web Worker: messages are still stringified and
|
|
269
265
|
parsed on the main thread, but the browser's own send and receive work
|
|
270
|
-
and the heartbeat
|
|
266
|
+
and the heartbeat happen off it. In Chromium that work is the
|
|
271
267
|
expensive part of moving large payloads; Firefox still routes a
|
|
272
268
|
worker's socket events through the main thread, so it gains less.
|
|
273
269
|
`npm run bench:worker` measures it.
|
|
@@ -322,10 +318,25 @@ down, or the page itself unloading) are retried after a random 2–4s
|
|
|
322
318
|
delay, so clients don't all come back at once and a page being
|
|
323
319
|
reloaded doesn't open a second connection on its way out.
|
|
324
320
|
|
|
321
|
+
The client keeps the heartbeat: every 30s it sends an empty
|
|
322
|
+
message to a server it hasn't heard from or written to for
|
|
323
|
+
25s, which the server answers, and it takes a
|
|
324
|
+
connection silent for over 60s for dead and reconnects. The server drops
|
|
325
|
+
a socket it hasn't heard from for as long. These times are fixed.
|
|
326
|
+
|
|
327
|
+
Going `online` reconnects at once if the client isn't connected, and does
|
|
328
|
+
nothing if it is.
|
|
329
|
+
|
|
330
|
+
A page Chromium keeps in its back/forward cache holds its connection
|
|
331
|
+
open, unanswering, until the server times it out or the page is
|
|
332
|
+
restored; either way the client reconnects as it is restored.
|
|
333
|
+
|
|
325
334
|
On reconnect, any messages still queued for send are dropped, and
|
|
326
335
|
pending reply promises (from `request`, or `emit` with a callback)
|
|
327
336
|
reject with an Error whose message starts with `reconnecting:` as soon as the connection drops;
|
|
328
337
|
after `disconnect(true)` they reject with an Error whose message starts with `disconnected:`.
|
|
338
|
+
A caller that ignores the promise hears nothing of a lost reply, while a
|
|
339
|
+
callback that throws still surfaces as an unhandled rejection.
|
|
329
340
|
Messages queued before the first connection opened wait for it.
|
|
330
341
|
The library can't tell which queued messages are safe to replay (a
|
|
331
342
|
typing indicator: yes; a "transfer $100": maybe not), so recovery is
|
package/server/Server.js
CHANGED
|
@@ -12,12 +12,14 @@ import {
|
|
|
12
12
|
import {
|
|
13
13
|
DEFAULT_MAX_MESSAGE_SIZE_MB,
|
|
14
14
|
DEFAULT_PORT,
|
|
15
|
-
DEFAULT_TIMEOUT_MS,
|
|
16
15
|
LIFECYCLE_EVENTS,
|
|
17
16
|
MAX_PORT,
|
|
18
|
-
MIN_TIMEOUT_MS,
|
|
19
17
|
} from './constants.js'
|
|
20
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
DEFAULT_PATH,
|
|
20
|
+
PACKED_PARAM,
|
|
21
|
+
TIMEOUT_MS,
|
|
22
|
+
} from '../shared/protocol.js'
|
|
21
23
|
|
|
22
24
|
import { Socket } from './Socket.js'
|
|
23
25
|
import { Emitter } from './Emitter.js'
|
|
@@ -31,9 +33,6 @@ import { createServer as createHttpsServer } from 'node:https'
|
|
|
31
33
|
|
|
32
34
|
import { WebSocketServer } from 'ws'
|
|
33
35
|
|
|
34
|
-
// The heartbeat is a constant empty message — frame once, reuse forever.
|
|
35
|
-
const PING = frame('')
|
|
36
|
-
|
|
37
36
|
// Close code for a connection that ended without a close frame, which is
|
|
38
37
|
// what `terminate()` does.
|
|
39
38
|
const ABNORMAL_CLOSURE = 1006
|
|
@@ -50,8 +49,6 @@ const DEFAULT_WATCH_DEBOUNCE_MS = 1000
|
|
|
50
49
|
* @property {number} [port] TCP port; `0` lets the OS pick one, readable
|
|
51
50
|
* from `port` after `listen()`.
|
|
52
51
|
* @property {string} [path] WebSocket path clients connect to.
|
|
53
|
-
* @property {number} [timeout] Milliseconds of silence after which a
|
|
54
|
-
* socket is considered gone.
|
|
55
52
|
* @property {number} [maxMessageSize] Max size of a single incoming
|
|
56
53
|
* message, in megabytes.
|
|
57
54
|
* @property {boolean} [skipUTF8Validation] Skip `ws`'s UTF-8 validation
|
|
@@ -83,7 +80,6 @@ const DEFAULT_WATCH_DEBOUNCE_MS = 1000
|
|
|
83
80
|
* @property {() => void} [listen]
|
|
84
81
|
* @property {(socket: Socket, request: ConnectRequest) => void} [connect]
|
|
85
82
|
* @property {(socket: Socket, code: number, reason: string) => void} [disconnect]
|
|
86
|
-
* @property {(socket: Socket) => void} [ping]
|
|
87
83
|
* @property {(socket: Socket, delay: number) => void} [timeout]
|
|
88
84
|
* @property {(socket: Socket, data: any) => void} [garbage]
|
|
89
85
|
* @property {(socket: Socket, messages: any[]) => void} [incoming]
|
|
@@ -180,7 +176,6 @@ export class Server extends Emitter {
|
|
|
180
176
|
this.port = DEFAULT_PORT
|
|
181
177
|
this.path = DEFAULT_PATH
|
|
182
178
|
|
|
183
|
-
this.timeout = DEFAULT_TIMEOUT_MS
|
|
184
179
|
|
|
185
180
|
this.maxMessageSize = DEFAULT_MAX_MESSAGE_SIZE_MB
|
|
186
181
|
|
|
@@ -264,10 +259,6 @@ export class Server extends Emitter {
|
|
|
264
259
|
|
|
265
260
|
this.path = options.path || this.path
|
|
266
261
|
|
|
267
|
-
const timeout = +(options.timeout ?? 0)
|
|
268
|
-
this.timeout =
|
|
269
|
-
timeout >= MIN_TIMEOUT_MS ? timeout : DEFAULT_TIMEOUT_MS
|
|
270
|
-
|
|
271
262
|
const maxMessageSize = +(options.maxMessageSize ?? 0)
|
|
272
263
|
this.maxMessageSize =
|
|
273
264
|
maxMessageSize > 0 ? maxMessageSize : DEFAULT_MAX_MESSAGE_SIZE_MB
|
|
@@ -323,7 +314,7 @@ export class Server extends Emitter {
|
|
|
323
314
|
this.since = this.now
|
|
324
315
|
|
|
325
316
|
// `unref` so a stopped server doesn't hold the process alive.
|
|
326
|
-
this.#pingTimer = setInterval(this.
|
|
317
|
+
this.#pingTimer = setInterval(this.sweep, TIMEOUT_MS / 2)
|
|
327
318
|
this.#pingTimer.unref()
|
|
328
319
|
|
|
329
320
|
this.#nowTimer = setInterval(this.#updateNow, 500)
|
|
@@ -511,8 +502,6 @@ export class Server extends Emitter {
|
|
|
511
502
|
else socket.garbage(m)
|
|
512
503
|
}
|
|
513
504
|
|
|
514
|
-
// ping on connect is usually high
|
|
515
|
-
setTimeout(() => this.#pingSocket(socket), 200).unref()
|
|
516
505
|
}
|
|
517
506
|
|
|
518
507
|
#onerror = err => {
|
|
@@ -579,62 +568,26 @@ export class Server extends Emitter {
|
|
|
579
568
|
return framed
|
|
580
569
|
}
|
|
581
570
|
|
|
582
|
-
// ping. Only `ping` is public so tests can drive the heartbeat
|
|
583
|
-
// sweep manually; the rest is internal.
|
|
584
|
-
|
|
585
|
-
/**
|
|
586
|
-
* @internal Sockets silent for longer than this get pinged. The sweep runs every
|
|
587
|
-
* `timeout / 2`; the margin makes sure a socket seen just after one
|
|
588
|
-
* sweep is still pinged before the next one times it out.
|
|
589
|
-
*/
|
|
590
|
-
get timeoutCheck() {
|
|
591
|
-
return this.timeout / 2 - 5000
|
|
592
|
-
}
|
|
593
|
-
|
|
594
571
|
#updateNow = () => {
|
|
595
572
|
this.now = now()
|
|
596
573
|
}
|
|
597
574
|
|
|
598
|
-
/** @internal
|
|
599
|
-
|
|
575
|
+
/** @internal Drops sockets gone silent; public so tests can run it. */
|
|
576
|
+
sweep = () => {
|
|
600
577
|
this.#updateNow()
|
|
601
|
-
const check = this.timeoutCheck
|
|
602
578
|
for (const socket of this.sockets) {
|
|
603
579
|
const delay = this.now - socket.seen
|
|
604
580
|
|
|
605
|
-
if (delay >
|
|
581
|
+
if (delay > TIMEOUT_MS) {
|
|
606
582
|
socket.timedout = true
|
|
607
583
|
this.hooks.timeout?.(socket, delay)
|
|
608
584
|
socket.io.terminate()
|
|
609
585
|
// Clean up now, as `disconnect()` does, not on the close event.
|
|
610
586
|
socket.onclose(ABNORMAL_CLOSURE, '')
|
|
611
|
-
} else if (delay > check) {
|
|
612
|
-
// `now` was just updated for the whole sweep.
|
|
613
|
-
socket.contacted = this.now
|
|
614
|
-
socket.write(PING)
|
|
615
587
|
}
|
|
616
588
|
}
|
|
617
589
|
}
|
|
618
590
|
|
|
619
|
-
/** @param {Socket} socket */
|
|
620
|
-
#pingSocket(socket) {
|
|
621
|
-
this.#updateNow()
|
|
622
|
-
socket.contacted = this.now
|
|
623
|
-
socket.write(PING)
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
/**
|
|
627
|
-
* @internal Called from Socket.onmessage on heartbeat receipt.
|
|
628
|
-
* @param {Socket} socket
|
|
629
|
-
*/
|
|
630
|
-
pong(socket) {
|
|
631
|
-
this.#updateNow()
|
|
632
|
-
socket.seen = this.now
|
|
633
|
-
socket.ping = this.now - socket.contacted
|
|
634
|
-
|
|
635
|
-
this.hooks.ping?.(socket)
|
|
636
|
-
}
|
|
637
|
-
|
|
638
591
|
/** `await using server = …` stops the server when the scope ends. */
|
|
639
592
|
[Symbol.asyncDispose]() {
|
|
640
593
|
return this.stop()
|
|
@@ -688,7 +641,6 @@ export class Server extends Emitter {
|
|
|
688
641
|
sockets: this.sockets.size,
|
|
689
642
|
served: this.served,
|
|
690
643
|
since: this.since,
|
|
691
|
-
timeout: this.timeout,
|
|
692
644
|
bytesSent: this.bytesSent,
|
|
693
645
|
bytesReceived: this.bytesReceived,
|
|
694
646
|
messagesSent: this.messagesSent,
|
package/server/Socket.js
CHANGED
|
@@ -20,6 +20,8 @@ import { WebSocket } from 'ws'
|
|
|
20
20
|
/** @import { Server } from './Server.js' */
|
|
21
21
|
|
|
22
22
|
const DISCONNECT = frame(stringify([[PROTOCOL_KEY, NO_RECONNECT]]))
|
|
23
|
+
// The answer to a client's heartbeat, an empty message: frame once, reuse forever.
|
|
24
|
+
const PONG = frame('')
|
|
23
25
|
|
|
24
26
|
// Rejection message for replies the client can no longer send.
|
|
25
27
|
const DISCONNECTED = 'disconnected: the socket closed before the client replied'
|
|
@@ -35,9 +37,6 @@ export class Socket {
|
|
|
35
37
|
/** Last callback id handed out; ids start at 1, as 0 means "none". */
|
|
36
38
|
#lastCallbackId = 0
|
|
37
39
|
|
|
38
|
-
/** @internal When the last ping was sent, to time the pong. */
|
|
39
|
-
contacted = 0
|
|
40
|
-
|
|
41
40
|
/**
|
|
42
41
|
* @internal Messages waiting for the next batch.
|
|
43
42
|
* @type {any[]}
|
|
@@ -78,9 +77,7 @@ export class Socket {
|
|
|
78
77
|
|
|
79
78
|
this.since = server.now
|
|
80
79
|
this.seen = server.now
|
|
81
|
-
this.contacted = server.now
|
|
82
80
|
|
|
83
|
-
this.ping = 0
|
|
84
81
|
this.timedout = false
|
|
85
82
|
this.closed = false
|
|
86
83
|
|
|
@@ -190,11 +187,11 @@ export class Socket {
|
|
|
190
187
|
|
|
191
188
|
/** @internal @param {Buffer} raw */
|
|
192
189
|
onmessage = raw => {
|
|
190
|
+
this.seen = this.server.now
|
|
193
191
|
if (raw.length === 0) {
|
|
194
|
-
this.
|
|
192
|
+
this.write(PONG)
|
|
195
193
|
return
|
|
196
194
|
}
|
|
197
|
-
this.seen = this.server.now
|
|
198
195
|
|
|
199
196
|
this.server.bytesReceived += raw.length
|
|
200
197
|
this.bytesReceived += raw.length
|
|
@@ -397,7 +394,6 @@ export class Socket {
|
|
|
397
394
|
params: this.params,
|
|
398
395
|
closed: this.closed,
|
|
399
396
|
timedout: this.timedout,
|
|
400
|
-
ping: this.ping,
|
|
401
397
|
since: this.since,
|
|
402
398
|
seen: this.seen,
|
|
403
399
|
rooms: [...this.rooms].map(room => room.id),
|
package/server/constants.js
CHANGED
|
@@ -5,7 +5,6 @@ export const LIFECYCLE_EVENTS = new Set([
|
|
|
5
5
|
'listen',
|
|
6
6
|
'connect',
|
|
7
7
|
'disconnect',
|
|
8
|
-
'ping',
|
|
9
8
|
'timeout',
|
|
10
9
|
'garbage',
|
|
11
10
|
'incoming',
|
|
@@ -14,9 +13,7 @@ export const LIFECYCLE_EVENTS = new Set([
|
|
|
14
13
|
|
|
15
14
|
// Server defaults
|
|
16
15
|
export const DEFAULT_PORT = 3001
|
|
17
|
-
export const DEFAULT_TIMEOUT_MS = 60_000
|
|
18
16
|
export const DEFAULT_MAX_MESSAGE_SIZE_MB = 5
|
|
19
17
|
|
|
20
18
|
// Server bounds
|
|
21
|
-
export const MIN_TIMEOUT_MS = 10_000
|
|
22
19
|
export const MAX_PORT = 65535
|
package/shared/protocol.js
CHANGED
|
@@ -15,6 +15,9 @@ export const PROTOCOL_KEY = ''
|
|
|
15
15
|
/** Command telling the client to close and not reconnect. */
|
|
16
16
|
export const NO_RECONNECT = 'disconnect'
|
|
17
17
|
|
|
18
|
+
/** Milliseconds of silence after which a connection counts as gone. */
|
|
19
|
+
export const TIMEOUT_MS = 60_000
|
|
20
|
+
|
|
18
21
|
/** WebSocket close frames carry at most this many bytes of reason. */
|
|
19
22
|
const MAX_CLOSE_REASON_BYTES = 123
|
|
20
23
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The Client's socket lives in a Web Worker, so the browser's own
|
|
3
|
-
* send/receive work (expensive in Chromium) and the heartbeat
|
|
3
|
+
* send/receive work (expensive in Chromium) and the heartbeat stay
|
|
4
4
|
* off the main thread (see `bench/browser`). Only strings cross
|
|
5
5
|
* `postMessage`; everything else stays in Client.
|
|
6
6
|
*
|
package/types/server/Server.d.ts
CHANGED
|
@@ -17,11 +17,6 @@ export type ServerListenOptions = {
|
|
|
17
17
|
* WebSocket path clients connect to.
|
|
18
18
|
*/
|
|
19
19
|
path?: string;
|
|
20
|
-
/**
|
|
21
|
-
* Milliseconds of silence after which a
|
|
22
|
-
* socket is considered gone.
|
|
23
|
-
*/
|
|
24
|
-
timeout?: number;
|
|
25
20
|
/**
|
|
26
21
|
* Max size of a single incoming
|
|
27
22
|
* message, in megabytes.
|
|
@@ -66,7 +61,6 @@ export type PredefinedServerHandlers = {
|
|
|
66
61
|
listen?: () => void;
|
|
67
62
|
connect?: (socket: Socket, request: ConnectRequest) => void;
|
|
68
63
|
disconnect?: (socket: Socket, code: number, reason: string) => void;
|
|
69
|
-
ping?: (socket: Socket) => void;
|
|
70
64
|
timeout?: (socket: Socket, delay: number) => void;
|
|
71
65
|
garbage?: (socket: Socket, data: any) => void;
|
|
72
66
|
incoming?: (socket: Socket, messages: any[]) => void;
|
|
@@ -87,7 +81,6 @@ export declare class Server extends Emitter {
|
|
|
87
81
|
hostname: string;
|
|
88
82
|
port: number;
|
|
89
83
|
path: string;
|
|
90
|
-
timeout: number;
|
|
91
84
|
maxMessageSize: number;
|
|
92
85
|
/**
|
|
93
86
|
* When true, read `x-forwarded-for` from the upgrade headers for
|
package/types/server/Socket.d.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
export declare const parent: unique symbol;
|
|
2
2
|
export declare const LIFECYCLE_EVENTS: Set<string>;
|
|
3
3
|
export declare const DEFAULT_PORT = 3001;
|
|
4
|
-
export declare const DEFAULT_TIMEOUT_MS = 60000;
|
|
5
4
|
export declare const DEFAULT_MAX_MESSAGE_SIZE_MB = 5;
|
|
6
|
-
export declare const MIN_TIMEOUT_MS = 10000;
|
|
7
5
|
export declare const MAX_PORT = 65535;
|
|
@@ -9,6 +9,8 @@ export declare const PACKED_PARAM = "m";
|
|
|
9
9
|
export declare const PROTOCOL_KEY = "";
|
|
10
10
|
/** Command telling the client to close and not reconnect. */
|
|
11
11
|
export declare const NO_RECONNECT = "disconnect";
|
|
12
|
+
/** Milliseconds of silence after which a connection counts as gone. */
|
|
13
|
+
export declare const TIMEOUT_MS = 60000;
|
|
12
14
|
/**
|
|
13
15
|
* Throw before closing with a reason the WebSocket would reject, so a
|
|
14
16
|
* bad reason fails with nothing half done.
|