@fuzdev/fuz_app 0.128.0 → 0.130.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/dist/actions/CLAUDE.md +23 -6
- package/dist/actions/connection_closer.d.ts +3 -1
- package/dist/actions/connection_closer.d.ts.map +1 -1
- package/dist/actions/register_action_ws.d.ts +46 -1
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +105 -14
- package/dist/actions/transports.d.ts +2 -1
- package/dist/actions/transports.d.ts.map +1 -1
- package/dist/actions/transports.js +2 -1
- package/dist/actions/transports_ws_backend.d.ts +37 -4
- package/dist/actions/transports_ws_backend.d.ts.map +1 -1
- package/dist/actions/transports_ws_backend.js +68 -4
- package/dist/auth/CLAUDE.md +1 -1
- package/dist/auth/actor_lookup_action_specs.d.ts +2 -2
- package/dist/auth/actor_lookup_action_specs.js +2 -2
- package/dist/auth/audit_emitter.d.ts +14 -7
- package/dist/auth/audit_emitter.d.ts.map +1 -1
- package/dist/auth/audit_emitter.js +6 -8
- package/dist/auth/audit_log_routes.d.ts +7 -3
- package/dist/auth/audit_log_routes.d.ts.map +1 -1
- package/dist/auth/audit_log_routes.js +48 -26
- package/dist/auth/bearer_auth.d.ts +3 -2
- package/dist/auth/bearer_auth.d.ts.map +1 -1
- package/dist/auth/bearer_auth.js +3 -2
- package/dist/auth/request_context.d.ts +1 -1
- package/dist/auth/request_context.js +1 -1
- package/dist/auth/token_scope.d.ts +4 -2
- package/dist/auth/token_scope.d.ts.map +1 -1
- package/dist/auth/token_scope.js +10 -2
- package/dist/http/CLAUDE.md +4 -5
- package/dist/http/auth_shape.d.ts +3 -2
- package/dist/http/auth_shape.d.ts.map +1 -1
- package/dist/http/auth_shape.js +3 -2
- package/dist/http/common_routes.d.ts +1 -14
- package/dist/http/common_routes.d.ts.map +1 -1
- package/dist/http/common_routes.js +1 -19
- package/dist/http/ip_canonical.d.ts +1 -1
- package/dist/http/ip_canonical.js +1 -1
- package/dist/http/jsonrpc_errors.d.ts.map +1 -1
- package/dist/http/jsonrpc_errors.js +7 -5
- package/dist/realtime/sse.d.ts +14 -1
- package/dist/realtime/sse.d.ts.map +1 -1
- package/dist/realtime/sse.js +77 -1
- package/dist/realtime/sse_auth_guard.d.ts +3 -1
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
- package/dist/realtime/sse_auth_guard.js +4 -2
- package/dist/realtime/subscriber_registry.d.ts +44 -8
- package/dist/realtime/subscriber_registry.d.ts.map +1 -1
- package/dist/realtime/subscriber_registry.js +64 -10
- package/dist/server/app_server.d.ts +122 -8
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +136 -91
- package/dist/server/surface_route.d.ts +32 -0
- package/dist/server/surface_route.d.ts.map +1 -0
- package/dist/server/surface_route.js +46 -0
- package/dist/testing/CLAUDE.md +33 -12
- package/dist/testing/app_server.d.ts +39 -4
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +84 -68
- package/dist/testing/audit_completeness.js +1 -1
- package/dist/testing/audit_drift_guard.d.ts +1 -1
- package/dist/testing/cross_backend/default_spine_surface.d.ts +2 -2
- package/dist/testing/cross_backend/default_spine_surface.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_spine_surface.js +5 -2
- package/dist/testing/cross_backend/identity_parity.js +1 -1
- package/dist/testing/cross_backend/in_process_setup.d.ts.map +1 -1
- package/dist/testing/cross_backend/in_process_setup.js +4 -1
- package/dist/testing/cross_backend/method_coverage.d.ts +1 -1
- package/dist/testing/cross_backend/method_coverage.d.ts.map +1 -1
- package/dist/testing/cross_backend/method_coverage.js +2 -2
- package/dist/testing/cross_backend/peer_ping_ws.d.ts +1 -1
- package/dist/testing/cross_backend/peer_ping_ws.d.ts.map +1 -1
- package/dist/testing/cross_backend/peer_ping_ws.js +4 -4
- package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts +1 -1
- package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts.map +1 -1
- package/dist/testing/cross_backend/setup.d.ts +4 -4
- package/dist/testing/cross_backend/setup.js +1 -1
- package/dist/testing/cross_backend/sse_round_trip.d.ts +1 -1
- package/dist/testing/cross_backend/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +24 -14
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +40 -32
- package/dist/testing/cross_backend/testing_server_bun.d.ts +1 -1
- package/dist/testing/cross_backend/testing_server_bun.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_server_bun.js +80 -64
- package/dist/testing/cross_backend/testing_server_core.d.ts +10 -4
- package/dist/testing/cross_backend/testing_server_core.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_server_deno.d.ts +1 -1
- package/dist/testing/cross_backend/testing_server_deno.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_server_deno.js +1 -1
- package/dist/testing/cross_backend/testing_server_node.d.ts +1 -1
- package/dist/testing/cross_backend/testing_server_node.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_server_node.js +2 -2
- package/dist/testing/cross_backend/ws_inbound_dispatch.d.ts +20 -0
- package/dist/testing/cross_backend/ws_inbound_dispatch.d.ts.map +1 -0
- package/dist/testing/cross_backend/ws_inbound_dispatch.js +177 -0
- package/dist/testing/cross_backend/ws_round_trip.d.ts +1 -1
- package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/cross_backend/ws_round_trip.js +11 -9
- package/dist/testing/stubs.d.ts +45 -7
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +60 -31
- package/dist/testing/ws_round_trip.d.ts +1 -3
- package/dist/testing/ws_round_trip.d.ts.map +1 -1
- package/dist/ui/AdminSurface.svelte +3 -1
- package/dist/ui/AdminSurface.svelte.d.ts.map +1 -1
- package/dist/ui/CLAUDE.md +3 -1
- package/dist/ui/async_slot.svelte.d.ts +4 -4
- package/dist/ui/async_slot.svelte.js +4 -4
- package/dist/ui/keyed_async_slot.svelte.d.ts +4 -4
- package/dist/ui/keyed_async_slot.svelte.js +3 -3
- package/package.json +1 -1
package/dist/actions/CLAUDE.md
CHANGED
|
@@ -362,7 +362,7 @@ and `allow_fallback: boolean` (default `true`). Explicit
|
|
|
362
362
|
- `WS_CLOSE_CLIENT_HEARTBEAT_TIMEOUT = 4002` — client observed receive-silence past its receive timeout (`resolve_heartbeat_receive_timeout`).
|
|
363
363
|
- `WS_CLOSE_SERVER_HEARTBEAT_TIMEOUT = 4003` — server observed receive-silence past `DEFAULT_SERVER_HEARTBEAT_TIMEOUT` (60s).
|
|
364
364
|
- `WS_CLOSE_CONNECTION_LIMIT = 4004` — server closed this socket to admit a newer one on the same account, past the per-account connection cap (reason `connection limit`). Not a revocation: the client stays `closed` with `superseded` set and no reconnect, until `connect()` is called. Twin of the Rust spine's constant.
|
|
365
|
-
- `WS_CLOSE_GOING_AWAY = 1001` — the server is shutting down (reason `WS_CLOSE_GOING_AWAY_REASON`, `Server shutting down`): `close_all_sockets`, which `AppServer.close` runs. Not a revocation: the client reconnects under its ordinary backoff.
|
|
365
|
+
- `WS_CLOSE_GOING_AWAY = 1001` — the server is shutting down (reason `WS_CLOSE_GOING_AWAY_REASON`, `Server shutting down`): `close_all_sockets`, which `AppServer.close` runs. Also the close for an upgrade the shutdown refuses — pending at the close-all, or born closed after it. Not a revocation: the client reconnects under its ordinary backoff.
|
|
366
366
|
- `WS_CLOSE_MESSAGE_TOO_BIG = 1009` — server received a message over its `max_message_bytes` cap (RFC 6455 "Message Too Big").
|
|
367
367
|
- `WS_CLOSE_POLICY_VIOLATION = 1008` — a socket sent too much while it was still opening (before admission and `on_socket_open` completed): past `MAX_PRE_ADMISSION_FRAMES` frames or the `PRE_ADMISSION_QUEUE_BYTES_FACTOR` byte budget.
|
|
368
368
|
- `WS_CLOSE_INTERNAL_ERROR = 1011` — the admission credential re-check itself failed (reason `internal error`), `on_socket_open` threw (reason `socket bootstrap failed`), or the socket's connection was removed from the transport without a close (reason `connection unregistered`). Not a revocation: the client reconnects under its ordinary backoff. Twin of the Rust spine's constant.
|
|
@@ -487,7 +487,15 @@ each closed connection's controller):
|
|
|
487
487
|
|
|
488
488
|
`close_all_sockets()` closes every connection, pending and admitted, with
|
|
489
489
|
`WS_CLOSE_GOING_AWAY` instead — the shutdown close, removing and aborting each
|
|
490
|
-
the same way
|
|
490
|
+
the same way — and leaves the transport **closing** for good (`is_closing()`):
|
|
491
|
+
every later registration is **born closed**. `register_pending` and
|
|
492
|
+
`add_connection` then insert nothing, abort the caller's controller, and close
|
|
493
|
+
the socket with `WS_CLOSE_GOING_AWAY` before returning an id that names
|
|
494
|
+
nothing, so `admit` refuses it — an upgrade in flight when the server shuts
|
|
495
|
+
down is closed rather than outliving it. The flag is set in the same
|
|
496
|
+
synchronous step as the closes, so an admission that finds its entry gone
|
|
497
|
+
during shutdown reads the shutdown, never a revocation. Twin of the Rust
|
|
498
|
+
registry's closing flag.
|
|
491
499
|
|
|
492
500
|
Fan-out, to admitted connections only: `send(notification)` broadcasts to every
|
|
493
501
|
connection; `broadcast_filtered(message, predicate)` runs per-connection ACL
|
|
@@ -542,7 +550,10 @@ a `SubscriberRegistry` (`close_all_sockets` is `SubscriberRegistry.close_all`).
|
|
|
542
550
|
Closing is idempotent and synchronous. `close_all_sockets` is the one method no
|
|
543
551
|
revocation calls: `AppServer.close` runs it on `deps.connection_closer` to end
|
|
544
552
|
every live connection of the backend at shutdown, and it has no
|
|
545
|
-
`ConnectionCloseTarget` variant
|
|
553
|
+
`ConnectionCloseTarget` variant. It also leaves each member closing — the WS
|
|
554
|
+
transport and `SubscriberRegistry` refuse every later registration (born
|
|
555
|
+
closed) — so a closed transport or registry hosts nothing more. Its Rust
|
|
556
|
+
counterpart is `ShutdownCloser::close_all_sockets`, a supertrait of
|
|
546
557
|
`SocketRevoker`.
|
|
547
558
|
|
|
548
559
|
**Every revocation closes after its commit.** A handler never calls a closer
|
|
@@ -727,6 +738,8 @@ HTTP RPC also calls. `register_action_ws` owns only WS-specific concerns:
|
|
|
727
738
|
|
|
728
739
|
- **Wire envelope parsing** — JSON.parse → batch rejection → peer-response routing → envelope-version guard → notification interception (cancel, silent drop) → `JsonrpcRequest.safeParse` → per-message dispatch. The version guard is what makes `{method: 'x'}` and `{jsonrpc: '1.0', method: 'x'}` an `invalid_request` answer rather than a silently dropped notification, matching the first check the Rust `classify` runs. Peer-response routing is the twin's response shape exactly — no `method` key, a string/number `id`, exactly one of `result` / `error` — so a hybrid frame like `{method, id, result}` stays a dispatchable request instead of being demuxed and swallowed. The envelope schema is the same one the HTTP POST path runs, so `params: null`, an array `params`, or a non-string/number `id` answers `invalid_request` (-32600) on both transports rather than reaching the dispatcher as `invalid_params`. It runs **after** the notification/cancel block: notifications carry no id to echo and stay unvalidated, and `cancel` routing precedes it. The version guard echoes the id via `to_jsonrpc_envelope_id` (a scalar frame answers `id: null`), the later steps via `to_jsonrpc_message_id`.
|
|
729
740
|
- **Cancel-notification interception** — `{request_id → AbortController}` map; aborts the matching pending controller before the cancel bubbles past the dispatcher
|
|
741
|
+
- **Duplicate live ids refused** — a request whose id names a request still running on the socket is answered `invalid_request` with `data.reason` `ERROR_DUPLICATE_REQUEST_ID` and not dispatched; the running request keeps its controller, its cancel entry, and its reply id. An id is free again once its request is answered. Same refusal on the Rust spine
|
|
742
|
+
- **In-flight ceiling** — at most `MAX_INBOUND_DISPATCHES_PER_CONNECTION` (128) requests run at once per socket (`@hono/node-ws` does not await `onMessage`, so nothing else bounds them); one more is answered `queue_overflow` (`WS_INBOUND_DISPATCH_OVERFLOW_MESSAGE`) and not dispatched, and the socket stays open and keeps reading — a reply to a server-initiated request still gets through. The duplicate check runs first; neither refusal reaches `perform_action`, so neither charges the action rate limiters. Same cap, message, and order as the Rust spine's `MAX_INBOUND_DISPATCHES_PER_CONN`
|
|
730
743
|
- **Socket-scoped notify** — `(method, params) => ws.send(notification)`, threaded into `perform_action` as `notify`
|
|
731
744
|
- **Composed abort signal** — `AbortSignal.any([socket_close, per_request_cancel])`, threaded as `signal`
|
|
732
745
|
- **Connection lifecycle** — admission (`transport.register_pending` → credential re-read → `transport.admit`, which enforces the per-account connection cap) / `remove_connection`, `on_socket_open` / `_close` hooks, server heartbeat
|
|
@@ -741,8 +754,12 @@ audit stream shares, read off the keys the auth middleware set), then
|
|
|
741
754
|
meanwhile, closes the socket with `WS_CLOSE_SESSION_REVOKED` and the reason a
|
|
742
755
|
revoked socket gets; a re-check that throws closes it with
|
|
743
756
|
`WS_CLOSE_INTERNAL_ERROR` (`internal error`) — fail closed, never admitted
|
|
744
|
-
unchecked.
|
|
745
|
-
|
|
757
|
+
unchecked. During shutdown a refusal is the shutdown's instead: an upgrade
|
|
758
|
+
pending at the close-all was closed by it with `WS_CLOSE_GOING_AWAY` and its
|
|
759
|
+
controller aborted, so the refusal after the re-read — even one the closing
|
|
760
|
+
database failed — writes nothing more; an upgrade that registers after it is
|
|
761
|
+
born closed and skips the re-read. A refused socket gets nothing else written
|
|
762
|
+
to it, evicts nothing, and runs neither lifecycle hook. An upgrade with no resolved credential on its
|
|
746
763
|
context, or one on another account than its request context, throws before the
|
|
747
764
|
socket exists. The test-preset escape hatch
|
|
748
765
|
(`TEST_CONTEXT_PRESET_KEY`) skips the re-read — a pre-baked credential has no
|
|
@@ -859,7 +876,7 @@ A timeout unregisters the connection and aborts its signal along with the
|
|
|
859
876
|
Two abort signals composed via `AbortSignal.any`:
|
|
860
877
|
|
|
861
878
|
- `socket_abort_controller` — per-socket, fires when the socket ends: at the server's close (revocation, cap eviction, heartbeat timeout, …) or the client's. Registered with the transport, which aborts it on the closes it makes. Drives every handler's `ctx.signal`.
|
|
862
|
-
- `pending_controllers: Map<JsonrpcRequestId, AbortController>` — per-request. Registered before dispatch, cleared in `finally` so late cancels for a completed id (or a reused id) can't null-abort the wrong handler. Unknown cancels no-op.
|
|
879
|
+
- `pending_controllers: Map<JsonrpcRequestId, AbortController>` — per-request. Registered before dispatch (one entry per running request — a duplicate live id is refused — so its size is the in-flight count the ceiling bounds), cleared in `finally` so late cancels for a completed id (or a reused id) can't null-abort the wrong handler. Unknown cancels no-op.
|
|
863
880
|
|
|
864
881
|
## Protocol actions (`actions/protocol.ts`)
|
|
865
882
|
|
|
@@ -74,7 +74,9 @@ export interface ConnectionCloser {
|
|
|
74
74
|
* Close every connection, whoever opened it — the shutdown close
|
|
75
75
|
* `AppServer.close` runs. Not a revocation: a WebSocket gets
|
|
76
76
|
* `WS_CLOSE_GOING_AWAY`, so its client reconnects rather than treating
|
|
77
|
-
* its credential as revoked.
|
|
77
|
+
* its credential as revoked. The transport is left closing: a
|
|
78
|
+
* registration that arrives afterward is born closed, refused at once
|
|
79
|
+
* (`BackendWebsocketTransport` and `SubscriberRegistry` both do).
|
|
78
80
|
*/
|
|
79
81
|
close_all_sockets: () => number;
|
|
80
82
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection_closer.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/connection_closer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAGnD,OAAO,EAAqB,KAAK,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAE5F;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,gBAAgB;IAChC,qGAAqG;IACrG,yBAAyB,EAAE,CAAC,kBAAkB,EAAE,MAAM,KAAK,MAAM,CAAC;IAClE,4EAA4E;IAC5E,uBAAuB,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1D;;;;OAIG;IACH,yBAAyB,EAAE,CAAC,UAAU,EAAE,IAAI,KAAK,MAAM,CAAC;IACxD
|
|
1
|
+
{"version":3,"file":"connection_closer.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/connection_closer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAGnD,OAAO,EAAqB,KAAK,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAE5F;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,gBAAgB;IAChC,qGAAqG;IACrG,yBAAyB,EAAE,CAAC,kBAAkB,EAAE,MAAM,KAAK,MAAM,CAAC;IAClE,4EAA4E;IAC5E,uBAAuB,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1D;;;;OAIG;IACH,yBAAyB,EAAE,CAAC,UAAU,EAAE,IAAI,KAAK,MAAM,CAAC;IACxD;;;;;;;OAOG;IACH,iBAAiB,EAAE,MAAM,MAAM,CAAC;CAChC;AAED;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAC9B;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,kBAAkB,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,IAAI,CAAA;CAAE,CAAC;AAEzC;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,GAC7B,QAAQ,gBAAgB,EACxB,QAAQ,qBAAqB,KAC3B,MAWF,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,sBAAsB,GAClC,KAAK,sBAAsB,EAC3B,QAAQ,gBAAgB,EACxB,QAAQ,qBAAqB,KAC3B,IAIF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,sBAAsB,EAAE,gBAKnC,CAAC;AAEH;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,cAAe,SAAQ,gBAAgB;IACvD;;;;;;;;;;;;;;;OAeG;IACH,GAAG,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,MAAM,IAAI,CAAC;IAC9C,2EAA2E;IAC3E,YAAY,EAAE,MAAM,MAAM,CAAC;CAC3B;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,QAAO,cA4CzC,CAAC"}
|
|
@@ -59,6 +59,14 @@
|
|
|
59
59
|
* re-read itself failed — fail closed, never admitted unchecked. The handshake
|
|
60
60
|
* has already answered `101`, so a refusal can only be a close frame.
|
|
61
61
|
*
|
|
62
|
+
* Once the server is shutting down (`AppServer.close` ran the transport's
|
|
63
|
+
* `close_all_sockets`) a refusal is `WS_CLOSE_GOING_AWAY` instead, so the
|
|
64
|
+
* client reconnects rather than treating its credential as revoked: an upgrade
|
|
65
|
+
* pending at the close-all is closed by it — and its abort makes the refusal
|
|
66
|
+
* that follows the re-read send nothing more, even when the database closed
|
|
67
|
+
* under that re-read — and one that registers after it is born closed and
|
|
68
|
+
* skips the re-read (`BackendWebsocketTransport` module doc, "Shutdown").
|
|
69
|
+
*
|
|
62
70
|
* Frames that arrive before admission are queued, not dispatched: nothing runs
|
|
63
71
|
* on a credential that has not been re-read. They dispatch in arrival order
|
|
64
72
|
* once the socket is admitted and `on_socket_open` has completed, and are
|
|
@@ -95,7 +103,6 @@ import type { Db } from '../db/db.js';
|
|
|
95
103
|
import { type Action } from './action_types.js';
|
|
96
104
|
import type { RealtimeCloser } from './connection_closer.js';
|
|
97
105
|
import { BackendWebsocketTransport, type ConnectionIdentity } from './transports_ws_backend.js';
|
|
98
|
-
export type { Action };
|
|
99
106
|
/** Default inactivity window before the server closes a silent socket. */
|
|
100
107
|
export declare const DEFAULT_SERVER_HEARTBEAT_TIMEOUT = 60000;
|
|
101
108
|
/**
|
|
@@ -138,6 +145,38 @@ export declare const PRE_ADMISSION_QUEUE_BYTES_FACTOR = 4;
|
|
|
138
145
|
* overflows its pre-admission queue, by count or by bytes.
|
|
139
146
|
*/
|
|
140
147
|
export declare const WS_CLOSE_PRE_ADMISSION_OVERFLOW_REASON = "too much sent before the socket opened";
|
|
148
|
+
/**
|
|
149
|
+
* Max requests one socket may have dispatching at once. A request that
|
|
150
|
+
* arrives past it is answered `queue_overflow` with its id
|
|
151
|
+
* (`WS_INBOUND_DISPATCH_OVERFLOW_MESSAGE`) and not dispatched; the socket
|
|
152
|
+
* stays open, and the requests already running keep answering.
|
|
153
|
+
*
|
|
154
|
+
* A runtime adapter can hand frames over without awaiting the previous one's
|
|
155
|
+
* dispatch (`@hono/node-ws` does not await `onMessage`), so without a ceiling
|
|
156
|
+
* one socket could hold any number of handlers — and their pooled database
|
|
157
|
+
* work — at once. Shedding rather than queueing keeps the socket reading, so a
|
|
158
|
+
* reply to a server-initiated request still reaches the handler awaiting it.
|
|
159
|
+
* Generous, so ordinary pipelining never meets it. Same value as the Rust
|
|
160
|
+
* spine's `MAX_INBOUND_DISPATCHES_PER_CONN`.
|
|
161
|
+
*
|
|
162
|
+
* Counted are the requests that passed envelope validation and are not yet
|
|
163
|
+
* answered — the entries of the socket's cancellation map. Notifications,
|
|
164
|
+
* replies to server-initiated requests, and refused frames never count.
|
|
165
|
+
*/
|
|
166
|
+
export declare const MAX_INBOUND_DISPATCHES_PER_CONNECTION = 128;
|
|
167
|
+
/**
|
|
168
|
+
* The `queue_overflow` message a request gets past
|
|
169
|
+
* `MAX_INBOUND_DISPATCHES_PER_CONNECTION`. Same text as the Rust spine's.
|
|
170
|
+
*/
|
|
171
|
+
export declare const WS_INBOUND_DISPATCH_OVERFLOW_MESSAGE = "too many concurrent requests on this connection";
|
|
172
|
+
/**
|
|
173
|
+
* The `data.reason` of the `invalid_request` a WebSocket request gets when its
|
|
174
|
+
* id names a request still running on the same socket. The duplicate is not
|
|
175
|
+
* dispatched; the running request keeps its id, so its reply, and a `cancel`
|
|
176
|
+
* naming that id, stay its own. An id is free again once its request has been
|
|
177
|
+
* answered.
|
|
178
|
+
*/
|
|
179
|
+
export declare const ERROR_DUPLICATE_REQUEST_ID = "duplicate_request_id";
|
|
141
180
|
/**
|
|
142
181
|
* Context passed to the `on_socket_open` hook.
|
|
143
182
|
*
|
|
@@ -340,6 +379,12 @@ export interface RegisterActionWsResult {
|
|
|
340
379
|
* - Notifications (method + no id) are silently dropped per JSON-RPC spec.
|
|
341
380
|
* Exception: `cancel` notifications abort the matching pending request's
|
|
342
381
|
* `ctx.signal` before bubbling out.
|
|
382
|
+
* - A request whose id names a request still running on the socket is
|
|
383
|
+
* answered `invalid_request` with `data.reason` `ERROR_DUPLICATE_REQUEST_ID`
|
|
384
|
+
* and not dispatched.
|
|
385
|
+
* - At most `MAX_INBOUND_DISPATCHES_PER_CONNECTION` requests run at once per
|
|
386
|
+
* socket; one more is answered `queue_overflow` and not dispatched, and the
|
|
387
|
+
* socket stays open.
|
|
343
388
|
* - Per-message dispatch goes through `perform_action`: pre-authorization
|
|
344
389
|
* auth (401) → authorization phase → post-authorization auth (403) →
|
|
345
390
|
* rate limit (429) → input validation (400) → handler (with transaction
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register_action_ws.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_action_ws.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"register_action_ws.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_action_ws.ts"],"names":[],"mappings":"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"}
|
|
@@ -59,6 +59,14 @@
|
|
|
59
59
|
* re-read itself failed — fail closed, never admitted unchecked. The handshake
|
|
60
60
|
* has already answered `101`, so a refusal can only be a close frame.
|
|
61
61
|
*
|
|
62
|
+
* Once the server is shutting down (`AppServer.close` ran the transport's
|
|
63
|
+
* `close_all_sockets`) a refusal is `WS_CLOSE_GOING_AWAY` instead, so the
|
|
64
|
+
* client reconnects rather than treating its credential as revoked: an upgrade
|
|
65
|
+
* pending at the close-all is closed by it — and its abort makes the refusal
|
|
66
|
+
* that follows the re-read send nothing more, even when the database closed
|
|
67
|
+
* under that re-read — and one that registers after it is born closed and
|
|
68
|
+
* skips the re-read (`BackendWebsocketTransport` module doc, "Shutdown").
|
|
69
|
+
*
|
|
62
70
|
* Frames that arrive before admission are queued, not dispatched: nothing runs
|
|
63
71
|
* on a credential that has not been re-read. They dispatch in arrival order
|
|
64
72
|
* once the socket is admitted and `on_socket_open` has completed, and are
|
|
@@ -145,6 +153,38 @@ export const PRE_ADMISSION_QUEUE_BYTES_FACTOR = 4;
|
|
|
145
153
|
* overflows its pre-admission queue, by count or by bytes.
|
|
146
154
|
*/
|
|
147
155
|
export const WS_CLOSE_PRE_ADMISSION_OVERFLOW_REASON = 'too much sent before the socket opened';
|
|
156
|
+
/**
|
|
157
|
+
* Max requests one socket may have dispatching at once. A request that
|
|
158
|
+
* arrives past it is answered `queue_overflow` with its id
|
|
159
|
+
* (`WS_INBOUND_DISPATCH_OVERFLOW_MESSAGE`) and not dispatched; the socket
|
|
160
|
+
* stays open, and the requests already running keep answering.
|
|
161
|
+
*
|
|
162
|
+
* A runtime adapter can hand frames over without awaiting the previous one's
|
|
163
|
+
* dispatch (`@hono/node-ws` does not await `onMessage`), so without a ceiling
|
|
164
|
+
* one socket could hold any number of handlers — and their pooled database
|
|
165
|
+
* work — at once. Shedding rather than queueing keeps the socket reading, so a
|
|
166
|
+
* reply to a server-initiated request still reaches the handler awaiting it.
|
|
167
|
+
* Generous, so ordinary pipelining never meets it. Same value as the Rust
|
|
168
|
+
* spine's `MAX_INBOUND_DISPATCHES_PER_CONN`.
|
|
169
|
+
*
|
|
170
|
+
* Counted are the requests that passed envelope validation and are not yet
|
|
171
|
+
* answered — the entries of the socket's cancellation map. Notifications,
|
|
172
|
+
* replies to server-initiated requests, and refused frames never count.
|
|
173
|
+
*/
|
|
174
|
+
export const MAX_INBOUND_DISPATCHES_PER_CONNECTION = 128;
|
|
175
|
+
/**
|
|
176
|
+
* The `queue_overflow` message a request gets past
|
|
177
|
+
* `MAX_INBOUND_DISPATCHES_PER_CONNECTION`. Same text as the Rust spine's.
|
|
178
|
+
*/
|
|
179
|
+
export const WS_INBOUND_DISPATCH_OVERFLOW_MESSAGE = 'too many concurrent requests on this connection';
|
|
180
|
+
/**
|
|
181
|
+
* The `data.reason` of the `invalid_request` a WebSocket request gets when its
|
|
182
|
+
* id names a request still running on the same socket. The duplicate is not
|
|
183
|
+
* dispatched; the running request keeps its id, so its reply, and a `cancel`
|
|
184
|
+
* naming that id, stay its own. An id is free again once its request has been
|
|
185
|
+
* answered.
|
|
186
|
+
*/
|
|
187
|
+
export const ERROR_DUPLICATE_REQUEST_ID = 'duplicate_request_id';
|
|
148
188
|
/** Send one JSON-RPC error response frame on `ws`. */
|
|
149
189
|
const send_error_response = (ws, id, error) => {
|
|
150
190
|
ws.send(JSON.stringify(create_jsonrpc_error_response(id, error)));
|
|
@@ -187,6 +227,12 @@ const is_peer_response = (json) => {
|
|
|
187
227
|
* - Notifications (method + no id) are silently dropped per JSON-RPC spec.
|
|
188
228
|
* Exception: `cancel` notifications abort the matching pending request's
|
|
189
229
|
* `ctx.signal` before bubbling out.
|
|
230
|
+
* - A request whose id names a request still running on the socket is
|
|
231
|
+
* answered `invalid_request` with `data.reason` `ERROR_DUPLICATE_REQUEST_ID`
|
|
232
|
+
* and not dispatched.
|
|
233
|
+
* - At most `MAX_INBOUND_DISPATCHES_PER_CONNECTION` requests run at once per
|
|
234
|
+
* socket; one more is answered `queue_overflow` and not dispatched, and the
|
|
235
|
+
* socket stays open.
|
|
190
236
|
* - Per-message dispatch goes through `perform_action`: pre-authorization
|
|
191
237
|
* auth (401) → authorization phase → post-authorization auth (403) →
|
|
192
238
|
* rate limit (429) → input validation (400) → handler (with transaction
|
|
@@ -333,7 +379,10 @@ export const register_action_ws = (options) => {
|
|
|
333
379
|
// Populated on request dispatch, cleared in the handler's `finally`
|
|
334
380
|
// so a late-arriving cancel for a completed id (or a reused id)
|
|
335
381
|
// can't null-abort a freshly-arrived request. Idempotent: cancel
|
|
336
|
-
// for unknown ids no-ops.
|
|
382
|
+
// for unknown ids no-ops. One entry per running request — a
|
|
383
|
+
// request reusing a live id is refused before it gets one — so its
|
|
384
|
+
// size is the in-flight count `MAX_INBOUND_DISPATCHES_PER_CONNECTION`
|
|
385
|
+
// bounds.
|
|
337
386
|
const pending_controllers = new Map();
|
|
338
387
|
// Identity is assembled at upgrade time so `on_socket_close` can
|
|
339
388
|
// read it — by the time the hook runs the transport record is gone,
|
|
@@ -407,8 +456,10 @@ export const register_action_ws = (options) => {
|
|
|
407
456
|
}
|
|
408
457
|
};
|
|
409
458
|
// Refuse an upgrade at admission. A socket that already ended needs no
|
|
410
|
-
// close frame:
|
|
411
|
-
// transport sent the 4001)
|
|
459
|
+
// close frame: a revocation closed its pending registration (the
|
|
460
|
+
// transport sent the 4001), the shutdown did (the 1001 — so a refusal
|
|
461
|
+
// during shutdown never sends a revocation's or a failure's code), or
|
|
462
|
+
// the client went away.
|
|
412
463
|
const refuse_upgrade = (ws, code, reason) => {
|
|
413
464
|
if (captured_connection_id !== undefined) {
|
|
414
465
|
transport.remove_connection(captured_connection_id);
|
|
@@ -435,6 +486,17 @@ export const register_action_ws = (options) => {
|
|
|
435
486
|
// Pending first, synchronously: from here every revocation finds it.
|
|
436
487
|
const connection_id = transport.register_pending(ws, token_hash, account_id, api_token_id, socket_abort_controller);
|
|
437
488
|
captured_connection_id = connection_id;
|
|
489
|
+
// Born closed — the server is shutting down, and the transport has
|
|
490
|
+
// already closed the socket with `WS_CLOSE_GOING_AWAY` — or ended
|
|
491
|
+
// otherwise before the re-read: nothing to re-read for. The removal
|
|
492
|
+
// is a no-op for a born-closed registration, which was never inserted.
|
|
493
|
+
if (socket_abort_controller.signal.aborted) {
|
|
494
|
+
transport.remove_connection(connection_id);
|
|
495
|
+
log.info(transport.is_closing()
|
|
496
|
+
? 'ws upgrade admission: refused, the server is shutting down'
|
|
497
|
+
: 'ws upgrade admission: socket ended before the re-check', connection_id, account_id);
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
438
500
|
if (!upgrade_preset) {
|
|
439
501
|
let credential_is_live;
|
|
440
502
|
try {
|
|
@@ -443,8 +505,15 @@ export const register_action_ws = (options) => {
|
|
|
443
505
|
credential_is_live = await revalidate_resolved_auth({ db }, resolved);
|
|
444
506
|
}
|
|
445
507
|
catch (error) {
|
|
446
|
-
// fail closed — never admitted unchecked
|
|
447
|
-
|
|
508
|
+
// fail closed — never admitted unchecked. During shutdown the
|
|
509
|
+
// database may close under the re-read; that is the shutdown's
|
|
510
|
+
// refusal, not a failure.
|
|
511
|
+
if (transport.is_closing()) {
|
|
512
|
+
log.info('ws upgrade admission: re-check ended by the shutdown', error);
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
log.error('ws upgrade admission: credential re-check failed', error);
|
|
516
|
+
}
|
|
448
517
|
refuse_upgrade(ws, WS_CLOSE_INTERNAL_ERROR, 'internal error');
|
|
449
518
|
return null;
|
|
450
519
|
}
|
|
@@ -457,7 +526,9 @@ export const register_action_ws = (options) => {
|
|
|
457
526
|
// The cap's eviction happens here, after every gate and the re-read,
|
|
458
527
|
// so a refused upgrade never closes someone else's socket.
|
|
459
528
|
if (!transport.admit(connection_id)) {
|
|
460
|
-
log.info(
|
|
529
|
+
log.info(transport.is_closing()
|
|
530
|
+
? 'ws upgrade admission: closed by the shutdown while pending'
|
|
531
|
+
: 'ws upgrade admission: closed by a revocation while pending', connection_id, account_id);
|
|
461
532
|
refuse_upgrade(ws, WS_CLOSE_SESSION_REVOKED, WS_CLOSE_SESSION_REVOKED_REASON);
|
|
462
533
|
return null;
|
|
463
534
|
}
|
|
@@ -614,6 +685,24 @@ export const register_action_ws = (options) => {
|
|
|
614
685
|
return;
|
|
615
686
|
}
|
|
616
687
|
const { method, id, params } = envelope.data;
|
|
688
|
+
// A request is live from here until its reply is sent. One reusing
|
|
689
|
+
// a live id is refused rather than dispatched: two requests under
|
|
690
|
+
// one id would share a cancel entry and a reply id, and the first to
|
|
691
|
+
// finish would clear the other's. Checked before the ceiling — a
|
|
692
|
+
// duplicate is the client's error whatever the load, and retrying it
|
|
693
|
+
// cannot succeed while the first runs. Neither refusal reaches
|
|
694
|
+
// `perform_action`, so neither charges the action rate limiters —
|
|
695
|
+
// the same on the Rust spine.
|
|
696
|
+
if (pending_controllers.has(id)) {
|
|
697
|
+
send_error_response(ws, id, jsonrpc_error_messages.invalid_request({ reason: ERROR_DUPLICATE_REQUEST_ID }));
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
// The in-flight ceiling: shed, don't queue — see
|
|
701
|
+
// `MAX_INBOUND_DISPATCHES_PER_CONNECTION`.
|
|
702
|
+
if (pending_controllers.size >= MAX_INBOUND_DISPATCHES_PER_CONNECTION) {
|
|
703
|
+
send_error_response(ws, id, jsonrpc_error_messages.queue_overflow(WS_INBOUND_DISPATCH_OVERFLOW_MESSAGE));
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
617
706
|
// Per-action method lookup — return method_not_found before
|
|
618
707
|
// we engage the dispatch machinery. Specs without a handler
|
|
619
708
|
// (client-only / dispatcher-handled) miss action_map and
|
|
@@ -623,16 +712,14 @@ export const register_action_ws = (options) => {
|
|
|
623
712
|
send_error_response(ws, id, jsonrpc_error_messages.method_not_found(method));
|
|
624
713
|
return;
|
|
625
714
|
}
|
|
626
|
-
if (artificial_delay > 0) {
|
|
627
|
-
log.debug(`throttling ${artificial_delay}ms`);
|
|
628
|
-
await wait(artificial_delay);
|
|
629
|
-
}
|
|
630
715
|
// Per-request controller — fires on explicit `cancel` or when the
|
|
631
716
|
// socket ends (via the socket_abort_controller chain below).
|
|
632
|
-
// Registered before dispatch
|
|
633
|
-
//
|
|
634
|
-
//
|
|
635
|
-
//
|
|
717
|
+
// Registered before dispatch — ahead of `artificial_delay` too, so
|
|
718
|
+
// the request is live (counted, and its id taken) from the moment
|
|
719
|
+
// it is accepted — so a cancel arriving mid-handler finds it;
|
|
720
|
+
// cleared in `finally` so late cancels for a completed id (or a
|
|
721
|
+
// future request that reuses the id) can't null-abort the wrong
|
|
722
|
+
// handler.
|
|
636
723
|
const request_controller = new AbortController();
|
|
637
724
|
pending_controllers.set(id, request_controller);
|
|
638
725
|
// Per-message side-effect queues. `pending_effects` collects
|
|
@@ -669,6 +756,10 @@ export const register_action_ws = (options) => {
|
|
|
669
756
|
const request_client = (request_method, request_params, request_options) => transport.request_connection(connection_id, request_method, request_params, request_options);
|
|
670
757
|
const signal = AbortSignal.any([socket_abort_controller.signal, request_controller.signal]);
|
|
671
758
|
try {
|
|
759
|
+
if (artificial_delay > 0) {
|
|
760
|
+
log.debug(`throttling ${artificial_delay}ms`);
|
|
761
|
+
await wait(artificial_delay);
|
|
762
|
+
}
|
|
672
763
|
const result = await perform_action({
|
|
673
764
|
action,
|
|
674
765
|
raw_params: params,
|
|
@@ -42,7 +42,8 @@ export declare const WS_CLOSE_CONNECTION_LIMIT = 4004;
|
|
|
42
42
|
/**
|
|
43
43
|
* WebSocket close code — the server is shutting down (RFC 6455 §7.4.1 "Going
|
|
44
44
|
* Away"). Sent to every live socket by `close_all_sockets`, which
|
|
45
|
-
* `AppServer.close` runs
|
|
45
|
+
* `AppServer.close` runs, and to an upgrade the shutdown refuses — one
|
|
46
|
+
* pending at the close-all, or born closed after it. Not a revocation: the credential is still good, so a
|
|
46
47
|
* client reconnects under its ordinary backoff once a server is back.
|
|
47
48
|
*/
|
|
48
49
|
export declare const WS_CLOSE_GOING_AWAY = 1001;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transports.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/transports.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EACX,gCAAgC,EAChC,gCAAgC,EAChC,mBAAmB,EACnB,cAAc,EACd,sBAAsB,EACtB,oBAAoB,EACpB,MAAM,oBAAoB,CAAC;AAE5B;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAC7C;;;;GAIG;AACH,eAAO,MAAM,+BAA+B,oBAAoB,CAAC;AACjE,sEAAsE;AACtE,eAAO,MAAM,iCAAiC,OAAO,CAAC;AACtD,yEAAyE;AACzE,eAAO,MAAM,iCAAiC,OAAO,CAAC;AACtD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAC9C
|
|
1
|
+
{"version":3,"file":"transports.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/transports.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EACX,gCAAgC,EAChC,gCAAgC,EAChC,mBAAmB,EACnB,cAAc,EACd,sBAAsB,EACtB,oBAAoB,EACpB,MAAM,oBAAoB,CAAC;AAE5B;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAC7C;;;;GAIG;AACH,eAAO,MAAM,+BAA+B,oBAAoB,CAAC;AACjE,sEAAsE;AACtE,eAAO,MAAM,iCAAiC,OAAO,CAAC;AACtD,yEAAyE;AACzE,eAAO,MAAM,iCAAiC,OAAO,CAAC;AACtD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAC9C;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,OAAO,CAAC;AACxC,wDAAwD;AACxD,eAAO,MAAM,0BAA0B,yBAAyB,CAAC;AACjE;;;GAGG;AACH,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAC7C;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAC9C;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAE5C;;;;;;;GAOG;AACH,eAAO,MAAM,4BAA4B,QAAc,CAAC;AAIxD;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,EAAE,WAAW,MAAM,KAAG,MAAM,GAAG,IAI3E,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,MAAM,MAAM,KAAG,MAA8C,CAAC;AAK1F,eAAO,MAAM,aAAa,aAAa,CAAC;AACxC,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAE1D;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACpC;;;;;OAKG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACzB,cAAc,EAAE,aAAa,CAAC;IAE9B,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC/F,IAAI,CACH,OAAO,EAAE,mBAAmB,EAC5B,OAAO,CAAC,EAAE,oBAAoB,GAC5B,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IACxC,IAAI,CACH,OAAO,EAAE,gCAAgC,EACzC,OAAO,CAAC,EAAE,oBAAoB,GAC5B,OAAO,CAAC,gCAAgC,GAAG,IAAI,CAAC,CAAC;IACpD,QAAQ,EAAE,MAAM,OAAO,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,qBAAa,UAAU;;IAItB;;;OAGG;IACH,cAAc,EAAE,OAAO,CAAQ;IAE/B;;;;;OAKG;IACH,kBAAkB,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI;IAQ9C;;;;;OAKG;IACH,qBAAqB,CAAC,cAAc,EAAE,aAAa,GAAG,IAAI;IAM1D;;;;;;OAMG;IACH,aAAa,CAAC,cAAc,CAAC,EAAE,aAAa,GAAG,SAAS,GAAG,IAAI;IAO/D,QAAQ,IAAI,OAAO,GAAG,IAAI;IAM1B,qBAAqB,IAAI,SAAS,GAAG,IAAI;IAIzC,0BAA0B,IAAI,aAAa,GAAG,IAAI;IAIlD,qBAAqB,CAAC,cAAc,EAAE,aAAa,GAAG,SAAS,GAAG,IAAI;CAwCtE"}
|
|
@@ -41,7 +41,8 @@ export const WS_CLOSE_CONNECTION_LIMIT = 4004;
|
|
|
41
41
|
/**
|
|
42
42
|
* WebSocket close code — the server is shutting down (RFC 6455 §7.4.1 "Going
|
|
43
43
|
* Away"). Sent to every live socket by `close_all_sockets`, which
|
|
44
|
-
* `AppServer.close` runs
|
|
44
|
+
* `AppServer.close` runs, and to an upgrade the shutdown refuses — one
|
|
45
|
+
* pending at the close-all, or born closed after it. Not a revocation: the credential is still good, so a
|
|
45
46
|
* client reconnects under its ordinary backoff once a server is back.
|
|
46
47
|
*/
|
|
47
48
|
export const WS_CLOSE_GOING_AWAY = 1001;
|
|
@@ -44,6 +44,21 @@
|
|
|
44
44
|
* uses the two-phase form. The twin of the Rust `fuz_realtime`
|
|
45
45
|
* `ConnectionRegistry`.
|
|
46
46
|
*
|
|
47
|
+
* ## Shutdown
|
|
48
|
+
*
|
|
49
|
+
* `close_all_sockets` — the close `AppServer.close` runs — closes every
|
|
50
|
+
* connection, pending or admitted, with `WS_CLOSE_GOING_AWAY`, and leaves the
|
|
51
|
+
* transport **closing**: from then on every registration is born closed. A
|
|
52
|
+
* `register_pending` or `add_connection` inserts nothing — it aborts the
|
|
53
|
+
* caller's controller and closes the socket with `WS_CLOSE_GOING_AWAY` at once
|
|
54
|
+
* — so an upgrade still in flight when the server shuts down is closed rather
|
|
55
|
+
* than outliving it. An upgrade registered before the close-all is closed by
|
|
56
|
+
* it; one registered after is born closed; none is admitted. The flag is set
|
|
57
|
+
* in the same synchronous step that closes the live connections, so an `admit`
|
|
58
|
+
* that finds its entry gone and reads `is_closing()` sees the shutdown, never
|
|
59
|
+
* a revocation. Closing is permanent: a closed transport hosts no further
|
|
60
|
+
* connections.
|
|
61
|
+
*
|
|
47
62
|
* @module
|
|
48
63
|
*/
|
|
49
64
|
import type { WSContext } from 'hono/ws';
|
|
@@ -142,6 +157,11 @@ export declare class BackendWebsocketTransport implements FilterableBroadcastTra
|
|
|
142
157
|
* The caller owns the entry until it is admitted: every refusal path calls
|
|
143
158
|
* `remove_connection` with the returned id.
|
|
144
159
|
*
|
|
160
|
+
* Once the transport is closing (`close_all_sockets`) the registration is
|
|
161
|
+
* born closed: nothing is inserted, `abort_controller` is aborted, and `ws`
|
|
162
|
+
* is closed with `WS_CLOSE_GOING_AWAY` before this returns. The id names
|
|
163
|
+
* nothing, so `admit` refuses it (module doc, "Shutdown").
|
|
164
|
+
*
|
|
145
165
|
* @param ws - the socket to close on revocation
|
|
146
166
|
* @param token_hash - blake3 session token hash, or `null` for non-session credentials
|
|
147
167
|
* @param account_id - the authenticated account
|
|
@@ -149,7 +169,8 @@ export declare class BackendWebsocketTransport implements FilterableBroadcastTra
|
|
|
149
169
|
* @param abort_controller - the socket-scoped controller to abort when the
|
|
150
170
|
* transport closes this connection (revocation or cap eviction)
|
|
151
171
|
* @returns the freshly assigned `connection_id` (branded `Uuid`)
|
|
152
|
-
* @mutates this - inserts an un-admitted entry into `#connections
|
|
172
|
+
* @mutates this - inserts an un-admitted entry into `#connections`, unless
|
|
173
|
+
* the transport is closing
|
|
153
174
|
*/
|
|
154
175
|
register_pending(ws: WSContext, token_hash: string | null, account_id: Uuid, api_token_id?: string | null, abort_controller?: AbortController | null): Uuid;
|
|
155
176
|
/**
|
|
@@ -192,7 +213,8 @@ export declare class BackendWebsocketTransport implements FilterableBroadcastTra
|
|
|
192
213
|
* inserted. A removed connection gets no further broadcast or peer request,
|
|
193
214
|
* stops counting toward the cap at once, and has its `abort_controller`
|
|
194
215
|
* aborted; its socket is closed when the close handshake completes. The new
|
|
195
|
-
* connection is always admitted
|
|
216
|
+
* connection is always admitted — unless the transport is closing: then it
|
|
217
|
+
* is born closed, as in `register_pending`, and evicts nothing.
|
|
196
218
|
*
|
|
197
219
|
* @returns the freshly assigned `connection_id` (branded `Uuid`)
|
|
198
220
|
* @mutates this - inserts an admitted entry into `#connections`; past the
|
|
@@ -261,12 +283,23 @@ export declare class BackendWebsocketTransport implements FilterableBroadcastTra
|
|
|
261
283
|
* revocation does, so its heartbeat timer stops and nothing more dispatches
|
|
262
284
|
* on it. A socket whose `close` throws does not stop the loop.
|
|
263
285
|
*
|
|
286
|
+
* Leaves the transport closing, for good: every later registration is born
|
|
287
|
+
* closed (module doc, "Shutdown"). A second call closes nothing.
|
|
288
|
+
*
|
|
264
289
|
* @returns the number of sockets closed
|
|
265
|
-
* @mutates this - removes every connection,
|
|
266
|
-
* controller, and closes its underlying `WSContext`
|
|
290
|
+
* @mutates this - sets the transport closing, removes every connection,
|
|
291
|
+
* aborts its registered controller, and closes its underlying `WSContext`
|
|
292
|
+
* with `WS_CLOSE_GOING_AWAY`
|
|
267
293
|
* @throws the first error a socket's `close` threw, after every connection was attempted
|
|
268
294
|
*/
|
|
269
295
|
close_all_sockets(): number;
|
|
296
|
+
/**
|
|
297
|
+
* Whether `close_all_sockets` has run — the transport is shutting down and
|
|
298
|
+
* every registration from now on is born closed (module doc, "Shutdown").
|
|
299
|
+
* For telemetry and logs: `register_action_ws` reads it to say why an
|
|
300
|
+
* upgrade was refused.
|
|
301
|
+
*/
|
|
302
|
+
is_closing(): boolean;
|
|
270
303
|
send(message: JsonrpcRequest, options?: TransportSendOptions): Promise<JsonrpcResponseOrError>;
|
|
271
304
|
send(message: JsonrpcNotification, options?: TransportSendOptions): Promise<JsonrpcErrorResponse | null>;
|
|
272
305
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transports_ws_backend.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/transports_ws_backend.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"transports_ws_backend.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/transports_ws_backend.ts"],"names":[],"mappings":"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"}
|