@rapidmx/meet-plugin 0.14.1 → 0.16.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 (98) hide show
  1. package/README.md +35 -55
  2. package/apps/meet/[token].tsx +341 -341
  3. package/apps/meet/_CallControls.tsx +508 -463
  4. package/apps/meet/_CallView.tsx +1060 -891
  5. package/apps/meet/_ChatPanel.tsx +194 -0
  6. package/apps/meet/_ColorWheel.tsx +136 -0
  7. package/apps/meet/_DiagnosticsPanel.tsx +65 -7
  8. package/apps/meet/_MeetLobby.tsx +224 -224
  9. package/apps/meet/_ParticipantTile.tsx +243 -169
  10. package/apps/meet/_ParticipantsDrawer.tsx +10 -7
  11. package/apps/meet/_Whiteboard.tsx +577 -0
  12. package/apps/meet/_icons.tsx +135 -80
  13. package/apps/meet/_meetApi.ts +213 -212
  14. package/apps/shared/collab/CollabSession.ts +675 -0
  15. package/apps/shared/collab/WhiteboardModel.ts +216 -0
  16. package/apps/shared/collab/chatMarkdown.tsx +375 -0
  17. package/apps/shared/collab/color.ts +67 -0
  18. package/apps/shared/collab/protocol.ts +321 -0
  19. package/apps/shared/collab/whiteboardRender.ts +311 -0
  20. package/apps/shared/media/filters/VideoFilterProcessor.ts +408 -408
  21. package/apps/shared/media/filters/mlModels.ts +174 -174
  22. package/apps/shared/relay/AudioPlayer.ts +29 -0
  23. package/apps/shared/relay/AudioSender.ts +23 -0
  24. package/apps/shared/relay/RelayClient.ts +54 -2
  25. package/apps/shared/relay/RelayReceiver.ts +43 -2
  26. package/apps/shared/relay/RelaySender.ts +23 -1
  27. package/apps/shared/relay/RelayTransport.ts +21 -1
  28. package/apps/shared/relay/SilenceMeter.ts +59 -0
  29. package/apps/shared/relay/relayEnv.ts +1 -1
  30. package/apps/shared/signaling/SignalingClient.ts +335 -0
  31. package/apps/shared/webrtc/MeshConnectionManager.ts +971 -949
  32. package/apps/shared/webrtc/types.ts +335 -247
  33. package/dist/apps/meet/[token].js +1 -1
  34. package/dist/apps/meet/_CallControls.d.ts +21 -6
  35. package/dist/apps/meet/_CallControls.js +14 -8
  36. package/dist/apps/meet/_CallView.d.ts +36 -7
  37. package/dist/apps/meet/_CallView.js +132 -16
  38. package/dist/apps/meet/_ChatPanel.d.ts +24 -0
  39. package/dist/apps/meet/_ChatPanel.js +116 -0
  40. package/dist/apps/meet/_ColorWheel.d.ts +14 -0
  41. package/dist/apps/meet/_ColorWheel.js +82 -0
  42. package/dist/apps/meet/_DiagnosticsPanel.d.ts +5 -3
  43. package/dist/apps/meet/_DiagnosticsPanel.js +17 -1
  44. package/dist/apps/meet/_ParticipantTile.d.ts +11 -3
  45. package/dist/apps/meet/_ParticipantTile.js +36 -5
  46. package/dist/apps/meet/_ParticipantsDrawer.js +3 -3
  47. package/dist/apps/meet/_Whiteboard.d.ts +64 -0
  48. package/dist/apps/meet/_Whiteboard.js +312 -0
  49. package/dist/apps/meet/_icons.d.ts +16 -0
  50. package/dist/apps/meet/_icons.js +16 -0
  51. package/dist/apps/meet/_meetApi.d.ts +8 -7
  52. package/dist/apps/meet/_meetApi.js +2 -2
  53. package/dist/apps/shared/collab/CollabSession.d.ts +208 -0
  54. package/dist/apps/shared/collab/CollabSession.js +559 -0
  55. package/dist/apps/shared/collab/WhiteboardModel.d.ts +54 -0
  56. package/dist/apps/shared/collab/WhiteboardModel.js +197 -0
  57. package/dist/apps/shared/collab/chatMarkdown.d.ts +65 -0
  58. package/dist/apps/shared/collab/chatMarkdown.js +311 -0
  59. package/dist/apps/shared/collab/color.d.ts +18 -0
  60. package/dist/apps/shared/collab/color.js +59 -0
  61. package/dist/apps/shared/collab/protocol.d.ts +157 -0
  62. package/dist/apps/shared/collab/protocol.js +222 -0
  63. package/dist/apps/shared/collab/whiteboardRender.d.ts +31 -0
  64. package/dist/apps/shared/collab/whiteboardRender.js +268 -0
  65. package/dist/apps/shared/relay/AudioPlayer.d.ts +9 -0
  66. package/dist/apps/shared/relay/AudioPlayer.js +24 -4
  67. package/dist/apps/shared/relay/AudioSender.d.ts +8 -0
  68. package/dist/apps/shared/relay/AudioSender.js +20 -4
  69. package/dist/apps/shared/relay/RelayClient.d.ts +16 -0
  70. package/dist/apps/shared/relay/RelayClient.js +46 -2
  71. package/dist/apps/shared/relay/RelayReceiver.d.ts +5 -0
  72. package/dist/apps/shared/relay/RelayReceiver.js +38 -6
  73. package/dist/apps/shared/relay/RelaySender.d.ts +7 -0
  74. package/dist/apps/shared/relay/RelaySender.js +20 -5
  75. package/dist/apps/shared/relay/RelayTransport.d.ts +6 -1
  76. package/dist/apps/shared/relay/RelayTransport.js +16 -1
  77. package/dist/apps/shared/relay/SilenceMeter.d.ts +25 -0
  78. package/dist/apps/shared/relay/SilenceMeter.js +55 -0
  79. package/dist/apps/shared/relay/relayEnv.d.ts +1 -1
  80. package/dist/apps/shared/signaling/SignalingClient.d.ts +95 -0
  81. package/dist/apps/shared/signaling/SignalingClient.js +278 -0
  82. package/dist/apps/shared/webrtc/MeshConnectionManager.d.ts +7 -7
  83. package/dist/apps/shared/webrtc/MeshConnectionManager.js +28 -9
  84. package/dist/apps/shared/webrtc/types.d.ts +104 -26
  85. package/dist/apps/shared/webrtc/types.js +3 -4
  86. package/dist/lib/routes/BaseVideoMeetingRoute.js +171 -69
  87. package/dist/lib/routes/BaseVideoMeetingRoute.js.map +1 -1
  88. package/dist/lib/util/RelayHub.js +15 -2
  89. package/dist/lib/util/RelayHub.js.map +1 -1
  90. package/dist/lib/util/SignalingHub.js +339 -0
  91. package/dist/lib/util/SignalingHub.js.map +1 -0
  92. package/dist/types/routes/BaseVideoMeetingRoute.d.ts +68 -46
  93. package/dist/types/util/RelayHub.d.ts +5 -2
  94. package/dist/types/util/SignalingHub.d.ts +112 -0
  95. package/package.json +208 -208
  96. package/apps/shared/push/GuestSignalingClient.ts +0 -359
  97. package/dist/apps/shared/push/GuestSignalingClient.d.ts +0 -98
  98. package/dist/apps/shared/push/GuestSignalingClient.js +0 -312
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SignalingHub.js","sourceRoot":"","sources":["../../../src/util/SignalingHub.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { type JWTUser } from "@rapidrest/core";
2
- import { HttpRequest } from "@rapidrest/service-core";
3
- import { Mailbox } from "@rapidmx/restapi";
2
+ import { HttpRequest, RepoUtils } from "@rapidrest/service-core";
3
+ import { CalendarEventAttendeeLink, Mailbox } from "@rapidmx/restapi";
4
4
  import { IceServerConfig } from "../util/IceServerUtils.js";
5
5
  import { VideoMeeting, VideoMeetingInvitee, VideoMeetingStatus, VideoMeetingVisibility } from "../models/types.js";
6
6
  /** How long a guest JWT minted by `join()` remains valid for. Four hours comfortably covers a realistic call's
@@ -97,12 +97,12 @@ export interface PublicVideoMeeting {
97
97
  * **`authenticated`/`selfUid`/`token` - reading this shape correctly.** A caller who already presented a valid
98
98
  * session for a real, non-guest RapidMX identity (see `join()`'s doc comment for exactly how that's told apart
99
99
  * from a returning guest) gets `authenticated: true` and no `token`/`expiresAt` at all - there is no guest JWT to
100
- * hand over, because the caller's own already-existing session cookie/header already authenticates `/push` for
101
- * them, with zero new client-side auth handling. The frontend must not write any cookie in that case (doing so
102
- * would be pointless at best, and at worst overwrite the caller's real cookie with... the value it already has).
103
- * The common anonymous case (`authenticated: false`) is unchanged from Phase 1: `token`/`expiresAt` are present
104
- * and the frontend applies `token` as the `jwt` cookie before connecting - see
105
- * `apps/shared/push/GuestSignalingClient.ts`.
100
+ * hand over, because the caller's own already-existing session cookie/header already authenticates the meeting's
101
+ * WebSockets (`signal()`, `relay()`) for them, with zero new client-side auth handling. The frontend must not write
102
+ * any cookie in that case (doing so would be pointless at best, and at worst overwrite the caller's real cookie
103
+ * with... the value it already has). The common anonymous case (`authenticated: false`) is unchanged from Phase 1:
104
+ * `token`/`expiresAt` are present and the frontend applies `token` as the `jwt` cookie before connecting - see
105
+ * `apps/shared/signaling/SignalingClient.ts`.
106
106
  */
107
107
  export interface VideoMeetingJoinResult {
108
108
  meeting: PublicVideoMeeting;
@@ -110,14 +110,14 @@ export interface VideoMeetingJoinResult {
110
110
  /** `true` when the caller already presented a valid session for a real RapidMX identity when calling `join()`;
111
111
  * `false` for the common anonymous case, where a fresh guest identity was just minted instead. */
112
112
  authenticated: boolean;
113
- /** The identity now holding `READ`/`CREATE` on the meeting's own push channel, and the uid the frontend's mesh
113
+ /** The identity now holding `READ`/`CREATE` on the meeting's uid (what `signal()` and `relay()` check), and the uid the frontend's mesh
114
114
  * connection manager (`apps/shared/webrtc/MeshConnectionManager.ts`) must identify itself as: the caller's own
115
115
  * real uid when `authenticated` is `true`, otherwise the freshly minted synthetic `guest:<random>` uid `token`
116
116
  * authenticates as. Always present - this replaces Phase 1's guest-only `guestUid` field now that the same
117
117
  * grant-and-identify step also runs for a real, already-authenticated caller. */
118
118
  selfUid: string;
119
- /** A short-lived guest JWT, immediately usable against `/push` to subscribe to and publish on `meeting.uid` -
120
- * and nothing else. Present only when `authenticated` is `false`; omitted entirely for an already-authenticated
119
+ /** A short-lived guest JWT, immediately usable to open the meeting's signaling (`signal()`) and relay (`relay()`)
120
+ * WebSockets for `meeting.uid` - and nothing else. Present only when `authenticated` is `false`; omitted entirely for an already-authenticated
121
121
  * real caller, who has no guest token minted for them at all (see `join()`). See this class's doc comment. */
122
122
  token?: string;
123
123
  /** ISO 8601 instant `token` expires at. Present only when `authenticated` is `false`, exactly when `token`
@@ -136,7 +136,7 @@ export interface VideoMeetingJoinResult {
136
136
  * (`meeting.hasPassword`) that the caller has not yet submitted - see `join()`'s doc comment for exactly when this
137
137
  * applies (never for the organizer's own slug, which already proves stronger authority). Carries only enough to
138
138
  * show a password prompt (the meeting's title/host, so the prompt isn't a bare form); nothing here grants
139
- * `/push` access, mints a guest identity, or includes ICE servers - that only happens once
139
+ * signaling access, mints a guest identity, or includes ICE servers - that only happens once
140
140
  * `verifyPassword()` succeeds, returning an ordinary `VideoMeetingJoinResult` instead. The literal `true` is a
141
141
  * discriminant: a caller narrows on `"requiresPassword" in result && result.requiresPassword`.
142
142
  */
@@ -195,11 +195,11 @@ export type VideoMeetingJoinResponse = VideoMeetingJoinResult | VideoMeetingPass
195
195
  *
196
196
  * ## Why `VideoMeeting` gets its own per-record `AccessControlList`
197
197
  *
198
- * Unlike `Booking`, `VideoMeeting.uid` doubles as a `/push` signaling channel (see `.claude/NOTES.md`'s "Signaling"
199
- * design note): SDP offers/answers and ICE candidates are ordinary `NotificationUtils.sendMessage()` payloads
200
- * published to it, exactly as `MailPushRoute`'s doc comment describes for a `Mailbox`/`Folder` channel. A `/push`
201
- * channel is authorized purely by `ACLUtils.hasPermission(user, channelUid, action)` against a per-record
202
- * `AccessControlList` document keyed by that exact uid - there being no such document at all (the case for
198
+ * Unlike `Booking`, `VideoMeeting.uid` doubles as the meeting's signaling channel: SDP offers/answers and ICE
199
+ * candidates travel over `signal()`'s WebSocket (see "The signaling WebSocket" below; until it replaced them they
200
+ * were published on the server's shared `/push` route), whose room is the meeting's uid. Like a `/push` channel
201
+ * before it, that socket is authorized purely by `ACLUtils.hasPermission(user, meetingUid, action)` against a
202
+ * per-record `AccessControlList` document keyed by that exact uid - there being no such document at all (the case for
203
203
  * `Booking`, whose `@Protect` sets `recordACL: false`) makes `hasPermission()` always answer `false`. So
204
204
  * `VideoMeetingMongo`/`VideoMeetingSQL` set `recordACL: true`, and `create()` below claims that per-record ACL
205
205
  * with `parentUid` set to the meeting's own `mailboxUid` (mirroring `BaseFolderRoute`'s identical `acl: { uid,
@@ -210,17 +210,17 @@ export type VideoMeetingJoinResponse = VideoMeetingJoinResult | VideoMeetingPass
210
210
  *
211
211
  * ## The channel-ACL-grant mechanism
212
212
  *
213
- * An anonymous guest holds no `AccessControlList` grant of their own and has no `JWTUser` to authenticate `/push`
214
- * with in the first place - `join()` therefore does two things together: it mints a short-lived, scope-limited
213
+ * An anonymous guest holds no `AccessControlList` grant of their own and has no `JWTUser` to authenticate the
214
+ * meeting's WebSockets with in the first place - `join()` therefore does two things together: it mints a short-lived, scope-limited
215
215
  * guest JWT (`mintGuestToken()`: a synthetic `guest:<random>` uid, no roles, `GUEST_JWT_TTL_SECONDS` expiry, real and
216
216
  * verifiable since it's signed with the same `auth` config every other token is), and it adds an explicit
217
217
  * `ACLRecord` for that exact uid onto the meeting's own `AccessControlList` (`ensureChannelGrant()`), granting
218
- * `READ` (so `BasePushRoute`'s SUBSCRIBE succeeds) and `CREATE` (so a publish does - see `MailPushRoute`'s doc
219
- * comment: "publishing to a channel needs CREATE on it as an ordinary user would"). The grant is scoped to exactly
218
+ * `READ` and `CREATE` - the pair `signal()` and `relay()` both require (`authorizeMeetingSocket()`), the same pair
219
+ * `/push` demanded to subscribe to and publish on a channel when signaling still went through it. The grant is scoped to exactly
220
220
  * that one meeting's uid, nothing else - the same "possession of a link is the credential" pattern
221
221
  * `BaseBookingRoute.requireBookingByToken()`/`resolveEffectiveUser()`'s `share:<token>` identity already establish
222
- * elsewhere in this codebase, extended one step further because signaling needs a channel *subscription*, not just
223
- * a stateless REST call.
222
+ * elsewhere in this codebase, extended one step further because signaling needs a long-lived socket, not just a
223
+ * stateless REST call.
224
224
  *
225
225
  * **Known limitation, documented rather than silently assumed away**: because each `join()` call for an anonymous
226
226
  * caller mints a *fresh* random guest uid (so simultaneous participants of one shared link are distinguishable from
@@ -240,16 +240,16 @@ export type VideoMeetingJoinResponse = VideoMeetingJoinResult | VideoMeetingPass
240
240
  * authenticated-optional endpoint in this codebase's family (e.g. `BaseScopedChildRoute.resolveEffectiveUser()`'s
241
241
  * "prefer the real authenticated user, fall back to the anonymous token" precedent). This matters because a
242
242
  * logged-in RapidMX user's browser already carries a real, `HttpOnly` `jwt` session cookie for this origin - a
243
- * cookie `apps/shared/push/GuestSignalingClient.ts` cannot overwrite with a guest token even if `join()` minted one
243
+ * cookie `apps/shared/signaling/SignalingClient.ts` cannot overwrite with a guest token even if `join()` minted one
244
244
  * (browsers refuse to let a script override an `HttpOnly` cookie of the same name), so a guest-only `join()` would
245
- * leave that browser's `/push` WebSocket authenticating as the real session while the meeting's ACL only names a
246
- * synthetic guest uid - the subscribe is simply, safely refused, and the real user could never actually join.
245
+ * leave that browser's signaling WebSocket authenticating as the real session while the meeting's ACL only names a
246
+ * synthetic guest uid - the socket is simply, safely refused, and the real user could never actually join.
247
247
  *
248
248
  * The fix: when `user` is present and is a *real* identity - not a guest uid from a previous `join()` call, told
249
249
  * apart by the `GUEST_UID_PREFIX` a guest uid always starts with and a real, mailbox-owning identity never can -
250
250
  * `join()` grants `user.uid` itself (not a synthetic one) `READ`/`CREATE` on the meeting's channel via
251
251
  * `ensureChannelGrant()`, mints no guest JWT at all, and returns `authenticated: true` with `selfUid: user.uid`. The
252
- * caller's own already-existing session cookie now already authenticates `/push` for them with zero new
252
+ * caller's own already-existing session cookie now already authenticates `signal()` for them with zero new
253
253
  * client-side auth handling - the frontend must not write a cookie of its own in this case (see
254
254
  * `VideoMeetingJoinResult`'s doc comment). When `user` is absent (the common, true-anonymous case - no existing
255
255
  * session at all), behavior is exactly Phase 1's: a fresh guest identity is minted and granted instead.
@@ -310,6 +310,19 @@ export type VideoMeetingJoinResponse = VideoMeetingJoinResult | VideoMeetingPass
310
310
  * it): a disabled relay closes every socket with 1008 "Relay disabled." and `join()` reports `relayEnabled: false`, so
311
311
  * clients don't try it.
312
312
  *
313
+ * ## The signaling WebSocket (`signal()`)
314
+ *
315
+ * Every participant's offers, answers, ICE candidates, presence, moderation signals, chat messages and whiteboard
316
+ * strokes travel over this plugin's own WebSocket, `signal()` at `/signal/:id`
317
+ * (`/api/mail/video-meetings/signal/<meetingUid>`), not the server's shared `/push` route: one socket in both
318
+ * directions, so a message is not a whole HTTP request, and a meeting's traffic never mixes with the webmail's own
319
+ * push notifications. `SignalingHub` carries the messages (see it for the protocol and limits) on the same bus as the
320
+ * relay, so it crosses replicas exactly when the relay does. It is authorized exactly as `relay()` is (the same
321
+ * `READ`/`CREATE` grant from `join()`, the same live-meeting check, the same "Not permitted." for every refusal) but
322
+ * is never switched off - without it there is no call. The hub stamps every message with the socket's own uid and
323
+ * peer id, so nobody can speak as someone else, and a point-to-point message (`to`) reaches only that peer. `kick()`
324
+ * closes the removed participant's sockets at once.
325
+ *
313
326
  * ## Other known limitations
314
327
  *
315
328
  * **`VideoMeetingInvitee.joinToken` never expires** and has no GC job - identical tradeoff to `Booking.manageToken`.
@@ -332,10 +345,10 @@ export declare abstract class BaseVideoMeetingRoute<VM extends VideoMeeting, VMI
332
345
  * `CalendarEventAttendeeLinkSQL`, from `@rapidmx/restapi`) - see `persistMeeting()`. */
333
346
  protected abstract attendeeLinkClass: any;
334
347
  private _objectFactory?;
335
- private meetingRepo?;
336
- private inviteeRepo?;
337
- private mailboxRepo?;
338
- private attendeeLinkRepo?;
348
+ protected meetingRepo?: RepoUtils<VM>;
349
+ protected inviteeRepo?: RepoUtils<VMI>;
350
+ protected mailboxRepo?: RepoUtils<M>;
351
+ protected attendeeLinkRepo?: RepoUtils<CalendarEventAttendeeLink>;
339
352
  private aclUtils?;
340
353
  private trustedRoles;
341
354
  /** The `auth` config every other JWT in this deployment is signed/verified with - used by `mintGuestToken()` to
@@ -359,12 +372,15 @@ export declare abstract class BaseVideoMeetingRoute<VM extends VideoMeeting, VMI
359
372
  * across replicas on; `null` when the deployment has none. Decides how `initRelayBus()` carries relay frames. */
360
373
  private eventsConfig;
361
374
  private logger;
362
- /** The rooms of this process's relay sockets. One per route instance. It starts out on an in-process bus, which
363
- * is what a route that was never initialized (a test's) keeps; `initRelayBus()` swaps in a hub on a Redis bus
364
- * when the deployment has one, before any socket can attach. Its socket caps are per server replica. */
365
- private relayHub;
375
+ /** The rooms of this process's relay sockets. One per route instance, built through the object factory by
376
+ * `initRelayBus()` - on a Redis bus when the deployment has one, else on an in-process bus - before any socket
377
+ * can attach. Its socket caps are per server replica. */
378
+ private relayHub?;
366
379
  /** The Redis bus `relayHub` runs on, when there is one. */
367
380
  private relayBus?;
381
+ /** The rooms of this process's signaling sockets (`signal()`), on the same bus as `relayHub` - built alongside it
382
+ * by `initRelayBus()`. Its socket caps are per server replica. */
383
+ private signalingHub?;
368
384
  /** Every meeting's pending admission requests (`VideoMeeting.waitingRoomEnabled`), keyed by meeting uid then by
369
385
  * the requester's own uid (real or freshly minted guest). In-memory and per server instance only - the same
370
386
  * accepted tradeoff `relayHub` above already has: a restart drops anyone currently waiting, who simply
@@ -381,9 +397,9 @@ export declare abstract class BaseVideoMeetingRoute<VM extends VideoMeeting, VMI
381
397
  */
382
398
  get modelClass(): any;
383
399
  /**
384
- * Picks how relay frames travel between server replicas, once the route is created: over Redis pub/sub
385
- * (`RedisRelayBus`) when `datastores:events` is configured, else in-process only with a single warning that
386
- * participants on different replicas cannot relay to each other. A Redis bus that cannot be created (the `redis`
400
+ * Picks how relay frames and signaling messages travel between server replicas, once the route is created: over
401
+ * Redis pub/sub (`RedisRelayBus`) when `datastores:events` is configured, else in-process only with a single
402
+ * warning that participants on different replicas cannot reach each other. A Redis bus that cannot be created (the `redis`
387
403
  * package is missing, an unusable URL) falls back the same way, with the error logged. Runs before the route
388
404
  * serves anything, so no socket is attached to the hub it replaces.
389
405
  */
@@ -391,7 +407,8 @@ export declare abstract class BaseVideoMeetingRoute<VM extends VideoMeeting, VMI
391
407
  /** Disconnects the Redis relay bus, if there is one, when the object factory destroys the route. (The factory
392
408
  * runs one destroy method per object: a subclass declaring its own `@Destroy` must call this one.) */
393
409
  destroyRelayBus(): void;
394
- private init;
410
+ /** Builds the model repositories once, before the route serves anything. */
411
+ protected initialize(): Promise<void>;
395
412
  /** Rejects a `403` unless `user` (with its trusted roles stripped - see `util/RouteAccessUtils.ts`) holds
396
413
  * `action` on `mailboxUid`, by ownership or an explicit ACL grant. */
397
414
  private requireMailboxAccess;
@@ -509,9 +526,9 @@ export declare abstract class BaseVideoMeetingRoute<VM extends VideoMeeting, VMI
509
526
  private ensureChannelGrant;
510
527
  /**
511
528
  * Removes `uid`'s `ACLRecord` from `meetingUid`'s own `AccessControlList`, if it has one - the mirror image of
512
- * `ensureChannelGrant()`, used to enforce a kick (see `kick()`): once removed, `uid` can no longer subscribe to
513
- * or publish on the meeting's `/push` channel, so even a client that ignores the cooperative `"kicked"` signal
514
- * (`SignalMessage`'s doc comment) cannot keep participating. A no-op when `uid` holds no grant (already kicked,
529
+ * `ensureChannelGrant()`, used to enforce a kick (see `kick()`, which also closes `uid`'s open signaling sockets):
530
+ * once removed, `uid` can no longer open the meeting's signaling (`signal()`) or relay (`relay()`) socket, so even
531
+ * a client that ignores the cooperative `"kicked"` signal (`SignalMessage`'s doc comment) cannot keep participating. A no-op when `uid` holds no grant (already kicked,
515
532
  * or never joined) or the meeting has no ACL, rather than an error - the caller wants "not able to rejoin",
516
533
  * which is already true either way. Same optimistic-lock retry as `ensureChannelGrant()`, for the same reason:
517
534
  * a concurrent joiner or the owner editing the ACL directly may save first.
@@ -585,7 +602,7 @@ export declare abstract class BaseVideoMeetingRoute<VM extends VideoMeeting, VMI
585
602
  /**
586
603
  * Decides whether `user` may open a relay socket to meeting `id`: the relay must be enabled, `user` must hold BOTH
587
604
  * `READ` and `CREATE` on the meeting's uid - exactly what `ensureChannelGrant()` gives a joined participant, and
588
- * what `/push` demands to subscribe and publish - and the meeting must exist and be neither cancelled nor ended.
605
+ * what `signal()` demands too (`authorizeMeetingSocket()`) - and the meeting must exist and be neither cancelled nor ended.
589
606
  * Returns the reason to close the socket with, or `undefined` when permitted.
590
607
  *
591
608
  * `hasPermission()` gets `user` with its trusted roles stripped (`stripTrustedRoles()`), exactly as
@@ -595,14 +612,19 @@ export declare abstract class BaseVideoMeetingRoute<VM extends VideoMeeting, VMI
595
612
  * failure gives the same `RELAY_NOT_PERMITTED`, whether the grant, the meeting or its state is what's missing.
596
613
  */
597
614
  private authorizeRelay;
615
+ /** Why a meeting WebSocket (`relay()`, `signal()`) of `user` for meeting `id` must be refused, or `undefined` to
616
+ * accept it - see `authorizeRelay()`'s doc comment for the rules, which are the same for both apart from the
617
+ * relay's own on/off setting. */
618
+ private authorizeMeetingSocket;
598
619
  /**
599
- * The meeting uid a relay socket's URL names. `@Param("id")` is filled in by the Bun router, but NOT by the uWS
620
+ * The meeting uid a relay or signaling socket's URL names. `@Param("id")` is filled in by the Bun router, but NOT by the uWS
600
621
  * router this framework actually serves with: it never populates `req.params` for a WebSocket upgrade (a
601
622
  * `@WebSocket()` route has, so far, never had a path parameter - `BasePushRoute.connect()` has none). So when the
602
623
  * decorated argument is empty this reads the last segment of the request path instead, which the
603
- * `/relay/:id` pattern guarantees is the id (the framework also registers the trailing-slash form; the path
624
+ * `/relay/:id` and `/signal/:id` patterns guarantee is the id (the framework also registers the trailing-slash form; the path
604
625
  * never carries the query string). Returns `undefined` when there is none or it isn't valid percent-encoding.
605
626
  */
606
- private relayMeetingId;
627
+ private socketMeetingId;
607
628
  relay(paramId: string | undefined, req: HttpRequest, sock: any, user?: JWTUser): Promise<void>;
629
+ signal(paramId: string | undefined, req: HttpRequest, sock: any, user?: JWTUser): Promise<void>;
608
630
  }
@@ -133,11 +133,14 @@ export declare function parseRelayEnabled(value: unknown): boolean;
133
133
  * same room replaces the older one, which is closed with 1008 "Replaced.".
134
134
  *
135
135
  * After that, `{"op":"want","peers":["<peerId>", ...]}` REPLACES the socket's interest set with the valid entries, at
136
- * most `RELAY_MAX_WANT_PEERS` of them; every other or malformed text message is ignored. Binary messages of 1 to
136
+ * most `RELAY_MAX_WANT_PEERS` of them, and `{"op":"ping","t":<number>}` is answered at once with
137
+ * `{"op":"pong","t":<the same number>}` (the client's own clock, never interpreted here - it lets the client time its
138
+ * round trip to the server, on the same socket and so behind the same queues as its media); every other or malformed
139
+ * text message, including a ping without a finite numeric `t`, is ignored. Binary messages of 1 to
137
140
  * `RelayHubOptions.maxPayloadBytes` bytes (`RELAY_MAX_PAYLOAD_BYTES` unless the route asked for more; opaque to the hub) are forwarded to every other socket of the room whose interest
138
141
  * set contains the sender's peer id; binary before `hello`, empty and oversized messages are dropped.
139
142
  *
140
- * Server to client: `{"op":"ready","v":1,"maxMessageBytes":N}` once, then binary messages `[1 byte: UTF-8 length N of the sender's peer
143
+ * Server to client: `{"op":"ready","v":1,"maxMessageBytes":N}` once, a `pong` for each ping, then binary messages `[1 byte: UTF-8 length N of the sender's peer
141
144
  * id][N bytes: the sender's peer id][the original payload]`. The sender is stamped by the hub from the `hello`,
142
145
  * never trusted from the payload.
143
146
  *
@@ -0,0 +1,112 @@
1
+ import { type RelayBus, type RelayConnection, type RelaySocket } from "./RelayHub.js";
2
+ /** The version of the signaling wire protocol this module implements - the `v` of `hello`/`ready`. */
3
+ export declare const SIGNALING_PROTOCOL_VERSION = 1;
4
+ /** The largest text message a socket may send when the route could not raise the framework's own limit. */
5
+ export declare const SIGNALING_MAX_PAYLOAD_BYTES: number;
6
+ /** The larger message limit the signaling route asks the framework for when it can (see the relay's identical
7
+ * `RELAY_LARGE_PAYLOAD_BYTES`): a whiteboard's catch-up for a late joiner then travels in a few messages, not dozens. */
8
+ export declare const SIGNALING_LARGE_PAYLOAD_BYTES: number;
9
+ /** The most sockets (registered or still awaiting their `hello`) one room holds at once. */
10
+ export declare const SIGNALING_MAX_SOCKETS_PER_ROOM = 16;
11
+ /** The most sockets one authenticated uid may hold in one room at once (several tabs or devices of one account). */
12
+ export declare const SIGNALING_MAX_SOCKETS_PER_UID = 4;
13
+ /** Sustained inbound messages per second per socket. Drawing on the whiteboard sends about 15 a second, and a
14
+ * connection being set up sends a burst of ICE candidates; this leaves room for both at once. */
15
+ export declare const SIGNALING_RATE_MESSAGES_PER_SECOND = 50;
16
+ /** Burst allowance, in messages, of a socket's message bucket - also its capacity. */
17
+ export declare const SIGNALING_BURST_MESSAGES = 200;
18
+ /** Sustained inbound bytes per second per socket. */
19
+ export declare const SIGNALING_RATE_BYTES_PER_SECOND: number;
20
+ /** Burst allowance, in bytes, of a socket's byte bucket - also its capacity. Enough for a late joiner's catch-up. */
21
+ export declare const SIGNALING_BURST_BYTES: number;
22
+ /** How long a socket may stay over budget continuously before it is closed with 1008 "Too much data.". */
23
+ export declare const SIGNALING_OVER_BUDGET_CLOSE_MS = 10000;
24
+ /** Two over-budget drops further apart than this are separate episodes - see `RELAY_OVER_BUDGET_GRACE_MS`. */
25
+ export declare const SIGNALING_OVER_BUDGET_GRACE_MS = 1000;
26
+ /** How long a new socket has to send its `hello` before it is closed with 1008 "Hello timeout.". */
27
+ export declare const SIGNALING_HELLO_TIMEOUT_MS = 10000;
28
+ /** The reason a socket is closed with (1008) when its uid is removed from the meeting - see `SignalingHub.evict()`. */
29
+ export declare const SIGNALING_EVICTED_REASON = "Removed from the meeting.";
30
+ /** The bus room a meeting's signaling travels in, kept apart from the same meeting's relay room on a shared bus. */
31
+ export declare function signalingBusRoom(roomId: string): string;
32
+ /** Counters of what the hub did with messages - for tests and diagnostics. */
33
+ export interface SignalingStats {
34
+ /** Messages handed to a receiving socket. */
35
+ delivered: number;
36
+ /** Messages dropped because their sender was over budget. */
37
+ droppedBudget: number;
38
+ /** Messages dropped for being malformed, oversized, or sent before `hello`. */
39
+ droppedInvalid: number;
40
+ }
41
+ export interface SignalingHubOptions {
42
+ /** Where messages travel between sockets. Defaults to a new `InProcessRelayBus`. A bus shared with a `RelayHub` is
43
+ * fine - the hub keeps to its own rooms (`signalingBusRoom()`). */
44
+ bus?: RelayBus;
45
+ logger?: {
46
+ debug(message: string): void;
47
+ };
48
+ /** The largest text message a socket may send, and what `ready` tells the client. Defaults to
49
+ * `SIGNALING_MAX_PAYLOAD_BYTES`; the route sets `SIGNALING_LARGE_PAYLOAD_BYTES` when the framework was asked for that. */
50
+ maxPayloadBytes?: number;
51
+ }
52
+ /**
53
+ * The meeting's own signaling channel: a WebSocket per participant tab, carrying the JSON `SignalMessage`s of the
54
+ * full-mesh call (`apps/shared/webrtc/types.ts`) - offers, answers, ICE candidates, presence, moderation, chat and
55
+ * whiteboard - between the participants of one room (one meeting). It replaces publishing on the server's shared `/push`
56
+ * route, so the call's traffic is neither an HTTP request per message nor shared with the webmail's own push
57
+ * notifications. Like `RelayHub` it knows nothing about meetings, users, HTTP or any framework: a caller authorizes a
58
+ * socket however it likes, hands it to `attach()` and feeds the returned connection the socket's events.
59
+ *
60
+ * ## Wire protocol (version 1, text messages only)
61
+ *
62
+ * Client to server, the first message must be `{"op":"hello","v":1,"peer":"<peerId>"}` within
63
+ * `SIGNALING_HELLO_TIMEOUT_MS`, `peer` being the socket's uid or starting with `<uid>~` (the same rule, and the same
64
+ * closing reasons, as the relay's `hello`). The server answers `{"op":"ready","v":1,"maxMessageBytes":N}`. A second
65
+ * socket registering an already registered peer in the same room replaces the older one ("Replaced.").
66
+ *
67
+ * After that, `{"op":"signal","data":{...}}` sends `data` to the room, and `{"op":"ping","t":<number>}` is answered
68
+ * with `{"op":"pong","t":<the same number>}` (a keepalive). The server overwrites `data.from` with the socket's uid
69
+ * and `data.peer` with its peer id, so nobody can speak as someone else. When `data.to` names a peer id, only that peer's
70
+ * socket receives it - an offer, an answer and ICE candidates never reach anyone they are not for; otherwise every
71
+ * other registered socket of the room does. Each arrives as `{"op":"signal","data":{...}}`. Anything malformed,
72
+ * binary, oversized, or sent before `hello`, is dropped.
73
+ *
74
+ * ## Limits and removal
75
+ *
76
+ * `SIGNALING_MAX_SOCKETS_PER_ROOM` sockets per room and `SIGNALING_MAX_SOCKETS_PER_UID` per uid per room ("Room full."
77
+ * / "Too many connections."). Each socket's messages are metered by two token buckets (messages and bytes); an
78
+ * over-budget message is dropped, and a socket kept over budget for `SIGNALING_OVER_BUDGET_CLOSE_MS` is closed with
79
+ * "Too much data.". `evict()` closes every socket of one uid in a room, on every replica the bus reaches - the host
80
+ * removing someone takes them off the signaling channel at once, not at their next reconnect.
81
+ *
82
+ * ## Replicas
83
+ *
84
+ * Exactly as `RelayHub`'s: messages cross replicas when the bus does (`RedisRelayBus`), and every limit is per replica.
85
+ */
86
+ export declare class SignalingHub {
87
+ readonly stats: SignalingStats;
88
+ private readonly bus;
89
+ private readonly rooms;
90
+ private readonly env;
91
+ private readonly logger?;
92
+ constructor(options?: SignalingHubOptions);
93
+ /** The number of rooms that currently hold at least one socket. */
94
+ get roomCount(): number;
95
+ /** The number of sockets in `roomId` (0 for an unknown room). */
96
+ socketCount(roomId: string): number;
97
+ /**
98
+ * Accepts an already-authorized `socket` of `uid` into room `roomId`, or refuses it (closing it with 1008) when
99
+ * the room or the uid is at its limit. Returns the connection to feed the socket's events to, or `undefined`
100
+ * when refused.
101
+ */
102
+ attach(roomId: string, uid: string, socket: RelaySocket): RelayConnection | undefined;
103
+ /** Closes every socket `uid` holds in room `roomId` (`SIGNALING_EVICTED_REASON`), on this replica and - through
104
+ * the bus - every other. */
105
+ evict(roomId: string, uid: string): void;
106
+ private evictLocal;
107
+ /** Forgets `conn`, and its room once that is empty. */
108
+ private remove;
109
+ /** Hands one bus frame to the sockets of `busRoom` it is for. A frame for a room that has just emptied, or one
110
+ * that is not a frame this hub publishes, is ignored. */
111
+ private deliver;
112
+ }