@fuzdev/fuz_app 0.118.0 → 0.120.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.
Files changed (169) hide show
  1. package/dist/actions/CLAUDE.md +301 -73
  2. package/dist/actions/action_rpc.d.ts.map +1 -1
  3. package/dist/actions/action_rpc.js +16 -1
  4. package/dist/actions/connection_closer.d.ts +136 -51
  5. package/dist/actions/connection_closer.d.ts.map +1 -1
  6. package/dist/actions/connection_closer.js +133 -34
  7. package/dist/actions/register_action_ws.d.ts +176 -24
  8. package/dist/actions/register_action_ws.d.ts.map +1 -1
  9. package/dist/actions/register_action_ws.js +581 -241
  10. package/dist/actions/register_ws_endpoint.d.ts +1 -0
  11. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  12. package/dist/actions/register_ws_endpoint.js +1 -0
  13. package/dist/actions/socket.svelte.d.ts +43 -9
  14. package/dist/actions/socket.svelte.d.ts.map +1 -1
  15. package/dist/actions/socket.svelte.js +59 -8
  16. package/dist/actions/transports.d.ts +55 -1
  17. package/dist/actions/transports.d.ts.map +1 -1
  18. package/dist/actions/transports.js +55 -1
  19. package/dist/actions/transports_ws_auth_guard.d.ts +53 -67
  20. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  21. package/dist/actions/transports_ws_auth_guard.js +87 -111
  22. package/dist/actions/transports_ws_backend.d.ts +212 -30
  23. package/dist/actions/transports_ws_backend.d.ts.map +1 -1
  24. package/dist/actions/transports_ws_backend.js +327 -70
  25. package/dist/actions/ws_endpoint_spec.d.ts +26 -13
  26. package/dist/actions/ws_endpoint_spec.d.ts.map +1 -1
  27. package/dist/auth/CLAUDE.md +113 -32
  28. package/dist/auth/account_actions.d.ts +12 -18
  29. package/dist/auth/account_actions.d.ts.map +1 -1
  30. package/dist/auth/account_actions.js +47 -33
  31. package/dist/auth/account_routes.d.ts +5 -11
  32. package/dist/auth/account_routes.d.ts.map +1 -1
  33. package/dist/auth/account_routes.js +40 -36
  34. package/dist/auth/account_schema.d.ts +27 -0
  35. package/dist/auth/account_schema.d.ts.map +1 -1
  36. package/dist/auth/account_schema.js +22 -0
  37. package/dist/auth/admin_actions.d.ts +8 -17
  38. package/dist/auth/admin_actions.d.ts.map +1 -1
  39. package/dist/auth/admin_actions.js +33 -21
  40. package/dist/auth/api_token_queries.d.ts +23 -2
  41. package/dist/auth/api_token_queries.d.ts.map +1 -1
  42. package/dist/auth/api_token_queries.js +39 -3
  43. package/dist/auth/audit_emitter.d.ts +61 -38
  44. package/dist/auth/audit_emitter.d.ts.map +1 -1
  45. package/dist/auth/audit_emitter.js +57 -14
  46. package/dist/auth/audit_log_route_schema.js +7 -7
  47. package/dist/auth/audit_log_routes.d.ts +31 -6
  48. package/dist/auth/audit_log_routes.d.ts.map +1 -1
  49. package/dist/auth/audit_log_routes.js +85 -11
  50. package/dist/auth/audit_log_schema.d.ts +48 -0
  51. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  52. package/dist/auth/audit_log_schema.js +58 -0
  53. package/dist/auth/cleanup.d.ts +203 -28
  54. package/dist/auth/cleanup.d.ts.map +1 -1
  55. package/dist/auth/cleanup.js +222 -26
  56. package/dist/auth/deps.d.ts +31 -3
  57. package/dist/auth/deps.d.ts.map +1 -1
  58. package/dist/auth/resolved_auth.d.ts +105 -0
  59. package/dist/auth/resolved_auth.d.ts.map +1 -0
  60. package/dist/auth/resolved_auth.js +124 -0
  61. package/dist/auth/session_middleware.d.ts +8 -1
  62. package/dist/auth/session_middleware.d.ts.map +1 -1
  63. package/dist/auth/session_middleware.js +9 -3
  64. package/dist/auth/session_queries.d.ts +31 -6
  65. package/dist/auth/session_queries.d.ts.map +1 -1
  66. package/dist/auth/session_queries.js +31 -6
  67. package/dist/auth/standard_rpc_actions.d.ts +9 -9
  68. package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
  69. package/dist/auth/standard_rpc_actions.js +3 -3
  70. package/dist/db/CLAUDE.md +19 -8
  71. package/dist/db/fact_disk_storage.d.ts +10 -8
  72. package/dist/db/fact_disk_storage.d.ts.map +1 -1
  73. package/dist/db/fact_disk_storage.js +13 -17
  74. package/dist/db/fact_store.d.ts +39 -38
  75. package/dist/db/fact_store.d.ts.map +1 -1
  76. package/dist/db/fact_store.js +48 -86
  77. package/dist/db/file_fact_url.d.ts +6 -6
  78. package/dist/db/file_fact_url.js +6 -6
  79. package/dist/db/status.d.ts +58 -1
  80. package/dist/db/status.d.ts.map +1 -1
  81. package/dist/db/status.js +205 -77
  82. package/dist/http/CLAUDE.md +7 -3
  83. package/dist/http/db_routes.d.ts +6 -2
  84. package/dist/http/db_routes.d.ts.map +1 -1
  85. package/dist/http/db_routes.js +2 -2
  86. package/dist/periodic.d.ts +80 -0
  87. package/dist/periodic.d.ts.map +1 -0
  88. package/dist/periodic.js +122 -0
  89. package/dist/realtime/sse_auth_guard.d.ts +79 -49
  90. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  91. package/dist/realtime/sse_auth_guard.js +103 -73
  92. package/dist/realtime/subscriber_registry.d.ts +119 -8
  93. package/dist/realtime/subscriber_registry.d.ts.map +1 -1
  94. package/dist/realtime/subscriber_registry.js +184 -25
  95. package/dist/server/app_backend.d.ts +12 -1
  96. package/dist/server/app_backend.d.ts.map +1 -1
  97. package/dist/server/app_backend.js +5 -2
  98. package/dist/server/app_server.d.ts +55 -15
  99. package/dist/server/app_server.d.ts.map +1 -1
  100. package/dist/server/app_server.js +41 -13
  101. package/dist/server/serve_fact_route.d.ts +10 -7
  102. package/dist/server/serve_fact_route.d.ts.map +1 -1
  103. package/dist/server/serve_fact_route.js +11 -8
  104. package/dist/testing/CLAUDE.md +140 -48
  105. package/dist/testing/app_server.d.ts +2 -2
  106. package/dist/testing/app_server.d.ts.map +1 -1
  107. package/dist/testing/app_server.js +11 -2
  108. package/dist/testing/audit_drift_guard.d.ts +1 -48
  109. package/dist/testing/audit_drift_guard.d.ts.map +1 -1
  110. package/dist/testing/audit_drift_guard.js +0 -42
  111. package/dist/testing/connection_closer_helpers.d.ts +9 -22
  112. package/dist/testing/connection_closer_helpers.d.ts.map +1 -1
  113. package/dist/testing/connection_closer_helpers.js +11 -17
  114. package/dist/testing/cross_backend/capabilities.d.ts +67 -13
  115. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  116. package/dist/testing/cross_backend/capabilities.js +27 -12
  117. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  118. package/dist/testing/cross_backend/default_backend_configs.js +21 -0
  119. package/dist/testing/cross_backend/role_grant_offer_notification_ws.d.ts.map +1 -1
  120. package/dist/testing/cross_backend/role_grant_offer_notification_ws.js +13 -4
  121. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  122. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +4 -1
  123. package/dist/testing/cross_backend/session_cap.d.ts +13 -0
  124. package/dist/testing/cross_backend/session_cap.d.ts.map +1 -1
  125. package/dist/testing/cross_backend/session_cap.js +55 -19
  126. package/dist/testing/cross_backend/sse_round_trip.d.ts +10 -2
  127. package/dist/testing/cross_backend/sse_round_trip.d.ts.map +1 -1
  128. package/dist/testing/cross_backend/sse_round_trip.js +93 -26
  129. package/dist/testing/cross_backend/testing_server_bun.js +4 -4
  130. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  131. package/dist/testing/cross_backend/ts_spine_backend_config.js +10 -2
  132. package/dist/testing/cross_backend/ws_connection_cap.d.ts +35 -0
  133. package/dist/testing/cross_backend/ws_connection_cap.d.ts.map +1 -0
  134. package/dist/testing/cross_backend/ws_connection_cap.js +131 -0
  135. package/dist/testing/cross_backend/ws_round_trip.d.ts +2 -1
  136. package/dist/testing/cross_backend/ws_round_trip.d.ts.map +1 -1
  137. package/dist/testing/cross_backend/ws_round_trip.js +156 -19
  138. package/dist/testing/sse_round_trip.d.ts +6 -4
  139. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  140. package/dist/testing/stubs.d.ts +1 -1
  141. package/dist/testing/stubs.d.ts.map +1 -1
  142. package/dist/testing/stubs.js +6 -4
  143. package/dist/testing/transports/sse_transport.d.ts +1 -1
  144. package/dist/testing/transports/ws_client.d.ts +7 -0
  145. package/dist/testing/transports/ws_client.d.ts.map +1 -1
  146. package/dist/testing/transports/ws_raw_client.d.ts +51 -0
  147. package/dist/testing/transports/ws_raw_client.d.ts.map +1 -0
  148. package/dist/testing/transports/ws_raw_client.js +156 -0
  149. package/dist/testing/transports/ws_transport.d.ts +28 -0
  150. package/dist/testing/transports/ws_transport.d.ts.map +1 -1
  151. package/dist/testing/transports/ws_transport.js +45 -1
  152. package/dist/testing/ws_round_trip.d.ts +8 -3
  153. package/dist/testing/ws_round_trip.d.ts.map +1 -1
  154. package/dist/testing/ws_round_trip.js +19 -4
  155. package/dist/ui/AdminAccounts.svelte +4 -3
  156. package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
  157. package/dist/ui/AdminOverview.svelte +3 -1
  158. package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
  159. package/dist/ui/CLAUDE.md +22 -6
  160. package/dist/ui/admin_accounts_state.svelte.d.ts +15 -1
  161. package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
  162. package/dist/ui/admin_accounts_state.svelte.js +16 -0
  163. package/dist/ui/auth_state.svelte.d.ts +25 -1
  164. package/dist/ui/auth_state.svelte.d.ts.map +1 -1
  165. package/dist/ui/auth_state.svelte.js +28 -1
  166. package/package.json +2 -2
  167. package/dist/server/file_fact_fetcher.d.ts +0 -42
  168. package/dist/server/file_fact_fetcher.d.ts.map +0 -1
  169. package/dist/server/file_fact_fetcher.js +0 -60
@@ -59,6 +59,7 @@ export interface RegisterWsEndpointOptions extends RegisterActionWsOptions {
59
59
  * @mutates options.app - applies origin/auth/scope/authorization/role middleware
60
60
  * via `app.use`, then registers the `GET path` route via the inner
61
61
  * `register_action_ws`
62
+ * @mutates options.connection_closer - adds the endpoint's transport
62
63
  */
63
64
  export declare const register_ws_endpoint: (options: RegisterWsEndpointOptions) => RegisterActionWsResult;
64
65
  //# sourceMappingURL=register_ws_endpoint.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"register_ws_endpoint.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_ws_endpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAaH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAGvD,OAAO,EAEN,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,MAAM,yBAAyB,CAAC;AAEjC,0CAA0C;AAC1C,MAAM,WAAW,yBAA0B,SAAQ,uBAAuB;IACzE;;;;OAIG;IACH,eAAe,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACvC;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;CACzC;AAgDD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,oBAAoB,GAChC,SAAS,yBAAyB,KAChC,sBAoBF,CAAC"}
1
+ {"version":3,"file":"register_ws_endpoint.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/register_ws_endpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAaH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAGvD,OAAO,EAEN,KAAK,uBAAuB,EAC5B,KAAK,sBAAsB,EAC3B,MAAM,yBAAyB,CAAC;AAEjC,0CAA0C;AAC1C,MAAM,WAAW,yBAA0B,SAAQ,uBAAuB;IACzE;;;;OAIG;IACH,eAAe,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACvC;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;CACzC;AAgDD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,oBAAoB,GAChC,SAAS,yBAAyB,KAChC,sBAoBF,CAAC"}
@@ -83,6 +83,7 @@ const create_ws_authorization_middleware = (db) => {
83
83
  * @mutates options.app - applies origin/auth/scope/authorization/role middleware
84
84
  * via `app.use`, then registers the `GET path` route via the inner
85
85
  * `register_action_ws`
86
+ * @mutates options.connection_closer - adds the endpoint's transport
86
87
  */
87
88
  export const register_ws_endpoint = (options) => {
88
89
  const { app, path, allowed_origins, db, required_roles, log = new Logger('[ws]'), ...rest } = options;
@@ -5,8 +5,10 @@
5
5
  * Drop into any SvelteKit frontend as the underlying connection for
6
6
  * `FrontendWebsocketTransport`. Handles auto-reconnect with exponential
7
7
  * backoff, respects `WS_CLOSE_SESSION_REVOKED` (no reconnect loop after the
8
- * server revokes auth), exposes reactive status for UI indicators, and ships
9
- * three correctness primitives default-on:
8
+ * server revokes auth) and `WS_CLOSE_CONNECTION_LIMIT` (no reconnect after the
9
+ * server closes this socket to admit a newer one — closed until the user
10
+ * acts), exposes reactive status for UI indicators, and ships three
11
+ * correctness primitives default-on:
10
12
  *
11
13
  * - `FrontendWebsocketClient.request` — promise-based JSON-RPC with
12
14
  * auto-assigned ids and a pending-id map. Intercepts responses on the
@@ -72,8 +74,9 @@ export declare const DEFAULT_QUEUE_MAX_SIZE = 100;
72
74
  * - `connecting` — WebSocket `readyState === CONNECTING`.
73
75
  * - `connected` — WebSocket `readyState === OPEN`.
74
76
  * - `reconnecting` — close fired; waiting out backoff before next attempt.
75
- * - `closed` — socket is not open. Terminal only when `revoked` is `true`
76
- * or auto-reconnect is disabled; otherwise `connect()` reopens.
77
+ * - `closed` — socket is not open, and nothing is scheduled to reopen it.
78
+ * Terminal only when `revoked` is `true`; otherwise `connect()` reopens,
79
+ * including after a `superseded` close.
77
80
  */
78
81
  export type SocketStatus = 'initial' | 'connecting' | 'connected' | 'reconnecting' | 'closed';
79
82
  export type SocketMessageHandler = (event: MessageEvent) => void;
@@ -145,6 +148,11 @@ export interface FrontendWebsocketClientOptions {
145
148
  *
146
149
  * Session-revocation close codes (`WS_CLOSE_SESSION_REVOKED`) put the client
147
150
  * in a permanently-closed state; reconnecting would just loop on 401.
151
+ *
152
+ * A connection-limit close (`WS_CLOSE_CONNECTION_LIMIT`) leaves the client
153
+ * closed with `superseded` set and no reconnect scheduled — the server closed
154
+ * this socket to admit a newer one on the same account, and reconnecting
155
+ * would close that one in turn. An explicit `connect()` reopens.
148
156
  */
149
157
  export declare class FrontendWebsocketClient implements WebsocketConnection, Disposable {
150
158
  #private;
@@ -247,14 +255,35 @@ export declare class FrontendWebsocketClient implements WebsocketConnection, Dis
247
255
  * reflects any closed state (incl. user-initiated `disconnect()`).
248
256
  */
249
257
  get revoked(): boolean;
258
+ /**
259
+ * Whether the server closed this client's socket to admit a newer
260
+ * connection on the same account (`WS_CLOSE_CONNECTION_LIMIT`) and it
261
+ * hasn't been reopened since. While `true` the client is `closed` and
262
+ * schedules no reconnect — reconnecting on its own would supersede a newer
263
+ * socket in turn, and clients past the cap would close each other in a loop.
264
+ *
265
+ * Unlike `revoked` this is not terminal: the credential is still good, and
266
+ * the next `connect()` clears it and reopens — only `connect()` does;
267
+ * `disconnect()` leaves it set. Requests in flight or queued at the close
268
+ * reject with `service_unavailable`; ones made afterwards buffer in the
269
+ * durable queue like any other call on a closed client. Reactive, so UI can
270
+ * say why the connection is closed and offer the reconnect.
271
+ *
272
+ * `socket_status_to_async_status` reads a superseded client as `initial`,
273
+ * the same as one that was never connected. An app that connects on its
274
+ * own when its status reads `initial` must also check `superseded`, or it
275
+ * rebuilds the loop.
276
+ */
277
+ get superseded(): boolean;
250
278
  /**
251
279
  * Open the WebSocket. No-op on SSR, or if the session has been revoked.
252
280
  * Cancels any pending reconnect and tears down any existing connection first;
253
- * an open prior socket is closed with a normal-closure code.
281
+ * an open prior socket is closed with a normal-closure code. Reopens after
282
+ * a `superseded` close, clearing the flag.
254
283
  *
255
- * @mutates this - replaces `ws`, sets `status` to `connecting` (or
256
- * `closed` on construction failure), and on construction failure may
257
- * schedule a reconnect (mutating `reconnect_count` /
284
+ * @mutates this - replaces `ws`, clears `superseded`, sets `status` to
285
+ * `connecting` (or `closed` on construction failure), and on construction
286
+ * failure may schedule a reconnect (mutating `reconnect_count` /
258
287
  * `current_reconnect_delay`)
259
288
  */
260
289
  connect(): void;
@@ -309,7 +338,7 @@ export declare class FrontendWebsocketClient implements WebsocketConnection, Dis
309
338
  * - `request_cancelled` — caller's `AbortSignal` fired
310
339
  * - `queue_overflow` — durable queue full
311
340
  * - `service_unavailable` — socket not connected / closed / torn down
312
- * mid-flight
341
+ * mid-flight, including a `superseded` close
313
342
  * - `internal_error` — `ws.send` threw (serialization, buffer full)
314
343
  * - server's wire code verbatim — JSON-RPC error frame from peer
315
344
  */
@@ -328,6 +357,11 @@ export declare class FrontendWebsocketClient implements WebsocketConnection, Dis
328
357
  * `failure` (UI shows "lost, retrying") and splits `closed` by `revoked` so
329
358
  * a terminal session-revocation read as `failure` while a clean client-
330
359
  * initiated close reads as `initial` (the "not connected, not trying" state).
360
+ * A `superseded` close reads as `initial` too — the client is not trying and
361
+ * an explicit `connect()` reopens it; read `FrontendWebsocketClient.superseded`
362
+ * to tell the user why, and before connecting on an `initial` status without
363
+ * the user asking (connecting a superseded client on its own closes a newer
364
+ * socket in turn, which is the loop the close code exists to stop).
331
365
  *
332
366
  * @param revoked - whether the session has been permanently revoked
333
367
  * (typically `FrontendWebsocketClient.revoked`)
@@ -1 +1 @@
1
- {"version":3,"file":"socket.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/socket.svelte.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"socket.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/socket.svelte.ts"],"names":[],"mappings":"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"}
@@ -5,8 +5,10 @@
5
5
  * Drop into any SvelteKit frontend as the underlying connection for
6
6
  * `FrontendWebsocketTransport`. Handles auto-reconnect with exponential
7
7
  * backoff, respects `WS_CLOSE_SESSION_REVOKED` (no reconnect loop after the
8
- * server revokes auth), exposes reactive status for UI indicators, and ships
9
- * three correctness primitives default-on:
8
+ * server revokes auth) and `WS_CLOSE_CONNECTION_LIMIT` (no reconnect after the
9
+ * server closes this socket to admit a newer one — closed until the user
10
+ * acts), exposes reactive status for UI indicators, and ships three
11
+ * correctness primitives default-on:
10
12
  *
11
13
  * - `FrontendWebsocketClient.request` — promise-based JSON-RPC with
12
14
  * auto-assigned ids and a pending-id map. Intercepts responses on the
@@ -27,7 +29,7 @@
27
29
  import { BROWSER } from 'esm-env';
28
30
  import { JSONRPC_VERSION } from '../http/jsonrpc.js';
29
31
  import { JSONRPC_ERROR_CODES, ThrownJsonrpcError, jsonrpc_errors } from '../http/jsonrpc_errors.js';
30
- import { WS_CLOSE_CLIENT_HEARTBEAT_TIMEOUT, WS_CLOSE_SESSION_REVOKED } from './transports.js';
32
+ import { WS_CLOSE_CLIENT_HEARTBEAT_TIMEOUT, WS_CLOSE_CONNECTION_LIMIT, WS_CLOSE_SESSION_REVOKED } from './transports.js';
31
33
  import { cancel_action_spec } from './cancel.js';
32
34
  import { heartbeat_action_spec } from './heartbeat.js';
33
35
  /** Default WebSocket close code (normal closure). */
@@ -77,6 +79,11 @@ export const DEFAULT_QUEUE_MAX_SIZE = 100;
77
79
  *
78
80
  * Session-revocation close codes (`WS_CLOSE_SESSION_REVOKED`) put the client
79
81
  * in a permanently-closed state; reconnecting would just loop on 401.
82
+ *
83
+ * A connection-limit close (`WS_CLOSE_CONNECTION_LIMIT`) leaves the client
84
+ * closed with `superseded` set and no reconnect scheduled — the server closed
85
+ * this socket to admit a newer one on the same account, and reconnecting
86
+ * would close that one in turn. An explicit `connect()` reopens.
80
87
  */
81
88
  export class FrontendWebsocketClient {
82
89
  #url;
@@ -136,6 +143,7 @@ export class FrontendWebsocketClient {
136
143
  #reconnect_timeout = null;
137
144
  #reconnect_scheduled_at = null;
138
145
  #revoked = $state.raw(false);
146
+ #superseded = $state.raw(false);
139
147
  #message_handlers = new Set();
140
148
  #error_handlers = new Set();
141
149
  connected = $derived(this.status === 'connected');
@@ -277,14 +285,37 @@ export class FrontendWebsocketClient {
277
285
  get revoked() {
278
286
  return this.#revoked;
279
287
  }
288
+ /**
289
+ * Whether the server closed this client's socket to admit a newer
290
+ * connection on the same account (`WS_CLOSE_CONNECTION_LIMIT`) and it
291
+ * hasn't been reopened since. While `true` the client is `closed` and
292
+ * schedules no reconnect — reconnecting on its own would supersede a newer
293
+ * socket in turn, and clients past the cap would close each other in a loop.
294
+ *
295
+ * Unlike `revoked` this is not terminal: the credential is still good, and
296
+ * the next `connect()` clears it and reopens — only `connect()` does;
297
+ * `disconnect()` leaves it set. Requests in flight or queued at the close
298
+ * reject with `service_unavailable`; ones made afterwards buffer in the
299
+ * durable queue like any other call on a closed client. Reactive, so UI can
300
+ * say why the connection is closed and offer the reconnect.
301
+ *
302
+ * `socket_status_to_async_status` reads a superseded client as `initial`,
303
+ * the same as one that was never connected. An app that connects on its
304
+ * own when its status reads `initial` must also check `superseded`, or it
305
+ * rebuilds the loop.
306
+ */
307
+ get superseded() {
308
+ return this.#superseded;
309
+ }
280
310
  /**
281
311
  * Open the WebSocket. No-op on SSR, or if the session has been revoked.
282
312
  * Cancels any pending reconnect and tears down any existing connection first;
283
- * an open prior socket is closed with a normal-closure code.
313
+ * an open prior socket is closed with a normal-closure code. Reopens after
314
+ * a `superseded` close, clearing the flag.
284
315
  *
285
- * @mutates this - replaces `ws`, sets `status` to `connecting` (or
286
- * `closed` on construction failure), and on construction failure may
287
- * schedule a reconnect (mutating `reconnect_count` /
316
+ * @mutates this - replaces `ws`, clears `superseded`, sets `status` to
317
+ * `connecting` (or `closed` on construction failure), and on construction
318
+ * failure may schedule a reconnect (mutating `reconnect_count` /
288
319
  * `current_reconnect_delay`)
289
320
  */
290
321
  connect() {
@@ -292,6 +323,7 @@ export class FrontendWebsocketClient {
292
323
  return;
293
324
  if (this.#revoked)
294
325
  return;
326
+ this.#superseded = false;
295
327
  this.#cancel_reconnect();
296
328
  this.#teardown(DEFAULT_CLOSE_CODE);
297
329
  try {
@@ -390,7 +422,7 @@ export class FrontendWebsocketClient {
390
422
  * - `request_cancelled` — caller's `AbortSignal` fired
391
423
  * - `queue_overflow` — durable queue full
392
424
  * - `service_unavailable` — socket not connected / closed / torn down
393
- * mid-flight
425
+ * mid-flight, including a `superseded` close
394
426
  * - `internal_error` — `ws.send` threw (serialization, buffer full)
395
427
  * - server's wire code verbatim — JSON-RPC error frame from peer
396
428
  */
@@ -697,6 +729,20 @@ export class FrontendWebsocketClient {
697
729
  this.#reject_all('session revoked', jsonrpc_errors.unauthenticated);
698
730
  return;
699
731
  }
732
+ // The server closed this socket to admit a newer one on the same account.
733
+ // Reconnecting would supersede that one in turn — two clients past the cap
734
+ // would close each other in a loop — so stay closed until `connect()` is
735
+ // called. Not a revocation: the credential is still good. Nothing will
736
+ // reopen the socket on its own, so the queue is rejected with the pending
737
+ // requests, as when auto-reconnect is off.
738
+ if (event.code === WS_CLOSE_CONNECTION_LIMIT) {
739
+ this.#superseded = true;
740
+ this.status = 'closed';
741
+ this.#cancel_reconnect();
742
+ this.#reset_reconnect_counters();
743
+ this.#reject_all('connection superseded', jsonrpc_errors.service_unavailable);
744
+ return;
745
+ }
700
746
  // Pending in-flight requests can't be correlated post-reconnect; reject
701
747
  // them. Queue stays so the flush on reopen replays unsent work.
702
748
  this.#reject_pending_only(`connection closed (code ${event.code})`, jsonrpc_errors.service_unavailable);
@@ -785,6 +831,11 @@ export class FrontendWebsocketClient {
785
831
  * `failure` (UI shows "lost, retrying") and splits `closed` by `revoked` so
786
832
  * a terminal session-revocation read as `failure` while a clean client-
787
833
  * initiated close reads as `initial` (the "not connected, not trying" state).
834
+ * A `superseded` close reads as `initial` too — the client is not trying and
835
+ * an explicit `connect()` reopens it; read `FrontendWebsocketClient.superseded`
836
+ * to tell the user why, and before connecting on an `initial` status without
837
+ * the user asking (connecting a superseded client on its own closes a newer
838
+ * socket in turn, which is the loop the close code exists to stop).
788
839
  *
789
840
  * @param revoked - whether the session has been permanently revoked
790
841
  * (typically `FrontendWebsocketClient.revoked`)
@@ -8,17 +8,64 @@
8
8
  */
9
9
  import { z } from 'zod';
10
10
  import type { JsonrpcMessageFromClientToServer, JsonrpcMessageFromServerToClient, JsonrpcNotification, JsonrpcRequest, JsonrpcResponseOrError, JsonrpcErrorResponse } from '../http/jsonrpc.js';
11
- /** WebSocket close code for session revocation. */
11
+ /**
12
+ * WebSocket close code for session revocation.
13
+ *
14
+ * Also the close for an upgrade refused at admission because its credential
15
+ * was revoked while the upgrade was in flight (`register_action_ws`) — the
16
+ * same outcome, reached a moment earlier.
17
+ */
12
18
  export declare const WS_CLOSE_SESSION_REVOKED = 4001;
19
+ /**
20
+ * The close reason sent with `WS_CLOSE_SESSION_REVOKED` — one definition, so a
21
+ * socket revoked mid-life and an upgrade refused at admission are
22
+ * indistinguishable on the wire. The Rust spine sends the same text.
23
+ */
24
+ export declare const WS_CLOSE_SESSION_REVOKED_REASON = "Session revoked";
13
25
  /** WebSocket close code — client timed out waiting for a response. */
14
26
  export declare const WS_CLOSE_CLIENT_HEARTBEAT_TIMEOUT = 4002;
15
27
  /** WebSocket close code — server timed out with no incoming activity. */
16
28
  export declare const WS_CLOSE_SERVER_HEARTBEAT_TIMEOUT = 4003;
29
+ /**
30
+ * WebSocket close code — the server closed this socket to admit a newer
31
+ * connection on the same account, past the per-account connection cap
32
+ * (`BackendWebsocketTransport`'s `max_connections_per_account`, evict-oldest).
33
+ *
34
+ * Distinct from `WS_CLOSE_SESSION_REVOKED`: the credential is still good, so
35
+ * a client must not enter its `revoked` state — but it must not auto-reconnect
36
+ * either, since a reconnect would supersede a newer socket in turn and two
37
+ * tabs past the cap would close each other in a loop. `FrontendWebsocketClient`
38
+ * treats it as closed until the user acts (`superseded`). The twin of the Rust
39
+ * spine's `WS_CLOSE_CONNECTION_LIMIT`.
40
+ */
41
+ export declare const WS_CLOSE_CONNECTION_LIMIT = 4004;
17
42
  /**
18
43
  * WebSocket close code — an inbound message exceeded the receiver's size cap
19
44
  * (RFC 6455 §7.4.1 "Message Too Big").
20
45
  */
21
46
  export declare const WS_CLOSE_MESSAGE_TOO_BIG = 1009;
47
+ /**
48
+ * WebSocket close code — the server closed a socket that sent too much while
49
+ * it was still opening: past `MAX_PRE_ADMISSION_FRAMES` frames or the
50
+ * `PRE_ADMISSION_QUEUE_BYTES_FACTOR` byte budget, before its admission and
51
+ * `on_socket_open` had completed (RFC 6455 §7.4.1 "Policy Violation"). Not a
52
+ * revocation: the client reconnects under its ordinary backoff.
53
+ */
54
+ export declare const WS_CLOSE_POLICY_VIOLATION = 1008;
55
+ /**
56
+ * WebSocket close code — the server hit an unexpected condition (RFC 6455
57
+ * §7.4.1 "Internal Error").
58
+ *
59
+ * Sent when an upgraded socket can't be admitted because its credential
60
+ * re-check itself failed (a database error), when an `on_socket_open` hook
61
+ * throws, and when a frame arrives on a socket whose connection was removed
62
+ * from the transport without a close (a bare `remove_connection`). The
63
+ * handshake has already answered `101`, so an HTTP `500` is no longer
64
+ * available. Not a revocation: the client's credential may be fine, so it
65
+ * reconnects under its ordinary backoff rather than entering its `revoked`
66
+ * state. The twin of the Rust spine's `WS_CLOSE_INTERNAL_ERROR`.
67
+ */
68
+ export declare const WS_CLOSE_INTERNAL_ERROR = 1011;
22
69
  /**
23
70
  * Default cap on one WebSocket message, in UTF-8 bytes — equal to the default
24
71
  * HTTP body cap (`DEFAULT_MAX_BODY_SIZE`), so one JSON-RPC request is bounded
@@ -38,6 +85,13 @@ export declare const DEFAULT_WS_MAX_MESSAGE_BYTES: number;
38
85
  * @returns the UTF-8 byte length when it exceeds `max_bytes`, else `null`
39
86
  */
40
87
  export declare const utf8_length_over: (text: string, max_bytes: number) => number | null;
88
+ /**
89
+ * The UTF-8 byte length of `text`.
90
+ *
91
+ * @param text - the string to measure
92
+ * @returns its length in UTF-8 bytes
93
+ */
94
+ export declare const utf8_length: (text: string) => number;
41
95
  export declare const TransportName: z.ZodString;
42
96
  export type TransportName = z.infer<typeof TransportName>;
43
97
  /**
@@ -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,mDAAmD;AACnD,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAC7C,sEAAsE;AACtE,eAAO,MAAM,iCAAiC,OAAO,CAAC;AACtD,yEAAyE;AACzE,eAAO,MAAM,iCAAiC,OAAO,CAAC;AACtD;;;GAGG;AACH,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAE7C;;;;;;;GAOG;AACH,eAAO,MAAM,4BAA4B,QAAc,CAAC;AAIxD;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,EAAE,WAAW,MAAM,KAAG,MAAM,GAAG,IAI3E,CAAC;AAKF,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"}
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;;;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"}
@@ -7,17 +7,64 @@
7
7
  * @module
8
8
  */
9
9
  import { z } from 'zod';
10
- /** WebSocket close code for session revocation. */
10
+ /**
11
+ * WebSocket close code for session revocation.
12
+ *
13
+ * Also the close for an upgrade refused at admission because its credential
14
+ * was revoked while the upgrade was in flight (`register_action_ws`) — the
15
+ * same outcome, reached a moment earlier.
16
+ */
11
17
  export const WS_CLOSE_SESSION_REVOKED = 4001;
18
+ /**
19
+ * The close reason sent with `WS_CLOSE_SESSION_REVOKED` — one definition, so a
20
+ * socket revoked mid-life and an upgrade refused at admission are
21
+ * indistinguishable on the wire. The Rust spine sends the same text.
22
+ */
23
+ export const WS_CLOSE_SESSION_REVOKED_REASON = 'Session revoked';
12
24
  /** WebSocket close code — client timed out waiting for a response. */
13
25
  export const WS_CLOSE_CLIENT_HEARTBEAT_TIMEOUT = 4002;
14
26
  /** WebSocket close code — server timed out with no incoming activity. */
15
27
  export const WS_CLOSE_SERVER_HEARTBEAT_TIMEOUT = 4003;
28
+ /**
29
+ * WebSocket close code — the server closed this socket to admit a newer
30
+ * connection on the same account, past the per-account connection cap
31
+ * (`BackendWebsocketTransport`'s `max_connections_per_account`, evict-oldest).
32
+ *
33
+ * Distinct from `WS_CLOSE_SESSION_REVOKED`: the credential is still good, so
34
+ * a client must not enter its `revoked` state — but it must not auto-reconnect
35
+ * either, since a reconnect would supersede a newer socket in turn and two
36
+ * tabs past the cap would close each other in a loop. `FrontendWebsocketClient`
37
+ * treats it as closed until the user acts (`superseded`). The twin of the Rust
38
+ * spine's `WS_CLOSE_CONNECTION_LIMIT`.
39
+ */
40
+ export const WS_CLOSE_CONNECTION_LIMIT = 4004;
16
41
  /**
17
42
  * WebSocket close code — an inbound message exceeded the receiver's size cap
18
43
  * (RFC 6455 §7.4.1 "Message Too Big").
19
44
  */
20
45
  export const WS_CLOSE_MESSAGE_TOO_BIG = 1009;
46
+ /**
47
+ * WebSocket close code — the server closed a socket that sent too much while
48
+ * it was still opening: past `MAX_PRE_ADMISSION_FRAMES` frames or the
49
+ * `PRE_ADMISSION_QUEUE_BYTES_FACTOR` byte budget, before its admission and
50
+ * `on_socket_open` had completed (RFC 6455 §7.4.1 "Policy Violation"). Not a
51
+ * revocation: the client reconnects under its ordinary backoff.
52
+ */
53
+ export const WS_CLOSE_POLICY_VIOLATION = 1008;
54
+ /**
55
+ * WebSocket close code — the server hit an unexpected condition (RFC 6455
56
+ * §7.4.1 "Internal Error").
57
+ *
58
+ * Sent when an upgraded socket can't be admitted because its credential
59
+ * re-check itself failed (a database error), when an `on_socket_open` hook
60
+ * throws, and when a frame arrives on a socket whose connection was removed
61
+ * from the transport without a close (a bare `remove_connection`). The
62
+ * handshake has already answered `101`, so an HTTP `500` is no longer
63
+ * available. Not a revocation: the client's credential may be fine, so it
64
+ * reconnects under its ordinary backoff rather than entering its `revoked`
65
+ * state. The twin of the Rust spine's `WS_CLOSE_INTERNAL_ERROR`.
66
+ */
67
+ export const WS_CLOSE_INTERNAL_ERROR = 1011;
21
68
  /**
22
69
  * Default cap on one WebSocket message, in UTF-8 bytes — equal to the default
23
70
  * HTTP body cap (`DEFAULT_MAX_BODY_SIZE`), so one JSON-RPC request is bounded
@@ -43,6 +90,13 @@ export const utf8_length_over = (text, max_bytes) => {
43
90
  const size = text_encoder.encode(text).byteLength;
44
91
  return size > max_bytes ? size : null;
45
92
  };
93
+ /**
94
+ * The UTF-8 byte length of `text`.
95
+ *
96
+ * @param text - the string to measure
97
+ * @returns its length in UTF-8 bytes
98
+ */
99
+ export const utf8_length = (text) => text_encoder.encode(text).byteLength;
46
100
  // TODO figure out the symmetry of frontend and backend transports (none/partial/full?) --
47
101
  // we may also need orthogonal abstractions to clarify the transport role
48
102
  export const TransportName = z.string(); // not branded for convenience, will just error at runtime, the schema is just for docs atm
@@ -1,101 +1,87 @@
1
1
  /**
2
- * WebSocket auth guard — bridges audit events to `BackendWebsocketTransport`.
2
+ * WebSocket auth guard — the audit-event listener that closes live
3
+ * connections when a row reports a revocation.
3
4
  *
4
5
  * **Why this exists.** `register_action_ws` captures `account_id` and
5
6
  * `credential_type` at upgrade time and reuses them for every message.
6
7
  * `perform_action`'s per-message authorization phase reloads role_grants
7
- * from the DB, but session and token VALIDITY are not re-queried — that
8
- * trade-off keeps chatty WS connections fast. The cost: nothing in the
9
- * dispatch path notices when a session is revoked or a token is rotated.
10
- * This guard is the enforcement mechanism — it listens on the audit
11
- * chain and closes affected sockets when revocation events fire, so
12
- * revocation actually takes effect on existing connections. Without it,
13
- * `session_revoke` / `token_revoke` are no-ops for open WS connections.
8
+ * from the DB, but session and token VALIDITY are not re-queried per message
9
+ * — they are re-read once, at admission (`revalidate_resolved_auth`) — a
10
+ * trade-off that keeps chatty WS connections fast. The cost: nothing in the
11
+ * dispatch path notices when a session is revoked or a token is rotated, so a
12
+ * revocation reaches an open connection only when something closes it.
14
13
  *
15
- * Mirror of `realtime/sse_auth_guard.ts` for the backend WebSocket
16
- * transport. Dispatches audit events to the right `close_sockets_for_*`
17
- * method so consumers do not re-implement the switch themselves.
14
+ * Two things do. The revocation handlers close directly, after their
15
+ * transaction commits (`queue_connection_close` in
16
+ * `actions/connection_closer.ts`) — that close does not depend on the audit
17
+ * write. This guard repeats it when the audit row is announced, which covers
18
+ * a revocation emitted by code that holds no closer. Closing is idempotent, so
19
+ * the overlap is a repeat, not a conflict. A success row is announced only
20
+ * after its request's transaction commits (`auth/audit_emitter.ts`), so the
21
+ * guard's close is post-commit too.
22
+ *
23
+ * ## Which rows close
24
+ *
25
+ * The guard dispatches on the `RevocationScope` each audit event declares in
26
+ * `audit_event_revocation_scopes` (`auth/audit_log_schema.ts`), the table the
27
+ * SSE guard (`realtime/sse_auth_guard.ts`) reads too — one declaration, so the
28
+ * two cannot drift into closing a revoked token's socket while leaving its
29
+ * stream open.
30
+ *
31
+ * `role` is the one scope this guard deliberately does not act on:
32
+ * `perform_action` re-authorizes every message, so a socket whose account just
33
+ * lost the gating role is refused at its next message, and closing it would
34
+ * only change when the caller learns. A stream has no later check, so the SSE
35
+ * guard closes there.
18
36
  *
19
37
  * For standard WS endpoints mounted via `AppServerOptions.ws_endpoints`,
20
- * `create_app_server` composes the guard automatically per
38
+ * `create_app_server` registers the guard automatically per
21
39
  * `WsEndpointSpec.auth_guard`. For custom wiring, register the handler
22
40
  * inside the consumer's `audit_factory` body (or via
23
41
  * `audit.add_listener(...)` post-assembly).
24
42
  *
43
+ * Twin of the Rust spine's `register_socket_revocation_listeners`.
44
+ *
25
45
  * @module
26
46
  */
27
47
  import type { Logger } from '@fuzdev/fuz_util/log.ts';
28
- import type { AuditLogEvent } from '../auth/audit_log_schema.js';
29
- import type { BackendWebsocketTransport } from './transports_ws_backend.js';
48
+ import { type AuditLogEvent } from '../auth/audit_log_schema.js';
49
+ import type { ConnectionCloser } from './connection_closer.js';
30
50
  /**
31
51
  * Audit-event callback shape — the function `CreateAuditEmitterOptions.on_audit_event`
32
- * accepts and that the helpers in this module return.
52
+ * accepts and that `create_ws_auth_guard` returns.
33
53
  *
34
54
  * Exported so consumers composing multiple handlers (typically
35
- * `create_ws_auth_guard` + `create_ws_logout_closer` + their own
36
- * pre-existing `on_audit_event`) can annotate their composed callback
37
- * without reaching for `Parameters<typeof create_ws_auth_guard>[0]`.
55
+ * `create_ws_auth_guard` + their own pre-existing `on_audit_event`) can
56
+ * annotate their composed callback without reaching for
57
+ * `Parameters<typeof create_ws_auth_guard>[0]`.
38
58
  */
39
59
  export type AuditEventHandler = (event: AuditLogEvent) => void;
40
60
  /**
41
- * Audit event types that trigger WebSocket socket closure.
61
+ * Create an audit event handler that closes connections on a successful
62
+ * revocation row, dispatching on the event's `RevocationScope`:
42
63
  *
43
- * - `session_revoke` — close only the socket tied to the revoked session hash.
44
- * - `token_revoke` — close only the socket(s) authenticated with the revoked `api_token.id`.
45
- * - `session_revoke_all` / `token_revoke_all` / `password_change` — close every socket
46
- * for the affected account (all credentials invalidated).
47
- *
48
- * `role_grant_revoke` is intentionally omitted: the WS transport does not track
49
- * per-connection role requirements, so role-scoped disconnection would
50
- * require either closing all sockets (too aggressive) or new tracking
51
- * (out of scope). Consumers that need it compose their own callback.
52
- */
53
- export declare const ws_disconnect_event_types: ReadonlySet<string>;
54
- /**
55
- * Create an audit event handler that closes WebSocket connections on auth changes.
64
+ * - `session` → `close_sockets_for_session(metadata.session_id)`
65
+ * - `token` → `close_sockets_for_token(metadata.token_id)`
66
+ * - `account` → `close_sockets_for_account(target_account_id ?? account_id)`
67
+ * — `logout`, `password_change`, `session_revoke_all`, `token_revoke_all`,
68
+ * `account_delete`, `account_purge`
69
+ * - `role` → nothing, deliberately (module doc)
70
+ * - `none` → nothing — every other event, and any event type fuz_app does not
71
+ * define
56
72
  *
57
73
  * Ignores `outcome === 'failure'` events — they carry attacker-controlled
58
74
  * identifiers (e.g. a `session_revoke` that the DB rejected still records
59
75
  * the submitted session_id), so reacting to them would let any authenticated
60
76
  * user close another user's socket by guessing a session hash or token id.
61
77
  *
78
+ * @param closer - what to close on — a `BackendWebsocketTransport`, or a
79
+ * `RealtimeCloser` to reach every transport from one listener
62
80
  * @param log - logger for disconnect events (info level on non-zero closures)
63
81
  * @returns an `on_audit_event` callback suitable for `create_audit_emitter`'s
64
82
  * `on_audit_event` slot, or for registering via
65
- * `audit.add_listener` post-assembly. The returned callback mutates
66
- * `transport` (closing matching sockets via
67
- * `close_sockets_for_session` / `_token` / `_account`) on every relevant event.
68
- */
69
- export declare const create_ws_auth_guard: (transport: BackendWebsocketTransport, log: Logger) => AuditEventHandler;
70
- /**
71
- * Create an audit event handler that closes WebSocket connections on
72
- * user-initiated logout.
73
- *
74
- * Sibling helper to `create_ws_auth_guard` — kept separate because
75
- * `ws_disconnect_event_types` deliberately omits `logout` (admin-initiated
76
- * revocations use `session_revoke`, while `logout` is the user-initiated
77
- * case). Multiple consumers hand-rolled this same branch before extraction.
78
- *
79
- * Compose with `create_ws_auth_guard` to handle both kinds of disconnect:
80
- *
81
- * ```ts
82
- * const ws_guard = create_ws_auth_guard(transport, log);
83
- * const ws_logout_closer = create_ws_logout_closer(transport, log);
84
- * const on_audit_event = (event: AuditLogEvent): void => {
85
- * ws_guard(event);
86
- * ws_logout_closer(event);
87
- * };
88
- * ```
89
- *
90
- * Ignores `outcome === 'failure'` events — failed logouts carry
91
- * unauthenticated identifiers (no session to close anyway), and reacting
92
- * to them would let an unauthenticated probe close the targeted account's
93
- * sockets by submitting a logout for an arbitrary `account_id`.
94
- *
95
- * @param log - logger for disconnect events (info level on non-zero closures)
96
- * @returns an `on_audit_event` callback wireable alongside `create_ws_auth_guard`.
97
- * The returned callback mutates `transport` via `close_sockets_for_account`
98
- * on every successful `logout` event with a non-empty `account_id`.
83
+ * `audit.add_listener` post-assembly. The returned callback closes matching
84
+ * connections on `closer` on every relevant event.
99
85
  */
100
- export declare const create_ws_logout_closer: (transport: BackendWebsocketTransport, log: Logger) => AuditEventHandler;
86
+ export declare const create_ws_auth_guard: (closer: ConnectionCloser, log: Logger) => AuditEventHandler;
101
87
  //# sourceMappingURL=transports_ws_auth_guard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"transports_ws_auth_guard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/transports_ws_auth_guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AAE5E;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;AAE/D;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,yBAAyB,EAAE,WAAW,CAAC,MAAM,CAMxD,CAAC;AAEH;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,oBAAoB,GAChC,WAAW,yBAAyB,EACpC,KAAK,MAAM,KACT,iBA6CF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,uBAAuB,GACnC,WAAW,yBAAyB,EACpC,KAAK,MAAM,KACT,iBAaF,CAAC"}
1
+ {"version":3,"file":"transports_ws_auth_guard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/actions/transports_ws_auth_guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAGtD,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACtF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;AAE/D;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,oBAAoB,GAAI,QAAQ,gBAAgB,EAAE,KAAK,MAAM,KAAG,iBAoD5E,CAAC"}