@rapidmx/meet-plugin 0.15.0 → 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.
- package/README.md +35 -55
- package/apps/meet/[token].tsx +341 -341
- package/apps/meet/_CallControls.tsx +508 -483
- package/apps/meet/_CallView.tsx +1060 -924
- package/apps/meet/_ChatPanel.tsx +194 -0
- package/apps/meet/_ColorWheel.tsx +136 -0
- package/apps/meet/_MeetLobby.tsx +224 -224
- package/apps/meet/_ParticipantTile.tsx +243 -242
- package/apps/meet/_Whiteboard.tsx +577 -0
- package/apps/meet/_icons.tsx +135 -90
- package/apps/meet/_meetApi.ts +213 -212
- package/apps/shared/collab/CollabSession.ts +675 -0
- package/apps/shared/collab/WhiteboardModel.ts +216 -0
- package/apps/shared/collab/chatMarkdown.tsx +375 -0
- package/apps/shared/collab/color.ts +67 -0
- package/apps/shared/collab/protocol.ts +321 -0
- package/apps/shared/collab/whiteboardRender.ts +311 -0
- package/apps/shared/media/filters/VideoFilterProcessor.ts +408 -408
- package/apps/shared/media/filters/mlModels.ts +174 -174
- package/apps/shared/relay/RelayClient.ts +2 -1
- package/apps/shared/relay/RelayTransport.ts +2 -1
- package/apps/shared/relay/relayEnv.ts +1 -1
- package/apps/shared/signaling/SignalingClient.ts +335 -0
- package/apps/shared/webrtc/MeshConnectionManager.ts +971 -971
- package/apps/shared/webrtc/types.ts +335 -316
- package/dist/apps/meet/[token].js +1 -1
- package/dist/apps/meet/_CallControls.d.ts +12 -4
- package/dist/apps/meet/_CallControls.js +5 -5
- package/dist/apps/meet/_CallView.d.ts +19 -4
- package/dist/apps/meet/_CallView.js +100 -11
- package/dist/apps/meet/_ChatPanel.d.ts +24 -0
- package/dist/apps/meet/_ChatPanel.js +116 -0
- package/dist/apps/meet/_ColorWheel.d.ts +14 -0
- package/dist/apps/meet/_ColorWheel.js +82 -0
- package/dist/apps/meet/_ParticipantTile.d.ts +2 -1
- package/dist/apps/meet/_ParticipantTile.js +1 -1
- package/dist/apps/meet/_Whiteboard.d.ts +64 -0
- package/dist/apps/meet/_Whiteboard.js +312 -0
- package/dist/apps/meet/_icons.d.ts +12 -0
- package/dist/apps/meet/_icons.js +12 -0
- package/dist/apps/meet/_meetApi.d.ts +8 -7
- package/dist/apps/meet/_meetApi.js +2 -2
- package/dist/apps/shared/collab/CollabSession.d.ts +208 -0
- package/dist/apps/shared/collab/CollabSession.js +559 -0
- package/dist/apps/shared/collab/WhiteboardModel.d.ts +54 -0
- package/dist/apps/shared/collab/WhiteboardModel.js +197 -0
- package/dist/apps/shared/collab/chatMarkdown.d.ts +65 -0
- package/dist/apps/shared/collab/chatMarkdown.js +311 -0
- package/dist/apps/shared/collab/color.d.ts +18 -0
- package/dist/apps/shared/collab/color.js +59 -0
- package/dist/apps/shared/collab/protocol.d.ts +157 -0
- package/dist/apps/shared/collab/protocol.js +222 -0
- package/dist/apps/shared/collab/whiteboardRender.d.ts +31 -0
- package/dist/apps/shared/collab/whiteboardRender.js +268 -0
- package/dist/apps/shared/relay/RelayClient.js +2 -1
- package/dist/apps/shared/relay/RelayTransport.d.ts +2 -1
- package/dist/apps/shared/relay/RelayTransport.js +2 -1
- package/dist/apps/shared/relay/relayEnv.d.ts +1 -1
- package/dist/apps/shared/signaling/SignalingClient.d.ts +95 -0
- package/dist/apps/shared/signaling/SignalingClient.js +278 -0
- package/dist/apps/shared/webrtc/MeshConnectionManager.d.ts +7 -7
- package/dist/apps/shared/webrtc/MeshConnectionManager.js +8 -8
- package/dist/apps/shared/webrtc/types.d.ts +38 -24
- package/dist/apps/shared/webrtc/types.js +3 -4
- package/dist/lib/routes/BaseVideoMeetingRoute.js +171 -69
- package/dist/lib/routes/BaseVideoMeetingRoute.js.map +1 -1
- package/dist/lib/util/SignalingHub.js +339 -0
- package/dist/lib/util/SignalingHub.js.map +1 -0
- package/dist/types/routes/BaseVideoMeetingRoute.d.ts +68 -46
- package/dist/types/util/SignalingHub.d.ts +112 -0
- package/package.json +208 -208
- package/apps/shared/push/GuestSignalingClient.ts +0 -359
- package/dist/apps/shared/push/GuestSignalingClient.d.ts +0 -98
- package/dist/apps/shared/push/GuestSignalingClient.js +0 -312
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
2
|
+
// Copyright (C) 2026 Jean-Philippe Steinmetz. All rights reserved.
|
|
3
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5
|
+
/**
|
|
6
|
+
* Draws the whiteboard's elements on a 2D canvas, in board units (`protocol.ts`'s `BOARD_WIDTH` x `BOARD_HEIGHT`)
|
|
7
|
+
* scaled to the canvas. Each pen has its own look:
|
|
8
|
+
*
|
|
9
|
+
* - **ballpoint** - a thin, even, opaque line.
|
|
10
|
+
* - **sharpie** - a bolder opaque line with round ends.
|
|
11
|
+
* - **marker** - a broad, square-tipped, translucent stroke laid down in `multiply`, so it highlights what is under
|
|
12
|
+
* it instead of covering it, and doesn't darken where it overlaps itself (the stroke is one path).
|
|
13
|
+
* - **crayon** - a waxy translucent base speckled with grain, like wax catching on paper.
|
|
14
|
+
* - **pencil** - a coloring pencil: several fine, slightly offset, faint passes that build up a grainy line.
|
|
15
|
+
*
|
|
16
|
+
* The grain of a crayon or pencil comes from a random generator seeded by the element's id, so every participant's
|
|
17
|
+
* board draws the very same texture for the same stroke - it is not re-randomized per screen or per redraw.
|
|
18
|
+
*/
|
|
19
|
+
import { BOARD_HEIGHT, BOARD_WIDTH } from "./protocol.js";
|
|
20
|
+
/** The board's paper color. */
|
|
21
|
+
export const BOARD_BACKGROUND = "#ffffff";
|
|
22
|
+
/** The font stack stamps are drawn in - each platform's color emoji font first. */
|
|
23
|
+
export const STAMP_FONT_FAMILY = '"Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif';
|
|
24
|
+
/** A small, fast, seedable PRNG (mulberry32) - returns numbers in [0, 1). */
|
|
25
|
+
export function seededRandom(seed) {
|
|
26
|
+
let state = 2166136261;
|
|
27
|
+
for (let i = 0; i < seed.length; i++) {
|
|
28
|
+
state = Math.imul(state ^ seed.charCodeAt(i), 16777619);
|
|
29
|
+
}
|
|
30
|
+
return () => {
|
|
31
|
+
state = (state + 0x6d2b79f5) | 0;
|
|
32
|
+
let t = Math.imul(state ^ (state >>> 15), 1 | state);
|
|
33
|
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
34
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** Clears the canvas to paper and draws `elements` in order, `scale` canvas pixels per board unit. */
|
|
38
|
+
export function renderBoard(ctx, elements, scale) {
|
|
39
|
+
ctx.save();
|
|
40
|
+
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
41
|
+
ctx.globalAlpha = 1;
|
|
42
|
+
ctx.globalCompositeOperation = "source-over";
|
|
43
|
+
ctx.fillStyle = BOARD_BACKGROUND;
|
|
44
|
+
ctx.fillRect(0, 0, BOARD_WIDTH * scale, BOARD_HEIGHT * scale);
|
|
45
|
+
ctx.setTransform(scale, 0, 0, scale, 0, 0);
|
|
46
|
+
for (const element of elements) {
|
|
47
|
+
drawElement(ctx, element);
|
|
48
|
+
}
|
|
49
|
+
ctx.restore();
|
|
50
|
+
}
|
|
51
|
+
export function drawElement(ctx, element) {
|
|
52
|
+
ctx.save();
|
|
53
|
+
switch (element.kind) {
|
|
54
|
+
case "stroke":
|
|
55
|
+
drawStroke(ctx, element);
|
|
56
|
+
break;
|
|
57
|
+
case "shape":
|
|
58
|
+
drawShape(ctx, element);
|
|
59
|
+
break;
|
|
60
|
+
case "stamp":
|
|
61
|
+
drawStamp(ctx, element);
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
ctx.restore();
|
|
65
|
+
}
|
|
66
|
+
/** Traces a smooth path through `points` (curves through the midpoints between them), offset by (`dx`, `dy`). */
|
|
67
|
+
function tracePath(ctx, points, dx = 0, dy = 0) {
|
|
68
|
+
ctx.beginPath();
|
|
69
|
+
ctx.moveTo(points[0] + dx, points[1] + dy);
|
|
70
|
+
if (points.length === 2) {
|
|
71
|
+
// A single tap still leaves a dot.
|
|
72
|
+
ctx.lineTo(points[0] + dx + 0.01, points[1] + dy);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
for (let i = 2; i < points.length - 2; i += 2) {
|
|
76
|
+
const midX = (points[i] + points[i + 2]) / 2;
|
|
77
|
+
const midY = (points[i + 1] + points[i + 3]) / 2;
|
|
78
|
+
ctx.quadraticCurveTo(points[i] + dx, points[i + 1] + dy, midX + dx, midY + dy);
|
|
79
|
+
}
|
|
80
|
+
ctx.lineTo(points[points.length - 2] + dx, points[points.length - 1] + dy);
|
|
81
|
+
}
|
|
82
|
+
function drawStroke(ctx, stroke) {
|
|
83
|
+
const { points, size, color } = stroke;
|
|
84
|
+
ctx.strokeStyle = color;
|
|
85
|
+
ctx.fillStyle = color;
|
|
86
|
+
ctx.lineJoin = "round";
|
|
87
|
+
ctx.lineCap = "round";
|
|
88
|
+
switch (stroke.pen) {
|
|
89
|
+
case "ballpoint":
|
|
90
|
+
ctx.lineWidth = Math.max(1.2, size * 0.6);
|
|
91
|
+
tracePath(ctx, points);
|
|
92
|
+
ctx.stroke();
|
|
93
|
+
return;
|
|
94
|
+
case "sharpie":
|
|
95
|
+
ctx.lineWidth = size * 1.4;
|
|
96
|
+
tracePath(ctx, points);
|
|
97
|
+
ctx.stroke();
|
|
98
|
+
return;
|
|
99
|
+
case "marker":
|
|
100
|
+
ctx.globalCompositeOperation = "multiply";
|
|
101
|
+
ctx.globalAlpha = 0.4;
|
|
102
|
+
ctx.lineCap = "square";
|
|
103
|
+
ctx.lineWidth = size * 3;
|
|
104
|
+
tracePath(ctx, points);
|
|
105
|
+
ctx.stroke();
|
|
106
|
+
return;
|
|
107
|
+
case "crayon":
|
|
108
|
+
drawCrayon(ctx, stroke);
|
|
109
|
+
return;
|
|
110
|
+
case "pencil":
|
|
111
|
+
drawPencil(ctx, stroke);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function drawCrayon(ctx, stroke) {
|
|
116
|
+
const random = seededRandom(stroke.id);
|
|
117
|
+
const width = stroke.size * 1.8;
|
|
118
|
+
ctx.globalAlpha = 0.55;
|
|
119
|
+
ctx.lineWidth = width;
|
|
120
|
+
tracePath(ctx, stroke.points);
|
|
121
|
+
ctx.stroke();
|
|
122
|
+
// Grain: specks scattered across the stroke's width along every segment, more for a longer segment.
|
|
123
|
+
ctx.globalAlpha = 0.7;
|
|
124
|
+
const speck = Math.max(1, width / 6);
|
|
125
|
+
eachSegment(stroke.points, (x0, y0, x1, y1, length) => {
|
|
126
|
+
const count = Math.ceil((length * width) / 18);
|
|
127
|
+
for (let i = 0; i < count; i++) {
|
|
128
|
+
const t = random();
|
|
129
|
+
const spread = (random() - 0.5) * width;
|
|
130
|
+
const along = (random() - 0.5) * speck;
|
|
131
|
+
ctx.fillRect(x0 + (x1 - x0) * t + spread * 0.7 + along, y0 + (y1 - y0) * t + spread * 0.7 - along, speck, speck);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
function drawPencil(ctx, stroke) {
|
|
136
|
+
const random = seededRandom(stroke.id);
|
|
137
|
+
ctx.lineWidth = Math.max(0.8, stroke.size * 0.45);
|
|
138
|
+
ctx.globalAlpha = 0.35;
|
|
139
|
+
const passes = 3;
|
|
140
|
+
const jitter = Math.max(0.6, stroke.size * 0.35);
|
|
141
|
+
for (let pass = 0; pass < passes; pass++) {
|
|
142
|
+
tracePath(ctx, stroke.points, (random() - 0.5) * jitter, (random() - 0.5) * jitter);
|
|
143
|
+
ctx.stroke();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/** Calls `visit` with each segment of `points` and its length; a single point is a zero-length segment. */
|
|
147
|
+
function eachSegment(points, visit) {
|
|
148
|
+
if (points.length === 2) {
|
|
149
|
+
visit(points[0], points[1], points[0], points[1], 1);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
for (let i = 0; i < points.length - 2; i += 2) {
|
|
153
|
+
const [x0, y0, x1, y1] = [points[i], points[i + 1], points[i + 2], points[i + 3]];
|
|
154
|
+
visit(x0, y0, x1, y1, Math.hypot(x1 - x0, y1 - y0));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function drawShape(ctx, shape) {
|
|
158
|
+
const { x1, y1, x2, y2 } = shape;
|
|
159
|
+
ctx.strokeStyle = shape.color;
|
|
160
|
+
ctx.lineWidth = shape.size;
|
|
161
|
+
ctx.lineJoin = "round";
|
|
162
|
+
ctx.lineCap = "round";
|
|
163
|
+
ctx.beginPath();
|
|
164
|
+
const left = Math.min(x1, x2);
|
|
165
|
+
const top = Math.min(y1, y2);
|
|
166
|
+
const width = Math.abs(x2 - x1);
|
|
167
|
+
const height = Math.abs(y2 - y1);
|
|
168
|
+
switch (shape.shape) {
|
|
169
|
+
case "line":
|
|
170
|
+
ctx.moveTo(x1, y1);
|
|
171
|
+
ctx.lineTo(x2, y2);
|
|
172
|
+
break;
|
|
173
|
+
case "arrow": {
|
|
174
|
+
ctx.moveTo(x1, y1);
|
|
175
|
+
ctx.lineTo(x2, y2);
|
|
176
|
+
const angle = Math.atan2(y2 - y1, x2 - x1);
|
|
177
|
+
const head = Math.max(12, shape.size * 4);
|
|
178
|
+
for (const side of [-1, 1]) {
|
|
179
|
+
ctx.moveTo(x2, y2);
|
|
180
|
+
ctx.lineTo(x2 - head * Math.cos(angle + side * (Math.PI / 7)), y2 - head * Math.sin(angle + side * (Math.PI / 7)));
|
|
181
|
+
}
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
case "rect":
|
|
185
|
+
ctx.moveTo(left, top);
|
|
186
|
+
ctx.lineTo(left + width, top);
|
|
187
|
+
ctx.lineTo(left + width, top + height);
|
|
188
|
+
ctx.lineTo(left, top + height);
|
|
189
|
+
ctx.closePath();
|
|
190
|
+
break;
|
|
191
|
+
case "ellipse":
|
|
192
|
+
ctx.ellipse(left + width / 2, top + height / 2, width / 2, height / 2, 0, 0, Math.PI * 2);
|
|
193
|
+
break;
|
|
194
|
+
case "triangle":
|
|
195
|
+
ctx.moveTo(left + width / 2, top);
|
|
196
|
+
ctx.lineTo(left + width, top + height);
|
|
197
|
+
ctx.lineTo(left, top + height);
|
|
198
|
+
ctx.closePath();
|
|
199
|
+
break;
|
|
200
|
+
case "star":
|
|
201
|
+
for (const [i, [px, py]] of starPoints(left, top, width, height).entries()) {
|
|
202
|
+
if (i === 0) {
|
|
203
|
+
ctx.moveTo(px, py);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
ctx.lineTo(px, py);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
ctx.closePath();
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
ctx.stroke();
|
|
213
|
+
}
|
|
214
|
+
/** The ten corners of a five-pointed star filling the given box, starting at its top point. */
|
|
215
|
+
export function starPoints(left, top, width, height) {
|
|
216
|
+
const cx = left + width / 2;
|
|
217
|
+
const cy = top + height / 2;
|
|
218
|
+
const corners = [];
|
|
219
|
+
for (let i = 0; i < 10; i++) {
|
|
220
|
+
const radius = i % 2 === 0 ? 1 : 0.4;
|
|
221
|
+
const angle = -Math.PI / 2 + (i * Math.PI) / 5;
|
|
222
|
+
corners.push([cx + (Math.cos(angle) * radius * width) / 2, cy + (Math.sin(angle) * radius * height) / 2]);
|
|
223
|
+
}
|
|
224
|
+
return corners;
|
|
225
|
+
}
|
|
226
|
+
function drawStamp(ctx, stamp) {
|
|
227
|
+
ctx.font = `${stamp.size}px ${STAMP_FONT_FAMILY}`;
|
|
228
|
+
ctx.textAlign = "center";
|
|
229
|
+
ctx.textBaseline = "middle";
|
|
230
|
+
ctx.fillStyle = "#000000";
|
|
231
|
+
ctx.fillText(stamp.stamp, stamp.x, stamp.y);
|
|
232
|
+
}
|
|
233
|
+
function distanceToSegment(px, py, x0, y0, x1, y1) {
|
|
234
|
+
const dx = x1 - x0;
|
|
235
|
+
const dy = y1 - y0;
|
|
236
|
+
const lengthSquared = dx * dx + dy * dy;
|
|
237
|
+
const t = lengthSquared === 0 ? 0 : Math.max(0, Math.min(1, ((px - x0) * dx + (py - y0) * dy) / lengthSquared));
|
|
238
|
+
return Math.hypot(px - (x0 + t * dx), py - (y0 + t * dy));
|
|
239
|
+
}
|
|
240
|
+
/** Whether the eraser at (`x`, `y`) with `radius`, in board units, touches `element`. A stroke or line counts its own
|
|
241
|
+
* width; a closed shape is touched on its outline's bounding box, a stamp anywhere on its square. */
|
|
242
|
+
export function hitTest(element, x, y, radius) {
|
|
243
|
+
switch (element.kind) {
|
|
244
|
+
case "stroke": {
|
|
245
|
+
const reach = radius + element.size;
|
|
246
|
+
let hit = false;
|
|
247
|
+
eachSegment(element.points, (x0, y0, x1, y1) => {
|
|
248
|
+
hit = hit || distanceToSegment(x, y, x0, y0, x1, y1) <= reach;
|
|
249
|
+
});
|
|
250
|
+
return hit;
|
|
251
|
+
}
|
|
252
|
+
case "shape": {
|
|
253
|
+
const reach = radius + element.size;
|
|
254
|
+
if (element.shape === "line" || element.shape === "arrow") {
|
|
255
|
+
return distanceToSegment(x, y, element.x1, element.y1, element.x2, element.y2) <= reach;
|
|
256
|
+
}
|
|
257
|
+
const left = Math.min(element.x1, element.x2) - reach;
|
|
258
|
+
const right = Math.max(element.x1, element.x2) + reach;
|
|
259
|
+
const top = Math.min(element.y1, element.y2) - reach;
|
|
260
|
+
const bottom = Math.max(element.y1, element.y2) + reach;
|
|
261
|
+
return x >= left && x <= right && y >= top && y <= bottom;
|
|
262
|
+
}
|
|
263
|
+
case "stamp": {
|
|
264
|
+
const half = element.size / 2 + radius;
|
|
265
|
+
return Math.abs(x - element.x) <= half && Math.abs(y - element.y) <= half;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
* - Binary messages may be dropped under backpressure and the socket may close at any moment, so this client
|
|
24
24
|
* reconnects with exponential backoff and treats every media send as best effort.
|
|
25
25
|
*
|
|
26
|
-
* Authentication is the `jwt` cookie already in place (see `
|
|
26
|
+
* Authentication is the `jwt` cookie already in place (see `../signaling/SignalingClient.ts`), so this client sets no
|
|
27
|
+
* cookie.
|
|
27
28
|
*/
|
|
28
29
|
import { DEFAULT_MESSAGE_BYTES } from "./frames.js";
|
|
29
30
|
/** The largest binary message an older server accepts - it closes the socket on a larger one (uWS's default limit).
|
|
@@ -31,7 +31,8 @@ export interface RelayTransportOptions {
|
|
|
31
31
|
env?: RelayEnv;
|
|
32
32
|
}
|
|
33
33
|
/** The relay WebSocket's URL for a meeting: this deployment's origin (the API origin when the UI is served from a
|
|
34
|
-
* different one, else the page's own), `http` becoming `ws` - the same derivation `
|
|
34
|
+
* different one, else the page's own), `http` becoming `ws` - the same derivation `signalingUrl()` uses for the
|
|
35
|
+
* signaling socket.
|
|
35
36
|
* `undefined` when there is no origin at all (not running in a browser). */
|
|
36
37
|
export declare function relayUrl(meetingUid: string): string | undefined;
|
|
37
38
|
/**
|
|
@@ -24,7 +24,8 @@ import { detectRelayEnv } from "./relayEnv.js";
|
|
|
24
24
|
import { RelayReceiver } from "./RelayReceiver.js";
|
|
25
25
|
import { RelaySender } from "./RelaySender.js";
|
|
26
26
|
/** The relay WebSocket's URL for a meeting: this deployment's origin (the API origin when the UI is served from a
|
|
27
|
-
* different one, else the page's own), `http` becoming `ws` - the same derivation `
|
|
27
|
+
* different one, else the page's own), `http` becoming `ws` - the same derivation `signalingUrl()` uses for the
|
|
28
|
+
* signaling socket.
|
|
28
29
|
* `undefined` when there is no origin at all (not running in a browser). */
|
|
29
30
|
export function relayUrl(meetingUid) {
|
|
30
31
|
const origin = apiOrigin() || (typeof window !== "undefined" ? window.location.origin : "");
|
|
@@ -122,7 +122,7 @@ export interface VideoElementLike {
|
|
|
122
122
|
play(): Promise<void> | undefined;
|
|
123
123
|
pause(): void;
|
|
124
124
|
}
|
|
125
|
-
/** The subset of `WebSocket` the relay client uses (the same seam shape `
|
|
125
|
+
/** The subset of `WebSocket` the relay client uses (the same seam shape `SignalingClient`'s `SignalingSocket` has). */
|
|
126
126
|
export interface RelaySocket {
|
|
127
127
|
readyState: number;
|
|
128
128
|
/** Bytes queued by `send()` that the browser has not yet handed to the network - the backpressure signal. */
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import type { SignalMessage, SignalingChannel } from "../webrtc/types.js";
|
|
2
|
+
/** The version of the signaling wire protocol this client speaks - `SignalingHub`'s `SIGNALING_PROTOCOL_VERSION`. */
|
|
3
|
+
export declare const SIGNALING_PROTOCOL_VERSION = 1;
|
|
4
|
+
/** The first reconnect waits about this long, doubling on each further failure up to `SIGNALING_BACKOFF_MAX_MS`. */
|
|
5
|
+
export declare const SIGNALING_BACKOFF_BASE_MS = 1000;
|
|
6
|
+
export declare const SIGNALING_BACKOFF_MAX_MS = 30000;
|
|
7
|
+
/** How often an idle socket is pinged. */
|
|
8
|
+
export declare const SIGNALING_KEEPALIVE_MS = 25000;
|
|
9
|
+
/** The most messages held while the socket is not ready. */
|
|
10
|
+
export declare const SIGNALING_MAX_QUEUED = 256;
|
|
11
|
+
/** What the server allows per message until it says otherwise in `ready` - its smallest possible limit. */
|
|
12
|
+
export declare const SIGNALING_DEFAULT_MAX_MESSAGE_BYTES: number;
|
|
13
|
+
/** The reason the server closes the socket with once the participant is removed from the meeting - `SignalingHub`'s
|
|
14
|
+
* `SIGNALING_EVICTED_REASON` (`apps/` cannot import from the backend's `src/`, so this mirrors it). */
|
|
15
|
+
export declare const SIGNALING_EVICTED_REASON = "Removed from the meeting.";
|
|
16
|
+
/** The subset of the browser's `WebSocket` this client uses, so a test's fake can be a plain object. */
|
|
17
|
+
export interface SignalingSocket {
|
|
18
|
+
readyState: number;
|
|
19
|
+
send(data: string): void;
|
|
20
|
+
close(code?: number, reason?: string): void;
|
|
21
|
+
onopen: ((event: unknown) => void) | null;
|
|
22
|
+
onmessage: ((event: {
|
|
23
|
+
data: unknown;
|
|
24
|
+
}) => void) | null;
|
|
25
|
+
onclose: ((event: {
|
|
26
|
+
code?: number;
|
|
27
|
+
reason?: string;
|
|
28
|
+
}) => void) | null;
|
|
29
|
+
onerror: ((event: unknown) => void) | null;
|
|
30
|
+
}
|
|
31
|
+
export type SignalingSocketFactory = (url: string) => SignalingSocket;
|
|
32
|
+
/** The signaling WebSocket's URL for a meeting - the same origin derivation as the relay's `relayUrl()`. `undefined`
|
|
33
|
+
* when there is no origin at all (not running in a browser). */
|
|
34
|
+
export declare function signalingUrl(meetingUid: string): string | undefined;
|
|
35
|
+
export interface SignalingClientOptions {
|
|
36
|
+
/** The meeting's uid. */
|
|
37
|
+
meetingUid: string;
|
|
38
|
+
/** This tab's peer id (`<uid>~<random>` - see `_CallView.tsx`'s `newPeerId()`), announced in `hello`. */
|
|
39
|
+
peerId: string;
|
|
40
|
+
/** The guest JWT from `VideoMeetingJoinResult.token`; omitted for a caller already signed in - see this module's
|
|
41
|
+
* doc comment on authentication. */
|
|
42
|
+
token?: string;
|
|
43
|
+
/** Called once when the server closes the socket for good after `connect()` resolved, with its reason. */
|
|
44
|
+
onClosed?: (reason: string) => void;
|
|
45
|
+
/** Overrides `signalingUrl(meetingUid)`. */
|
|
46
|
+
url?: string;
|
|
47
|
+
createSocket?: SignalingSocketFactory;
|
|
48
|
+
/** Overrides `document`, for a test - see `applyAuthCookie()`. */
|
|
49
|
+
documentRef?: Pick<Document, "cookie"> & {
|
|
50
|
+
location?: Pick<Location, "protocol">;
|
|
51
|
+
};
|
|
52
|
+
random?: () => number;
|
|
53
|
+
}
|
|
54
|
+
export declare class SignalingClient implements SignalingChannel {
|
|
55
|
+
private readonly options;
|
|
56
|
+
private socket;
|
|
57
|
+
private ready;
|
|
58
|
+
private closed;
|
|
59
|
+
private everReady;
|
|
60
|
+
private attempt;
|
|
61
|
+
private reconnectTimer;
|
|
62
|
+
private keepaliveTimer;
|
|
63
|
+
private cookieApplied;
|
|
64
|
+
private maxBytes;
|
|
65
|
+
private readonly queue;
|
|
66
|
+
private readonly listeners;
|
|
67
|
+
private pendingConnect;
|
|
68
|
+
constructor(options: SignalingClientOptions);
|
|
69
|
+
/** The largest message (the whole `signal` envelope, in bytes) the server accepts - what its `ready` said. */
|
|
70
|
+
get maxMessageBytes(): number;
|
|
71
|
+
/** Opens the socket and resolves once the server is ready; rejects when the server refuses it, or there is no way
|
|
72
|
+
* to open one at all (no `WebSocket`, no origin). Calling it on a closed client rejects at once. */
|
|
73
|
+
connect(): Promise<void>;
|
|
74
|
+
/** Closes the socket, stops reconnecting and clears the guest auth cookie this client applied - idempotent. */
|
|
75
|
+
close(): void;
|
|
76
|
+
onMessage(handler: (message: SignalMessage) => void): () => void;
|
|
77
|
+
/** Sends `message` now, or once the socket is ready again - see this module's doc comment. A no-op once closed. */
|
|
78
|
+
send(message: SignalMessage): void;
|
|
79
|
+
private openSocket;
|
|
80
|
+
private handleFrame;
|
|
81
|
+
private handleReady;
|
|
82
|
+
/** Writes `text` to the open socket; `false` when there is none or it threw. */
|
|
83
|
+
private write;
|
|
84
|
+
private startKeepalive;
|
|
85
|
+
private stopKeepalive;
|
|
86
|
+
private scheduleReconnect;
|
|
87
|
+
/** The server closed the socket for good - see this module's doc comment. */
|
|
88
|
+
private refused;
|
|
89
|
+
private fail;
|
|
90
|
+
/** Sets the `jwt` cookie to the guest `token`, so the browser attaches it to the upgrade this client is about to
|
|
91
|
+
* open - see this module's doc comment. A no-op without a token or a document. */
|
|
92
|
+
private applyAuthCookie;
|
|
93
|
+
/** Expires the cookie `applyAuthCookie()` set; a no-op when it set none. */
|
|
94
|
+
private clearAuthCookie;
|
|
95
|
+
}
|