sleepy-serv 0.22.0 → 0.23.0
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/README.md +6 -6
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/socket.d.ts +2 -3
- package/dist/core/socket.d.ts.map +1 -1
- package/dist/core/utils.d.ts +20 -15
- package/dist/core/utils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/index.ts +7 -1
- package/src/core/socket.ts +35 -26
- package/src/core/utils.ts +19 -16
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ object with these properties:
|
|
|
65
65
|
directly.
|
|
66
66
|
- `ws`: WebSocket commands for interacting with connected clients:
|
|
67
67
|
`query(fn)`, `send(fn, event, body)`, `broadcast(event, body)`, and
|
|
68
|
-
`drop(
|
|
68
|
+
`drop(signal, fn)`.
|
|
69
69
|
- `close`: an `async` function that shuts the app down. See
|
|
70
70
|
[Shutting Down](#shutting-down).
|
|
71
71
|
|
|
@@ -551,7 +551,7 @@ const app = createApp(3000, {
|
|
|
551
551
|
reclaimTtl: 300_000,
|
|
552
552
|
ticketTtl: 10_000,
|
|
553
553
|
onOpen: clientId => console.log('connected:', clientId),
|
|
554
|
-
onClose: (clientId,
|
|
554
|
+
onClose: (clientId, signal) => console.log('closed:', clientId, signal),
|
|
555
555
|
},
|
|
556
556
|
})
|
|
557
557
|
```
|
|
@@ -565,7 +565,7 @@ and the server does not accept WebSocket upgrades. Calling `app.ws` or
|
|
|
565
565
|
- `reclaimTtl`: how long an inactive (reaped/dropped) session stays reclaimable, in milliseconds. Defaults to `300_000`.
|
|
566
566
|
- `ticketTtl`: how long a minted upgrade ticket stays valid, in milliseconds. Defaults to `10_000`.
|
|
567
567
|
- `onOpen(clientId)`: fires after a client's welcome message is sent. Wrapped in try/catch so a throwing hook does not break the connection.
|
|
568
|
-
- `onClose(clientId,
|
|
568
|
+
- `onClose(clientId, signal)`: fires when a connection closes. `signal` is a `CloseSignal` value (`{ code, reason }`). Also wrapped in try/catch.
|
|
569
569
|
|
|
570
570
|
## WebSocket Commands
|
|
571
571
|
|
|
@@ -574,7 +574,7 @@ The `app.ws` object exposes four methods for interacting with connected clients:
|
|
|
574
574
|
- `query(fn)`: return a filtered list of active sessions. The filter function receives `(clientId, data, index)` and returns a boolean. Each entry in the returned array is a `SessionEntry` with `clientId` and `app` (the application context). To list all sessions: `app.ws.query(() => true)`.
|
|
575
575
|
- `send(event, body, fn)`: push a notification to clients matching a filter. The filter function receives `(clientId, data, index)` and returns a boolean. To target one client: `app.ws.send('ping', body, id => id === targetId)`.
|
|
576
576
|
- `broadcast(event, body)`: push a notification to all connected clients.
|
|
577
|
-
- `drop(
|
|
577
|
+
- `drop(signal, fn)`: close connections matching a filter. `signal` is a `CloseSignal` (`{ code, reason }`) with `code` validated in the range [4000-4099]. The filter function receives `(clientId, data, index)` and returns a boolean. To drop one client: `app.ws.drop({ code: 4000, reason: 'kicked' }, id => id === targetId)`.
|
|
578
578
|
|
|
579
579
|
The same commands are available inside endpoint handlers via `req.ws`:
|
|
580
580
|
|
|
@@ -594,8 +594,8 @@ This works from both HTTP and WebSocket transports.
|
|
|
594
594
|
|
|
595
595
|
`sleepy-serv` exports several runtime constants and types:
|
|
596
596
|
|
|
597
|
-
- `
|
|
598
|
-
- `
|
|
597
|
+
- `InternalCloseSignal`: internal close signals: `Ok` (`{ code: 1000, reason: 'ok' }`), `Reaped` (`{ code: 4998, reason: 'reaped' }`), `Superseded` (`{ code: 4999, reason: 'superseded' }`)
|
|
598
|
+
- `CloseSignal`: the type for close signals: `{ code: number, reason: string }`
|
|
599
599
|
- `StatusCode`: the full range of HTTP status codes (1xx through 5xx)
|
|
600
600
|
- `HttpMethod`: HTTP verbs: `Head`, `Get`, `Post`, `Put`, `Patch`, `Delete`
|
|
601
601
|
- `SessionEntry`: the shape returned by `query()`: `{ clientId: string, app: unknown }`
|
package/dist/core/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { RouteConfig, SocketCommands, AppOptions, Server } from './utils';
|
|
2
2
|
export * from './errors';
|
|
3
|
-
export { StatusCode,
|
|
3
|
+
export { StatusCode, HttpMethod, InternalCloseSignal, } from './utils';
|
|
4
4
|
export { parseJsonBody, setValidationFormats, validateSchemas, } from './middleware';
|
|
5
5
|
export type { FilterFn, SessionEntry, SocketCommands } from './utils';
|
|
6
|
-
export type { AppOptions, AsyncHandlerResult, BaseRequest, EndpointRequest, FormattedError, Handler, MetaEntry, Middleware, MiddlewareChain, NextFn, HandlerResult, Request, RouteConfig, RouteDefinition, Server, SocketConnection, SocketOptions, WebSocketRequest, } from './utils';
|
|
6
|
+
export type { AppOptions, AsyncHandlerResult, BaseRequest, CloseSignal, EndpointRequest, FormattedError, Handler, MetaEntry, Middleware, MiddlewareChain, NextFn, HandlerResult, Request, RouteConfig, RouteDefinition, Server, SocketConnection, SocketOptions, WebSocketRequest, } from './utils';
|
|
7
7
|
export type { FormatterField, FormatterSchema, ValidationSchemas, } from './middleware';
|
|
8
8
|
type OutputRoutes = Record<string, string[]>;
|
|
9
9
|
type CloseFn = (force?: boolean) => Promise<void>;
|
package/dist/core/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAOV,WAAW,EAEX,cAAc,EAEd,UAAU,EACV,MAAM,EACP,MAAM,SAAS,CAAA;AAOhB,cAAc,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAOV,WAAW,EAEX,cAAc,EAEd,UAAU,EACV,MAAM,EACP,MAAM,SAAS,CAAA;AAOhB,cAAc,UAAU,CAAA;AAExB,OAAO,EACL,UAAU,EACV,UAAU,EACV,mBAAmB,GACpB,MAAM,SAAS,CAAA;AAEhB,OAAO,EACL,aAAa,EACb,oBAAoB,EACpB,eAAe,GAChB,MAAM,cAAc,CAAA;AAErB,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAErE,YAAY,EACV,UAAU,EACV,kBAAkB,EAClB,WAAW,EACX,WAAW,EACX,eAAe,EACf,cAAc,EACd,OAAO,EACP,SAAS,EACT,UAAU,EACV,eAAe,EACf,MAAM,EACN,aAAa,EACb,OAAO,EACP,WAAW,EACX,eAAe,EACf,MAAM,EACN,gBAAgB,EAChB,aAAa,EACb,gBAAgB,GACjB,MAAM,SAAS,CAAA;AAEhB,YAAY,EACV,cAAc,EACd,eAAe,EACf,iBAAiB,GAClB,MAAM,cAAc,CAAA;AAErB,KAAK,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;AA0B5C,KAAK,OAAO,GAAG,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;AAEjD,MAAM,MAAM,GAAG,GAAG;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,YAAY,CAAA;IACpB,EAAE,EAAE,cAAc,CAAA;IAClB,KAAK,EAAE,OAAO,CAAA;CACf,CAAA;AAsRD,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,WAAW,EACnB,IAAI,GAAE,UAAe,GACpB,GAAG,CAqBL"}
|
package/dist/core/socket.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { CloseReason } from './utils';
|
|
2
1
|
import type { WebSocketHandler } from 'bun';
|
|
3
|
-
import type { AsyncHandlerResult, HttpMethod, Request, MiddlewareChain, SocketCommands, SocketData, ActiveSession, InactiveSession, SocketOptions } from './utils';
|
|
2
|
+
import type { AsyncHandlerResult, CloseSignal, HttpMethod, Request, MiddlewareChain, SocketCommands, SocketData, ActiveSession, InactiveSession, SocketOptions } from './utils';
|
|
4
3
|
type SocketHandler = (req: Request, res: unknown) => AsyncHandlerResult;
|
|
5
4
|
type SocketEndpoint = {
|
|
6
5
|
method: HttpMethod;
|
|
@@ -22,7 +21,7 @@ export type SocketState = {
|
|
|
22
21
|
activeSessions: Map<string, ActiveSession>;
|
|
23
22
|
inactiveSessions: Map<string, InactiveSession>;
|
|
24
23
|
onOpen: ((clientId: string) => void) | null;
|
|
25
|
-
onClose: ((clientId: string,
|
|
24
|
+
onClose: ((clientId: string, signal: CloseSignal) => void) | null;
|
|
26
25
|
};
|
|
27
26
|
export type SocketRoute = {
|
|
28
27
|
method: HttpMethod;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"socket.d.ts","sourceRoot":"","sources":["../../src/core/socket.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"socket.d.ts","sourceRoot":"","sources":["../../src/core/socket.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,KAAK,CAAA;AAE3C,OAAO,KAAK,EACV,kBAAkB,EAClB,WAAW,EACX,UAAU,EACV,OAAO,EAEP,eAAe,EACf,cAAc,EAGd,UAAU,EAEV,aAAa,EACb,eAAe,EAEf,aAAa,EACd,MAAM,SAAS,CAAA;AAShB,KAAK,aAAa,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,KAAK,kBAAkB,CAAA;AAYvE,KAAK,cAAc,GAAG;IACpB,MAAM,EAAE,UAAU,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,aAAa,CAAA;CACvB,CAAA;AAqBD,MAAM,MAAM,MAAM,GAAG;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,OAAO,CAAA;CACd,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,aAAa,EAAE,MAAM,CAAA;IACrB,iBAAiB,EAAE,MAAM,CAAA;IACzB,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC5B,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;IAC1C,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;IAC9C,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,IAAI,CAAA;IAC3C,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC,GAAG,IAAI,CAAA;CAClE,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,UAAU,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,KAAK,EAAE,eAAe,CAAA;CACvB,CAAA;AA+QD,wBAAgB,gBAAgB,CAAE,IAAI,GAAE,aAAkB,GAAG,WAAW,CAavE;AAED,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,WAAW,EAAE,EACrB,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,cAAc,GACvB,gBAAgB,CAAC,UAAU,CAAC,CA8J9B;AAED,wBAAgB,mBAAmB,CAAE,KAAK,EAAE,WAAW,GAAG,cAAc,EAAE,CAwIzE;AAED,wBAAgB,2BAA2B,IAAK,cAAc,CAS7D;AAED,wBAAgB,mBAAmB,CAAE,KAAK,EAAE,WAAW,GAAG,cAAc,CAwEvE"}
|
package/dist/core/utils.d.ts
CHANGED
|
@@ -104,7 +104,7 @@ export type FilterFn = (clientId: string, data: unknown, index: number) => boole
|
|
|
104
104
|
export type SocketCommands = {
|
|
105
105
|
broadcast: (event: string, body: unknown) => void;
|
|
106
106
|
send: (event: string, body: unknown, fn: FilterFn) => void;
|
|
107
|
-
drop: (
|
|
107
|
+
drop: (signal: CloseSignal, fn: FilterFn) => void;
|
|
108
108
|
query: (fn: FilterFn) => SessionEntry[];
|
|
109
109
|
};
|
|
110
110
|
export type BaseRequest = {
|
|
@@ -125,19 +125,24 @@ export type WebSocketRequest = BaseRequest & {
|
|
|
125
125
|
clientId: string;
|
|
126
126
|
};
|
|
127
127
|
export type Request = EndpointRequest | WebSocketRequest;
|
|
128
|
-
export
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
readonly Reaped:
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
128
|
+
export type CloseSignal = {
|
|
129
|
+
code: number;
|
|
130
|
+
reason: string;
|
|
131
|
+
};
|
|
132
|
+
export declare const InternalCloseSignal: {
|
|
133
|
+
readonly Ok: {
|
|
134
|
+
readonly code: 1000;
|
|
135
|
+
readonly reason: "ok";
|
|
136
|
+
};
|
|
137
|
+
readonly Reaped: {
|
|
138
|
+
readonly code: 4998;
|
|
139
|
+
readonly reason: "reaped";
|
|
140
|
+
};
|
|
141
|
+
readonly Superseded: {
|
|
142
|
+
readonly code: 4999;
|
|
143
|
+
readonly reason: "superseded";
|
|
144
|
+
};
|
|
145
|
+
};
|
|
141
146
|
export type SocketOptions = {
|
|
142
147
|
dropThreshold?: number;
|
|
143
148
|
heartbeatInterval?: number;
|
|
@@ -145,7 +150,7 @@ export type SocketOptions = {
|
|
|
145
150
|
reclaimTtl?: number;
|
|
146
151
|
ticketTtl?: number;
|
|
147
152
|
onOpen?: (clientId: string) => void;
|
|
148
|
-
onClose?: (clientId: string,
|
|
153
|
+
onClose?: (clientId: string, signal: CloseSignal) => void;
|
|
149
154
|
};
|
|
150
155
|
export type SocketData = {
|
|
151
156
|
clientId: string;
|
package/dist/core/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/core/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAA;AACtC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,IAAI,SAAS,EAAE,MAAM,KAAK,CAAA;AAE1D,eAAO,MAAM,UAAU;;;;;;;CAOb,CAAA;AAEV,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC,MAAM,OAAO,UAAU,CAAC,CAAA;AAEnE,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmEb,CAAA;AAEV,MAAM,MAAM,UAAU,GAAG,OAAO,UAAU,CAAC,MAAM,OAAO,UAAU,CAAC,CAAA;AAEnE,MAAM,MAAM,eAAe,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;AAElD,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;AAClD,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,kBAAkB,CAAA;AACzD,MAAM,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,KAAK,aAAa,CAAA;AAEtD,MAAM,MAAM,UAAU,GAAG,CACvB,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,OAAO,EACZ,IAAI,EAAE,MAAM,KACT,aAAa,CAAA;AAElB,MAAM,MAAM,OAAO,GAAG,CACpB,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,OAAO,KACT,aAAa,CAAA;AAElB,MAAM,MAAM,eAAe,GAAG,CAAC,UAAU,GAAG,OAAO,CAAC,EAAE,CAAA;AAEtD,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,EAAE,EAAE,gBAAgB,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,GAAG,EAAE,OAAO,CAAA;CACb,CAAA;AAED,MAAM,MAAM,cAAc,GAAG,WAAW,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;AAC/D,MAAM,MAAM,OAAO,GAAG,aAAa,GAAG,eAAe,CAAA;AAErD,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,EAAE,OAAO,CAAA;CACb,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG,CACrB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,OAAO,EACb,KAAK,EAAE,MAAM,KACV,OAAO,CAAA;AAEZ,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACjD,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,QAAQ,KAAK,IAAI,CAAA;IAC1D,IAAI,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/core/utils.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/core/index.ts
CHANGED
|
@@ -46,7 +46,12 @@ import type {
|
|
|
46
46
|
} from './socket'
|
|
47
47
|
|
|
48
48
|
export * from './errors'
|
|
49
|
-
|
|
49
|
+
|
|
50
|
+
export {
|
|
51
|
+
StatusCode,
|
|
52
|
+
HttpMethod,
|
|
53
|
+
InternalCloseSignal,
|
|
54
|
+
} from './utils'
|
|
50
55
|
|
|
51
56
|
export {
|
|
52
57
|
parseJsonBody,
|
|
@@ -60,6 +65,7 @@ export type {
|
|
|
60
65
|
AppOptions,
|
|
61
66
|
AsyncHandlerResult,
|
|
62
67
|
BaseRequest,
|
|
68
|
+
CloseSignal,
|
|
63
69
|
EndpointRequest,
|
|
64
70
|
FormattedError,
|
|
65
71
|
Handler,
|
package/src/core/socket.ts
CHANGED
|
@@ -10,8 +10,7 @@ import {
|
|
|
10
10
|
|
|
11
11
|
import {
|
|
12
12
|
StatusCode,
|
|
13
|
-
|
|
14
|
-
CloseReason,
|
|
13
|
+
InternalCloseSignal,
|
|
15
14
|
toSegments,
|
|
16
15
|
formatError,
|
|
17
16
|
executeMiddlewareChain,
|
|
@@ -32,8 +31,10 @@ import type { WebSocketHandler } from 'bun'
|
|
|
32
31
|
|
|
33
32
|
import type {
|
|
34
33
|
AsyncHandlerResult,
|
|
34
|
+
CloseSignal,
|
|
35
35
|
HttpMethod,
|
|
36
36
|
Request,
|
|
37
|
+
FilterFn,
|
|
37
38
|
MiddlewareChain,
|
|
38
39
|
SocketCommands,
|
|
39
40
|
SessionEntry,
|
|
@@ -106,7 +107,7 @@ export type SocketState = {
|
|
|
106
107
|
activeSessions: Map<string, ActiveSession>
|
|
107
108
|
inactiveSessions: Map<string, InactiveSession>
|
|
108
109
|
onOpen: ((clientId: string) => void) | null
|
|
109
|
-
onClose: ((clientId: string,
|
|
110
|
+
onClose: ((clientId: string, signal: CloseSignal) => void) | null
|
|
110
111
|
}
|
|
111
112
|
|
|
112
113
|
export type SocketRoute = {
|
|
@@ -385,16 +386,6 @@ function buildErrorMessage (
|
|
|
385
386
|
})
|
|
386
387
|
}
|
|
387
388
|
|
|
388
|
-
function getCloseReason (ws: SocketConnection, code: number): CloseReason {
|
|
389
|
-
if (ws.data.reaped) {
|
|
390
|
-
return CloseReason.Reaped
|
|
391
|
-
} else if (code === CloseCode.Ok) {
|
|
392
|
-
return CloseReason.Ok
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
return CloseReason.Dropped
|
|
396
|
-
}
|
|
397
|
-
|
|
398
389
|
export function buildSocketState (opts: SocketOptions = {}): SocketState {
|
|
399
390
|
return {
|
|
400
391
|
dropThreshold: opts.dropThreshold ?? 120_000,
|
|
@@ -433,7 +424,10 @@ export function buildSocketServer (
|
|
|
433
424
|
ws.data.reaperHandle = setTimeout(() => {
|
|
434
425
|
ws.data.reaped = true
|
|
435
426
|
|
|
436
|
-
ws.close(
|
|
427
|
+
ws.close(
|
|
428
|
+
InternalCloseSignal.Reaped.code,
|
|
429
|
+
InternalCloseSignal.Reaped.reason,
|
|
430
|
+
)
|
|
437
431
|
}, dropThreshold)
|
|
438
432
|
}
|
|
439
433
|
|
|
@@ -447,10 +441,10 @@ export function buildSocketServer (
|
|
|
447
441
|
}
|
|
448
442
|
}
|
|
449
443
|
|
|
450
|
-
function invokeClose (ws: SocketConnection,
|
|
444
|
+
function invokeClose (ws: SocketConnection, signal: CloseSignal) {
|
|
451
445
|
if (onClose) {
|
|
452
446
|
try {
|
|
453
|
-
onClose(ws.data.clientId,
|
|
447
|
+
onClose(ws.data.clientId, signal)
|
|
454
448
|
} catch (err) {
|
|
455
449
|
console.error(err)
|
|
456
450
|
}
|
|
@@ -467,7 +461,10 @@ export function buildSocketServer (
|
|
|
467
461
|
if (existingSession) {
|
|
468
462
|
existingSession.ws.data.superseded = true
|
|
469
463
|
|
|
470
|
-
existingSession.ws.close(
|
|
464
|
+
existingSession.ws.close(
|
|
465
|
+
InternalCloseSignal.Superseded.code,
|
|
466
|
+
InternalCloseSignal.Superseded.reason,
|
|
467
|
+
)
|
|
471
468
|
}
|
|
472
469
|
|
|
473
470
|
inactiveSessions.delete(ws.data.clientId)
|
|
@@ -494,18 +491,22 @@ export function buildSocketServer (
|
|
|
494
491
|
ws.send(JSON.stringify(welcomeMessage))
|
|
495
492
|
invokeOpen(ws)
|
|
496
493
|
},
|
|
497
|
-
close (ws: SocketConnection, code: number): void {
|
|
494
|
+
close (ws: SocketConnection, code: number, reason: string): void {
|
|
495
|
+
const signal: CloseSignal = {
|
|
496
|
+
code,
|
|
497
|
+
reason,
|
|
498
|
+
}
|
|
499
|
+
|
|
498
500
|
if (ws.data.reaperHandle) {
|
|
499
501
|
clearTimeout(ws.data.reaperHandle)
|
|
500
502
|
}
|
|
501
503
|
|
|
502
504
|
if (ws.data.superseded) {
|
|
503
|
-
invokeClose(ws,
|
|
505
|
+
invokeClose(ws, signal)
|
|
504
506
|
|
|
505
507
|
return
|
|
506
508
|
}
|
|
507
509
|
|
|
508
|
-
const reason = getCloseReason(ws, code)
|
|
509
510
|
const exists = activeSessions.get(ws.data.clientId)
|
|
510
511
|
|
|
511
512
|
if (!exists || exists.ws !== ws) {
|
|
@@ -514,7 +515,7 @@ export function buildSocketServer (
|
|
|
514
515
|
|
|
515
516
|
activeSessions.delete(ws.data.clientId)
|
|
516
517
|
|
|
517
|
-
if (code !==
|
|
518
|
+
if (code !== InternalCloseSignal.Ok.code || ws.data.reaped) {
|
|
518
519
|
inactiveSessions.set(ws.data.clientId, {
|
|
519
520
|
token: exists.token,
|
|
520
521
|
expiresAt: Date.now() + reclaimTtl,
|
|
@@ -522,7 +523,7 @@ export function buildSocketServer (
|
|
|
522
523
|
})
|
|
523
524
|
}
|
|
524
525
|
|
|
525
|
-
invokeClose(ws,
|
|
526
|
+
invokeClose(ws, signal)
|
|
526
527
|
},
|
|
527
528
|
async message (ws: SocketConnection, raw: string | Buffer): Promise<void> {
|
|
528
529
|
const incomingMsg = parseMessage(raw)
|
|
@@ -731,12 +732,12 @@ export function buildSocketCommands (state: SocketState): SocketCommands {
|
|
|
731
732
|
}
|
|
732
733
|
|
|
733
734
|
return {
|
|
734
|
-
broadcast (event, body) {
|
|
735
|
+
broadcast (event: string, body: unknown) {
|
|
735
736
|
for (const clientId of state.activeSessions.keys()) {
|
|
736
737
|
sendToClient(clientId, event, body)
|
|
737
738
|
}
|
|
738
739
|
},
|
|
739
|
-
send (event, body, fn) {
|
|
740
|
+
send (event: string, body: unknown, fn: FilterFn) {
|
|
740
741
|
let index = 0
|
|
741
742
|
|
|
742
743
|
/* TODO: look into concurrency at some point */
|
|
@@ -748,9 +749,17 @@ export function buildSocketCommands (state: SocketState): SocketCommands {
|
|
|
748
749
|
index += 1
|
|
749
750
|
}
|
|
750
751
|
},
|
|
751
|
-
drop (
|
|
752
|
+
drop (signal: CloseSignal, fn: FilterFn) {
|
|
753
|
+
const { code, reason } = signal
|
|
754
|
+
|
|
752
755
|
let index = 0
|
|
753
756
|
|
|
757
|
+
if (!Number.isInteger(code) || code < 4000 || code > 4099) {
|
|
758
|
+
throw new RangeError(
|
|
759
|
+
`Signal code must be an integer in [4000, 4099], got ${code}`,
|
|
760
|
+
)
|
|
761
|
+
}
|
|
762
|
+
|
|
754
763
|
for (const [clientId, session] of state.activeSessions) {
|
|
755
764
|
if (fn(clientId, session.ws.data, index)) {
|
|
756
765
|
session.ws.close(code, reason)
|
|
@@ -759,7 +768,7 @@ export function buildSocketCommands (state: SocketState): SocketCommands {
|
|
|
759
768
|
index += 1
|
|
760
769
|
}
|
|
761
770
|
},
|
|
762
|
-
query (fn) {
|
|
771
|
+
query (fn: FilterFn) {
|
|
763
772
|
const results: SessionEntry[] = []
|
|
764
773
|
let index = 0
|
|
765
774
|
|
package/src/core/utils.ts
CHANGED
|
@@ -135,7 +135,7 @@ export type FilterFn = (
|
|
|
135
135
|
export type SocketCommands = {
|
|
136
136
|
broadcast: (event: string, body: unknown) => void
|
|
137
137
|
send: (event: string, body: unknown, fn: FilterFn) => void
|
|
138
|
-
drop: (
|
|
138
|
+
drop: (signal: CloseSignal, fn: FilterFn) => void
|
|
139
139
|
query: (fn: FilterFn) => SessionEntry[]
|
|
140
140
|
}
|
|
141
141
|
|
|
@@ -161,23 +161,26 @@ export type WebSocketRequest = BaseRequest & {
|
|
|
161
161
|
|
|
162
162
|
export type Request = EndpointRequest | WebSocketRequest
|
|
163
163
|
|
|
164
|
-
export
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
} as const
|
|
169
|
-
|
|
170
|
-
export type CloseCode = typeof CloseCode[keyof typeof CloseCode]
|
|
164
|
+
export type CloseSignal = {
|
|
165
|
+
code: number
|
|
166
|
+
reason: string
|
|
167
|
+
}
|
|
171
168
|
|
|
172
|
-
export const
|
|
173
|
-
Ok:
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
169
|
+
export const InternalCloseSignal = {
|
|
170
|
+
Ok: {
|
|
171
|
+
code: 1000,
|
|
172
|
+
reason: 'ok',
|
|
173
|
+
},
|
|
174
|
+
Reaped: {
|
|
175
|
+
code: 4998,
|
|
176
|
+
reason: 'reaped',
|
|
177
|
+
},
|
|
178
|
+
Superseded: {
|
|
179
|
+
code: 4999,
|
|
180
|
+
reason: 'superseded',
|
|
181
|
+
},
|
|
177
182
|
} as const
|
|
178
183
|
|
|
179
|
-
export type CloseReason = typeof CloseReason[keyof typeof CloseReason]
|
|
180
|
-
|
|
181
184
|
export type SocketOptions = {
|
|
182
185
|
dropThreshold?: number
|
|
183
186
|
heartbeatInterval?: number
|
|
@@ -185,7 +188,7 @@ export type SocketOptions = {
|
|
|
185
188
|
reclaimTtl?: number
|
|
186
189
|
ticketTtl?: number
|
|
187
190
|
onOpen?: (clientId: string) => void
|
|
188
|
-
onClose?: (clientId: string,
|
|
191
|
+
onClose?: (clientId: string, signal: CloseSignal) => void
|
|
189
192
|
}
|
|
190
193
|
|
|
191
194
|
export type SocketData = {
|