@4players/odin-plugin-web 1.0.0-alpha.58 → 1.0.0-alpha.59

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.
@@ -1,4421 +1 @@
1
- "use strict";
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
11
- };
12
- var __copyProps = (to, from, except, desc) => {
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
- }
18
- return to;
19
- };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
-
30
- // src/plugin/index.ts
31
- var plugin_exports = {};
32
- __export(plugin_exports, {
33
- SESSION_ID: () => SESSION_ID,
34
- createPlugin: () => createPlugin
35
- });
36
- module.exports = __toCommonJS(plugin_exports);
37
- var import_odin_common22 = require("@4players/odin-common");
38
-
39
- // src/common/config.ts
40
- var config_exports = {};
41
- __export(config_exports, {
42
- AdditionalCaptureBlocks: () => AdditionalCaptureBlocks,
43
- AdditionalPlaybackBlocks: () => AdditionalPlaybackBlocks,
44
- ClockJumpCheckInterval: () => ClockJumpCheckInterval,
45
- ClockJumpThreshold: () => ClockJumpThreshold,
46
- ConnectInterval: () => ConnectInterval,
47
- ConnectTimeout: () => ConnectTimeout,
48
- DefaultRefreshRate: () => DefaultRefreshRate,
49
- HealthCheckTimeout: () => HealthCheckTimeout,
50
- MainConnectionPingInterval: () => MainConnectionPingInterval,
51
- MaxNumberOfDecoders: () => MaxNumberOfDecoders,
52
- MinDBFs: () => MinDBFs,
53
- MinTokenLifetime: () => MinTokenLifetime,
54
- MinTokenRefreshInterval: () => MinTokenRefreshInterval,
55
- PlaybackPreload: () => PlaybackPreload,
56
- RenderQuantumSize: () => RenderQuantumSize,
57
- VadPreload: () => VadPreload,
58
- WebRtcIceTimeout: () => WebRtcIceTimeout,
59
- WebRtcTimeout: () => WebRtcTimeout
60
- });
61
- var AdditionalPlaybackBlocks = 3;
62
- var AdditionalCaptureBlocks = 3;
63
- var PlaybackPreload = 3;
64
- var DefaultRefreshRate = 250;
65
- var VadPreload = 3;
66
- var MaxNumberOfDecoders = 512;
67
- var RenderQuantumSize = 128;
68
- var MinDBFs = -758.596;
69
- var HealthCheckTimeout = 1e4;
70
- var ClockJumpCheckInterval = 1e3;
71
- var ClockJumpThreshold = 1e4;
72
- var MainConnectionPingInterval = 1e3;
73
- var WebRtcIceTimeout = 3e3;
74
- var WebRtcTimeout = 1e4;
75
- var ConnectTimeout = 5e3;
76
- var MinTokenLifetime = 6e4;
77
- var MinTokenRefreshInterval = 5e3;
78
- var ConnectInterval = [0, 100, 200, 200, 500];
79
-
80
- // src/common/sentinel.ts
81
- var Unencrypted = Symbol("Unencrypted");
82
- var Invalid = Symbol("Invalid");
83
- var Retry = Symbol("Retry");
84
- var UnexpectedEof = Symbol("UnexpectedEof");
85
- var Unresolved = Symbol("Unresolved");
86
- function successful(value) {
87
- return typeof value !== "symbol";
88
- }
89
- function failed(value) {
90
- return typeof value === "symbol";
91
- }
92
-
93
- // src/common/messages.ts
94
- function send(port, message, transfer) {
95
- return port.postMessage(message, transfer);
96
- }
97
-
98
- // src/common/msgpack.ts
99
- var MsgPackReader = class {
100
- constructor(bytes, position = 0) {
101
- this.bytes = bytes;
102
- this.position = position;
103
- }
104
- readArrayLength() {
105
- const tag = this.readTag();
106
- if (failed(tag)) return tag;
107
- if ((tag & 240) === 144) {
108
- return tag & 15;
109
- } else if (tag === 220) {
110
- return this.readUint16();
111
- } else if (tag === 221) {
112
- return this.readUint32();
113
- } else return Invalid;
114
- }
115
- readNumber() {
116
- const tag = this.readTag();
117
- if (failed(tag)) return tag;
118
- if ((tag & 128) === 0) {
119
- return tag;
120
- } else if ((tag & 224) === 224) {
121
- return tag - 256;
122
- } else if (tag === 202) {
123
- return this.readFloat32();
124
- } else if (tag === 203) {
125
- return this.readFloat64();
126
- } else if (tag === 204) {
127
- return this.readUint8();
128
- } else if (tag === 205) {
129
- return this.readUint16();
130
- } else if (tag === 206) {
131
- return this.readUint32();
132
- } else if (tag === 207) {
133
- return this.readUint64();
134
- } else if (tag === 208) {
135
- return this.readInt8();
136
- } else if (tag === 209) {
137
- return this.readInt16();
138
- } else if (tag === 210) {
139
- return this.readInt32();
140
- } else if (tag === 211) {
141
- return this.readInt64();
142
- } else return Invalid;
143
- }
144
- readBytes() {
145
- const tag = this.readTag();
146
- if (failed(tag)) return tag;
147
- let length;
148
- if (tag === 196) {
149
- length = this.readUint8();
150
- } else if (tag === 197) {
151
- length = this.readUint16();
152
- } else if (tag === 198) {
153
- length = this.readUint32();
154
- } else return Invalid;
155
- if (failed(length)) return length;
156
- const offset = this.consume(length);
157
- return offset !== UnexpectedEof ? this.bytes.subarray(offset, offset + length) : UnexpectedEof;
158
- }
159
- readObjects(n) {
160
- for (; n > 0; n -= 1) {
161
- const tag = this.readTag();
162
- if (failed(tag)) return tag;
163
- let m;
164
- let length = 0;
165
- let fixedLength = 0;
166
- switch (tag) {
167
- case 192:
168
- break;
169
- case 194:
170
- break;
171
- case 195:
172
- break;
173
- case 196:
174
- length = this.readUint8();
175
- break;
176
- case 197:
177
- length = this.readUint16();
178
- break;
179
- case 198:
180
- length = this.readUint32();
181
- break;
182
- case 199:
183
- length = this.readUint8();
184
- fixedLength = 1;
185
- break;
186
- case 200:
187
- length = this.readUint8();
188
- fixedLength = 1;
189
- break;
190
- case 201:
191
- length = this.readUint32();
192
- fixedLength = 1;
193
- break;
194
- case 202:
195
- length = 4;
196
- break;
197
- case 203:
198
- length = 8;
199
- break;
200
- case 204:
201
- length = 1;
202
- break;
203
- case 205:
204
- length = 2;
205
- break;
206
- case 206:
207
- length = 4;
208
- break;
209
- case 207:
210
- length = 8;
211
- break;
212
- case 208:
213
- length = 1;
214
- break;
215
- case 209:
216
- length = 2;
217
- break;
218
- case 210:
219
- length = 4;
220
- break;
221
- case 211:
222
- length = 8;
223
- break;
224
- case 212:
225
- length = 2;
226
- break;
227
- case 213:
228
- length = 3;
229
- break;
230
- case 214:
231
- length = 5;
232
- break;
233
- case 215:
234
- length = 9;
235
- break;
236
- case 216:
237
- length = 17;
238
- break;
239
- case 217:
240
- length = this.readUint8();
241
- break;
242
- case 218:
243
- length = this.readUint16();
244
- break;
245
- case 219:
246
- length = this.readUint32();
247
- break;
248
- case 220:
249
- m = this.readUint16();
250
- if (failed(m)) return m;
251
- n += m;
252
- break;
253
- case 221:
254
- m = this.readUint32();
255
- if (failed(m)) return m;
256
- n += m;
257
- break;
258
- case 222:
259
- m = this.readUint16();
260
- if (failed(m)) return m;
261
- n += m * 2;
262
- break;
263
- case 223:
264
- m = this.readUint32();
265
- if (failed(m)) return m;
266
- n += m * 2;
267
- break;
268
- default:
269
- if ((tag & 128) === 0) {
270
- } else if ((tag & 240) === 128) {
271
- n += (tag & 15) * 2;
272
- } else if ((tag & 240) === 144) {
273
- n += tag & 15;
274
- } else if ((tag & 224) === 160) {
275
- length = tag & 31;
276
- } else if ((tag & 224) === 224) {
277
- } else return Invalid;
278
- }
279
- if (length === UnexpectedEof) return UnexpectedEof;
280
- const offset = this.consume(length + fixedLength);
281
- if (offset === UnexpectedEof) return UnexpectedEof;
282
- }
283
- }
284
- consume(n) {
285
- if (n === UnexpectedEof || this.position + n > this.bytes.length) {
286
- return UnexpectedEof;
287
- }
288
- const offset = this.position;
289
- this.position = offset + n;
290
- return offset;
291
- }
292
- readTag() {
293
- const offset = this.consume(1);
294
- return successful(offset) ? this.bytes[offset] : offset;
295
- }
296
- readUint8() {
297
- const offset = this.consume(1);
298
- return successful(offset) ? this.bytes[offset] : offset;
299
- }
300
- readUint16() {
301
- const offset = this.consume(2);
302
- if (failed(offset)) return offset;
303
- return this.bytes[offset] << 8 | this.bytes[offset + 1];
304
- }
305
- readUint32() {
306
- const offset = this.consume(4);
307
- if (failed(offset)) return offset;
308
- return this.bytes[offset] << 24 | this.bytes[offset + 1] << 16 | this.bytes[offset + 2] << 8 | this.bytes[offset + 3];
309
- }
310
- readUint64() {
311
- const upper = this.readUint32();
312
- if (failed(upper)) return upper;
313
- if (upper >= 2097152) return Invalid;
314
- const lower = this.readUint32();
315
- if (failed(lower)) return lower;
316
- return upper << 32 | lower;
317
- }
318
- readInt8() {
319
- let value = this.readUint8();
320
- if (failed(value)) return value;
321
- if (value >= 128) {
322
- value = value - 256;
323
- }
324
- return value;
325
- }
326
- readInt16() {
327
- let value = this.readUint16();
328
- if (failed(value)) return value;
329
- if (value >= 32768) {
330
- value = value - 65536;
331
- }
332
- return value;
333
- }
334
- readInt32() {
335
- let value = this.readUint32();
336
- if (failed(value)) return value;
337
- if (value >= 2147483648) {
338
- value = value - 4294967296;
339
- }
340
- return value;
341
- }
342
- readInt64() {
343
- const upper = this.readUint32();
344
- if (failed(upper)) return upper;
345
- if (upper >= 2097152 || upper < -2097152) return Invalid;
346
- const lower = this.readUint32();
347
- if (failed(lower)) return lower;
348
- return upper << 32 | lower;
349
- }
350
- readFloat32() {
351
- const offset = this.consume(4);
352
- if (failed(offset)) return offset;
353
- const array = new Float32Array(this.bytes.subarray(offset, offset + 4));
354
- return array[0];
355
- }
356
- readFloat64() {
357
- const offset = this.consume(8);
358
- if (failed(offset)) return offset;
359
- const array = new Float64Array(this.bytes.subarray(offset, offset + 8));
360
- return array[0];
361
- }
362
- };
363
- function getByteSize(buffer, offset) {
364
- const reader = new MsgPackReader(buffer);
365
- reader.position = offset;
366
- const result = reader.readObjects(1);
367
- return successful(result) ? reader.position : result;
368
- }
369
-
370
- // src/common/version.ts
371
- var version = "1.0.0-alpha.58";
372
-
373
- // src/common/logging.ts
374
- var import_odin_common = require("@4players/odin-common");
375
- var instance;
376
- function initLogger(logger, cb) {
377
- if (!instance) {
378
- instance = logger ?? new import_odin_common.Logger("DEBUG", "odin-plugin-web", {
379
- handlers: [
380
- /* no default handlers */
381
- ]
382
- });
383
- if (cb) {
384
- instance.addHandler(new LogCallbackHandler(cb));
385
- }
386
- }
387
- }
388
- function error(msg, ...args) {
389
- instance?.error(msg, ...args) ?? (0, import_odin_common.error)(msg, ...args);
390
- }
391
- function warn(msg, ...args) {
392
- instance?.warn(msg, ...args) ?? (0, import_odin_common.warn)(msg, ...args);
393
- }
394
- function info(msg, ...args) {
395
- instance?.info(msg, ...args) ?? (0, import_odin_common.info)(msg, ...args);
396
- }
397
- function debug(msg, ...args) {
398
- instance?.debug(msg, ...args) ?? (0, import_odin_common.debug)(msg, ...args);
399
- }
400
- var LogCallbackHandler = class extends import_odin_common.LogHandler {
401
- constructor(cb) {
402
- super("DEBUG", import_odin_common.PRETTY_FORMATTER);
403
- this.cb = cb;
404
- }
405
- log(formattedString, record) {
406
- this.cb(formattedString, record);
407
- }
408
- };
409
-
410
- // src/common/index.ts
411
- var Silence = { isSilent: true, rmsDBFS: MinDBFs };
412
- function mergePlaybackId(id) {
413
- return id.connectionId * 4294967296 + id.mediaId;
414
- }
415
-
416
- // src/plugin/mediaController.ts
417
- var import_odin_common7 = require("@4players/odin-common");
418
-
419
- // src/plugin/medias/audio-capture.ts
420
- var import_odin_common4 = require("@4players/odin-common");
421
-
422
- // src/plugin/deviceManager.ts
423
- var import_odin_common2 = require("@4players/odin-common");
424
- async function openCaptureDevice(controller, parameters, apm) {
425
- const deviceId = parameters.device ? {
426
- exact: parameters.device.device.deviceId === "default" ? void 0 : parameters.device.device.deviceId
427
- } : void 0;
428
- let stream;
429
- try {
430
- const result = await Promise.race([
431
- navigator.mediaDevices.getUserMedia({
432
- audio: {
433
- echoCancellation: apm.echoCanceller,
434
- noiseSuppression: apm.noiseSuppression !== "None",
435
- autoGainControl: apm.gainController,
436
- // @ts-ignore chrome fix
437
- echoCancellationType: "system",
438
- // sampleRate: 48000,
439
- deviceId
440
- }
441
- }),
442
- (0, import_odin_common2.sleep)(3e4, "timeout")
443
- ]);
444
- if (result === "timeout") return (0, import_odin_common2.failure)("getUserMedia; timeout");
445
- stream = result;
446
- } catch (reason) {
447
- return (0, import_odin_common2.failure)(`getUserMedia; ${reason}`);
448
- }
449
- const sampleRate = stream.getAudioTracks()[0].getSettings().sampleRate;
450
- const context = await controller.audioContexts.acquire(sampleRate);
451
- return (0, import_odin_common2.success)(context.createMediaStreamSource(stream));
452
- }
453
- function stopMediaStream(ms) {
454
- ms.getTracks().forEach((track) => {
455
- track.stop();
456
- ms.removeTrack(track);
457
- });
458
- }
459
- async function enumerateDevices() {
460
- if ((0, import_odin_common2.isFirefoxBrowser)()) {
461
- const permissionStatus = await getPermission("microphone");
462
- if (permissionStatus === "granted" || permissionStatus === false) {
463
- await requestMediaStreamPrompt("audio");
464
- }
465
- }
466
- const mediaDeviceInfos = (await navigator.mediaDevices.enumerateDevices()).filter((devices2) => !!devices2.label);
467
- const devices = [];
468
- let inputDefaultDevice = new Device(
469
- { deviceId: "default", name: "Default", kind: "audioinput" },
470
- true
471
- );
472
- let outputDefaultDevice = new Device(
473
- { deviceId: "default", name: "Default", kind: "audiooutput" },
474
- true
475
- );
476
- devices.push(inputDefaultDevice);
477
- devices.push(outputDefaultDevice);
478
- for (const mediaDeviceInfo of mediaDeviceInfos) {
479
- if (mediaDeviceInfo.deviceId === "default") {
480
- if (mediaDeviceInfo.kind === "audioinput") {
481
- inputDefaultDevice = new Device(
482
- {
483
- deviceId: "default",
484
- name: mediaDeviceInfo.label,
485
- kind: "audioinput"
486
- },
487
- true
488
- );
489
- devices[0] = inputDefaultDevice;
490
- }
491
- if (mediaDeviceInfo.kind === "audiooutput") {
492
- outputDefaultDevice = new Device(
493
- {
494
- deviceId: "default",
495
- name: mediaDeviceInfo.label,
496
- kind: "audiooutput"
497
- },
498
- true
499
- );
500
- devices[1] = outputDefaultDevice;
501
- }
502
- continue;
503
- }
504
- if (mediaDeviceInfo.kind === "audioinput") {
505
- devices.push(
506
- new Device(
507
- {
508
- deviceId: mediaDeviceInfo.deviceId,
509
- name: mediaDeviceInfo.label,
510
- kind: "audioinput"
511
- },
512
- false
513
- )
514
- );
515
- }
516
- if (mediaDeviceInfo.kind === "audiooutput") {
517
- devices.push(
518
- new Device(
519
- {
520
- deviceId: mediaDeviceInfo.deviceId,
521
- name: mediaDeviceInfo.label,
522
- kind: "audiooutput"
523
- },
524
- false
525
- )
526
- );
527
- }
528
- }
529
- return devices;
530
- }
531
- async function getPermission(permission) {
532
- try {
533
- const result = await navigator.permissions.query({
534
- name: permission
535
- });
536
- return result.state;
537
- } catch (error2) {
538
- return false;
539
- }
540
- }
541
- async function requestMediaStreamPrompt(kind) {
542
- try {
543
- const ms = await navigator.mediaDevices.getUserMedia({
544
- [kind]: true
545
- });
546
- ms?.getTracks().forEach((track) => {
547
- track.stop();
548
- ms.removeTrack(track);
549
- });
550
- return true;
551
- } catch (e) {
552
- return false;
553
- }
554
- }
555
-
556
- // src/plugin/worker.ts
557
- var import_odin_common3 = require("@4players/odin-common");
558
- var implementation = (0, import_odin_common3.isSharedArrayBufferCapable)() && !(0, import_odin_common3.isSafariBrowser)() ? import("../worker/shared") : import("../worker/unshared");
559
- async function createCaptureWorker(audioContext, sink, weakOwner) {
560
- return implementation.then((instance2) => {
561
- return instance2.createCaptureWorker(
562
- audioContext,
563
- sink,
564
- weakOwner,
565
- config_exports.DefaultRefreshRate
566
- );
567
- });
568
- }
569
- async function createPlaybackWorker(audioContext) {
570
- return implementation.then((instance2) => {
571
- return instance2.createPlaybackWorker(
572
- audioContext,
573
- config_exports.DefaultRefreshRate
574
- );
575
- });
576
- }
577
-
578
- // src/plugin/medias/audio-capture.ts
579
- var defaultVad = {
580
- voiceActivity: {
581
- attackThreshold: 0.9,
582
- releaseThreshold: 0.8
583
- },
584
- volumeGate: {
585
- attackThreshold: -30,
586
- releaseThreshold: -40
587
- }
588
- };
589
- var defaultApm = {
590
- echoCanceller: false,
591
- highPassFilter: false,
592
- noiseSuppression: "None",
593
- transientSuppressor: false,
594
- gainController: false
595
- };
596
- var defaultVif = {
597
- enabled: false
598
- };
599
- var AudioCapture = class {
600
- constructor(controller, parameters) {
601
- this.controller = controller;
602
- this._Medias = /* @__PURE__ */ new Map();
603
- this._Vad = defaultVad;
604
- this._Apm = defaultApm;
605
- this._Vif = defaultVif;
606
- this._Volume = 1;
607
- this._ProcessingDevice = false;
608
- this._DeviceCb = async () => {
609
- const devices = await enumerateDevices();
610
- const isListed = devices.some((device) => {
611
- return device.device.deviceId === this._DeviceParameters?.device?.device.deviceId;
612
- });
613
- if (!isListed) {
614
- this._DeviceParameters?.onStatusChanged?.("stopped");
615
- navigator.mediaDevices.removeEventListener(
616
- "devicechange",
617
- this._DeviceCb
618
- );
619
- this._DeviceParameters = void 0;
620
- if (this._Capture !== void 0) {
621
- stopMediaStream(this._Capture.source.mediaStream);
622
- this._Capture.source.disconnect(this._Capture.worker.worklet);
623
- }
624
- }
625
- };
626
- if (parameters.apm) {
627
- this.setApm(parameters.apm);
628
- }
629
- if (parameters.vad) {
630
- this.setVad(parameters.vad);
631
- }
632
- if (parameters.volume) {
633
- this.setVolume(parameters.volume);
634
- }
635
- }
636
- get vad() {
637
- return this._Vad;
638
- }
639
- get apm() {
640
- return this._Apm;
641
- }
642
- get vif() {
643
- return this._Vif;
644
- }
645
- get volume() {
646
- return this._Volume;
647
- }
648
- get activity() {
649
- return this._Capture?.worker.activity ?? Silence;
650
- }
651
- get speechProbability() {
652
- return this._Capture?.worker.speechProbability ?? 0;
653
- }
654
- get deviceId() {
655
- return this._DeviceParameters?.device?.device.deviceId;
656
- }
657
- start(room, media, uid) {
658
- let entry = this._Medias.get(media.connectionId);
659
- if (entry === void 0) {
660
- this._Medias.set(media.connectionId, entry = /* @__PURE__ */ new Set());
661
- }
662
- info(
663
- `starting audio capture media ${media.mediaId} on connection ${media.connectionId}`
664
- );
665
- if (entry.has(media.mediaId)) return;
666
- entry.add(media.mediaId);
667
- if (room.mainConnection?.id === media.connectionId) {
668
- room.request("StartMedia", {
669
- media_id: media.mediaId,
670
- properties: { kind: "audio", uid }
671
- }).catch((reason) => error(`StartMedia got rejected; ${reason}`));
672
- }
673
- this._UpdateMedias();
674
- }
675
- stop(room, media) {
676
- const entry = this._Medias.get(media.connectionId);
677
- info(
678
- `stopping audio capture media ${media.mediaId} on connection ${media.connectionId}`
679
- );
680
- if (!entry?.delete(media.mediaId)) return;
681
- if (room.mainConnection?.id === media.connectionId) {
682
- room.request("StopMedia", { media_id: media.mediaId }).catch((reason) => error(`StopMedia got rejected; ${reason}`));
683
- }
684
- this._UpdateMedias();
685
- }
686
- setVad(config) {
687
- debug("updating vad config", config);
688
- this._Vad = config;
689
- if (this._Capture) {
690
- this._Capture.worker.setVad(config);
691
- }
692
- }
693
- async setApm(config) {
694
- debug("updating apm config", config);
695
- this._Apm = config;
696
- if (this._DeviceParameters) {
697
- await this.setDevice(this._DeviceParameters);
698
- }
699
- }
700
- setVif(config) {
701
- this._Vif = config;
702
- if (this._Capture) {
703
- this._Capture.worker.setVif(this._Vif);
704
- }
705
- }
706
- async setDevice(parameters) {
707
- debug("updating capture device parameters", parameters);
708
- if (this._ProcessingDevice) return;
709
- this._ProcessingDevice = true;
710
- const rollbackParams = this._DeviceParameters;
711
- this._DeviceParameters = parameters;
712
- if (this._Volume === "muted") {
713
- this._ProcessingDevice = false;
714
- return;
715
- }
716
- try {
717
- const device = await openCaptureDevice(
718
- this.controller,
719
- parameters,
720
- this._Apm
721
- );
722
- if (device.type !== "Success") {
723
- error(`unable to open capture device; ${device.reason}`);
724
- this._DeviceParameters = rollbackParams;
725
- this._ProcessingDevice = false;
726
- return;
727
- }
728
- if (this._DeviceParameters.onStatusChanged) {
729
- navigator.mediaDevices.addEventListener("devicechange", this._DeviceCb);
730
- }
731
- if (this._Capture !== void 0) {
732
- stopMediaStream(this._Capture.source.mediaStream);
733
- this._Capture.source.disconnect(this._Capture.worker.worklet);
734
- }
735
- if (this._Capture === void 0 || this._Capture.worker.worklet.context !== device.value.context) {
736
- this._Capture?.worker.close();
737
- this._Capture = {
738
- worker: await createCaptureWorker(
739
- device.value.context,
740
- this.controller.sink,
741
- new WeakRef(this)
742
- ),
743
- source: device.value
744
- };
745
- await this._UpdateMedias();
746
- await this.setVolume(this._Volume);
747
- this.setVad(this.vad);
748
- } else {
749
- this._Capture.source = device.value;
750
- }
751
- this._Capture.source.connect(this._Capture.worker.worklet);
752
- } catch (e) {
753
- this._ProcessingDevice = false;
754
- throw new Error(`Error in setDevice(); ${e}`);
755
- }
756
- this._ProcessingDevice = false;
757
- }
758
- async setVolume(value) {
759
- debug(`updating capture volume to ${value}`);
760
- this._Volume = value;
761
- if (value === "muted") {
762
- await this.close();
763
- } else {
764
- if (!this._Capture && this._DeviceParameters) {
765
- await this.setDevice(this._DeviceParameters);
766
- }
767
- this._Capture?.worker.setVolume(value);
768
- }
769
- if (value !== this._Volume) {
770
- await this.setVolume(this._Volume);
771
- }
772
- }
773
- async close() {
774
- debug("closing audio capture");
775
- if (this._Capture !== void 0) {
776
- this._Capture.worker.close();
777
- stopMediaStream(this._Capture.source.mediaStream);
778
- try {
779
- this._Capture.source.disconnect(this._Capture.worker.worklet);
780
- } catch (e) {
781
- warn("failed to disconnect audio node", e);
782
- }
783
- await this.controller.audioContexts.release(this._Capture.source.context);
784
- }
785
- navigator.mediaDevices.removeEventListener("devicechange", this._DeviceCb);
786
- this._Capture = void 0;
787
- }
788
- async dump() {
789
- const medias = [];
790
- for (const [connectionId, mediaIds] of this._Medias) {
791
- medias.push(
792
- `${connectionId} => ${Array.from(mediaIds.values()).join(",")}`
793
- );
794
- }
795
- return {
796
- worker: await this._Capture?.worker.dump() ?? null,
797
- vad: this._Vad,
798
- apm: this._Apm,
799
- volume: this._Volume,
800
- processingSetDevice: this._ProcessingDevice,
801
- medias
802
- };
803
- }
804
- async _UpdateMedias() {
805
- if (this._Capture !== void 0) {
806
- const connections = Array.from(this._Medias).map(([key, value]) => {
807
- return { id: key, mediaIds: Array.from(value) };
808
- });
809
- this._Capture.worker.setMedias(connections);
810
- }
811
- }
812
- startLoopback() {
813
- throw new Error("Not available");
814
- }
815
- stopLoopback() {
816
- throw new Error("Not available");
817
- }
818
- };
819
-
820
- // src/plugin/medias/video-capture.ts
821
- var import_odin_common5 = require("@4players/odin-common");
822
- var VideoCapture = class {
823
- constructor(controller, options) {
824
- this.controller = controller;
825
- this.options = options;
826
- this._Medias = /* @__PURE__ */ new Map();
827
- }
828
- get mediaStream() {
829
- return this._mediaStream;
830
- }
831
- async setDevice(ms) {
832
- this._mediaStream = ms;
833
- }
834
- start(room, media, uid) {
835
- let entry = this._Medias.get(media.connectionId);
836
- if (entry === void 0) {
837
- this._Medias.set(media.connectionId, entry = /* @__PURE__ */ new Set());
838
- }
839
- if (entry.has(media.mediaId)) return;
840
- entry.add(media.mediaId);
841
- if (room.mainConnection?.id === media.connectionId) {
842
- const properties = {
843
- kind: "video",
844
- codec: this.options?.codec ?? "H264",
845
- uid
846
- };
847
- if (this.options?.customType) {
848
- properties.customType = this.options.customType;
849
- }
850
- room.request("StartMedia", {
851
- media_id: media.mediaId,
852
- properties
853
- }).catch((reason) => error(`StartMedia got rejected; ${reason}`));
854
- }
855
- }
856
- stop(room, media) {
857
- if (this._mediaStream) {
858
- stopMediaStream(this._mediaStream);
859
- }
860
- const entry = this._Medias.get(media.connectionId);
861
- if (!entry?.delete(media.mediaId)) return;
862
- if (room.mainConnection?.id === media.connectionId) {
863
- room.request("StopMedia", { media_id: media.mediaId }).catch((reason) => error(`StopMedia got rejected; ${reason}`));
864
- }
865
- }
866
- close() {
867
- throw new Error("Method not implemented.");
868
- }
869
- };
870
-
871
- // src/plugin/medias/audio-playback.ts
872
- var import_odin_common6 = require("@4players/odin-common");
873
- var AudioPlayback = class {
874
- constructor(controller, uid) {
875
- this.controller = controller;
876
- this.uid = uid;
877
- }
878
- get volume() {
879
- return this.controller.playbackGetVolume(this.uid);
880
- }
881
- get activity() {
882
- return this.controller.playbackGetActivity(this.uid);
883
- }
884
- get jitterStats() {
885
- return this.controller.playbackGetJitterStats(this.uid);
886
- }
887
- start(id) {
888
- info(
889
- `starting audio playback media ${id.mediaId} on connection ${id.connectionId}`
890
- );
891
- this.controller.playbackStart(this.uid, id);
892
- }
893
- stop(id) {
894
- info(
895
- `stopping audio playback media ${id.mediaId} on connection ${id.connectionId}`
896
- );
897
- this.controller.playbackStop(this.uid, id);
898
- }
899
- setVolume(value) {
900
- debug(`updating audio playback media '${this.uid}' volume to ${value}`);
901
- this.controller.playbackSetVolume(this.uid, value);
902
- }
903
- close() {
904
- debug(`closing audio playback media '${this.uid}'`);
905
- this.controller.closePlayback(this.uid);
906
- }
907
- };
908
-
909
- // src/plugin/mediaController.ts
910
- var AudioContextCollection = class {
911
- constructor(audioContextFactory) {
912
- this.audioContextFactory = audioContextFactory;
913
- this.entries = [];
914
- }
915
- async acquire(sampleRate) {
916
- for (const entry2 of this.entries) {
917
- if (entry2.sampleRate === sampleRate) {
918
- entry2.references += 1;
919
- return entry2.audioContext;
920
- } else if (entry2.sampleRate === void 0) {
921
- const audioContext = entry2.audioContext;
922
- if (audioContext.sampleRate === sampleRate) {
923
- entry2.references += 1;
924
- return entry2.audioContext;
925
- }
926
- }
927
- }
928
- const entry = {
929
- sampleRate,
930
- audioContext: await this.audioContextFactory(sampleRate),
931
- references: 1
932
- };
933
- this.entries.push(entry);
934
- return entry.audioContext;
935
- }
936
- async release(audioContext) {
937
- const index = this.entries.findIndex(
938
- (entry) => entry.audioContext === audioContext
939
- );
940
- if (index < 0) return;
941
- this.entries[index].references -= 1;
942
- if (this.entries[index].references <= 0) {
943
- const context = this.entries[index].audioContext;
944
- this.entries.splice(index, 1);
945
- await context.close();
946
- }
947
- }
948
- close() {
949
- for (const entry of this.entries) {
950
- entry.audioContext.close();
951
- }
952
- }
953
- };
954
- var MediaController2 = class _MediaController {
955
- constructor(audioContexts, sink) {
956
- this.audioContexts = audioContexts;
957
- this.sink = sink;
958
- this.audioCaptures = [];
959
- this.globalVolume = [1, 1];
960
- this.playbacks = /* @__PURE__ */ new Map();
961
- this.setPlaybackStrand = new import_odin_common7.Strand();
962
- }
963
- async create(audioContexts, sink) {
964
- return new _MediaController(audioContexts, sink);
965
- }
966
- receivePacket(connectionId, packet) {
967
- this.playbackWorker?.receivePacket(connectionId, packet);
968
- }
969
- async createAudioPlayback(parameters) {
970
- let entry = this.playbacks.get(parameters.uid);
971
- let volume = parameters.volume;
972
- if (entry === void 0) {
973
- volume = volume ?? [1, 1];
974
- const playback = new AudioPlayback(this, parameters.uid);
975
- entry = { id: void 0, volume, playback };
976
- this.playbacks.set(parameters.uid, entry);
977
- }
978
- if (volume !== void 0) {
979
- await entry.playback.setVolume(volume);
980
- }
981
- return entry.playback;
982
- }
983
- async createAudioCapture(parameters) {
984
- const capture = new AudioCapture(this, parameters);
985
- await capture.setDevice(parameters);
986
- this.audioCaptures.push(new WeakRef(capture));
987
- return capture;
988
- }
989
- async createVideoCapture(ms, options) {
990
- const videoCapture = new VideoCapture(this, options);
991
- await videoCapture.setDevice(ms);
992
- return videoCapture;
993
- }
994
- playbackSetDevice(device) {
995
- return this.setPlaybackStrand.enqueue(async () => {
996
- let deviceId = device?.device.deviceId ?? "";
997
- if (deviceId === "default") {
998
- deviceId = "";
999
- }
1000
- let audioContext;
1001
- if (this.playbackWorker === void 0) {
1002
- audioContext = await this.audioContexts.acquire(48e3);
1003
- this.playbackWorker = await createPlaybackWorker(audioContext);
1004
- this.playbackWorker.setGlobalVolume(this.globalVolume);
1005
- } else {
1006
- audioContext = this.playbackWorker.audioContext;
1007
- }
1008
- if (audioContext?.setSinkId !== void 0 && device && audioContext?.state === "running") {
1009
- await audioContext.setSinkId(deviceId);
1010
- }
1011
- });
1012
- }
1013
- playbackSetGlobalVolume(value) {
1014
- this.globalVolume = value;
1015
- this.playbackWorker?.setGlobalVolume(this.globalVolume);
1016
- }
1017
- playbackStart(uid, id) {
1018
- const entry = this.playbacks.get(uid);
1019
- if (entry === void 0) return;
1020
- if (entry.id !== void 0) {
1021
- this.playbackStop(uid, entry.id);
1022
- }
1023
- entry.id = id;
1024
- this.playbackWorker?.addMedias(id.connectionId, [id.mediaId], entry.volume);
1025
- }
1026
- playbackStop(uid, id) {
1027
- const entry = this.playbacks.get(uid);
1028
- if (entry === void 0) return;
1029
- if (entry.id !== id) {
1030
- warn(
1031
- `playback media ${id.mediaId} on connection ${id.connectionId} is out of sync`,
1032
- entry.id,
1033
- id
1034
- );
1035
- }
1036
- entry.id = void 0;
1037
- this.playbackWorker?.removeMedias(id.connectionId, [id.mediaId]);
1038
- }
1039
- playbackGetActivity(uid) {
1040
- const entry = this.playbacks.get(uid);
1041
- if (entry?.id === void 0) return Silence;
1042
- return this.playbackWorker?.getActivity(entry.id) ?? Silence;
1043
- }
1044
- playbackGetJitterStats(uid) {
1045
- const entry = this.playbacks.get(uid);
1046
- if (entry?.id === void 0) return import_odin_common7.JITTER_STATS_INITIAL;
1047
- return this.playbackWorker?.getJitterStats(entry.id) ?? import_odin_common7.JITTER_STATS_INITIAL;
1048
- }
1049
- playbackSetVolume(uid, volume) {
1050
- const entry = this.playbacks.get(uid);
1051
- if (entry === void 0) return;
1052
- entry.volume = volume;
1053
- if (entry.id !== void 0) {
1054
- this.playbackWorker?.setVolume(entry.id, entry.volume);
1055
- }
1056
- }
1057
- playbackGetVolume(uid) {
1058
- const entry = this.playbacks.get(uid);
1059
- return entry?.volume ?? [1, 1];
1060
- }
1061
- closePlayback(uid) {
1062
- const entry = this.playbacks.get(uid);
1063
- if (entry === void 0) return;
1064
- if (entry.id !== void 0) this.playbackStop(uid, entry.id);
1065
- this.playbacks.delete(uid);
1066
- }
1067
- close() {
1068
- this.playbackWorker?.close();
1069
- this.audioContexts.close();
1070
- }
1071
- async dump() {
1072
- const captures = [];
1073
- for (const weakAudioCapture of this.audioCaptures) {
1074
- const audioCapture = weakAudioCapture.deref();
1075
- if (audioCapture !== void 0) {
1076
- captures.push(await audioCapture.dump());
1077
- }
1078
- }
1079
- const playbacks = [];
1080
- for (const [uid, playback] of this.playbacks) {
1081
- playbacks.push({
1082
- uid,
1083
- id: playback.id || null,
1084
- volume: playback.volume
1085
- });
1086
- }
1087
- return {
1088
- globalVolume: this.globalVolume,
1089
- playbackWorker: await this.playbackWorker?.dump() || null,
1090
- captures,
1091
- playbacks
1092
- };
1093
- }
1094
- };
1095
- var Device = class {
1096
- constructor(device, isDefault = false) {
1097
- this.device = device;
1098
- this.isDefault = isDefault;
1099
- }
1100
- get type() {
1101
- switch (this.device.kind) {
1102
- case "audioinput":
1103
- return "AudioCapture";
1104
- case "audiooutput":
1105
- return "AudioPlayback";
1106
- default:
1107
- (0, import_odin_common7.assert)(false, "not supported device");
1108
- }
1109
- }
1110
- get id() {
1111
- return this.device.deviceId;
1112
- }
1113
- get name() {
1114
- return this.device.name;
1115
- }
1116
- };
1117
-
1118
- // src/plugin/connectionPool.ts
1119
- var import_odin_common15 = require("@4players/odin-common");
1120
- var import_msgpack2 = require("@msgpack/msgpack");
1121
-
1122
- // src/plugin/mainConnection.ts
1123
- var import_odin_common13 = require("@4players/odin-common");
1124
-
1125
- // src/plugin/h3/mainConnection.ts
1126
- var import_odin_common10 = require("@4players/odin-common");
1127
-
1128
- // src/plugin/token.ts
1129
- var import_odin_common8 = require("@4players/odin-common");
1130
- var Token = class _Token {
1131
- constructor(text, claims) {
1132
- this.text = text;
1133
- this.claims = claims;
1134
- if (claims.ups !== void 0) {
1135
- const upstream = _Token.parse(claims.ups);
1136
- if (upstream.type === "Success") {
1137
- this._Upstream = upstream.value;
1138
- } else {
1139
- error(`upstream in token can not be parsed; ${upstream.reason}`);
1140
- }
1141
- }
1142
- }
1143
- static parse(token) {
1144
- const segments = token.split(".");
1145
- if (segments.length !== 3) return (0, import_odin_common8.failure)("expected three segments");
1146
- const body = (0, import_odin_common8.fromBase64Url)(segments[1]);
1147
- if (body.type === "Failure") {
1148
- return (0, import_odin_common8.failure)(`invalid token; ${body.reason}`);
1149
- }
1150
- let json;
1151
- try {
1152
- json = JSON.parse(body.value);
1153
- } catch (reason) {
1154
- return (0, import_odin_common8.failure)(`invalid token; ${reason}`);
1155
- }
1156
- const parsed = import_odin_common8.TokenClaimsSchema.safeParse(json);
1157
- if (parsed.success === false) {
1158
- return (0, import_odin_common8.failure)(`invalid token; ${parsed.error}`);
1159
- }
1160
- return (0, import_odin_common8.success)(new _Token(token, parsed.data));
1161
- }
1162
- get timeLeft() {
1163
- if (this.claims.exp === void 0) return Infinity;
1164
- return this.claims.exp * 1e3 - Date.now();
1165
- }
1166
- get upstream() {
1167
- return this._Upstream;
1168
- }
1169
- get defaultRoomId() {
1170
- if (Array.isArray(this.claims.rid)) {
1171
- return this.claims.rid.length === 1 ? this.claims.rid[0] : void 0;
1172
- } else {
1173
- return this.claims.rid;
1174
- }
1175
- }
1176
- hasAudience(audience) {
1177
- if (this.claims.aud === void 0 && audience === "gateway") return true;
1178
- if (Array.isArray(this.claims.aud)) {
1179
- return this.claims.aud.includes(audience);
1180
- } else {
1181
- return this.claims.aud === audience;
1182
- }
1183
- }
1184
- toString() {
1185
- return this.text;
1186
- }
1187
- };
1188
-
1189
- // src/plugin/rpc.ts
1190
- var import_odin_common9 = require("@4players/odin-common");
1191
- var import_msgpack = require("@msgpack/msgpack");
1192
- var RpcConnection = class {
1193
- constructor(stream) {
1194
- this.stream = stream;
1195
- this._SendQueue = [];
1196
- this._RecvQueue = [];
1197
- this._EventQueue = [];
1198
- this._Requests = /* @__PURE__ */ new Map();
1199
- this._Close = new AbortController();
1200
- this._NextId = 1;
1201
- if (stream instanceof WebSocket) {
1202
- stream.addEventListener("open", this._OnWebSocketOpen.bind(this));
1203
- stream.addEventListener("close", this.close.bind(this));
1204
- stream.addEventListener(
1205
- "message",
1206
- (event) => this._OnRpcMessage(event.data)
1207
- );
1208
- stream.binaryType = "arraybuffer";
1209
- } else {
1210
- readRpcFromStream(
1211
- stream.readable.getReader(),
1212
- this._OnRpcMessage.bind(this)
1213
- ).finally(this.close.bind(this));
1214
- this._Writer = stream.writable.getWriter();
1215
- }
1216
- this._Close.signal.addEventListener("abort", this._Cleanup.bind(this), {
1217
- once: true
1218
- });
1219
- }
1220
- get closed() {
1221
- return this._Close.signal.aborted;
1222
- }
1223
- get closeSignal() {
1224
- return this._Close.signal;
1225
- }
1226
- async request(name, properties, timeout) {
1227
- if (this.closed) {
1228
- return Promise.resolve("closed");
1229
- }
1230
- const id = this._NextId;
1231
- const timeoutId = timeout === void 0 ? void 0 : setTimeout(() => this._TimeoutRequest(id), timeout);
1232
- this._Send([0, id, name, properties]);
1233
- return new Promise((accept) => {
1234
- this._Requests.set(id, { accept, timeoutId });
1235
- this._NextId += 1;
1236
- });
1237
- }
1238
- notify(name, properties) {
1239
- if (!this.closed) {
1240
- this._Send([2, name, properties]);
1241
- }
1242
- }
1243
- recv() {
1244
- if (this.closed) {
1245
- return Promise.resolve("closed");
1246
- }
1247
- const event = this._EventQueue.shift();
1248
- if (event !== void 0) {
1249
- return Promise.resolve(event);
1250
- } else {
1251
- return new Promise((accept) => this._RecvQueue.push(accept));
1252
- }
1253
- }
1254
- close() {
1255
- this._Close.abort();
1256
- }
1257
- _TimeoutRequest(requestId) {
1258
- const request = this._Requests.get(requestId);
1259
- if (request === void 0) return;
1260
- this._Requests.delete(requestId);
1261
- clearTimeout(request.timeoutId);
1262
- request.accept("timeout");
1263
- }
1264
- _Send(message) {
1265
- let encoded;
1266
- if (message instanceof Uint8Array) {
1267
- encoded = message;
1268
- } else {
1269
- encoded = new Uint8Array(
1270
- (0, import_msgpack.encode)(message, {
1271
- ignoreUndefined: true,
1272
- forceFloat32: true
1273
- })
1274
- );
1275
- }
1276
- if (this.stream instanceof WebSocket) {
1277
- this.stream.send(encoded);
1278
- } else {
1279
- this._Writer.write(encoded);
1280
- }
1281
- }
1282
- _OnWebSocketOpen(ev) {
1283
- for (const data of this._SendQueue) {
1284
- this._Send(data);
1285
- }
1286
- this._SendQueue.length = 0;
1287
- }
1288
- _OnRpcMessage(bytes) {
1289
- const rpc = parseRpc(bytes);
1290
- if (rpc.type !== "Success") {
1291
- this.close();
1292
- return;
1293
- }
1294
- switch (rpc.value[0]) {
1295
- case 0:
1296
- this._Send([1, rpc.value[1], "not supported", null]);
1297
- break;
1298
- case 1:
1299
- this._OnResponse(rpc.value[1], rpc.value[2], rpc.value[3]);
1300
- break;
1301
- case 2:
1302
- this._OnEvent(rpc.value[1], rpc.value[2]);
1303
- break;
1304
- }
1305
- }
1306
- _OnResponse(id, error2, result) {
1307
- const request = this._Requests.get(id);
1308
- if (request === void 0) return;
1309
- clearTimeout(request.timeoutId);
1310
- request.accept(error2 === null ? (0, import_odin_common9.success)(result) : (0, import_odin_common9.failure)(error2));
1311
- }
1312
- _OnEvent(name, properties) {
1313
- const receiver = this._RecvQueue.shift();
1314
- if (receiver !== void 0) {
1315
- receiver({ name, properties });
1316
- } else {
1317
- this._EventQueue.push({ name, properties });
1318
- }
1319
- }
1320
- _Cleanup() {
1321
- for (const request of this._Requests.values()) {
1322
- clearTimeout(request.timeoutId);
1323
- request.accept("closed");
1324
- }
1325
- this._Requests.clear();
1326
- for (const accept of this._RecvQueue) {
1327
- accept("closed");
1328
- }
1329
- this._RecvQueue.length = 0;
1330
- this._SendQueue.length = 0;
1331
- if (this.stream instanceof WebSocket) {
1332
- this.stream.close();
1333
- } else {
1334
- this._Writer?.close().catch(() => {
1335
- });
1336
- }
1337
- }
1338
- };
1339
- var TypedRpcConnection = class {
1340
- constructor(rpc, _Commands, _Events) {
1341
- this.rpc = rpc;
1342
- this._Commands = _Commands;
1343
- this._Events = _Events;
1344
- }
1345
- get closed() {
1346
- return this.rpc.closed;
1347
- }
1348
- get closeSignal() {
1349
- return this.rpc.closeSignal;
1350
- }
1351
- async request(name, properties, timeout) {
1352
- properties = this._Commands[name].request.parse(properties);
1353
- const response = await this.rpc.request(name, properties, timeout);
1354
- if (response === "closed" || response === "timeout" || response.type === "Failure") {
1355
- return response;
1356
- }
1357
- const parsed = this._Commands[name].response.safeParse(response.value);
1358
- if (parsed.success) {
1359
- response.value = parsed.data;
1360
- return response;
1361
- } else {
1362
- return (0, import_odin_common9.failure)(`response schema failed validation: ${parsed.error}`);
1363
- }
1364
- }
1365
- notify(name, properties) {
1366
- properties = this._Commands[name].request.parse(properties);
1367
- this.rpc.notify(name, properties);
1368
- }
1369
- async recv() {
1370
- const event = await this.rpc.recv();
1371
- if (event === "closed") return event;
1372
- const parsed = parseRpcMessage(this._Events, event);
1373
- if (parsed !== void 0) {
1374
- return parsed;
1375
- }
1376
- if (!isKnownRpcMessage(this._Events, event.name)) {
1377
- warn(`dropping unknown event ${event.name}`);
1378
- } else {
1379
- error(`schema validation for ${event.name} failed; closing connection`);
1380
- this.rpc.close();
1381
- }
1382
- return "closed";
1383
- }
1384
- close() {
1385
- this.rpc.close();
1386
- }
1387
- };
1388
- function parseRpc(buffer) {
1389
- try {
1390
- return (0, import_odin_common9.success)(
1391
- import_odin_common9.MessagePackRpcSchema.parse((0, import_msgpack.decode)(new Uint8Array(buffer)))
1392
- );
1393
- } catch (error2) {
1394
- return (0, import_odin_common9.failure)(String(error2));
1395
- }
1396
- }
1397
- function parseRpcMessage(events, rpc) {
1398
- if (!isKnownRpcMessage(events, rpc.name)) return void 0;
1399
- const parsed = events[rpc.name].safeParse(rpc.properties);
1400
- if (!parsed.success) {
1401
- return void 0;
1402
- }
1403
- return {
1404
- name: rpc.name,
1405
- properties: parsed.data
1406
- };
1407
- }
1408
- function isKnownRpcMessage(events, name) {
1409
- return name in events;
1410
- }
1411
- async function readRpcFromStream(reader, onRpc) {
1412
- let buffer = new Uint8Array();
1413
- for (; ; ) {
1414
- const size = getByteSize(buffer, 0);
1415
- if (size === Invalid) {
1416
- reader.cancel("protocol violation");
1417
- break;
1418
- }
1419
- if (size !== UnexpectedEof && size <= buffer.length) {
1420
- onRpc(buffer.subarray(0, size));
1421
- buffer = buffer.subarray(size);
1422
- continue;
1423
- }
1424
- let read;
1425
- try {
1426
- read = await reader.read();
1427
- } catch {
1428
- break;
1429
- }
1430
- if (read.done) break;
1431
- buffer = mergeBuffer(buffer, read.value);
1432
- }
1433
- if (buffer.length > 0) {
1434
- warn("unprocessed bytes left in rpc stream", buffer);
1435
- }
1436
- }
1437
- function mergeBuffer(a, b) {
1438
- if (a.length === 0) return b;
1439
- if (b.length === 0) return a;
1440
- const merged = new Uint8Array(a.length + b.length);
1441
- merged.set(a, 0);
1442
- merged.set(b, a.length);
1443
- return merged;
1444
- }
1445
-
1446
- // src/plugin/h3/mainConnection.ts
1447
- var H3MainConnection = class _H3MainConnection {
1448
- constructor(id, address, onDatagram, connection, rpc, closeController) {
1449
- this.id = id;
1450
- this.address = address;
1451
- this.connection = connection;
1452
- this.rpc = rpc;
1453
- this.closeController = closeController;
1454
- this.status = 0 /* Connecting */;
1455
- this._ConnectionStats = { ...import_odin_common10.CONNECTION_STATS_INITIAL };
1456
- this.closePromise = (async () => {
1457
- try {
1458
- await this.connection.closed;
1459
- debug("h3 main connection closePromise.closed resolved");
1460
- } catch {
1461
- }
1462
- return "closed";
1463
- })();
1464
- if (config_exports.MainConnectionPingInterval !== void 0) {
1465
- this._PingIntervalId = setInterval(async () => {
1466
- this.connectionStats.rtt = await this.ping(config_exports.HealthCheckTimeout);
1467
- }, config_exports.MainConnectionPingInterval);
1468
- }
1469
- closeController.signal.addEventListener("abort", () => this._CleanUp(), {
1470
- once: true
1471
- });
1472
- this.datagramWriter = connection.datagrams.writable.getWriter();
1473
- this.connection.closed.then(() => {
1474
- debug("h3 main connection connection.closed resolved");
1475
- this.status = 2 /* Closed */;
1476
- }).catch(() => {
1477
- });
1478
- this.connection.ready.then(() => {
1479
- this.status = 1 /* Connected */;
1480
- }).catch(() => {
1481
- });
1482
- this.readDatagrams(this.connection, this.id, (connectionId, datagram) => {
1483
- this._ConnectionStats.packetsReceived += 1;
1484
- onDatagram(connectionId, datagram);
1485
- });
1486
- }
1487
- get connectionStats() {
1488
- return this._ConnectionStats;
1489
- }
1490
- get closeSignal() {
1491
- return this.closeController.signal;
1492
- }
1493
- get transport() {
1494
- return "H3";
1495
- }
1496
- get closed() {
1497
- return this.closeSignal.aborted;
1498
- }
1499
- static async create(id, address, onDatagram, signal) {
1500
- try {
1501
- const url = new URL(
1502
- `https://${address}?version=${version}&uid=${SESSION_ID}`
1503
- );
1504
- const closeController = new AbortController();
1505
- signal.addEventListener("abort", () => closeController.abort(), {
1506
- once: true
1507
- });
1508
- const connection = new WebTransport(url);
1509
- closeController.signal.addEventListener(
1510
- "abort",
1511
- async () => {
1512
- try {
1513
- try {
1514
- const closed = await connection.closed;
1515
- if (closed) return;
1516
- await connection.close();
1517
- } catch (e) {
1518
- console.warn(e);
1519
- }
1520
- } catch {
1521
- }
1522
- },
1523
- { once: true }
1524
- );
1525
- await connection.ready;
1526
- const stream = await connection.createBidirectionalStream();
1527
- const rpc = new TypedRpcConnection(
1528
- new RpcConnection(stream),
1529
- import_odin_common10.MainCommandsRpc,
1530
- import_odin_common10.MainNotificationsRpc
1531
- );
1532
- closeController.signal.addEventListener("abort", () => rpc.close(), {
1533
- once: true
1534
- });
1535
- rpc.notify("Hello", { stream: "main" });
1536
- return (0, import_odin_common10.success)(
1537
- new _H3MainConnection(
1538
- id,
1539
- address,
1540
- onDatagram,
1541
- connection,
1542
- rpc,
1543
- closeController
1544
- )
1545
- );
1546
- } catch (error2) {
1547
- return (0, import_odin_common10.failure)(`connecting to ${address} failed with ${error2}`);
1548
- }
1549
- }
1550
- waitForConnected() {
1551
- return Promise.race([
1552
- this.connection.ready,
1553
- this.connection.closed.then(() => {
1554
- throw new Error("connection closed");
1555
- })
1556
- ]);
1557
- }
1558
- close() {
1559
- debug("closing h3 main connection");
1560
- this.closeController.abort("request");
1561
- }
1562
- async joinRoom(token, roomId, userData, position) {
1563
- try {
1564
- if (this.closed) return "closed";
1565
- const stream = await this.connection.createBidirectionalStream().catch(() => "closed");
1566
- if (stream === "closed") return "closed";
1567
- const rpc = new TypedRpcConnection(
1568
- new RpcConnection(stream),
1569
- import_odin_common10.RoomCommandsRpc,
1570
- import_odin_common10.RoomNotificationsRpc
1571
- );
1572
- const response = await rpc.request("Hello", {
1573
- stream: "room",
1574
- token: String(token),
1575
- room_id: roomId,
1576
- user_data: userData,
1577
- position
1578
- });
1579
- if (response === "closed" || response.type === "Failure") return response;
1580
- return (0, import_odin_common10.success)(rpc.rpc);
1581
- } catch (error2) {
1582
- return (0, import_odin_common10.failure)(`JoinRoom failed; ${error2}`);
1583
- }
1584
- }
1585
- async getReconnectToken(peerId) {
1586
- const response = await this._Request("RequestReconnectToken", {
1587
- peer_id: peerId
1588
- });
1589
- if (response.type === "Failure") return response;
1590
- return Token.parse(response.value);
1591
- }
1592
- async healthCheck() {
1593
- const success10 = this._RequestPing(config_exports.HealthCheckTimeout);
1594
- if (!success10) this.close();
1595
- return success10;
1596
- }
1597
- async ping(timeout = 3e4) {
1598
- const startTime = Date.now();
1599
- const success10 = await this._RequestPing(timeout);
1600
- if (success10) {
1601
- return Date.now() - startTime;
1602
- }
1603
- return 0;
1604
- }
1605
- async _RequestPing(timeout) {
1606
- const response = await this._Request("Ping", {}, timeout);
1607
- return response !== "timeout" && response.type === "Success";
1608
- }
1609
- sendDatagram(datagram) {
1610
- try {
1611
- this.datagramWriter.write(datagram);
1612
- this._ConnectionStats.packetsSent += 1;
1613
- const size = datagram.byteLength;
1614
- this.connectionStats.bytesSent += size;
1615
- } catch {
1616
- }
1617
- }
1618
- async readDatagrams(connection, connectionId, onDatagram) {
1619
- try {
1620
- const reader = connection.datagrams.readable.getReader();
1621
- for (; ; ) {
1622
- const read = await reader.read();
1623
- if (read.done) {
1624
- break;
1625
- }
1626
- const size = read.value.byteLength;
1627
- this.connectionStats.bytesReceived += size;
1628
- onDatagram(connectionId, read.value);
1629
- }
1630
- } catch {
1631
- (0, import_odin_common10.info)("H3 MainConnection.readDatagrams() drop silently");
1632
- }
1633
- }
1634
- startLocalVideo(mediaId, track) {
1635
- throw new Error("not supported.");
1636
- }
1637
- stopLocalVideo(mediaId) {
1638
- throw new Error("not supported.");
1639
- }
1640
- startRemoteVideo(mediaId) {
1641
- throw new Error("not supported.");
1642
- }
1643
- stopRemoteVideo(mediaId) {
1644
- throw new Error("not supported.");
1645
- }
1646
- _CleanUp() {
1647
- debug(`performing cleanup for connection ${this.id} to '${this.address}'`);
1648
- clearInterval(this._PingIntervalId);
1649
- }
1650
- async dump() {
1651
- return {
1652
- type: "H3",
1653
- id: this.id,
1654
- address: this.address,
1655
- closed: this.closed,
1656
- status: this.status,
1657
- datagrams: {
1658
- sent: this._ConnectionStats.packetsSent,
1659
- received: this._ConnectionStats.packetsReceived
1660
- }
1661
- };
1662
- }
1663
- async _Request(name, properties, timeout) {
1664
- const start = Date.now();
1665
- const result = await (timeout === void 0 ? this.rpc.request(name, properties) : this.rpc.request(name, properties, timeout));
1666
- const duration = (Date.now() - start) / 1e3;
1667
- if (name !== "Ping") {
1668
- debug(`sent main rpc '${name}'; took: ${duration}s`, {
1669
- properties,
1670
- timeout,
1671
- result
1672
- });
1673
- }
1674
- if (result === "closed") return (0, import_odin_common10.failure)("connection lost");
1675
- return result;
1676
- }
1677
- };
1678
-
1679
- // src/plugin/webrtc/mainConnection.ts
1680
- var import_odin_common12 = require("@4players/odin-common");
1681
-
1682
- // src/plugin/webrtc/websocket.ts
1683
- var import_odin_common11 = require("@4players/odin-common");
1684
- async function connect(url, signal) {
1685
- const start = performance.now();
1686
- let connection = (0, import_odin_common11.failure)("no connection was attempted");
1687
- for (let failures = 0; failures < config_exports.ConnectInterval.length && !signal.aborted; failures++) {
1688
- const nextReconnectAt = start + whenToConnect(failures);
1689
- if (await (0, import_odin_common11.abortableSleep)(nextReconnectAt - performance.now(), signal) === "aborted") {
1690
- return (0, import_odin_common11.failure)("aborted");
1691
- }
1692
- connection = await connectOnce(url, signal);
1693
- if (connection.type === "Success") {
1694
- return connection;
1695
- }
1696
- }
1697
- return connection;
1698
- }
1699
- async function connectOnce(url, signal) {
1700
- let webSocket;
1701
- try {
1702
- webSocket = new WebSocket(url);
1703
- } catch (reason) {
1704
- return (0, import_odin_common11.failure)(`unable to create websocket; ${reason}`);
1705
- }
1706
- const abort = new AbortController();
1707
- const onOpen = new Promise(
1708
- (accept) => webSocket.addEventListener("open", () => accept((0, import_odin_common11.success)(webSocket)), {
1709
- signal: abort.signal
1710
- })
1711
- );
1712
- const onClose = new Promise(
1713
- (accept) => webSocket.addEventListener(
1714
- "close",
1715
- (event) => accept(
1716
- (0, import_odin_common11.failure)(
1717
- event.reason !== "" ? event.reason : "unable to establish websocket connection"
1718
- )
1719
- ),
1720
- { signal: abort.signal }
1721
- )
1722
- );
1723
- const onAbort = new Promise(
1724
- (accept) => signal.addEventListener("abort", () => accept((0, import_odin_common11.failure)("aborted")), {
1725
- signal: abort.signal
1726
- })
1727
- );
1728
- const result = await Promise.race([onOpen, onClose, onAbort]);
1729
- abort.abort();
1730
- return result;
1731
- }
1732
- function whenToConnect(failures) {
1733
- const past_connects = config_exports.ConnectInterval.slice(0, failures).reduce(
1734
- (a, b) => a + b,
1735
- 0
1736
- );
1737
- const interval = config_exports.ConnectInterval[failures] * Math.random();
1738
- return past_connects + interval;
1739
- }
1740
-
1741
- // src/plugin/webrtc/mainConnection.ts
1742
- var WebRtcMainConnection = class _WebRtcMainConnection {
1743
- constructor(id, address, url, onDatagram, connection, webRtc) {
1744
- this.id = id;
1745
- this.address = address;
1746
- this.url = url;
1747
- this.onDatagram = onDatagram;
1748
- this.connection = connection;
1749
- this.webRtc = webRtc;
1750
- this._PeerIdMap = /* @__PURE__ */ new WeakMap();
1751
- this._LocalVideos = /* @__PURE__ */ new Map();
1752
- this._RemoteVideos = /* @__PURE__ */ new Map();
1753
- this._Negotiation = { requested: false, running: false };
1754
- this._ConnectionStats = { ...import_odin_common12.CONNECTION_STATS_INITIAL };
1755
- info(
1756
- `creating new webrtc connection to '${this.address}' with id ${this.id}`
1757
- );
1758
- this.closeSignal.addEventListener("abort", () => this._CleanUp(), {
1759
- once: true
1760
- });
1761
- this.addWebRtcToGlobal(webRtc);
1762
- this._ClosePromise = new Promise((accept) => {
1763
- this.closeSignal.addEventListener(
1764
- "abort",
1765
- () => {
1766
- accept("closed");
1767
- },
1768
- {
1769
- once: true
1770
- }
1771
- );
1772
- });
1773
- this._AudioChannel = this.webRtc.createDataChannel("audio", {
1774
- id: 2,
1775
- negotiated: true,
1776
- ordered: false,
1777
- maxRetransmits: 0
1778
- });
1779
- this._AudioChannel.binaryType = "arraybuffer";
1780
- this._AudioChannel.onmessage = (event) => {
1781
- this._ConnectionStats.packetsReceived++;
1782
- const datagram = new Uint8Array(event.data);
1783
- this._ConnectionStats.bytesReceived += datagram.byteLength;
1784
- this.onDatagram(this.id, datagram);
1785
- };
1786
- this.addDataChannelToGlobal(this._AudioChannel);
1787
- this._AudioChannel.onerror = (error2) => warn(
1788
- `webrtc audio data channel for connection with id ${id} raised an error`,
1789
- error2
1790
- );
1791
- this._AudioChannel.onclosing = (event) => info(
1792
- `webrtc audio data channel for connection with id ${id} begins shutting down`,
1793
- event
1794
- );
1795
- this._AudioChannel.onclose = (event) => info(
1796
- `webrtc audio data channel for connection with id ${id} has closed`,
1797
- event
1798
- );
1799
- this.webRtc.onicecandidateerror = (error2) => warn(
1800
- `webrtc ice negotiation for connection with id ${id} raised an error`,
1801
- error2
1802
- );
1803
- this.webRtc.onicegatheringstatechange = (event) => info(
1804
- `webrtc ice candidate gathering state for connection with id ${id} changed`,
1805
- event
1806
- );
1807
- this.webRtc.oniceconnectionstatechange = (event) => info(`webrtc ice connection state for connection ${id} changed`, event);
1808
- this.webRtc.onicecandidate = this._OnIceCandidate.bind(this);
1809
- this.webRtc.onconnectionstatechange = () => this._OnWebRtcStatusChanged();
1810
- this.webRtc.ontrack = this.onTrack.bind(this);
1811
- this._SendOffer();
1812
- if (config_exports.MainConnectionPingInterval !== void 0) {
1813
- this._PingIntervalId = setInterval(async () => {
1814
- this.connectionStats.rtt = await this.ping(config_exports.HealthCheckTimeout);
1815
- }, config_exports.MainConnectionPingInterval);
1816
- }
1817
- this._readWebRtcUpdate();
1818
- }
1819
- static async create(id, address, onDatagram, signal) {
1820
- if (signal.aborted) return (0, import_odin_common12.failure)("aborted");
1821
- let url;
1822
- try {
1823
- url = new URL(`wss://${address}`);
1824
- } catch (error2) {
1825
- return (0, import_odin_common12.fail)(`address in token(${address}) can not be parsed; ${error2}`);
1826
- }
1827
- const webSocketUrl = (0, import_odin_common12.extendUrl)(url, `main?uid=${SESSION_ID}`);
1828
- if (webSocketUrl.type !== "Success") return webSocketUrl;
1829
- const webSocket = await connect(webSocketUrl.value, signal);
1830
- if (webSocket.type !== "Success") return webSocket;
1831
- const connection = new TypedRpcConnection(
1832
- new RpcConnection(webSocket.value),
1833
- import_odin_common12.MainCommandsRpc,
1834
- import_odin_common12.MainNotificationsRpc
1835
- );
1836
- try {
1837
- return (0, import_odin_common12.success)(
1838
- new _WebRtcMainConnection(
1839
- id,
1840
- address,
1841
- url,
1842
- onDatagram,
1843
- connection,
1844
- new RTCPeerConnection()
1845
- )
1846
- );
1847
- } catch (reason) {
1848
- connection.close();
1849
- return (0, import_odin_common12.failure)(`unable to create webrtc connection; ${reason}`);
1850
- }
1851
- }
1852
- get connectionStats() {
1853
- return this._ConnectionStats;
1854
- }
1855
- get transport() {
1856
- return "WebRTC";
1857
- }
1858
- get closePromise() {
1859
- return this._ClosePromise;
1860
- }
1861
- get closeSignal() {
1862
- return this.connection.closeSignal;
1863
- }
1864
- get closed() {
1865
- return this.connection.closed;
1866
- }
1867
- get status() {
1868
- switch (this.webRtc.connectionState) {
1869
- case "connected":
1870
- return 1 /* Connected */;
1871
- case "failed":
1872
- case "closed":
1873
- return 2 /* Closed */;
1874
- default:
1875
- return 0 /* Connecting */;
1876
- }
1877
- }
1878
- async waitForConnected() {
1879
- return new Promise((resolve, reject) => {
1880
- const onConnectionStateChanged = () => {
1881
- const status = this.status;
1882
- if (status === 0 /* Connecting */) return;
1883
- this.webRtc.removeEventListener(
1884
- "connectionstatechange",
1885
- onConnectionStateChanged
1886
- );
1887
- if (status === 1 /* Connected */) {
1888
- resolve();
1889
- } else {
1890
- reject(new Error("connection closed"));
1891
- }
1892
- };
1893
- this.webRtc.addEventListener(
1894
- "connectionstatechange",
1895
- onConnectionStateChanged
1896
- );
1897
- onConnectionStateChanged();
1898
- });
1899
- }
1900
- close() {
1901
- info(`closing webrtc connection to '${this.address}' with id ${this.id}`);
1902
- this.connection.close();
1903
- }
1904
- async joinRoom(token, roomId, userData, position) {
1905
- const response = await this.connection.request("JoinRoom", {
1906
- token: String(token),
1907
- room_id: roomId,
1908
- user_data: userData,
1909
- position
1910
- });
1911
- if (response === "closed" || response.type === "Failure") return response;
1912
- const responseValue = response.value;
1913
- if (!("token" in responseValue)) {
1914
- return (0, import_odin_common12.failure)("JoinRoom responded without token");
1915
- }
1916
- const url = (0, import_odin_common12.extendUrl)(this.url, `room?token=${responseValue.token}`);
1917
- if (url.type === "Failure") return url;
1918
- const webSocket = await connect(url.value, this.closeSignal);
1919
- if (webSocket.type === "Success") {
1920
- this._PeerIdMap.set(webSocket.value, responseValue.peer_id);
1921
- return (0, import_odin_common12.success)(new RpcConnection(webSocket.value));
1922
- } else {
1923
- return webSocket;
1924
- }
1925
- }
1926
- async getReconnectToken(peerId) {
1927
- const response = await this._Request("RequestReconnectToken", {
1928
- peer_id: peerId
1929
- });
1930
- if (response.type === "Failure") return response;
1931
- return Token.parse(response.value);
1932
- }
1933
- sendDatagram(datagram) {
1934
- if (this._AudioChannel.readyState === "open") {
1935
- const datagramSize = datagram.byteLength;
1936
- this._AudioChannel.send(datagram);
1937
- this._ConnectionStats.packetsSent += 1;
1938
- this._ConnectionStats.bytesSent += datagramSize;
1939
- } else {
1940
- }
1941
- }
1942
- async healthCheck() {
1943
- const success10 = this._RequestPing(config_exports.HealthCheckTimeout);
1944
- if (!success10) this.close();
1945
- return success10;
1946
- }
1947
- async ping(timeout = 3e4) {
1948
- const startTime = Date.now();
1949
- const success10 = await this._RequestPing(timeout);
1950
- if (success10) {
1951
- return Date.now() - startTime;
1952
- }
1953
- return 0;
1954
- }
1955
- async _RequestPing(timeout = 3e4) {
1956
- const response = await this._Request("Ping", {}, timeout);
1957
- return response !== "timeout" && response.type === "Success";
1958
- }
1959
- async dump() {
1960
- return {
1961
- type: "WebRtc",
1962
- id: this.id,
1963
- address: this.address,
1964
- url: this.url.toString(),
1965
- closed: this.closed,
1966
- audioChannel: {
1967
- readyState: this._AudioChannel.readyState,
1968
- bufferedAmount: this._AudioChannel.bufferedAmount
1969
- },
1970
- datagrams: {
1971
- sent: this.connectionStats.packetsSent,
1972
- received: this.connectionStats.packetsReceived
1973
- },
1974
- webRtc: {
1975
- signalingState: this.webRtc.signalingState,
1976
- connectionState: this.webRtc.connectionState,
1977
- iceGatheringState: this.webRtc.iceGatheringState,
1978
- iceConnectionState: this.webRtc.iceConnectionState
1979
- }
1980
- };
1981
- }
1982
- async _Request(name, properties, timeout) {
1983
- const start = Date.now();
1984
- const result = await (timeout === void 0 ? this.connection.request(name, properties) : this.connection.request(name, properties, timeout));
1985
- const duration = (Date.now() - start) / 1e3;
1986
- if (name !== "Ping") {
1987
- debug(`sent main rpc '${name}'; took: ${duration}s`, {
1988
- properties,
1989
- timeout,
1990
- result
1991
- });
1992
- }
1993
- if (result === "closed") return (0, import_odin_common12.failure)("connection lost");
1994
- return result;
1995
- }
1996
- async _readWebRtcUpdate() {
1997
- while (true) {
1998
- const event = await this.connection.recv();
1999
- if (event === "closed") break;
2000
- if (event.name === "WebRtcUpdate") {
2001
- this._OnWebRtcUpdate(event.properties);
2002
- }
2003
- }
2004
- this.close();
2005
- }
2006
- async _SendOffer() {
2007
- try {
2008
- const offer = await this.webRtc.createOffer();
2009
- (0, import_odin_common12.assert)(
2010
- offer.type === "offer" && offer.sdp,
2011
- "generated broken webrtc offer"
2012
- );
2013
- await this.webRtc.setLocalDescription(offer);
2014
- const sdp = offer.sdp ?? "";
2015
- const media_map = [];
2016
- for (const [media_id, { transceiver }] of this._LocalVideos) {
2017
- media_map.push([media_id, transceiver.mid]);
2018
- }
2019
- await this._Request("WebRtcUpdate", {
2020
- kind: "Sdp",
2021
- type: "Offer",
2022
- sdp,
2023
- media_map
2024
- });
2025
- } catch (e) {
2026
- error("failed to create sdp offer");
2027
- }
2028
- }
2029
- _OnWebRtcStatusChanged() {
2030
- debug(`${this.webRtc.connectionState}`);
2031
- switch (this.webRtc.connectionState) {
2032
- case "closed":
2033
- case "failed":
2034
- this.close();
2035
- break;
2036
- case "connected":
2037
- clearTimeout(this._WebRtcIceTimeout);
2038
- this._WebRtcIceTimeout = void 0;
2039
- clearTimeout(this._WebRtcTimeout);
2040
- this._WebRtcTimeout = void 0;
2041
- break;
2042
- case "disconnected":
2043
- if (config_exports.MainConnectionPingInterval !== void 0) {
2044
- this.healthCheck().then();
2045
- }
2046
- // falls through
2047
- default:
2048
- if (this._WebRtcIceTimeout === void 0) {
2049
- this._WebRtcIceTimeout = setTimeout(() => {
2050
- debug("_WebRtcIceTimeout was reached");
2051
- this.webRtc.restartIce();
2052
- this._WebRtcIceTimeout = void 0;
2053
- }, config_exports.WebRtcIceTimeout);
2054
- }
2055
- if (this._WebRtcTimeout === void 0) {
2056
- this._WebRtcTimeout = setTimeout(() => {
2057
- debug("_WebRtcTimeout was reached");
2058
- this.close();
2059
- }, config_exports.WebRtcTimeout);
2060
- }
2061
- break;
2062
- }
2063
- }
2064
- _OnWebRtcUpdate(update) {
2065
- switch (update.kind) {
2066
- case "Sdp":
2067
- this._UpdateRemoteSdp(update);
2068
- break;
2069
- case "Trickle":
2070
- this._AddIceCandidate(update);
2071
- break;
2072
- case "TrickleFinished":
2073
- this.webRtc.addIceCandidate(void 0);
2074
- break;
2075
- }
2076
- }
2077
- async _UpdateRemoteSdp(update) {
2078
- for (const [media_id, mid] of update.media_map) {
2079
- const entry = this._RemoteVideos.get(media_id);
2080
- if (entry?.state === "Requested") {
2081
- this._RemoteVideos.set(media_id, {
2082
- state: "Mapped",
2083
- mid,
2084
- promise: entry.promise
2085
- });
2086
- }
2087
- }
2088
- const type = update.type === "Answer" ? "answer" : "offer";
2089
- this._ResolveOffer?.(true);
2090
- await this.webRtc.setRemoteDescription({ type, sdp: update.sdp });
2091
- }
2092
- _AddIceCandidate(candidate) {
2093
- const candidateInit = {
2094
- candidate: candidate.candidate,
2095
- sdpMid: candidate.spd_mid ?? void 0,
2096
- sdpMLineIndex: candidate.spd_mline_index ?? void 0,
2097
- usernameFragment: candidate.username_fragment ?? void 0
2098
- };
2099
- if (candidateInit.sdpMid === void 0 && candidateInit.sdpMLineIndex === void 0) {
2100
- candidateInit.sdpMid = "0";
2101
- }
2102
- this.webRtc.addIceCandidate(candidateInit);
2103
- }
2104
- async _OnIceCandidate(event) {
2105
- if (event.candidate) {
2106
- const { candidate, sdpMid, sdpMLineIndex, usernameFragment } = event.candidate;
2107
- this._Request("WebRtcUpdate", {
2108
- kind: "Trickle",
2109
- candidate,
2110
- spd_mid: sdpMid ?? void 0,
2111
- spd_mline_index: sdpMLineIndex ?? void 0,
2112
- username_fragment: usernameFragment ?? void 0
2113
- });
2114
- } else {
2115
- this._Request("WebRtcUpdate", { kind: "TrickleFinished" });
2116
- }
2117
- }
2118
- /**
2119
- * Creates the remote transceivers because the locals are already created in startLocalVideo().
2120
- * Find out which tranceiver is getting reused (Probably it gets currently closed forever).
2121
- *
2122
- * @private
2123
- */
2124
- ensureTransceivers() {
2125
- const needed = this._LocalVideos.size + this._RemoteVideos.size;
2126
- const available = this.webRtc.getTransceivers().length;
2127
- for (let i = available; i < needed; i++) {
2128
- this.webRtc.addTransceiver("video");
2129
- }
2130
- }
2131
- async startLocalVideo(mediaId, track, codec = "H264") {
2132
- (0, import_odin_common12.assert)(
2133
- !this._LocalVideos.has(mediaId),
2134
- `No LocalVideo with mediaID ${mediaId}`
2135
- );
2136
- debug("mediaId", mediaId);
2137
- debug("webRtc.getTransceivers()", this.webRtc.getTransceivers());
2138
- let transceiver = this.webRtc.getTransceivers().find((transceiver2) => transceiver2.sender.track === track);
2139
- if (transceiver) {
2140
- debug("Transceiver found -> replaceTrack");
2141
- await transceiver.sender.replaceTrack(track);
2142
- } else {
2143
- const sender = this.webRtc.addTrack(track);
2144
- transceiver = this.webRtc.getTransceivers().find((transceiver2) => transceiver2.sender === sender);
2145
- (0, import_odin_common12.assert)(transceiver, "");
2146
- }
2147
- const videoCodec = new import_odin_common12.VideoCodec(codec);
2148
- const supportedCodec = videoCodec.isSupported();
2149
- if (supportedCodec) {
2150
- transceiver.setCodecPreferences([supportedCodec]);
2151
- }
2152
- this._LocalVideos.set(mediaId, { transceiver });
2153
- this.requestNegotiation(false);
2154
- }
2155
- stopLocalVideo(mediaId) {
2156
- const localVideo = this._LocalVideos.get(mediaId);
2157
- if (!localVideo) return;
2158
- localVideo.transceiver.sender.track.enabled = false;
2159
- this._LocalVideos.delete(mediaId);
2160
- this.requestNegotiation(false);
2161
- }
2162
- startRemoteVideo(mediaId, codec = "H264") {
2163
- info(`starting remote video for media ${mediaId}`);
2164
- const transceiver = this.webRtc.addTransceiver("video");
2165
- const videoCodec = new import_odin_common12.VideoCodec(codec);
2166
- const supportedCodec = videoCodec.isSupported();
2167
- if (supportedCodec) {
2168
- transceiver.setCodecPreferences([supportedCodec]);
2169
- }
2170
- this.requestNegotiation(true);
2171
- return new Promise((resolve, reject) => {
2172
- this._RemoteVideos.set(mediaId, {
2173
- state: "Requested",
2174
- promise: { resolve, reject }
2175
- });
2176
- });
2177
- }
2178
- /**
2179
- * @param mediaId
2180
- */
2181
- stopRemoteVideo(mediaId) {
2182
- info(`stopping remote video for media ${mediaId}`);
2183
- const video = this._RemoteVideos.get(mediaId);
2184
- if (!video) return;
2185
- this._RemoteVideos.delete(mediaId);
2186
- if (video.state !== "Active") {
2187
- video.promise.reject(new Error("video stopped"));
2188
- }
2189
- this.requestNegotiation(false);
2190
- }
2191
- onTrack(event) {
2192
- debug("event.transceiver", event.transceiver);
2193
- debug("webRtc.getTransceivers()", this.webRtc.getTransceivers());
2194
- debug("_RemoteVideos", this._RemoteVideos);
2195
- const entry = Array.from(this._RemoteVideos).find((video2) => {
2196
- return video2[1].state === "Mapped" && video2[1].mid === event.transceiver.mid;
2197
- });
2198
- debug("Found the RemoteVideo", entry);
2199
- if (entry === void 0) {
2200
- warn(`Unexpected track(mid: ${event.transceiver.mid})`);
2201
- return;
2202
- }
2203
- const [media_id, video] = entry;
2204
- (0, import_odin_common12.assert)(video.state === "Mapped", "");
2205
- this._RemoteVideos.set(media_id, {
2206
- state: "Active",
2207
- transceiver: event.transceiver
2208
- });
2209
- event.track.onended = () => debug(`Track ended ${event.transceiver.mid}`, entry);
2210
- video.promise.resolve(event.track);
2211
- }
2212
- requestNegotiation(waitForAnswer) {
2213
- this._Negotiation.requested = true;
2214
- if (this._Negotiation.running) return;
2215
- this._Negotiation.running = true;
2216
- setTimeout(async () => {
2217
- this._Negotiation.requested = false;
2218
- try {
2219
- await this._SendOffer();
2220
- if (waitForAnswer) {
2221
- await new Promise((resolve) => {
2222
- this._ResolveOffer = resolve;
2223
- });
2224
- }
2225
- } finally {
2226
- this._Negotiation.running = false;
2227
- const retry = Array.from(this._RemoteVideos).find((video) => {
2228
- return video[1].state === "Requested";
2229
- });
2230
- if (this._Negotiation.requested) {
2231
- this.requestNegotiation(waitForAnswer);
2232
- }
2233
- }
2234
- });
2235
- }
2236
- _CleanUp() {
2237
- debug(`performing cleanup for connection ${this.id} to '${this.url}'`);
2238
- this.connection.close();
2239
- this.webRtc.close();
2240
- clearInterval(this._PingIntervalId);
2241
- clearTimeout(this._WebRtcIceTimeout);
2242
- clearTimeout(this._WebRtcTimeout);
2243
- }
2244
- addDataChannelToGlobal(dataChannel) {
2245
- if (typeof globalThis.dataChannels === "undefined") {
2246
- globalThis.dataChannels = [];
2247
- }
2248
- if (Array.isArray(globalThis.dataChannels) && globalThis.dataChannels.length <= 100) {
2249
- globalThis.dataChannels.push(dataChannel);
2250
- }
2251
- }
2252
- addWebRtcToGlobal(webRtc) {
2253
- if (typeof globalThis.webRtcConnections === "undefined") {
2254
- globalThis.webRtcConnections = [];
2255
- }
2256
- if (Array.isArray(globalThis.webRtcConnections) && globalThis.webRtcConnections.length <= 100) {
2257
- globalThis.webRtcConnections.push(webRtc);
2258
- }
2259
- }
2260
- };
2261
-
2262
- // src/plugin/mainConnection.ts
2263
- function createMainConnection(id, transport, address, onDatagram, signal) {
2264
- info(`creating ${transport} main connection to '${address}'`);
2265
- switch (transport) {
2266
- case "H3":
2267
- return H3MainConnection.create(id, address, onDatagram, signal);
2268
- case "WebRTC":
2269
- return WebRtcMainConnection.create(id, address, onDatagram, signal);
2270
- default:
2271
- throw new Error(`transport ${transport} is not supported`);
2272
- }
2273
- }
2274
-
2275
- // src/plugin/encrypted.ts
2276
- var import_odin_common14 = require("@4players/odin-common");
2277
- var EncryptedConnection = class {
2278
- constructor(id, connection, cipher, mediaMap) {
2279
- this.id = id;
2280
- this.connection = connection;
2281
- this.cipher = cipher;
2282
- this.mediaMap = mediaMap;
2283
- this._DatagramReceived = 0;
2284
- this._DatagramSent = 0;
2285
- this._DatagramReceivedInvalid = 0;
2286
- this._DatagramSentInvalid = 0;
2287
- connection.closeSignal.addEventListener("abort", () => this.close());
2288
- }
2289
- get connectionStats() {
2290
- return this.connection.connectionStats;
2291
- }
2292
- get transport() {
2293
- return this.connection.transport;
2294
- }
2295
- get address() {
2296
- return this.connection.address;
2297
- }
2298
- get status() {
2299
- return this.closed ? 2 /* Closed */ : this.connection.status;
2300
- }
2301
- get closePromise() {
2302
- return this.connection.closePromise;
2303
- }
2304
- get closeSignal() {
2305
- return this.connection.closeSignal;
2306
- }
2307
- get closed() {
2308
- return this.connection.closed;
2309
- }
2310
- waitForConnected() {
2311
- return this.connection.waitForConnected();
2312
- }
2313
- close() {
2314
- this.connection.close();
2315
- }
2316
- async joinRoom(token, roomId, userData, position) {
2317
- this.cipher.reset();
2318
- const encryptedUserData = await this.cipher.encryptUserData(userData);
2319
- if (encryptedUserData === Invalid) {
2320
- return (0, import_odin_common14.failure)("unable to encrypt initial user-data");
2321
- }
2322
- const result = await this.connection.joinRoom(
2323
- token,
2324
- roomId,
2325
- encryptedUserData,
2326
- position
2327
- );
2328
- if (result === "closed" || result.type === "Failure") return result;
2329
- return (0, import_odin_common14.success)(new EncryptedRpcStream(result.value, this.cipher, this));
2330
- }
2331
- getReconnectToken(peerId) {
2332
- return this.connection.getReconnectToken(peerId);
2333
- }
2334
- healthCheck() {
2335
- return this.connection.healthCheck();
2336
- }
2337
- async sendDatagram(datagram) {
2338
- const encrypted = await this.cipher.encryptDatagram(datagram);
2339
- if (encrypted !== Invalid) {
2340
- this._DatagramSent += 1;
2341
- this.connection.sendDatagram(encrypted);
2342
- } else {
2343
- this._DatagramSentInvalid += 1;
2344
- }
2345
- }
2346
- async decryptDatagram(peerId, ciphertext) {
2347
- const result = await this.cipher.decryptDatagram(peerId, ciphertext);
2348
- if (result !== Invalid) {
2349
- this._DatagramReceived += 1;
2350
- } else {
2351
- this._DatagramReceivedInvalid += 1;
2352
- }
2353
- return result;
2354
- }
2355
- startLocalVideo(mediaId, track) {
2356
- return this.connection.startLocalVideo(mediaId, track);
2357
- }
2358
- stopLocalVideo(mediaId) {
2359
- return this.connection.stopLocalVideo(mediaId);
2360
- }
2361
- startRemoteVideo(mediaId) {
2362
- return this.connection.startRemoteVideo(mediaId);
2363
- }
2364
- stopRemoteVideo(mediaId) {
2365
- return this.connection.stopRemoteVideo(mediaId);
2366
- }
2367
- async dump() {
2368
- return {
2369
- type: "Encrypted",
2370
- id: this.id,
2371
- address: this.address,
2372
- connectionId: this.connection.id,
2373
- closed: this.closed,
2374
- transport: this.transport,
2375
- datagrams: {
2376
- sent: this._DatagramSent,
2377
- received: this._DatagramReceived,
2378
- sentInvalid: this._DatagramSentInvalid,
2379
- receivedInvalid: this._DatagramReceivedInvalid
2380
- },
2381
- cipher: await this.cipher.dump()
2382
- };
2383
- }
2384
- };
2385
- var CipherActions = class {
2386
- constructor(room) {
2387
- this.room = room;
2388
- }
2389
- resendUserData() {
2390
- this.room.roomConnection?.notify("UpdatePeer", {
2391
- user_data: this.room.roomConnection.userData
2392
- });
2393
- }
2394
- setUserData(peerId, userData) {
2395
- const parameters = {
2396
- kind: "UserDataChanged",
2397
- peer_id: peerId,
2398
- user_data: userData
2399
- };
2400
- this.room.onEvent("PeerUpdated", parameters);
2401
- }
2402
- };
2403
- var EncryptedRpcStream = class {
2404
- constructor(rpc, cipher, connection) {
2405
- this.rpc = rpc;
2406
- this.cipher = cipher;
2407
- this.connection = connection;
2408
- this.peers = /* @__PURE__ */ new Map();
2409
- this.closeSignal.addEventListener("abort", this.cleanup.bind(this), {
2410
- once: true
2411
- });
2412
- }
2413
- get closed() {
2414
- return this.rpc.closed;
2415
- }
2416
- get closeSignal() {
2417
- return this.rpc.closeSignal;
2418
- }
2419
- async request(name, properties, timeout) {
2420
- const updatedProperties = await this.outgoing(name, properties);
2421
- if (updatedProperties === Invalid) return (0, import_odin_common14.failure)("can not be encrypted");
2422
- return await this.rpc.request(name, updatedProperties, timeout);
2423
- }
2424
- async notify(name, properties) {
2425
- const updatedProperties = await this.outgoing(name, properties);
2426
- if (updatedProperties !== Invalid) {
2427
- return this.rpc.notify(name, updatedProperties);
2428
- }
2429
- }
2430
- async recv() {
2431
- for (; ; ) {
2432
- const result = await this.rpc.recv();
2433
- if (result === "closed") return result;
2434
- const properties = await this.incoming(result.name, result.properties);
2435
- if (properties !== Invalid) return { name: result.name, properties };
2436
- }
2437
- }
2438
- close() {
2439
- return this.rpc.close();
2440
- }
2441
- async outgoing(name, properties) {
2442
- if (!(name in import_odin_common14.RoomCommandsRpc)) return properties;
2443
- const parsed = parseCommand(name, properties, import_odin_common14.RoomCommandsRpc);
2444
- if (parsed === void 0) return Invalid;
2445
- switch (parsed.name) {
2446
- case "UpdatePeer":
2447
- {
2448
- const userData = await this.cipher.encryptUserData(
2449
- parsed.request.user_data
2450
- );
2451
- if (userData === Invalid) return Invalid;
2452
- parsed.request.user_data = userData;
2453
- }
2454
- break;
2455
- case "SendMessage":
2456
- {
2457
- const message = await this.cipher.encryptMessage(
2458
- parsed.request.message
2459
- );
2460
- if (message === Invalid) return Invalid;
2461
- parsed.request.message = message;
2462
- }
2463
- break;
2464
- }
2465
- return parsed.request;
2466
- }
2467
- async incoming(name, properties) {
2468
- if (!(name in import_odin_common14.RoomNotificationsRpc)) return properties;
2469
- const parsed = parseNotification(name, properties, import_odin_common14.RoomNotificationsRpc);
2470
- if (parsed === Invalid) return Invalid;
2471
- switch (parsed.name) {
2472
- case "RoomUpdated":
2473
- for (let i = 0; i < parsed.properties.updates.length; i++) {
2474
- const update = await this.roomUpdate(parsed.properties.updates[i]);
2475
- if (update === Invalid) return Invalid;
2476
- parsed.properties.updates[i] = update;
2477
- }
2478
- break;
2479
- case "PeerUpdated":
2480
- {
2481
- const update = await this.peerUpdate(parsed.properties);
2482
- if (update === Invalid) return Invalid;
2483
- parsed.properties = update;
2484
- }
2485
- break;
2486
- case "MessageReceived":
2487
- {
2488
- const message = await this.cipher.decryptMessage(
2489
- parsed.properties.sender_peer_id,
2490
- parsed.properties.message
2491
- );
2492
- if (message === Invalid) return Invalid;
2493
- parsed.properties.message = message;
2494
- }
2495
- break;
2496
- }
2497
- return parsed.properties;
2498
- }
2499
- async peerUpdate(update) {
2500
- switch (update.kind) {
2501
- case "UserDataChanged":
2502
- {
2503
- const userData = await this.cipher.decryptUserData(
2504
- update.peer_id,
2505
- update.user_data
2506
- );
2507
- if (userData === Invalid) return Invalid;
2508
- update.user_data = userData;
2509
- }
2510
- break;
2511
- case "MediaStarted":
2512
- this.mediaStarted(update.peer_id, update.media.id);
2513
- break;
2514
- case "MediaStopped":
2515
- this.mediaStopped(update.peer_id, update.media_id);
2516
- break;
2517
- }
2518
- return update;
2519
- }
2520
- async roomUpdate(update) {
2521
- switch (update.kind) {
2522
- case "Joined":
2523
- for (const peer of update.room.peers) {
2524
- for (const media of peer.medias) {
2525
- this.mediaStarted(peer.id, media.id);
2526
- }
2527
- const userData = await this.cipher.decryptUserData(
2528
- peer.id,
2529
- peer.user_data
2530
- );
2531
- if (userData !== Invalid) {
2532
- peer.user_data = userData;
2533
- } else {
2534
- peer.user_data = new Uint8Array();
2535
- }
2536
- }
2537
- break;
2538
- case "PeerJoined":
2539
- {
2540
- for (const media of update.peer.medias) {
2541
- this.mediaStarted(update.peer.id, media.id);
2542
- }
2543
- const userData = await this.cipher.decryptUserData(
2544
- update.peer.id,
2545
- update.peer.user_data
2546
- );
2547
- if (userData !== Invalid) {
2548
- update.peer.user_data = userData;
2549
- } else {
2550
- update.peer.user_data = new Uint8Array();
2551
- }
2552
- }
2553
- break;
2554
- case "PeerLeft":
2555
- {
2556
- const mediaIds = this.peers.get(update.peer_id);
2557
- this.peers.delete(update.peer_id);
2558
- for (const mediaId of mediaIds ?? []) {
2559
- this.mediaStopped(update.peer_id, mediaId);
2560
- }
2561
- }
2562
- break;
2563
- }
2564
- return update;
2565
- }
2566
- mediaStarted(peerId, mediaId) {
2567
- let medias = this.peers.get(peerId);
2568
- if (medias === void 0) {
2569
- medias = /* @__PURE__ */ new Set();
2570
- this.peers.set(peerId, medias);
2571
- }
2572
- medias.add(mediaId);
2573
- const mergedId = mergePlaybackId({
2574
- connectionId: this.connection.connection.id,
2575
- mediaId
2576
- });
2577
- this.connection.mediaMap.set(mergedId, {
2578
- peerId,
2579
- connection: this.connection
2580
- });
2581
- }
2582
- mediaStopped(peerId, mediaId) {
2583
- const medias = this.peers.get(peerId);
2584
- medias?.delete(mediaId);
2585
- const mergedId = mergePlaybackId({
2586
- connectionId: this.connection.connection.id,
2587
- mediaId
2588
- });
2589
- this.connection.mediaMap.delete(mergedId);
2590
- }
2591
- cleanup() {
2592
- for (const [peerId, mediaIds] of this.peers) {
2593
- for (const mediaId of mediaIds) {
2594
- this.mediaStopped(peerId, mediaId);
2595
- }
2596
- }
2597
- }
2598
- };
2599
- function parseNotification(name, properties, schema) {
2600
- if (!(name in schema)) return Invalid;
2601
- const parsed = schema[name].safeParse(properties);
2602
- return parsed.success ? { name, properties: parsed.data } : Invalid;
2603
- }
2604
- function parseCommand(name, request, schema) {
2605
- if (!(name in schema)) return void 0;
2606
- const parsed = schema[name].request.safeParse(request);
2607
- return parsed.success ? { name, request: parsed.data } : void 0;
2608
- }
2609
-
2610
- // src/plugin/connectionPool.ts
2611
- var ConnectionPool = class {
2612
- constructor(onDatagram) {
2613
- this.onDatagram = onDatagram;
2614
- this._LastConnectionId = 0;
2615
- this._Connections = /* @__PURE__ */ new Map();
2616
- this._Close = new AbortController();
2617
- this._MediaMap = /* @__PURE__ */ new Map();
2618
- let lastCheckAt = Date.now();
2619
- const clockJumpDetector = setInterval(() => {
2620
- const elapsed = Math.abs(Date.now() - lastCheckAt);
2621
- if (elapsed > config_exports.ClockJumpThreshold) this.checkHealth();
2622
- lastCheckAt = Date.now();
2623
- }, config_exports.ClockJumpCheckInterval);
2624
- addEventListener("offline", () => this.checkHealth(), {
2625
- signal: this._Close.signal
2626
- });
2627
- this._Close.signal.addEventListener(
2628
- "abort",
2629
- () => {
2630
- clearInterval(clockJumpDetector);
2631
- for (const entry of this._Connections.values()) {
2632
- if (entry.type === "establishing") {
2633
- entry.abort.abort();
2634
- } else {
2635
- entry.connection.close();
2636
- }
2637
- }
2638
- this._Connections.clear();
2639
- },
2640
- { once: true }
2641
- );
2642
- }
2643
- get closed() {
2644
- return this._Close.signal.aborted;
2645
- }
2646
- sendDatagram(connectionId, packet) {
2647
- const entry = this._Connections.get(connectionId);
2648
- if (entry?.type === "established" || entry?.type === "virtual") {
2649
- entry.connection.sendDatagram(packet);
2650
- }
2651
- }
2652
- async recvDatagram(connectionId, datagram) {
2653
- const mediaIds = getMediaIds(datagram);
2654
- if (mediaIds === void 0 || mediaIds.length === 0) return;
2655
- const entry = this._MediaMap.get(
2656
- mergePlaybackId({ connectionId, mediaId: mediaIds[0] })
2657
- );
2658
- if (entry === void 0) {
2659
- this.onDatagram(connectionId, datagram);
2660
- } else {
2661
- const decrypted = await entry.connection.decryptDatagram(
2662
- entry.peerId,
2663
- datagram
2664
- );
2665
- if (decrypted === Invalid) return;
2666
- this.onDatagram(entry.connection.id, decrypted);
2667
- }
2668
- }
2669
- async acquireConnection(transport, address, cipher, signal) {
2670
- await (0, import_odin_common15.nextTick)();
2671
- if (this.closed) return "closed";
2672
- const entry = this.findConnection(transport, address);
2673
- let established;
2674
- switch (entry?.type) {
2675
- case "established":
2676
- debug(`reusing existing connection ${entry.id}`);
2677
- established = entry;
2678
- break;
2679
- case "establishing":
2680
- {
2681
- debug(`waiting for connecting to '${address}' finish`);
2682
- const result = await entry.finished;
2683
- if (result.type !== "Success") return result;
2684
- established = result.value;
2685
- }
2686
- break;
2687
- case void 0:
2688
- {
2689
- debug(`connecting to '${address}'`);
2690
- const result = await this.createConnection(
2691
- transport,
2692
- address,
2693
- signal
2694
- );
2695
- if (result.type !== "Success") return result;
2696
- established = result.value;
2697
- }
2698
- break;
2699
- }
2700
- const id = established.id;
2701
- let connection = new SharedConnection(
2702
- established.connection,
2703
- () => this.releaseConnection(id)
2704
- );
2705
- established.references += 1;
2706
- if (cipher) {
2707
- connection = new EncryptedConnection(
2708
- ++this._LastConnectionId,
2709
- connection,
2710
- cipher,
2711
- this._MediaMap
2712
- );
2713
- this.addConnection({
2714
- type: "virtual",
2715
- id: connection.id,
2716
- connection,
2717
- createdAt: Date.now()
2718
- });
2719
- }
2720
- return (0, import_odin_common15.success)(connection);
2721
- }
2722
- async createConnection(transport, address, signal) {
2723
- const id = ++this._LastConnectionId;
2724
- const createdAt = Date.now();
2725
- const abortController = new AbortController();
2726
- signal.addEventListener("abort", () => abortController.abort(), {
2727
- once: true
2728
- });
2729
- debug(`creating new connection ${id}`);
2730
- const futureConnection = createMainConnection(
2731
- id,
2732
- transport,
2733
- address,
2734
- this.recvDatagram.bind(this),
2735
- signal
2736
- );
2737
- const { promise, resolve } = Promise.withResolvers();
2738
- this.addConnection({
2739
- type: "establishing",
2740
- id,
2741
- address,
2742
- transport,
2743
- finished: promise,
2744
- createdAt: Date.now(),
2745
- abort: abortController
2746
- });
2747
- const result = await futureConnection;
2748
- this._Connections.delete(id);
2749
- if (result.type !== "Success") {
2750
- resolve(result);
2751
- return result;
2752
- }
2753
- const entry = {
2754
- type: "established",
2755
- id,
2756
- address,
2757
- transport,
2758
- createdAt,
2759
- references: 0,
2760
- connection: result.value
2761
- };
2762
- this.addConnection(entry);
2763
- return (0, import_odin_common15.success)(entry);
2764
- }
2765
- addConnection(entry) {
2766
- if (entry.type === "established" || entry.type === "virtual") {
2767
- entry.connection.closeSignal.addEventListener(
2768
- "abort",
2769
- () => {
2770
- info(`connection ${entry.id} has been closed`);
2771
- this._Connections.delete(entry.id);
2772
- },
2773
- { once: true }
2774
- );
2775
- }
2776
- this._Connections.set(entry.id, entry);
2777
- }
2778
- releaseConnection(id) {
2779
- const entry = this._Connections.get(id);
2780
- if (entry?.type === "established") {
2781
- entry.references -= 1;
2782
- if (entry.references <= 0) {
2783
- entry.connection.close();
2784
- }
2785
- }
2786
- }
2787
- checkHealth() {
2788
- for (const entry of this._Connections.values()) {
2789
- if (entry.type === "established") {
2790
- entry.connection.healthCheck();
2791
- }
2792
- }
2793
- }
2794
- close() {
2795
- this._Close.abort();
2796
- }
2797
- async dump() {
2798
- const connections = [];
2799
- for (const entry of this._Connections.values()) {
2800
- let typeSpecific;
2801
- switch (entry.type) {
2802
- case "established":
2803
- typeSpecific = {
2804
- transport: entry.transport,
2805
- address: entry.address,
2806
- connection: await entry.connection.dump(),
2807
- references: entry.references
2808
- };
2809
- break;
2810
- case "establishing":
2811
- typeSpecific = {
2812
- transport: entry.transport,
2813
- address: entry.address,
2814
- aborted: entry.abort.signal.aborted
2815
- };
2816
- break;
2817
- case "virtual":
2818
- typeSpecific = {
2819
- connection: await entry.connection.dump()
2820
- };
2821
- break;
2822
- }
2823
- connections.push({
2824
- type: entry.type,
2825
- id: entry.id,
2826
- createdAt: entry.createdAt,
2827
- ...typeSpecific
2828
- });
2829
- }
2830
- const mediaMap = [];
2831
- for (const [peerId, entry] of this._MediaMap) {
2832
- mediaMap.push(`${peerId} -> ${entry.peerId} @${entry.connection.id} `);
2833
- }
2834
- return {
2835
- lastConnectionId: this._LastConnectionId,
2836
- connections,
2837
- mediaMap,
2838
- closed: this.closed
2839
- };
2840
- }
2841
- findConnection(transport, address) {
2842
- for (const entry of this._Connections.values()) {
2843
- if (entry.type !== "virtual" && entry.transport === transport && entry.address === address) {
2844
- return entry;
2845
- }
2846
- }
2847
- return void 0;
2848
- }
2849
- };
2850
- var SharedConnection = class {
2851
- constructor(connection, release) {
2852
- this.connection = connection;
2853
- this.release = release;
2854
- this.closeController = new AbortController();
2855
- this.closePromise = new Promise(
2856
- (resolve) => this.closeController.signal.addEventListener(
2857
- "abort",
2858
- () => resolve("closed"),
2859
- { once: true }
2860
- )
2861
- );
2862
- connection.closeSignal.addEventListener("abort", () => this.close(), {
2863
- once: true
2864
- });
2865
- }
2866
- get closeSignal() {
2867
- return this.closeController.signal;
2868
- }
2869
- get closed() {
2870
- return this.closeSignal.aborted;
2871
- }
2872
- close() {
2873
- this.release();
2874
- this.closeController.abort("closed");
2875
- }
2876
- get connectionStats() {
2877
- return this.connection.connectionStats;
2878
- }
2879
- get id() {
2880
- return this.connection.id;
2881
- }
2882
- get transport() {
2883
- return this.connection.transport;
2884
- }
2885
- get address() {
2886
- return this.connection.address;
2887
- }
2888
- get status() {
2889
- return this.connection.status;
2890
- }
2891
- waitForConnected() {
2892
- return this.connection.waitForConnected();
2893
- }
2894
- joinRoom(token, roomId, userData, position) {
2895
- return this.connection.joinRoom(token, roomId, userData, position);
2896
- }
2897
- getReconnectToken(peerId) {
2898
- return this.connection.getReconnectToken(peerId);
2899
- }
2900
- healthCheck() {
2901
- return this.connection.healthCheck();
2902
- }
2903
- sendDatagram(datagram) {
2904
- return this.connection.sendDatagram(datagram);
2905
- }
2906
- startLocalVideo(mediaId, track) {
2907
- return this.connection.startLocalVideo(mediaId, track);
2908
- }
2909
- stopLocalVideo(mediaId) {
2910
- return this.connection.stopLocalVideo(mediaId);
2911
- }
2912
- startRemoteVideo(mediaId) {
2913
- return this.connection.startRemoteVideo(mediaId);
2914
- }
2915
- stopRemoteVideo(mediaId) {
2916
- return this.connection.stopRemoteVideo(mediaId);
2917
- }
2918
- async dump() {
2919
- let dump = await this.connection.dump();
2920
- if (typeof dump === "object") {
2921
- dump = {
2922
- ...dump,
2923
- isShared: true,
2924
- referenceClosed: this.closed
2925
- };
2926
- }
2927
- return dump;
2928
- }
2929
- };
2930
- function getMediaIds(datagram) {
2931
- let packet;
2932
- try {
2933
- packet = (0, import_msgpack2.decode)(datagram);
2934
- } catch {
2935
- return void 0;
2936
- }
2937
- if (!Array.isArray(packet) || packet.length < 2) return void 0;
2938
- const mediaIds = packet[1];
2939
- if (!Array.isArray(mediaIds)) return void 0;
2940
- return mediaIds.map(Number);
2941
- }
2942
-
2943
- // src/plugin/medias/video-playback.ts
2944
- var VideoPlayback = class {
2945
- constructor(uid, customType) {
2946
- this.uid = uid;
2947
- this.customType = customType;
2948
- }
2949
- setMediaStream(ms) {
2950
- if (this._mediaStream) return;
2951
- this._mediaStream = ms;
2952
- }
2953
- get mediaStream() {
2954
- return this._mediaStream;
2955
- }
2956
- close() {
2957
- throw new Error("Method not implemented.");
2958
- }
2959
- };
2960
-
2961
- // src/plugin/room.ts
2962
- var import_odin_common21 = require("@4players/odin-common");
2963
-
2964
- // src/plugin/roomConnection.ts
2965
- var import_odin_common16 = require("@4players/odin-common");
2966
- var RoomConnection = class {
2967
- constructor(connectionPool, transport, onEvent, gateway, token, roomId, userData, position, cipher) {
2968
- this.connectionPool = connectionPool;
2969
- this.transport = transport;
2970
- this.onEvent = onEvent;
2971
- this.gateway = gateway;
2972
- this.roomId = roomId;
2973
- this.cipher = cipher;
2974
- this._Close = new AbortController();
2975
- this._TokenUsed = false;
2976
- this._WasConnected = false;
2977
- this._FailedConnections = 0;
2978
- this._LastTokenRenew = performance.now();
2979
- this._LastReconnectStartedAt = 0;
2980
- this._Closed = new Promise(
2981
- (accept) => this._Close.signal.addEventListener(
2982
- "abort",
2983
- () => accept(this._Close.signal.reason ? "disconnected" : "closed"),
2984
- {
2985
- once: true
2986
- }
2987
- )
2988
- );
2989
- this._Token = token;
2990
- this._UserData = userData;
2991
- this._Position = position;
2992
- this._Main().then(({ reason }) => debug(`_Main() resolved; ${reason}`));
2993
- }
2994
- get token() {
2995
- return this._Token;
2996
- }
2997
- get userData() {
2998
- return this._UserData;
2999
- }
3000
- get position() {
3001
- return this._Position;
3002
- }
3003
- get closed() {
3004
- return this._Close.signal.aborted;
3005
- }
3006
- get mainConnection() {
3007
- return this._MainConnection;
3008
- }
3009
- async requestUnsafe(name, properties) {
3010
- if (this._RoomConnection === void 0) {
3011
- return (0, import_odin_common16.failure)("currently not connected");
3012
- }
3013
- const response = await this._RoomConnection.request(
3014
- name,
3015
- properties,
3016
- void 0
3017
- );
3018
- (0, import_odin_common16.assert)(response !== "timeout", "request should not timeout");
3019
- if (response !== "closed" && response.type === "Success") {
3020
- this._HandleRequests(name, properties, response.value);
3021
- }
3022
- return response;
3023
- }
3024
- async request(name, properties) {
3025
- const start = Date.now();
3026
- const schema = import_odin_common16.RoomCommandsRpc[name];
3027
- const result = await this.requestUnsafe(name, properties);
3028
- const duration = (Date.now() - start) / 1e3;
3029
- debug(`sent room rpc '${name}'; took: ${duration}s`, {
3030
- properties,
3031
- result
3032
- });
3033
- if (result === "closed" || result.type === "Failure") return result;
3034
- const parsed = schema.response.safeParse(result.value);
3035
- if (parsed.success) return (0, import_odin_common16.success)(parsed.data);
3036
- else {
3037
- return (0, import_odin_common16.failure)(`received invalid response from ${name}; ${parsed.error}`);
3038
- }
3039
- }
3040
- notify(name, properties) {
3041
- if (this._RoomConnection !== void 0) {
3042
- this._RoomConnection.notify(name, properties);
3043
- }
3044
- }
3045
- close() {
3046
- this._Close.abort(true);
3047
- }
3048
- get renewTokenIn() {
3049
- const earliest = this._LastTokenRenew - performance.now() + config_exports.MinTokenRefreshInterval;
3050
- const latest = this._Token.timeLeft - config_exports.MinTokenLifetime;
3051
- return Math.max(earliest, latest);
3052
- }
3053
- async _Main() {
3054
- let closeReason = "unable to establish connection";
3055
- for (let nextTryAt = 0; ; this._FailedConnections++) {
3056
- await (0, import_odin_common16.sleep)(Math.max(nextTryAt - performance.now(), 0));
3057
- nextTryAt = performance.now() + 1e3;
3058
- this._TokenUsed = true;
3059
- if (this._Token.hasAudience("sfu")) {
3060
- const result = await this._ConnectToServer();
3061
- if (result.type === "Success" && result.value === "shutdown") {
3062
- debug("connection closed by user request; shutting down");
3063
- closeReason = "user requested";
3064
- break;
3065
- }
3066
- if (this._LastReconnectStartedAt === 0) {
3067
- this._LastReconnectStartedAt = performance.now();
3068
- }
3069
- if (result.type === "Failure" && !this._WasConnected && this._FailedConnections >= 3) {
3070
- closeReason = result.reason;
3071
- break;
3072
- }
3073
- if (result.type === "Failure" && !isTimeLimitReached(this._LastReconnectStartedAt)) {
3074
- continue;
3075
- }
3076
- }
3077
- this.reset();
3078
- if (this.gateway !== void 0 && this._Token.hasAudience("gateway")) {
3079
- this._ChangeStatus(
3080
- "Joining",
3081
- `requesting new token from ${this.gateway}`
3082
- );
3083
- const newToken = await contactGateway(
3084
- this.gateway,
3085
- this._Token,
3086
- this._Close.signal
3087
- );
3088
- if (newToken.type === "Success") {
3089
- if (newToken.value instanceof Token) {
3090
- nextTryAt = 0;
3091
- this._Token = newToken.value;
3092
- this._TokenUsed = false;
3093
- } else {
3094
- closeReason = `gateway error; ${newToken.value}`;
3095
- break;
3096
- }
3097
- } else {
3098
- if (!this._WasConnected && this._FailedConnections >= 3) {
3099
- closeReason = "unable to contact the gateway";
3100
- break;
3101
- }
3102
- }
3103
- }
3104
- if (this._TokenUsed && this._Token.timeLeft <= 0) {
3105
- closeReason = "token has expired";
3106
- break;
3107
- }
3108
- if (this._Close.signal.aborted) {
3109
- closeReason = "user requested";
3110
- break;
3111
- }
3112
- }
3113
- this._ChangeStatus("Closed", closeReason);
3114
- this.close();
3115
- return (0, import_odin_common16.failure)(closeReason);
3116
- }
3117
- async _ConnectToServer() {
3118
- let token = this._Token;
3119
- while (token.upstream !== void 0) {
3120
- token = token.upstream;
3121
- }
3122
- const address = token.claims.adr ?? this.gateway?.toString();
3123
- if (!address) {
3124
- return (0, import_odin_common16.success)("continue");
3125
- }
3126
- this._ChangeStatus("Joining", `connecting to ${token.claims.adr}`);
3127
- if (this._MainConnection === void 0 || this._MainConnection.closed || this._MainConnection.address !== token.claims.adr) {
3128
- this._MainConnection?.close();
3129
- const newConnection = await this.connectionPool.acquireConnection(
3130
- this.transport,
3131
- address,
3132
- this.cipher,
3133
- this._Close.signal
3134
- );
3135
- if (newConnection === "closed") return (0, import_odin_common16.failure)("connection pool closed");
3136
- if (newConnection.type === "Failure") {
3137
- this._ChangeStatus(
3138
- "Joining",
3139
- `connection failed; ${newConnection.reason}`
3140
- );
3141
- return (0, import_odin_common16.failure)(`connection failed; ${newConnection.reason}`);
3142
- }
3143
- this._MainConnection = newConnection.value;
3144
- this._OwnPeerId = void 0;
3145
- }
3146
- const result = await Promise.race([
3147
- Promise.all([
3148
- this._MainConnection.joinRoom(
3149
- token,
3150
- this.roomId,
3151
- this.userData,
3152
- this.position
3153
- )
3154
- ]),
3155
- (0, import_odin_common16.sleep)(config_exports.ConnectTimeout, "timeout"),
3156
- this._Closed
3157
- ]);
3158
- if (result === "closed" || result === "disconnected") {
3159
- return (0, import_odin_common16.success)("continue");
3160
- }
3161
- if (result === "timeout") {
3162
- this._ChangeStatus("Joining", "timed out");
3163
- return (0, import_odin_common16.success)("continue");
3164
- }
3165
- const [rpc] = result;
3166
- if (rpc === "closed") return (0, import_odin_common16.success)("continue");
3167
- if (rpc.type === "Failure") {
3168
- return (0, import_odin_common16.failure)(`unable to join room; ${rpc.reason}`);
3169
- }
3170
- this._RoomConnection = rpc.value;
3171
- this._WasConnected = true;
3172
- this._FailedConnections = 0;
3173
- this._ChangeStatus("Joined", `joined ${this.roomId}`);
3174
- try {
3175
- await this._MainConnection.waitForConnected();
3176
- const shutdown = await this._HandleConnection(this._MainConnection);
3177
- return (0, import_odin_common16.success)(shutdown ? "shutdown" : "continue");
3178
- } catch (e) {
3179
- return (0, import_odin_common16.failure)(`unable to handle connection; ${e}`);
3180
- }
3181
- }
3182
- _ChangeStatus(status, message) {
3183
- const properties = {
3184
- status,
3185
- message
3186
- };
3187
- this.onEvent("RoomStatusChanged", properties);
3188
- }
3189
- async _HandleConnection(main) {
3190
- if (this._RoomConnection === void 0) return false;
3191
- const selector = new import_odin_common16.Selector([
3192
- () => (0, import_odin_common16.sleep)(this.renewTokenIn, "renewToken"),
3193
- () => (0, import_odin_common16.sleep)(500, "tick"),
3194
- async () => await this._RoomConnection?.recv(),
3195
- () => this._Closed,
3196
- () => main.closePromise
3197
- ]);
3198
- while (true) {
3199
- const value = await selector.next();
3200
- switch (value) {
3201
- case void 0:
3202
- case "closed":
3203
- this._RoomConnection?.close();
3204
- this._MainConnection?.close();
3205
- return false;
3206
- case "disconnected":
3207
- this._RoomConnection?.close();
3208
- this._MainConnection?.close();
3209
- return true;
3210
- case "renewToken":
3211
- this._RenewToken();
3212
- break;
3213
- case "tick":
3214
- break;
3215
- default:
3216
- this._HandleEvent(value);
3217
- break;
3218
- }
3219
- }
3220
- }
3221
- _HandleEvent(event) {
3222
- if (event.name === "RoomUpdated") {
3223
- const roomUpdated = import_odin_common16.RoomNotificationsRpc.RoomUpdated.safeParse(
3224
- event.properties
3225
- );
3226
- if (!roomUpdated.success) return;
3227
- for (const update of roomUpdated.data.updates) {
3228
- switch (update.kind) {
3229
- case "Joined":
3230
- this._OwnPeerId = update.own_peer_id;
3231
- break;
3232
- }
3233
- }
3234
- }
3235
- this.onEvent(event.name, event.properties);
3236
- }
3237
- _HandleRequests(name, properties, response) {
3238
- switch (name) {
3239
- case "SetPeerPosition": {
3240
- const setPeerPosition = import_odin_common16.RoomCommandsRpc.SetPeerPosition.request.safeParse(properties);
3241
- if (setPeerPosition.success) {
3242
- const value = setPeerPosition.data.position;
3243
- this._Position = value.length === 2 ? [value[0], value[1], 0] : value;
3244
- } else {
3245
- warn("unable to process SetPeerPosition command");
3246
- }
3247
- break;
3248
- }
3249
- case "UpdatePeer": {
3250
- const updatePeer = import_odin_common16.RoomCommandsRpc.UpdatePeer.request.safeParse(properties);
3251
- if (updatePeer.success) {
3252
- this._UserData = updatePeer.data.user_data;
3253
- } else {
3254
- warn("unable to process UpdatePeer command");
3255
- }
3256
- break;
3257
- }
3258
- }
3259
- }
3260
- async _RenewToken(force) {
3261
- this._LastTokenRenew = performance.now();
3262
- if (!force && this.token.timeLeft > config_exports.MinTokenLifetime) return;
3263
- if (this._MainConnection === void 0 || this._OwnPeerId === void 0) {
3264
- return (0, import_odin_common16.failure)("connection is not established");
3265
- }
3266
- const response = await this._MainConnection.getReconnectToken(
3267
- this._OwnPeerId
3268
- );
3269
- if (response.type === "Success") {
3270
- this._Token = response.value;
3271
- return (0, import_odin_common16.success)();
3272
- } else {
3273
- return (0, import_odin_common16.failure)(
3274
- `${this._MainConnection.address} rejected reconnect token request; ${response.reason}`
3275
- );
3276
- }
3277
- }
3278
- reset() {
3279
- this._LastReconnectStartedAt = 0;
3280
- this._RoomConnection?.close();
3281
- this._RoomConnection = void 0;
3282
- this._MainConnection?.close();
3283
- this._MainConnection = void 0;
3284
- this._OwnPeerId = void 0;
3285
- }
3286
- };
3287
- async function contactGateway(gateway, token, signal) {
3288
- try {
3289
- const payload = { jsonrpc: "2.0", id: 1, method: "Connect", params: {} };
3290
- info(`sending authentication request to '${gateway}'`, payload);
3291
- const response = await fetch(gateway, {
3292
- body: JSON.stringify(payload),
3293
- method: "POST",
3294
- headers: {
3295
- "content-type": "application/json",
3296
- authorization: `Bearer ${token}`
3297
- },
3298
- signal
3299
- });
3300
- const body = await response.json();
3301
- info(`received authentication response fom '${gateway}'`, body);
3302
- if ("result" in body) {
3303
- return Token.parse(body.result.token);
3304
- } else {
3305
- const message = body.error?.message ?? "malformed response";
3306
- error(`gateway rejected token; ${message}`);
3307
- return (0, import_odin_common16.success)(message);
3308
- }
3309
- } catch (error2) {
3310
- return (0, import_odin_common16.failure)(String(error2));
3311
- }
3312
- }
3313
- function isTimeLimitReached(start, limit = 8e3) {
3314
- const dif = performance.now() - start;
3315
- return dif > limit;
3316
- }
3317
-
3318
- // src/cipher/index.ts
3319
- var import_odin_common20 = require("@4players/odin-common");
3320
-
3321
- // src/cipher/odinCrypto/index.ts
3322
- var import_odin_common19 = require("@4players/odin-common");
3323
-
3324
- // src/cipher/odinCrypto/keys.ts
3325
- var import_odin_common17 = require("@4players/odin-common");
3326
-
3327
- // src/cipher/odinCrypto/utils.ts
3328
- var import_msgpack3 = require("@msgpack/msgpack");
3329
-
3330
- // src/cipher/odinCrypto/config.ts
3331
- var MASTER_KEY_ALGORITHM = {
3332
- name: "PBKDF2",
3333
- hash: "SHA-256",
3334
- iterations: 6e5,
3335
- salt: new Uint8Array([
3336
- 111,
3337
- 100,
3338
- 105,
3339
- 110,
3340
- 95,
3341
- 99,
3342
- 114,
3343
- 121,
3344
- 112,
3345
- 116,
3346
- 111,
3347
- 95,
3348
- 109,
3349
- 97,
3350
- 115,
3351
- 116,
3352
- 101,
3353
- 114,
3354
- 95,
3355
- 107,
3356
- 101,
3357
- 121
3358
- ])
3359
- };
3360
- var PEER_KEY_ALGORITHM = (salt) => ({
3361
- name: "PBKDF2",
3362
- hash: "SHA-256",
3363
- iterations: 600,
3364
- salt
3365
- });
3366
- var PEER_KEY_SEED_LENGTH = 32;
3367
- var PEER_KEY_MAX_PACKETS = 1e6;
3368
- var PEER_KEY_MAX_BYTES = 2147483648;
3369
- var REPLAY_WINDOW_SIZE = 32;
3370
- var ODIN_CRYPTO_PREFIX = [
3371
- 79,
3372
- 68,
3373
- 73,
3374
- 78,
3375
- 1,
3376
- 75,
3377
- 105,
3378
- 109
3379
- ];
3380
- var TAG_SIZE = 16;
3381
- var ENCRYPTED_FLAG = 64;
3382
- var MAX_MEDIA_IDS = 16;
3383
-
3384
- // src/cipher/odinCrypto/utils.ts
3385
- function isBytesEqual(a, b) {
3386
- if (a.length !== b.length) return false;
3387
- for (let i = 0; i < a.length; i++) {
3388
- if (a[i] !== b[i]) return false;
3389
- }
3390
- return true;
3391
- }
3392
- function isNumber(value) {
3393
- return isFinite(value);
3394
- }
3395
- function isArray(value) {
3396
- return Array.isArray(value);
3397
- }
3398
- function asNumber(value) {
3399
- return isNumber(value) ? value : Invalid;
3400
- }
3401
- function asBytes(value) {
3402
- return value instanceof Uint8Array ? value : Invalid;
3403
- }
3404
- function unpack(data, ...converters) {
3405
- let decoded;
3406
- try {
3407
- decoded = (0, import_msgpack3.decode)(data);
3408
- } catch {
3409
- return Invalid;
3410
- }
3411
- if (!isArray(decoded)) return Invalid;
3412
- const result = new Array(decoded.length);
3413
- for (const index in converters) {
3414
- result[index] = converters[index](decoded[index]);
3415
- if (result[index] === Invalid) return Invalid;
3416
- }
3417
- return result;
3418
- }
3419
- function unpackWithPrefix(data, ...converters) {
3420
- if (data.length < ODIN_CRYPTO_PREFIX.length) return Unencrypted;
3421
- for (let i = 0; i < ODIN_CRYPTO_PREFIX.length; i++) {
3422
- if (data[i] !== ODIN_CRYPTO_PREFIX[i]) return Unencrypted;
3423
- }
3424
- return unpack(
3425
- data.subarray(ODIN_CRYPTO_PREFIX.length),
3426
- ...converters
3427
- );
3428
- }
3429
-
3430
- // src/cipher/odinCrypto/antiReplay.ts
3431
- var AntiReplay = class {
3432
- constructor(windowSize) {
3433
- this.first = 1;
3434
- this.offset = 0;
3435
- this.window = Array(windowSize);
3436
- }
3437
- isReplay(sequenceNumber) {
3438
- let relative = sequenceNumber - this.first;
3439
- if (relative < 0) return true;
3440
- if (relative >= this.window.length) {
3441
- if (relative < this.window.length * 2) {
3442
- const removed = relative - this.window.length + 1;
3443
- relative -= removed;
3444
- for (let i = 0; i < removed; i++) {
3445
- this.advance();
3446
- }
3447
- } else {
3448
- this.first = sequenceNumber + 1 - self.window.length;
3449
- this.offset = 0;
3450
- this.window.fill(false);
3451
- return false;
3452
- }
3453
- }
3454
- const index = (this.offset + relative) % this.window.length;
3455
- const isReplay = this.window[index] === true;
3456
- this.window[index] = true;
3457
- while (this.window[this.offset] === true) {
3458
- this.advance();
3459
- }
3460
- return isReplay;
3461
- }
3462
- advance() {
3463
- this.window[this.offset] = false;
3464
- this.first += 1;
3465
- this.offset = (this.offset + 1) % this.window.length;
3466
- }
3467
- };
3468
-
3469
- // src/cipher/odinCrypto/keys.ts
3470
- var MasterKey = class _MasterKey {
3471
- constructor(key) {
3472
- this.key = key;
3473
- }
3474
- static async create(password) {
3475
- if (typeof password === "string") {
3476
- const encoder = new TextEncoder();
3477
- password = encoder.encode(password);
3478
- }
3479
- const importedPassword = await globalThis.crypto.subtle.importKey(
3480
- "raw",
3481
- password,
3482
- "PBKDF2",
3483
- false,
3484
- ["deriveBits"]
3485
- );
3486
- const keyBits = await globalThis.crypto.subtle.deriveBits(
3487
- MASTER_KEY_ALGORITHM,
3488
- importedPassword,
3489
- 256
3490
- );
3491
- const key = await globalThis.crypto.subtle.importKey(
3492
- "raw",
3493
- keyBits,
3494
- "PBKDF2",
3495
- false,
3496
- ["deriveKey"]
3497
- );
3498
- return new _MasterKey(key);
3499
- }
3500
- async generatePeerKey(seed, keyUsage) {
3501
- return await globalThis.crypto.subtle.deriveKey(
3502
- PEER_KEY_ALGORITHM(seed),
3503
- this.key,
3504
- { name: "AES-GCM", length: 256 },
3505
- false,
3506
- [keyUsage]
3507
- );
3508
- }
3509
- };
3510
- var OwnKey = class _OwnKey {
3511
- constructor(seed, key) {
3512
- this.seed = seed;
3513
- this.key = key;
3514
- this.sequenceNumbers = {
3515
- datagram: 1,
3516
- message: 1,
3517
- userData: 1
3518
- };
3519
- this.number_of_packets = 0;
3520
- this.number_of_bytes = 0;
3521
- }
3522
- static async create(masterKey) {
3523
- const seed = globalThis.crypto.getRandomValues(
3524
- new Uint8Array(PEER_KEY_SEED_LENGTH)
3525
- );
3526
- const key = await masterKey.generatePeerKey(seed, "encrypt");
3527
- return new _OwnKey(seed, key);
3528
- }
3529
- get limitReached() {
3530
- if (this.key === void 0) return false;
3531
- return this.number_of_bytes >= PEER_KEY_MAX_BYTES || this.number_of_packets >= PEER_KEY_MAX_PACKETS;
3532
- }
3533
- nextSequenceNumber(content) {
3534
- return this.sequenceNumbers[content]++;
3535
- }
3536
- };
3537
- function createPeerKeyEncrypted(key, material) {
3538
- return {
3539
- kind: "encrypted",
3540
- key,
3541
- material,
3542
- antiReplays: {
3543
- datagram: new AntiReplay(REPLAY_WINDOW_SIZE),
3544
- userData: new AntiReplay(0),
3545
- message: new AntiReplay(0)
3546
- }
3547
- };
3548
- }
3549
- var KeyingMaterial = class _KeyingMaterial {
3550
- constructor(additionalData, sequenceNumber, seed, encrypted) {
3551
- this.additionalData = additionalData;
3552
- this.sequenceNumber = sequenceNumber;
3553
- this.seed = seed;
3554
- this.encrypted = encrypted;
3555
- }
3556
- static parse(bytes) {
3557
- const fields = unpackWithPrefix(bytes, asBytes, asNumber, asBytes);
3558
- if (fields === Invalid || fields === Unencrypted) return fields;
3559
- const [seed, sequenceNumber, encrypted] = fields;
3560
- const additionalData = bytes.subarray(0, bytes.length - encrypted.length);
3561
- return new _KeyingMaterial(additionalData, sequenceNumber, seed, encrypted);
3562
- }
3563
- isSameSeed(other) {
3564
- return isBytesEqual(this.seed, other.seed);
3565
- }
3566
- async generateKey(masterKey) {
3567
- return await masterKey.generatePeerKey(this.seed, "decrypt");
3568
- }
3569
- };
3570
- function createIv(content, sequenceNumber) {
3571
- const result = new Uint8Array(12);
3572
- result[0] = sequenceNumber & 255;
3573
- result[1] = sequenceNumber >> 8 & 255;
3574
- result[2] = sequenceNumber >> 16 & 255;
3575
- result[3] = sequenceNumber >> 24 & 255;
3576
- switch (content) {
3577
- case "datagram" /* Datagram */:
3578
- result[5] = 1;
3579
- break;
3580
- case "userData" /* UserData */:
3581
- result[5] = 2;
3582
- break;
3583
- case "message" /* Message */:
3584
- result[5] = 3;
3585
- break;
3586
- default:
3587
- (0, import_odin_common17.assert)(false, "missing content implementation");
3588
- }
3589
- return result;
3590
- }
3591
-
3592
- // src/cipher/odinCrypto/encryption.ts
3593
- var import_msgpack4 = require("@msgpack/msgpack");
3594
- var import_odin_common18 = require("@4players/odin-common");
3595
- var Encryption = class {
3596
- constructor(actions) {
3597
- this.actions = actions;
3598
- }
3599
- changeKey(masterKey) {
3600
- if (this.masterKey === masterKey) return;
3601
- this.masterKey = masterKey;
3602
- this.ownKey = Invalid;
3603
- this.actions.resendUserData();
3604
- }
3605
- async encryptDatagram(plaintext) {
3606
- const key = await this.getKey("datagram" /* Datagram */);
3607
- if (key === void 0) return plaintext;
3608
- if (key === Retry) return Invalid;
3609
- return await encryptDatagram(key, plaintext);
3610
- }
3611
- async encryptMessage(plaintext) {
3612
- const key = await this.getKey("message" /* Message */);
3613
- if (key === void 0) return plaintext;
3614
- if (key === Retry) return Retry;
3615
- return await encryptWithPrefix(key, plaintext, "message" /* Message */);
3616
- }
3617
- async encryptUserData(plaintext) {
3618
- const key = await this.getKey("userData" /* UserData */);
3619
- if (key === void 0) return plaintext;
3620
- (0, import_odin_common18.assert)(key !== Retry, "key unavailable for user data encryption");
3621
- return await encryptWithPrefix(key, plaintext, "userData" /* UserData */, key.seed);
3622
- }
3623
- async getKey(content) {
3624
- if (this.ownKey === Invalid || this.ownKey?.limitReached) {
3625
- if (content === "userData" /* UserData */) {
3626
- this.ownKey = this.masterKey ? await OwnKey.create(this.masterKey) : void 0;
3627
- } else {
3628
- if (this.ownKey !== Invalid) {
3629
- this.ownKey = Invalid;
3630
- this.actions.resendUserData();
3631
- }
3632
- return Retry;
3633
- }
3634
- }
3635
- return this.ownKey;
3636
- }
3637
- reset() {
3638
- this.ownKey = this.masterKey ? Invalid : void 0;
3639
- }
3640
- };
3641
- async function encryptWithPrefix(key, plaintext, content, ...additionalFields) {
3642
- const sequenceNumber = key.nextSequenceNumber(content);
3643
- const encryptedLength = plaintext.length + TAG_SIZE;
3644
- const packed = packWithPrefix(
3645
- sequenceNumber,
3646
- additionalFields,
3647
- encryptedLength
3648
- );
3649
- const additionalDataLength = packed.length - encryptedLength;
3650
- const encrypted = await encrypt(
3651
- key,
3652
- content,
3653
- sequenceNumber,
3654
- packed.subarray(0, additionalDataLength),
3655
- plaintext
3656
- );
3657
- packed.set(new Uint8Array(encrypted), additionalDataLength);
3658
- return packed;
3659
- }
3660
- function packWithPrefix(sequenceNumber, additionalFields, encryptedLength) {
3661
- const array = [];
3662
- additionalFields.forEach((field) => array.push(field));
3663
- array.push(sequenceNumber);
3664
- array.push(new Uint8Array(encryptedLength));
3665
- const packedArray = (0, import_msgpack4.encode)(array);
3666
- const packed = new Uint8Array(ODIN_CRYPTO_PREFIX.length + packedArray.length);
3667
- packed.set(ODIN_CRYPTO_PREFIX, 0);
3668
- packed.set(packedArray, ODIN_CRYPTO_PREFIX.length);
3669
- return packed;
3670
- }
3671
- async function encryptDatagram(key, plaintext) {
3672
- const splitted = splitDatagram(plaintext);
3673
- if (splitted === Invalid) return Invalid;
3674
- if ((splitted.kind & ENCRYPTED_FLAG) !== 0) return Invalid;
3675
- const encryptedLength = splitted.body.length + TAG_SIZE;
3676
- const sequenceNumber = key.nextSequenceNumber("datagram" /* Datagram */);
3677
- const packed = (0, import_msgpack4.encode)([
3678
- splitted.kind | ENCRYPTED_FLAG,
3679
- splitted.mediaIds,
3680
- sequenceNumber,
3681
- new Uint8Array(encryptedLength)
3682
- ]);
3683
- const additionalDataLength = packed.length - encryptedLength;
3684
- const encrypted = await encrypt(
3685
- key,
3686
- "datagram" /* Datagram */,
3687
- sequenceNumber,
3688
- packed.subarray(0, additionalDataLength),
3689
- splitted.body
3690
- );
3691
- packed.set(encrypted, additionalDataLength);
3692
- return packed;
3693
- }
3694
- async function encrypt(key, content, sequenceNumber, additionalData, encrypted) {
3695
- return new Uint8Array(
3696
- await globalThis.crypto.subtle.encrypt(
3697
- {
3698
- name: "AES-GCM",
3699
- iv: createIv(content, sequenceNumber),
3700
- additionalData,
3701
- tagLength: TAG_SIZE * 8
3702
- },
3703
- key.key,
3704
- encrypted
3705
- )
3706
- );
3707
- }
3708
- function splitDatagram(datagram) {
3709
- const reader = new MsgPackReader(datagram);
3710
- const fieldsLengths = reader.readArrayLength();
3711
- if (failed(fieldsLengths) || fieldsLengths < 2) return Invalid;
3712
- const kind = reader.readNumber();
3713
- const mediaIdsLength = reader.readArrayLength();
3714
- if (failed(kind) || failed(mediaIdsLength)) return Invalid;
3715
- if (mediaIdsLength > MAX_MEDIA_IDS) return Invalid;
3716
- const mediaIds = Array(mediaIdsLength);
3717
- for (let i = 0; i < mediaIds.length; i++) {
3718
- const mediaId = reader.readNumber();
3719
- if (failed(mediaId)) return Invalid;
3720
- mediaIds[i] = mediaId;
3721
- }
3722
- const body = datagram.subarray(reader.position);
3723
- return { kind, mediaIds, body };
3724
- }
3725
-
3726
- // src/cipher/odinCrypto/decryption.ts
3727
- var import_msgpack5 = require("@msgpack/msgpack");
3728
- var Decryption = class {
3729
- constructor(actions) {
3730
- this.actions = actions;
3731
- this.peerKeys = /* @__PURE__ */ new Map();
3732
- }
3733
- async changeKey(masterKey) {
3734
- this.masterKey = masterKey;
3735
- for (const [peerId, { kind, material }] of this.peerKeys) {
3736
- if (masterKey !== void 0) {
3737
- const key = await material.generateKey(masterKey);
3738
- const peerKey = createPeerKeyEncrypted(key, material);
3739
- const userData = await decrypt(
3740
- peerKey,
3741
- "userData" /* UserData */,
3742
- material.additionalData,
3743
- material.sequenceNumber,
3744
- material.encrypted
3745
- );
3746
- if (userData !== Invalid) {
3747
- this.peerKeys.set(peerId, peerKey);
3748
- this.actions.setUserData(peerId, userData);
3749
- } else {
3750
- this.peerKeys.set(peerId, { kind: "broken", material });
3751
- this.actions.setUserData(peerId, new Uint8Array());
3752
- }
3753
- } else if (kind === "encrypted") {
3754
- this.peerKeys.set(peerId, { kind: "broken", material });
3755
- }
3756
- }
3757
- }
3758
- async decryptDatagram(peerId, ciphertext) {
3759
- const datagram = unpackDatagram(ciphertext);
3760
- if (this.masterKey === void 0) {
3761
- return datagram === Unencrypted ? ciphertext : Invalid;
3762
- }
3763
- if (datagram === Invalid || datagram === Unencrypted) return Invalid;
3764
- const peerKey = this.peerKeys.get(peerId);
3765
- if (peerKey?.kind !== "encrypted") return Invalid;
3766
- const decrypted = await decrypt(
3767
- peerKey,
3768
- "datagram" /* Datagram */,
3769
- datagram.additionalData,
3770
- datagram.sequenceNumber,
3771
- datagram.encrypted
3772
- );
3773
- if (decrypted === Invalid) return Invalid;
3774
- return reassembleDatagram(
3775
- datagram.kind & ~ENCRYPTED_FLAG,
3776
- datagram.mediaIds,
3777
- decrypted
3778
- );
3779
- }
3780
- async decryptMessage(peerId, ciphertext) {
3781
- const fields = unpackWithPrefix(ciphertext, asNumber, asBytes);
3782
- if (this.masterKey === void 0) {
3783
- return fields === Unencrypted ? ciphertext : Invalid;
3784
- }
3785
- if (fields === Invalid || fields === Unencrypted) return Invalid;
3786
- const peerKey = this.peerKeys.get(peerId);
3787
- if (peerKey?.kind !== "encrypted") return Invalid;
3788
- const [sequenceNumber, encrypted] = fields;
3789
- const additionalData = ciphertext.subarray(
3790
- 0,
3791
- ciphertext.length - encrypted.length
3792
- );
3793
- return decrypt(
3794
- peerKey,
3795
- "message" /* Message */,
3796
- additionalData,
3797
- sequenceNumber,
3798
- encrypted
3799
- );
3800
- }
3801
- async decryptUserData(peerId, ciphertext) {
3802
- const material = KeyingMaterial.parse(ciphertext);
3803
- if (material === Invalid) return Invalid;
3804
- if (material === Unencrypted) {
3805
- this.peerKeys.delete(peerId);
3806
- return this.masterKey === void 0 ? ciphertext : Invalid;
3807
- }
3808
- if (this.masterKey === void 0) {
3809
- this.peerKeys.set(peerId, { kind: "broken", material });
3810
- return Invalid;
3811
- }
3812
- let peerKey = this.peerKeys.get(peerId);
3813
- if (peerKey?.kind === "encrypted" && peerKey.material.isSameSeed(material)) {
3814
- peerKey.material = material;
3815
- } else {
3816
- const key = await material.generateKey(this.masterKey);
3817
- peerKey = createPeerKeyEncrypted(key, material);
3818
- }
3819
- const decrypted = await decrypt(
3820
- peerKey,
3821
- "userData" /* UserData */,
3822
- material.additionalData,
3823
- material.sequenceNumber,
3824
- material.encrypted
3825
- );
3826
- if (decrypted === Invalid) {
3827
- this.peerKeys.set(peerId, { kind: "broken", material });
3828
- } else if (peerKey !== void 0) {
3829
- this.peerKeys.set(peerId, peerKey);
3830
- }
3831
- return decrypted;
3832
- }
3833
- reset() {
3834
- this.peerKeys.clear();
3835
- }
3836
- };
3837
- function unpackDatagram(datagram) {
3838
- const reader = new MsgPackReader(datagram);
3839
- const fieldLength = reader.readArrayLength();
3840
- if (failed(fieldLength) || fieldLength < 1) return Invalid;
3841
- const kind = reader.readNumber();
3842
- if (failed(kind)) return Invalid;
3843
- if ((kind & ENCRYPTED_FLAG) === 0) return Unencrypted;
3844
- if (fieldLength !== 4) return Invalid;
3845
- const beginMediaIds = reader.position;
3846
- reader.readObjects(1);
3847
- const mediaIds = datagram.subarray(beginMediaIds, reader.position);
3848
- const sequenceNumber = reader.readNumber();
3849
- const encrypted = reader.readBytes();
3850
- if (failed(sequenceNumber) || failed(encrypted)) return Invalid;
3851
- const additionalData = datagram.subarray(
3852
- 0,
3853
- datagram.length - encrypted.length
3854
- );
3855
- return { kind, mediaIds, additionalData, sequenceNumber, encrypted };
3856
- }
3857
- async function decrypt(peerKey, content, additionalData, sequenceNumber, encrypted) {
3858
- const decrypted = await globalThis.crypto.subtle.decrypt(
3859
- {
3860
- name: "AES-GCM",
3861
- iv: createIv(content, sequenceNumber),
3862
- additionalData,
3863
- tagLength: TAG_SIZE * 8
3864
- },
3865
- peerKey.key,
3866
- encrypted
3867
- ).catch(() => Invalid);
3868
- if (decrypted === Invalid) return Invalid;
3869
- if (peerKey.antiReplays[content].isReplay(sequenceNumber)) return Invalid;
3870
- return new Uint8Array(decrypted);
3871
- }
3872
- function reassembleDatagram(kind, mediaIds, decrypted) {
3873
- let numberOfFields = 2;
3874
- try {
3875
- const reader = new MsgPackReader(decrypted);
3876
- reader.position = 0;
3877
- while (reader.position < decrypted.length) {
3878
- reader.readObjects(1);
3879
- numberOfFields += 1;
3880
- }
3881
- } catch {
3882
- return Invalid;
3883
- }
3884
- const kindBytes = (0, import_msgpack5.encode)(kind);
3885
- const datagram = new Uint8Array(
3886
- 3 + kindBytes.length + mediaIds.length + decrypted.length
3887
- );
3888
- datagram[0] = 220;
3889
- datagram[1] = numberOfFields >> 8 & 255;
3890
- datagram[2] = numberOfFields & 255;
3891
- datagram.set(kindBytes, 3);
3892
- datagram.set(mediaIds, 3 + kindBytes.length);
3893
- datagram.set(decrypted, 3 + kindBytes.length + mediaIds.length);
3894
- return datagram;
3895
- }
3896
-
3897
- // src/cipher/odinCrypto/index.ts
3898
- var OdinCrypto = class {
3899
- constructor(settings, actions) {
3900
- this.active = false;
3901
- const decryption = new Decryption(actions);
3902
- const encryption = new Encryption(actions);
3903
- this.crypto = new import_odin_common19.Strand({ encryption, decryption });
3904
- this.peerKeys = decryption.peerKeys;
3905
- this.configure(settings);
3906
- }
3907
- encryptUserData(plaintext) {
3908
- return this.crypto.enqueue(
3909
- ({ encryption }) => encryption.encryptUserData(plaintext)
3910
- );
3911
- }
3912
- async encryptMessage(plaintext) {
3913
- let result;
3914
- do {
3915
- result = await this.crypto.enqueue(
3916
- ({ encryption }) => encryption.encryptMessage(plaintext)
3917
- );
3918
- } while (result === Retry);
3919
- return result;
3920
- }
3921
- async encryptDatagram(plaintext) {
3922
- return await this.crypto.enqueue(
3923
- ({ encryption }) => encryption.encryptDatagram(plaintext)
3924
- );
3925
- }
3926
- decryptUserData(peerId, ciphertext) {
3927
- return this.crypto.enqueue(
3928
- ({ decryption }) => decryption.decryptUserData(peerId, ciphertext)
3929
- );
3930
- }
3931
- decryptMessage(peerId, ciphertext) {
3932
- return this.crypto.enqueue(
3933
- ({ decryption }) => decryption.decryptMessage(peerId, ciphertext)
3934
- );
3935
- }
3936
- decryptDatagram(peerId, ciphertext) {
3937
- return this.crypto.enqueue(
3938
- ({ decryption }) => decryption.decryptDatagram(peerId, ciphertext)
3939
- );
3940
- }
3941
- configure(settings) {
3942
- return this.crypto.enqueue(async (cipher) => {
3943
- const masterKey = settings.password ? await MasterKey.create(settings.password) : void 0;
3944
- this.active = masterKey !== void 0;
3945
- await cipher.encryption.changeKey(masterKey);
3946
- await cipher.decryption.changeKey(masterKey);
3947
- });
3948
- }
3949
- getPeerStatus(peerId) {
3950
- switch (this.peerKeys.get(peerId)?.kind ?? "unencrypted") {
3951
- case "broken":
3952
- return import_odin_common19.Backend.PeerCipherStatus.InvalidPassword;
3953
- case "encrypted":
3954
- return import_odin_common19.Backend.PeerCipherStatus.Encrypted;
3955
- case "unencrypted": {
3956
- if (typeof this.active === "undefined") {
3957
- return import_odin_common19.Backend.PeerCipherStatus.Unencrypted;
3958
- } else {
3959
- return import_odin_common19.Backend.PeerCipherStatus.InvalidPassword;
3960
- }
3961
- }
3962
- default:
3963
- return import_odin_common19.Backend.PeerCipherStatus.Unknown;
3964
- }
3965
- }
3966
- reset() {
3967
- return this.crypto.enqueue(async ({ encryption, decryption }) => {
3968
- encryption.reset();
3969
- decryption.reset();
3970
- });
3971
- }
3972
- async dump() {
3973
- return this.crypto.enqueue(async () => ({
3974
- active: this.active,
3975
- peers: Array.from(this.peerKeys).map(
3976
- ([peerId, peerKey]) => `${peerId} => ${peerKey.kind}`
3977
- )
3978
- }));
3979
- }
3980
- };
3981
-
3982
- // src/cipher/index.ts
3983
- function createCipher(settings, actions) {
3984
- (0, import_odin_common20.assert)(settings.type === "OdinCrypto", "only OdinCrypto supported");
3985
- return new OdinCrypto(settings, actions);
3986
- }
3987
-
3988
- // src/plugin/room.ts
3989
- var Room = class {
3990
- constructor(connectionPool, parameters) {
3991
- this._Captures = /* @__PURE__ */ new Map();
3992
- this._Playbacks = /* @__PURE__ */ new Map();
3993
- this._Peers = /* @__PURE__ */ new Map();
3994
- this._AvailableMediaIds = [];
3995
- const { url, onEvent } = parameters;
3996
- const token = (0, import_odin_common21.unwrap)(Token.parse(parameters.token));
3997
- const roomId = parameters.roomId ?? token.defaultRoomId;
3998
- const userData = parameters.userData ?? new Uint8Array(0);
3999
- const position = parameters.position ?? [0, 0, 0];
4000
- (0, import_odin_common21.assert)(roomId !== void 0, "room id required for multi-room-tokens");
4001
- this.cipher = parameters.cipher ? createCipher(parameters.cipher, new CipherActions(this)) : void 0;
4002
- this.onEvent = onEvent;
4003
- info(
4004
- `creating new room '${roomId}' ${this.cipher ? "with" : "without"} cipher`,
4005
- token,
4006
- userData,
4007
- position
4008
- );
4009
- this._Connection = new RoomConnection(
4010
- connectionPool,
4011
- parameters.transport ?? "H3",
4012
- this._HandleEvent.bind(this),
4013
- url ? new URL(url) : void 0,
4014
- token,
4015
- roomId,
4016
- userData,
4017
- position,
4018
- this.cipher
4019
- );
4020
- }
4021
- get connectionStats() {
4022
- const stats = { ...import_odin_common21.CONNECTION_STATS_INITIAL };
4023
- if (this.mainConnection) {
4024
- return this.mainConnection.connectionStats;
4025
- }
4026
- return stats;
4027
- }
4028
- get roomConnection() {
4029
- return this._Connection;
4030
- }
4031
- get mainConnection() {
4032
- return this.roomConnection?.mainConnection;
4033
- }
4034
- get token() {
4035
- return this._Connection.token.text;
4036
- }
4037
- request(method, properties) {
4038
- return this._Connection.requestUnsafe(method, properties);
4039
- }
4040
- async link(media) {
4041
- if (media instanceof AudioPlayback) {
4042
- const entry = this._Playbacks.get(media.uid) ?? {
4043
- media: void 0,
4044
- playback: void 0
4045
- };
4046
- if (entry.playback !== void 0) {
4047
- this.unlink(entry.playback);
4048
- }
4049
- if (entry.media !== void 0) {
4050
- media.start(entry.media);
4051
- }
4052
- entry.playback = media;
4053
- info(
4054
- `linking audio playback media '${media.uid}' with volume ${media.volume} to room '${this._Connection.roomId}'`
4055
- );
4056
- this._Playbacks.set(media.uid, entry);
4057
- } else if (media instanceof AudioCapture) {
4058
- let entry = this._Captures.get(media);
4059
- if (entry) {
4060
- this.unlink(media);
4061
- }
4062
- const mediaId = this.mainConnection !== void 0 && this._AvailableMediaIds.shift();
4063
- entry = {
4064
- media: mediaId ? {
4065
- connectionId: this.mainConnection.id,
4066
- mediaId
4067
- } : void 0,
4068
- uid: (0, import_odin_common21.generateUUID)()
4069
- };
4070
- info(
4071
- `linking audio capture media '${entry.uid}' with volume ${media.volume} to room '${this._Connection.roomId}'`
4072
- );
4073
- this._Captures.set(media, entry);
4074
- if (entry.media !== void 0) {
4075
- media.start(this._Connection, entry.media, entry.uid);
4076
- }
4077
- } else if (media instanceof VideoPlayback) {
4078
- const entry = this._Playbacks.get(media.uid) ?? {
4079
- media: void 0,
4080
- playback: void 0
4081
- };
4082
- if (entry.playback !== void 0) {
4083
- this.unlink(entry.playback);
4084
- }
4085
- if (entry.media !== void 0) {
4086
- const track = await this.mainConnection?.startRemoteVideo(
4087
- entry.media.mediaId
4088
- );
4089
- if (track) {
4090
- media.setMediaStream(new MediaStream([track]));
4091
- }
4092
- }
4093
- entry.playback = media;
4094
- info(
4095
- `linking video playback media '${media.uid}' with type '${media.customType}' to room '${this._Connection.roomId}'`
4096
- );
4097
- this._Playbacks.set(media.uid, entry);
4098
- } else if (media instanceof VideoCapture) {
4099
- let mediaEntry = this._Captures.get(media);
4100
- if (mediaEntry) {
4101
- this.unlink(media);
4102
- }
4103
- if (media.mediaStream) {
4104
- const mediaId = this.mainConnection !== void 0 && this._AvailableMediaIds.shift();
4105
- mediaEntry = {
4106
- media: mediaId ? {
4107
- connectionId: this.mainConnection.id,
4108
- mediaId
4109
- } : void 0,
4110
- uid: (0, import_odin_common21.generateUUID)()
4111
- };
4112
- info(
4113
- `linking video capture media '${mediaEntry.uid}' with type '${media.options?.customType}' to room '${this._Connection.roomId}'`
4114
- );
4115
- this._Captures.set(media, mediaEntry);
4116
- if (mediaEntry.media !== void 0) {
4117
- this.mainConnection?.startLocalVideo(
4118
- mediaEntry.media.mediaId,
4119
- media.mediaStream.getTracks()[0],
4120
- media.options?.codec
4121
- );
4122
- media.start(this._Connection, mediaEntry.media, mediaEntry.uid);
4123
- }
4124
- }
4125
- } else {
4126
- throw new Error("invalid media");
4127
- }
4128
- }
4129
- unlink(media) {
4130
- if (media instanceof AudioPlayback || media instanceof VideoPlayback) {
4131
- const entry = this._Playbacks.get(media.uid);
4132
- if (entry?.playback !== media) return;
4133
- entry.playback = void 0;
4134
- if (entry.media !== void 0) {
4135
- if (media instanceof AudioPlayback) {
4136
- media.stop(entry.media);
4137
- } else {
4138
- this.mainConnection?.stopRemoteVideo(entry.media.mediaId);
4139
- media.setMediaStream(void 0);
4140
- }
4141
- }
4142
- info(
4143
- `unlinking playback media '${media.uid}' from room '${this._Connection.roomId}'`
4144
- );
4145
- this._Playbacks.set(media.uid, entry);
4146
- } else if (media instanceof AudioCapture || media instanceof VideoCapture) {
4147
- const entry = this._Captures.get(media);
4148
- if (entry === void 0) return;
4149
- if (entry.media !== void 0) {
4150
- media.stop(this._Connection, entry.media);
4151
- this._AvailableMediaIds.push(entry.media.mediaId);
4152
- if (media instanceof VideoCapture) {
4153
- this.mainConnection?.stopLocalVideo(entry.media.mediaId);
4154
- }
4155
- }
4156
- info(
4157
- `unlinking capture media '${entry.uid}' from room '${this._Connection.roomId}'`
4158
- );
4159
- this._Captures.delete(media);
4160
- } else {
4161
- throw new Error("invalid media");
4162
- }
4163
- }
4164
- close() {
4165
- info(`closing room '${this._Connection.roomId}'`);
4166
- this._Connection.close();
4167
- }
4168
- _HandleEvent(method, properties) {
4169
- const rpc = parseRpcMessage(import_odin_common21.RoomNotificationsRpc, {
4170
- name: method,
4171
- properties
4172
- });
4173
- if (rpc !== void 0) {
4174
- this._HandleKnownEvent(rpc);
4175
- properties = rpc.properties;
4176
- }
4177
- this.onEvent(method, properties);
4178
- }
4179
- _HandleKnownEvent(event) {
4180
- debug(`processing event '${event.name}'`, event.properties);
4181
- switch (event.name) {
4182
- case "RoomStatusChanged":
4183
- if (event.properties.status !== "Joined") {
4184
- this._HandleConnectionLost();
4185
- }
4186
- break;
4187
- case "RoomUpdated":
4188
- for (const update of event.properties.updates) {
4189
- this._HandleRoomUpdate(update);
4190
- }
4191
- break;
4192
- case "PeerUpdated":
4193
- this._HandlePeerUpdate(event.properties);
4194
- break;
4195
- }
4196
- }
4197
- _HandleConnectionLost() {
4198
- for (const peerId of this._Peers.keys()) {
4199
- this._HandlePeerLeft(peerId);
4200
- }
4201
- for (const [capture, entry] of this._Captures) {
4202
- if (entry.media === void 0) continue;
4203
- if (capture instanceof VideoCapture) {
4204
- }
4205
- if (capture instanceof AudioCapture) {
4206
- capture.stop(this._Connection, entry.media);
4207
- entry.media = void 0;
4208
- }
4209
- }
4210
- this._AvailableMediaIds.length = 0;
4211
- }
4212
- _HandleRoomUpdate(update) {
4213
- if (update.kind === "Joined") {
4214
- (0, import_odin_common21.assert)(this._AvailableMediaIds.length === 0, "left over media ids");
4215
- this._AvailableMediaIds.push(...update.media_ids);
4216
- this._HandleRoomJoined();
4217
- for (const peer of update.room.peers) {
4218
- this._HandlePeerJoined(peer);
4219
- }
4220
- } else if (update.kind === "PeerJoined") {
4221
- this._HandlePeerJoined(update.peer);
4222
- } else if (update.kind === "PeerLeft") {
4223
- this._HandlePeerLeft(update.peer_id);
4224
- }
4225
- }
4226
- _HandleRoomJoined() {
4227
- for (const [capture, entry] of this._Captures) {
4228
- if (this._AvailableMediaIds.length === 0 || this.mainConnection === void 0) {
4229
- continue;
4230
- }
4231
- entry.media = {
4232
- connectionId: this.mainConnection.id,
4233
- mediaId: this._AvailableMediaIds.shift()
4234
- };
4235
- if (capture instanceof AudioCapture) {
4236
- capture.start(this._Connection, entry.media, entry.uid);
4237
- }
4238
- if (capture instanceof VideoCapture) {
4239
- }
4240
- }
4241
- }
4242
- _HandlePeerUpdate(update) {
4243
- if (update.kind === "MediaStarted") {
4244
- this._HandleMediaStart(update.peer_id, update.media);
4245
- } else if (update.kind === "MediaStopped") {
4246
- this._HandleMediaStopped(update.peer_id, update.media_id);
4247
- }
4248
- }
4249
- _HandlePeerJoined(peer) {
4250
- if (!this._Peers.has(peer.id)) {
4251
- this._Peers.set(peer.id, { mediaIds: /* @__PURE__ */ new Map() });
4252
- }
4253
- for (const media of peer.medias) {
4254
- this._HandleMediaStart(peer.id, media);
4255
- }
4256
- }
4257
- _HandlePeerLeft(peerId) {
4258
- const entry = this._Peers.get(peerId);
4259
- if (entry === void 0) return;
4260
- for (const mediaId of entry.mediaIds.keys()) {
4261
- this._HandleMediaStopped(peerId, mediaId);
4262
- }
4263
- this._Peers.delete(peerId);
4264
- }
4265
- _HandleMediaStart(peerId, media) {
4266
- if (this.mainConnection === void 0) return;
4267
- const peer = this._Peers.get(peerId);
4268
- if (peer === void 0 || media.properties.kind !== "audio" && media.properties.kind !== "video") {
4269
- return;
4270
- }
4271
- const uid = String(media.properties.uid ?? (0, import_odin_common21.generateUUID)());
4272
- media.properties.uid = uid;
4273
- const entry = this._Playbacks.get(uid) ?? {
4274
- media: void 0,
4275
- playback: void 0
4276
- };
4277
- this._Playbacks.set(uid, entry);
4278
- peer.mediaIds.set(media.id, uid);
4279
- entry.media = {
4280
- connectionId: this.mainConnection.id,
4281
- mediaId: media.id
4282
- };
4283
- if (entry.playback !== void 0) {
4284
- if (entry.playback instanceof AudioPlayback) {
4285
- entry.playback.start(entry.media);
4286
- }
4287
- if (entry.playback instanceof VideoPlayback) {
4288
- this.mainConnection.startRemoteVideo(entry.media.mediaId).then();
4289
- }
4290
- }
4291
- }
4292
- _HandleMediaStopped(peerId, mediaId) {
4293
- const peer = this._Peers.get(peerId);
4294
- if (peer === void 0) return;
4295
- const uid = peer?.mediaIds.get(mediaId);
4296
- if (uid === void 0) return;
4297
- const entry = this._Playbacks.get(uid);
4298
- if (entry === void 0) return;
4299
- if (entry.media !== void 0 && entry.playback !== void 0) {
4300
- if (entry.playback instanceof AudioPlayback) {
4301
- entry.playback.stop(entry.media);
4302
- }
4303
- if (entry.playback instanceof VideoPlayback) {
4304
- this.mainConnection?.stopRemoteVideo(entry.media.mediaId);
4305
- }
4306
- }
4307
- entry.media = void 0;
4308
- peer.mediaIds.delete(mediaId);
4309
- }
4310
- };
4311
-
4312
- // src/worker/base.ts
4313
- var createdWorkers = [];
4314
- var createdWorklets = [];
4315
- async function dump_all_background_threads() {
4316
- return {
4317
- workers: await Promise.all(createdWorkers.map(requestDump)),
4318
- worklets: await Promise.all(
4319
- createdWorklets.map((worklet) => requestDump(worklet.port))
4320
- )
4321
- };
4322
- }
4323
- function requestDump(source) {
4324
- const { port1, port2 } = new MessageChannel();
4325
- const { promise, resolve } = Promise.withResolvers();
4326
- port2.onmessage = (event) => resolve(event.data);
4327
- setTimeout(() => resolve("timeout"), 100);
4328
- send(source, ["dump", { result: port1 }], [port1]);
4329
- return promise;
4330
- }
4331
-
4332
- // src/plugin/index.ts
4333
- function createPlugin(audioContextFactory, options) {
4334
- initLogger(options?.logger, options?.onLogMessage);
4335
- let audioController;
4336
- const pool = new ConnectionPool(
4337
- (connectionId, packet) => audioController.receivePacket(connectionId, packet)
4338
- );
4339
- const audioContexts = new AudioContextCollection(audioContextFactory);
4340
- audioController = new MediaController2(
4341
- audioContexts,
4342
- pool.sendDatagram.bind(pool)
4343
- );
4344
- return new Plugin(pool, audioController);
4345
- }
4346
- var SESSION_ID = (0, import_odin_common22.generateUUID)();
4347
- var odin_plugins = [];
4348
- var Plugin = class {
4349
- constructor(connectionPool, audioController) {
4350
- this.version = "7";
4351
- this.supportedTransports = ["H3", "WebRTC"];
4352
- this._PlaybackVolume = [1, 1];
4353
- this._CreatedAt = Date.now();
4354
- this._ConnectionPool = connectionPool;
4355
- this._MediaController = audioController;
4356
- odin_plugins.push(new WeakRef(this));
4357
- info("plugin initialized");
4358
- }
4359
- get playbackVolume() {
4360
- return this._PlaybackVolume;
4361
- }
4362
- joinRoom(parameters) {
4363
- return new Room(this._ConnectionPool, parameters);
4364
- }
4365
- async createAudioPlayback(parameters) {
4366
- return this._MediaController.createAudioPlayback(parameters);
4367
- }
4368
- async createAudioCapture(parameters) {
4369
- return this._MediaController.createAudioCapture(parameters);
4370
- }
4371
- async createVideoCapture(ms, options) {
4372
- return this._MediaController.createVideoCapture(ms, options);
4373
- }
4374
- async createVideoPlayback(parameters) {
4375
- return new VideoPlayback(parameters.uid, parameters.customType);
4376
- }
4377
- enumerateDevices() {
4378
- return enumerateDevices();
4379
- }
4380
- setOutputVolume(volume) {
4381
- this._MediaController.playbackSetGlobalVolume(volume);
4382
- }
4383
- async setOutputDevice(device) {
4384
- await this._MediaController.playbackSetDevice(device.device);
4385
- }
4386
- close() {
4387
- this._MediaController.close();
4388
- this._ConnectionPool.close();
4389
- info("plugin closed");
4390
- }
4391
- async dump() {
4392
- return {
4393
- createdAt: this._CreatedAt,
4394
- connections: await this._ConnectionPool.dump(),
4395
- medias: await this._MediaController.dump()
4396
- };
4397
- }
4398
- };
4399
- async function odin_debug_dump_state() {
4400
- const instances = [];
4401
- for (const weakPlugin of odin_plugins) {
4402
- const plugin = weakPlugin.deref();
4403
- if (plugin !== void 0) {
4404
- instances.push(await plugin.dump());
4405
- }
4406
- }
4407
- return JSON.stringify({
4408
- timestamp: Date.now(),
4409
- instances,
4410
- background: await dump_all_background_threads()
4411
- });
4412
- }
4413
- globalThis.odin_debug_dump_state = odin_debug_dump_state;
4414
- if (window !== void 0) {
4415
- window.odin_debug_dump_state = odin_debug_dump_state;
4416
- }
4417
- // Annotate the CommonJS export names for ESM import in node:
4418
- 0 && (module.exports = {
4419
- SESSION_ID,
4420
- createPlugin
4421
- });
1
+ "use strict";var ft=Object.create;var Z=Object.defineProperty;var ht=Object.getOwnPropertyDescriptor;var yt=Object.getOwnPropertyNames;var bt=Object.getPrototypeOf,gt=Object.prototype.hasOwnProperty;var De=(i,e)=>{for(var t in e)Z(i,t,{get:e[t],enumerable:!0})},Ve=(i,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of yt(e))!gt.call(i,r)&&r!==t&&Z(i,r,{get:()=>e[r],enumerable:!(n=ht(e,r))||n.enumerable});return i};var Ee=(i,e,t)=>(t=i!=null?ft(bt(i)):{},Ve(e||!i||!i.__esModule?Z(t,"default",{value:i,enumerable:!0}):t,i)),kt=i=>Ve(Z({},"__esModule",{value:!0}),i);var mn={};De(mn,{SESSION_ID:()=>Y,createPlugin:()=>ln});module.exports=kt(mn);var Jo=require("@4players/odin-common");var f={};De(f,{AdditionalCaptureBlocks:()=>Ct,AdditionalPlaybackBlocks:()=>vt,ClockJumpCheckInterval:()=>It,ClockJumpThreshold:()=>Mt,ConnectInterval:()=>Wt,ConnectTimeout:()=>Dt,DefaultRefreshRate:()=>wt,HealthCheckTimeout:()=>St,MainConnectionPingInterval:()=>At,MaxNumberOfDecoders:()=>Pt,MinDBFs:()=>we,MinTokenLifetime:()=>Vt,MinTokenRefreshInterval:()=>Et,PlaybackPreload:()=>_t,RenderQuantumSize:()=>xt,VadPreload:()=>Rt,WebRtcIceTimeout:()=>Ut,WebRtcTimeout:()=>Tt});var vt=3,Ct=3,_t=3,wt=250,Rt=3,Pt=512,xt=128,we=-758.596,St=1e4,It=1e3,Mt=1e4,At=1e3,Ut=3e3,Tt=1e4,Dt=5e3,Vt=6e4,Et=5e3,Wt=[0,100,200,200,500];var _=Symbol("Unencrypted"),s=Symbol("Invalid"),N=Symbol("Retry"),w=Symbol("UnexpectedEof"),Nt=Symbol("Unresolved");function ee(i){return typeof i!="symbol"}function p(i){return typeof i=="symbol"}function We(i,e,t){return i.postMessage(e,t)}var B=class{constructor(e,t=0){this.bytes=e;this.position=t}readArrayLength(){let e=this.readTag();return p(e)?e:(e&240)===144?e&15:e===220?this.readUint16():e===221?this.readUint32():s}readNumber(){let e=this.readTag();return p(e)||(e&128)===0?e:(e&224)===224?e-256:e===202?this.readFloat32():e===203?this.readFloat64():e===204?this.readUint8():e===205?this.readUint16():e===206?this.readUint32():e===207?this.readUint64():e===208?this.readInt8():e===209?this.readInt16():e===210?this.readInt32():e===211?this.readInt64():s}readBytes(){let e=this.readTag();if(p(e))return e;let t;if(e===196)t=this.readUint8();else if(e===197)t=this.readUint16();else if(e===198)t=this.readUint32();else return s;if(p(t))return t;let n=this.consume(t);return n!==w?this.bytes.subarray(n,n+t):w}readObjects(e){for(;e>0;e-=1){let t=this.readTag();if(p(t))return t;let n,r=0,o=0;switch(t){case 192:break;case 194:break;case 195:break;case 196:r=this.readUint8();break;case 197:r=this.readUint16();break;case 198:r=this.readUint32();break;case 199:r=this.readUint8(),o=1;break;case 200:r=this.readUint8(),o=1;break;case 201:r=this.readUint32(),o=1;break;case 202:r=4;break;case 203:r=8;break;case 204:r=1;break;case 205:r=2;break;case 206:r=4;break;case 207:r=8;break;case 208:r=1;break;case 209:r=2;break;case 210:r=4;break;case 211:r=8;break;case 212:r=2;break;case 213:r=3;break;case 214:r=5;break;case 215:r=9;break;case 216:r=17;break;case 217:r=this.readUint8();break;case 218:r=this.readUint16();break;case 219:r=this.readUint32();break;case 220:if(n=this.readUint16(),p(n))return n;e+=n;break;case 221:if(n=this.readUint32(),p(n))return n;e+=n;break;case 222:if(n=this.readUint16(),p(n))return n;e+=n*2;break;case 223:if(n=this.readUint32(),p(n))return n;e+=n*2;break;default:if((t&128)!==0){if((t&240)===128)e+=(t&15)*2;else if((t&240)===144)e+=t&15;else if((t&224)===160)r=t&31;else if((t&224)!==224)return s}}if(r===w)return w;if(this.consume(r+o)===w)return w}}consume(e){if(e===w||this.position+e>this.bytes.length)return w;let t=this.position;return this.position=t+e,t}readTag(){let e=this.consume(1);return ee(e)?this.bytes[e]:e}readUint8(){let e=this.consume(1);return ee(e)?this.bytes[e]:e}readUint16(){let e=this.consume(2);return p(e)?e:this.bytes[e]<<8|this.bytes[e+1]}readUint32(){let e=this.consume(4);return p(e)?e:this.bytes[e]<<24|this.bytes[e+1]<<16|this.bytes[e+2]<<8|this.bytes[e+3]}readUint64(){let e=this.readUint32();if(p(e))return e;if(e>=2097152)return s;let t=this.readUint32();return p(t)?t:e<<32|t}readInt8(){let e=this.readUint8();return p(e)||e>=128&&(e=e-256),e}readInt16(){let e=this.readUint16();return p(e)||e>=32768&&(e=e-65536),e}readInt32(){let e=this.readUint32();return p(e)||e>=2147483648&&(e=e-4294967296),e}readInt64(){let e=this.readUint32();if(p(e))return e;if(e>=2097152||e<-2097152)return s;let t=this.readUint32();return p(t)?t:e<<32|t}readFloat32(){let e=this.consume(4);return p(e)?e:new Float32Array(this.bytes.subarray(e,e+4))[0]}readFloat64(){let e=this.consume(8);return p(e)?e:new Float64Array(this.bytes.subarray(e,e+8))[0]}};function Ne(i,e){let t=new B(i);t.position=e;let n=t.readObjects(1);return ee(n)?t.position:n}var Be="1.0.0-alpha.59";var k=require("@4players/odin-common"),$;function Le(i,e){$||($=i??new k.Logger("DEBUG","odin-plugin-web",{handlers:[]}),e&&$.addHandler(new Re(e)))}function g(i,...e){$?.error(i,...e)??(0,k.error)(i,...e)}function v(i,...e){$?.warn(i,...e)??(0,k.warn)(i,...e)}function m(i,...e){$?.info(i,...e)??(0,k.info)(i,...e)}function u(i,...e){$?.debug(i,...e)??(0,k.debug)(i,...e)}var Re=class extends k.LogHandler{constructor(t){super("DEBUG",k.PRETTY_FORMATTER);this.cb=t}log(t,n){this.cb(t,n)}};var j={isSilent:!0,rmsDBFS:-758.596};function O(i){return i.connectionId*4294967296+i.mediaId}var z=require("@4players/odin-common");var Bn=require("@4players/odin-common");var T=require("@4players/odin-common");async function qe(i,e,t){let n=e.device?{exact:e.device.device.deviceId==="default"?void 0:e.device.device.deviceId}:void 0,r;try{let c=await Promise.race([navigator.mediaDevices.getUserMedia({audio:{echoCancellation:t.echoCanceller,noiseSuppression:t.noiseSuppression!=="None",autoGainControl:t.gainController,echoCancellationType:"system",deviceId:n}}),(0,T.sleep)(3e4,"timeout")]);if(c==="timeout")return(0,T.failure)("getUserMedia; timeout");r=c}catch(c){return(0,T.failure)(`getUserMedia; ${c}`)}let o=r.getAudioTracks()[0].getSettings().sampleRate,a=await i.audioContexts.acquire(o);return(0,T.success)(a.createMediaStreamSource(r))}function F(i){i.getTracks().forEach(e=>{e.stop(),i.removeTrack(e)})}async function te(){if((0,T.isFirefoxBrowser)()){let r=await Bt("microphone");(r==="granted"||r===!1)&&await Lt("audio")}let i=(await navigator.mediaDevices.enumerateDevices()).filter(r=>!!r.label),e=[],t=new U({deviceId:"default",name:"Default",kind:"audioinput"},!0),n=new U({deviceId:"default",name:"Default",kind:"audiooutput"},!0);e.push(t),e.push(n);for(let r of i){if(r.deviceId==="default"){r.kind==="audioinput"&&(t=new U({deviceId:"default",name:r.label,kind:"audioinput"},!0),e[0]=t),r.kind==="audiooutput"&&(n=new U({deviceId:"default",name:r.label,kind:"audiooutput"},!0),e[1]=n);continue}r.kind==="audioinput"&&e.push(new U({deviceId:r.deviceId,name:r.label,kind:"audioinput"},!1)),r.kind==="audiooutput"&&e.push(new U({deviceId:r.deviceId,name:r.label,kind:"audiooutput"},!1))}return e}async function Bt(i){try{return(await navigator.permissions.query({name:i})).state}catch{return!1}}async function Lt(i){try{let e=await navigator.mediaDevices.getUserMedia({[i]:!0});return e?.getTracks().forEach(t=>{t.stop(),e.removeTrack(t)}),!0}catch{return!1}}var ne=require("@4players/odin-common");var $e=(0,ne.isSharedArrayBufferCapable)()&&!(0,ne.isSafariBrowser)()?import("../worker/shared"):import("../worker/unshared");async function Ke(i,e,t){return $e.then(n=>n.createCaptureWorker(i,e,t,f.DefaultRefreshRate))}async function Oe(i){return $e.then(e=>e.createPlaybackWorker(i,f.DefaultRefreshRate))}var qt={voiceActivity:{attackThreshold:.9,releaseThreshold:.8},volumeGate:{attackThreshold:-30,releaseThreshold:-40}},$t={echoCanceller:!1,highPassFilter:!1,noiseSuppression:"None",transientSuppressor:!1,gainController:!1},Kt={enabled:!1},D=class{constructor(e,t){this.controller=e;this._Medias=new Map;this._Vad=qt;this._Apm=$t;this._Vif=Kt;this._Volume=1;this._ProcessingDevice=!1;this._DeviceCb=async()=>{(await te()).some(n=>n.device.deviceId===this._DeviceParameters?.device?.device.deviceId)||(this._DeviceParameters?.onStatusChanged?.("stopped"),navigator.mediaDevices.removeEventListener("devicechange",this._DeviceCb),this._DeviceParameters=void 0,this._Capture!==void 0&&(F(this._Capture.source.mediaStream),this._Capture.source.disconnect(this._Capture.worker.worklet)))};t.apm&&this.setApm(t.apm),t.vad&&this.setVad(t.vad),t.volume&&this.setVolume(t.volume)}get vad(){return this._Vad}get apm(){return this._Apm}get vif(){return this._Vif}get volume(){return this._Volume}get activity(){return this._Capture?.worker.activity??j}get speechProbability(){return this._Capture?.worker.speechProbability??0}get deviceId(){return this._DeviceParameters?.device?.device.deviceId}start(e,t,n){let r=this._Medias.get(t.connectionId);r===void 0&&this._Medias.set(t.connectionId,r=new Set),m(`starting audio capture media ${t.mediaId} on connection ${t.connectionId}`),!r.has(t.mediaId)&&(r.add(t.mediaId),e.mainConnection?.id===t.connectionId&&e.request("StartMedia",{media_id:t.mediaId,properties:{kind:"audio",uid:n}}).catch(o=>g(`StartMedia got rejected; ${o}`)),this._UpdateMedias())}stop(e,t){let n=this._Medias.get(t.connectionId);m(`stopping audio capture media ${t.mediaId} on connection ${t.connectionId}`),n?.delete(t.mediaId)&&(e.mainConnection?.id===t.connectionId&&e.request("StopMedia",{media_id:t.mediaId}).catch(r=>g(`StopMedia got rejected; ${r}`)),this._UpdateMedias())}setVad(e){u("updating vad config",e),this._Vad=e,this._Capture&&this._Capture.worker.setVad(e)}async setApm(e){u("updating apm config",e),this._Apm=e,this._DeviceParameters&&await this.setDevice(this._DeviceParameters)}setVif(e){this._Vif=e,this._Capture&&this._Capture.worker.setVif(this._Vif)}async setDevice(e){if(u("updating capture device parameters",e),this._ProcessingDevice)return;this._ProcessingDevice=!0;let t=this._DeviceParameters;if(this._DeviceParameters=e,this._Volume==="muted"){this._ProcessingDevice=!1;return}try{let n=await qe(this.controller,e,this._Apm);if(n.type!=="Success"){g(`unable to open capture device; ${n.reason}`),this._DeviceParameters=t,this._ProcessingDevice=!1;return}this._DeviceParameters.onStatusChanged&&navigator.mediaDevices.addEventListener("devicechange",this._DeviceCb),this._Capture!==void 0&&(F(this._Capture.source.mediaStream),this._Capture.source.disconnect(this._Capture.worker.worklet)),this._Capture===void 0||this._Capture.worker.worklet.context!==n.value.context?(this._Capture?.worker.close(),this._Capture={worker:await Ke(n.value.context,this.controller.sink,new WeakRef(this)),source:n.value},await this._UpdateMedias(),await this.setVolume(this._Volume),this.setVad(this.vad)):this._Capture.source=n.value,this._Capture.source.connect(this._Capture.worker.worklet)}catch(n){throw this._ProcessingDevice=!1,new Error(`Error in setDevice(); ${n}`)}this._ProcessingDevice=!1}async setVolume(e){u(`updating capture volume to ${e}`),this._Volume=e,e==="muted"?await this.close():(!this._Capture&&this._DeviceParameters&&await this.setDevice(this._DeviceParameters),this._Capture?.worker.setVolume(e)),e!==this._Volume&&await this.setVolume(this._Volume)}async close(){if(u("closing audio capture"),this._Capture!==void 0){this._Capture.worker.close(),F(this._Capture.source.mediaStream);try{this._Capture.source.disconnect(this._Capture.worker.worklet)}catch(e){v("failed to disconnect audio node",e)}await this.controller.audioContexts.release(this._Capture.source.context)}navigator.mediaDevices.removeEventListener("devicechange",this._DeviceCb),this._Capture=void 0}async dump(){let e=[];for(let[t,n]of this._Medias)e.push(`${t} => ${Array.from(n.values()).join(",")}`);return{worker:await this._Capture?.worker.dump()??null,vad:this._Vad,apm:this._Apm,volume:this._Volume,processingSetDevice:this._ProcessingDevice,medias:e}}async _UpdateMedias(){if(this._Capture!==void 0){let e=Array.from(this._Medias).map(([t,n])=>({id:t,mediaIds:Array.from(n)}));this._Capture.worker.setMedias(e)}}startLoopback(){throw new Error("Not available")}stopLoopback(){throw new Error("Not available")}};var Jn=require("@4players/odin-common");var S=class{constructor(e,t){this.controller=e;this.options=t;this._Medias=new Map}get mediaStream(){return this._mediaStream}async setDevice(e){this._mediaStream=e}start(e,t,n){let r=this._Medias.get(t.connectionId);if(r===void 0&&this._Medias.set(t.connectionId,r=new Set),!r.has(t.mediaId)&&(r.add(t.mediaId),e.mainConnection?.id===t.connectionId)){let o={kind:"video",codec:this.options?.codec??"H264",uid:n};this.options?.customType&&(o.customType=this.options.customType),e.request("StartMedia",{media_id:t.mediaId,properties:o}).catch(a=>g(`StartMedia got rejected; ${a}`))}}stop(e,t){this._mediaStream&&F(this._mediaStream),this._Medias.get(t.connectionId)?.delete(t.mediaId)&&e.mainConnection?.id===t.connectionId&&e.request("StopMedia",{media_id:t.mediaId}).catch(r=>g(`StopMedia got rejected; ${r}`))}close(){throw new Error("Method not implemented.")}};var Qn=require("@4players/odin-common");var I=class{constructor(e,t){this.controller=e;this.uid=t}get volume(){return this.controller.playbackGetVolume(this.uid)}get activity(){return this.controller.playbackGetActivity(this.uid)}get jitterStats(){return this.controller.playbackGetJitterStats(this.uid)}start(e){m(`starting audio playback media ${e.mediaId} on connection ${e.connectionId}`),this.controller.playbackStart(this.uid,e)}stop(e){m(`stopping audio playback media ${e.mediaId} on connection ${e.connectionId}`),this.controller.playbackStop(this.uid,e)}setVolume(e){u(`updating audio playback media '${this.uid}' volume to ${e}`),this.controller.playbackSetVolume(this.uid,e)}close(){u(`closing audio playback media '${this.uid}'`),this.controller.closePlayback(this.uid)}};var re=class{constructor(e){this.audioContextFactory=e;this.entries=[]}async acquire(e){for(let n of this.entries){if(n.sampleRate===e)return n.references+=1,n.audioContext;if(n.sampleRate===void 0&&n.audioContext.sampleRate===e)return n.references+=1,n.audioContext}let t={sampleRate:e,audioContext:await this.audioContextFactory(e),references:1};return this.entries.push(t),t.audioContext}async release(e){let t=this.entries.findIndex(n=>n.audioContext===e);if(!(t<0)&&(this.entries[t].references-=1,this.entries[t].references<=0)){let n=this.entries[t].audioContext;this.entries.splice(t,1),await n.close()}}close(){for(let e of this.entries)e.audioContext.close()}},ie=class i{constructor(e,t){this.audioContexts=e;this.sink=t;this.audioCaptures=[];this.globalVolume=[1,1];this.playbacks=new Map;this.setPlaybackStrand=new z.Strand}async create(e,t){return new i(e,t)}receivePacket(e,t){this.playbackWorker?.receivePacket(e,t)}async createAudioPlayback(e){let t=this.playbacks.get(e.uid),n=e.volume;if(t===void 0){n=n??[1,1];let r=new I(this,e.uid);t={id:void 0,volume:n,playback:r},this.playbacks.set(e.uid,t)}return n!==void 0&&await t.playback.setVolume(n),t.playback}async createAudioCapture(e){let t=new D(this,e);return await t.setDevice(e),this.audioCaptures.push(new WeakRef(t)),t}async createVideoCapture(e,t){let n=new S(this,t);return await n.setDevice(e),n}playbackSetDevice(e){return this.setPlaybackStrand.enqueue(async()=>{let t=e?.device.deviceId??"";t==="default"&&(t="");let n;this.playbackWorker===void 0?(n=await this.audioContexts.acquire(48e3),this.playbackWorker=await Oe(n),this.playbackWorker.setGlobalVolume(this.globalVolume)):n=this.playbackWorker.audioContext,n?.setSinkId!==void 0&&e&&n?.state==="running"&&await n.setSinkId(t)})}playbackSetGlobalVolume(e){this.globalVolume=e,this.playbackWorker?.setGlobalVolume(this.globalVolume)}playbackStart(e,t){let n=this.playbacks.get(e);n!==void 0&&(n.id!==void 0&&this.playbackStop(e,n.id),n.id=t,this.playbackWorker?.addMedias(t.connectionId,[t.mediaId],n.volume))}playbackStop(e,t){let n=this.playbacks.get(e);n!==void 0&&(n.id!==t&&v(`playback media ${t.mediaId} on connection ${t.connectionId} is out of sync`,n.id,t),n.id=void 0,this.playbackWorker?.removeMedias(t.connectionId,[t.mediaId]))}playbackGetActivity(e){let t=this.playbacks.get(e);return t?.id===void 0?j:this.playbackWorker?.getActivity(t.id)??j}playbackGetJitterStats(e){let t=this.playbacks.get(e);return t?.id===void 0?z.JITTER_STATS_INITIAL:this.playbackWorker?.getJitterStats(t.id)??z.JITTER_STATS_INITIAL}playbackSetVolume(e,t){let n=this.playbacks.get(e);n!==void 0&&(n.volume=t,n.id!==void 0&&this.playbackWorker?.setVolume(n.id,n.volume))}playbackGetVolume(e){return this.playbacks.get(e)?.volume??[1,1]}closePlayback(e){let t=this.playbacks.get(e);t!==void 0&&(t.id!==void 0&&this.playbackStop(e,t.id),this.playbacks.delete(e))}close(){this.playbackWorker?.close(),this.audioContexts.close()}async dump(){let e=[];for(let n of this.audioCaptures){let r=n.deref();r!==void 0&&e.push(await r.dump())}let t=[];for(let[n,r]of this.playbacks)t.push({uid:n,id:r.id||null,volume:r.volume});return{globalVolume:this.globalVolume,playbackWorker:await this.playbackWorker?.dump()||null,captures:e,playbacks:t}}},U=class{constructor(e,t=!1){this.device=e;this.isDefault=t}get type(){switch(this.device.kind){case"audioinput":return"AudioCapture";case"audiooutput":return"AudioPlayback";default:(0,z.assert)(!1,"not supported device")}}get id(){return this.device.deviceId}get name(){return this.device.name}};var Q=require("@4players/odin-common"),Ge=require("@msgpack/msgpack");var ba=require("@4players/odin-common");var b=require("@4players/odin-common");var R=require("@4players/odin-common");var P=class i{constructor(e,t){this.text=e;this.claims=t;if(t.ups!==void 0){let n=i.parse(t.ups);n.type==="Success"?this._Upstream=n.value:g(`upstream in token can not be parsed; ${n.reason}`)}}static parse(e){let t=e.split(".");if(t.length!==3)return(0,R.failure)("expected three segments");let n=(0,R.fromBase64Url)(t[1]);if(n.type==="Failure")return(0,R.failure)(`invalid token; ${n.reason}`);let r;try{r=JSON.parse(n.value)}catch(a){return(0,R.failure)(`invalid token; ${a}`)}let o=R.TokenClaimsSchema.safeParse(r);return o.success===!1?(0,R.failure)(`invalid token; ${o.error}`):(0,R.success)(new i(e,o.data))}get timeLeft(){return this.claims.exp===void 0?1/0:this.claims.exp*1e3-Date.now()}get upstream(){return this._Upstream}get defaultRoomId(){return Array.isArray(this.claims.rid)?this.claims.rid.length===1?this.claims.rid[0]:void 0:this.claims.rid}hasAudience(e){return this.claims.aud===void 0&&e==="gateway"?!0:Array.isArray(this.claims.aud)?this.claims.aud.includes(e):this.claims.aud===e}toString(){return this.text}};var V=require("@4players/odin-common"),oe=require("@msgpack/msgpack");var L=class{constructor(e){this.stream=e;this._SendQueue=[];this._RecvQueue=[];this._EventQueue=[];this._Requests=new Map;this._Close=new AbortController;this._NextId=1;e instanceof WebSocket?(e.addEventListener("open",this._OnWebSocketOpen.bind(this)),e.addEventListener("close",this.close.bind(this)),e.addEventListener("message",t=>this._OnRpcMessage(t.data)),e.binaryType="arraybuffer"):(Ft(e.readable.getReader(),this._OnRpcMessage.bind(this)).finally(this.close.bind(this)),this._Writer=e.writable.getWriter()),this._Close.signal.addEventListener("abort",this._Cleanup.bind(this),{once:!0})}get closed(){return this._Close.signal.aborted}get closeSignal(){return this._Close.signal}async request(e,t,n){if(this.closed)return Promise.resolve("closed");let r=this._NextId,o=n===void 0?void 0:setTimeout(()=>this._TimeoutRequest(r),n);return this._Send([0,r,e,t]),new Promise(a=>{this._Requests.set(r,{accept:a,timeoutId:o}),this._NextId+=1})}notify(e,t){this.closed||this._Send([2,e,t])}recv(){if(this.closed)return Promise.resolve("closed");let e=this._EventQueue.shift();return e!==void 0?Promise.resolve(e):new Promise(t=>this._RecvQueue.push(t))}close(){this._Close.abort()}_TimeoutRequest(e){let t=this._Requests.get(e);t!==void 0&&(this._Requests.delete(e),clearTimeout(t.timeoutId),t.accept("timeout"))}_Send(e){let t;e instanceof Uint8Array?t=e:t=new Uint8Array((0,oe.encode)(e,{ignoreUndefined:!0,forceFloat32:!0})),this.stream instanceof WebSocket?this.stream.send(t):this._Writer.write(t)}_OnWebSocketOpen(e){for(let t of this._SendQueue)this._Send(t);this._SendQueue.length=0}_OnRpcMessage(e){let t=Ot(e);if(t.type!=="Success"){this.close();return}switch(t.value[0]){case 0:this._Send([1,t.value[1],"not supported",null]);break;case 1:this._OnResponse(t.value[1],t.value[2],t.value[3]);break;case 2:this._OnEvent(t.value[1],t.value[2]);break}}_OnResponse(e,t,n){let r=this._Requests.get(e);r!==void 0&&(clearTimeout(r.timeoutId),r.accept(t===null?(0,V.success)(n):(0,V.failure)(t)))}_OnEvent(e,t){let n=this._RecvQueue.shift();n!==void 0?n({name:e,properties:t}):this._EventQueue.push({name:e,properties:t})}_Cleanup(){for(let e of this._Requests.values())clearTimeout(e.timeoutId),e.accept("closed");this._Requests.clear();for(let e of this._RecvQueue)e("closed");this._RecvQueue.length=0,this._SendQueue.length=0,this.stream instanceof WebSocket?this.stream.close():this._Writer?.close().catch(()=>{})}},K=class{constructor(e,t,n){this.rpc=e;this._Commands=t;this._Events=n}get closed(){return this.rpc.closed}get closeSignal(){return this.rpc.closeSignal}async request(e,t,n){t=this._Commands[e].request.parse(t);let r=await this.rpc.request(e,t,n);if(r==="closed"||r==="timeout"||r.type==="Failure")return r;let o=this._Commands[e].response.safeParse(r.value);return o.success?(r.value=o.data,r):(0,V.failure)(`response schema failed validation: ${o.error}`)}notify(e,t){t=this._Commands[e].request.parse(t),this.rpc.notify(e,t)}async recv(){let e=await this.rpc.recv();if(e==="closed")return e;let t=Pe(this._Events,e);return t!==void 0?t:(Fe(this._Events,e.name)?(g(`schema validation for ${e.name} failed; closing connection`),this.rpc.close()):v(`dropping unknown event ${e.name}`),"closed")}close(){this.rpc.close()}};function Ot(i){try{return(0,V.success)(V.MessagePackRpcSchema.parse((0,oe.decode)(new Uint8Array(i))))}catch(e){return(0,V.failure)(String(e))}}function Pe(i,e){if(!Fe(i,e.name))return;let t=i[e.name].safeParse(e.properties);if(t.success)return{name:e.name,properties:t.data}}function Fe(i,e){return e in i}async function Ft(i,e){let t=new Uint8Array;for(;;){let n=Ne(t,0);if(n===s){i.cancel("protocol violation");break}if(n!==w&&n<=t.length){e(t.subarray(0,n)),t=t.subarray(n);continue}let r;try{r=await i.read()}catch{break}if(r.done)break;t=Ht(t,r.value)}t.length>0&&v("unprocessed bytes left in rpc stream",t)}function Ht(i,e){if(i.length===0)return e;if(e.length===0)return i;let t=new Uint8Array(i.length+e.length);return t.set(i,0),t.set(e,i.length),t}var ae=class i{constructor(e,t,n,r,o,a){this.id=e;this.address=t;this.connection=r;this.rpc=o;this.closeController=a;this.status=0;this._ConnectionStats={...b.CONNECTION_STATS_INITIAL};this.closePromise=(async()=>{try{await this.connection.closed,u("h3 main connection closePromise.closed resolved")}catch{}return"closed"})(),f.MainConnectionPingInterval!==void 0&&(this._PingIntervalId=setInterval(async()=>{this.connectionStats.rtt=await this.ping(f.HealthCheckTimeout)},f.MainConnectionPingInterval)),a.signal.addEventListener("abort",()=>this._CleanUp(),{once:!0}),this.datagramWriter=r.datagrams.writable.getWriter(),this.connection.closed.then(()=>{u("h3 main connection connection.closed resolved"),this.status=2}).catch(()=>{}),this.connection.ready.then(()=>{this.status=1}).catch(()=>{}),this.readDatagrams(this.connection,this.id,(c,d)=>{this._ConnectionStats.packetsReceived+=1,n(c,d)})}get connectionStats(){return this._ConnectionStats}get closeSignal(){return this.closeController.signal}get transport(){return"H3"}get closed(){return this.closeSignal.aborted}static async create(e,t,n,r){try{let o=new URL(`https://${t}?version=${Be}&uid=${Y}`),a=new AbortController;r.addEventListener("abort",()=>a.abort(),{once:!0});let c=new WebTransport(o);a.signal.addEventListener("abort",async()=>{try{try{if(await c.closed)return;await c.close()}catch(A){console.warn(A)}}catch{}},{once:!0}),await c.ready;let d=await c.createBidirectionalStream(),y=new K(new L(d),b.MainCommandsRpc,b.MainNotificationsRpc);return a.signal.addEventListener("abort",()=>y.close(),{once:!0}),y.notify("Hello",{stream:"main"}),(0,b.success)(new i(e,t,n,c,y,a))}catch(o){return(0,b.failure)(`connecting to ${t} failed with ${o}`)}}waitForConnected(){return Promise.race([this.connection.ready,this.connection.closed.then(()=>{throw new Error("connection closed")})])}close(){u("closing h3 main connection"),this.closeController.abort("request")}async joinRoom(e,t,n,r){try{if(this.closed)return"closed";let o=await this.connection.createBidirectionalStream().catch(()=>"closed");if(o==="closed")return"closed";let a=new K(new L(o),b.RoomCommandsRpc,b.RoomNotificationsRpc),c=await a.request("Hello",{stream:"room",token:String(e),room_id:t,user_data:n,position:r});return c==="closed"||c.type==="Failure"?c:(0,b.success)(a.rpc)}catch(o){return(0,b.failure)(`JoinRoom failed; ${o}`)}}async getReconnectToken(e){let t=await this._Request("RequestReconnectToken",{peer_id:e});return t.type==="Failure"?t:P.parse(t.value)}async healthCheck(){let e=this._RequestPing(f.HealthCheckTimeout);return e||this.close(),e}async ping(e=3e4){let t=Date.now();return await this._RequestPing(e)?Date.now()-t:0}async _RequestPing(e){let t=await this._Request("Ping",{},e);return t!=="timeout"&&t.type==="Success"}sendDatagram(e){try{this.datagramWriter.write(e),this._ConnectionStats.packetsSent+=1;let t=e.byteLength;this.connectionStats.bytesSent+=t}catch{}}async readDatagrams(e,t,n){try{let r=e.datagrams.readable.getReader();for(;;){let o=await r.read();if(o.done)break;let a=o.value.byteLength;this.connectionStats.bytesReceived+=a,n(t,o.value)}}catch{(0,b.info)("H3 MainConnection.readDatagrams() drop silently")}}startLocalVideo(e,t){throw new Error("not supported.")}stopLocalVideo(e){throw new Error("not supported.")}startRemoteVideo(e){throw new Error("not supported.")}stopRemoteVideo(e){throw new Error("not supported.")}_CleanUp(){u(`performing cleanup for connection ${this.id} to '${this.address}'`),clearInterval(this._PingIntervalId)}async dump(){return{type:"H3",id:this.id,address:this.address,closed:this.closed,status:this.status,datagrams:{sent:this._ConnectionStats.packetsSent,received:this._ConnectionStats.packetsReceived}}}async _Request(e,t,n){let r=Date.now(),o=await(n===void 0?this.rpc.request(e,t):this.rpc.request(e,t,n)),a=(Date.now()-r)/1e3;return e!=="Ping"&&u(`sent main rpc '${e}'; took: ${a}s`,{properties:t,timeout:n,result:o}),o==="closed"?(0,b.failure)("connection lost"):o}};var h=require("@4players/odin-common");var M=require("@4players/odin-common");async function xe(i,e){let t=performance.now(),n=(0,M.failure)("no connection was attempted");for(let r=0;r<f.ConnectInterval.length&&!e.aborted;r++){let o=t+Gt(r);if(await(0,M.abortableSleep)(o-performance.now(),e)==="aborted")return(0,M.failure)("aborted");if(n=await Jt(i,e),n.type==="Success")return n}return n}async function Jt(i,e){let t;try{t=new WebSocket(i)}catch(d){return(0,M.failure)(`unable to create websocket; ${d}`)}let n=new AbortController,r=new Promise(d=>t.addEventListener("open",()=>d((0,M.success)(t)),{signal:n.signal})),o=new Promise(d=>t.addEventListener("close",y=>d((0,M.failure)(y.reason!==""?y.reason:"unable to establish websocket connection")),{signal:n.signal})),a=new Promise(d=>e.addEventListener("abort",()=>d((0,M.failure)("aborted")),{signal:n.signal})),c=await Promise.race([r,o,a]);return n.abort(),c}function Gt(i){let e=f.ConnectInterval.slice(0,i).reduce((n,r)=>n+r,0),t=f.ConnectInterval[i]*Math.random();return e+t}var se=class i{constructor(e,t,n,r,o,a){this.id=e;this.address=t;this.url=n;this.onDatagram=r;this.connection=o;this.webRtc=a;this._PeerIdMap=new WeakMap;this._LocalVideos=new Map;this._RemoteVideos=new Map;this._Negotiation={requested:!1,running:!1};this._ConnectionStats={...h.CONNECTION_STATS_INITIAL};m(`creating new webrtc connection to '${this.address}' with id ${this.id}`),this.closeSignal.addEventListener("abort",()=>this._CleanUp(),{once:!0}),this.addWebRtcToGlobal(a),this._ClosePromise=new Promise(c=>{this.closeSignal.addEventListener("abort",()=>{c("closed")},{once:!0})}),this._AudioChannel=this.webRtc.createDataChannel("audio",{id:2,negotiated:!0,ordered:!1,maxRetransmits:0}),this._AudioChannel.binaryType="arraybuffer",this._AudioChannel.onmessage=c=>{this._ConnectionStats.packetsReceived++;let d=new Uint8Array(c.data);this._ConnectionStats.bytesReceived+=d.byteLength,this.onDatagram(this.id,d)},this.addDataChannelToGlobal(this._AudioChannel),this._AudioChannel.onerror=c=>v(`webrtc audio data channel for connection with id ${e} raised an error`,c),this._AudioChannel.onclosing=c=>m(`webrtc audio data channel for connection with id ${e} begins shutting down`,c),this._AudioChannel.onclose=c=>m(`webrtc audio data channel for connection with id ${e} has closed`,c),this.webRtc.onicecandidateerror=c=>v(`webrtc ice negotiation for connection with id ${e} raised an error`,c),this.webRtc.onicegatheringstatechange=c=>m(`webrtc ice candidate gathering state for connection with id ${e} changed`,c),this.webRtc.oniceconnectionstatechange=c=>m(`webrtc ice connection state for connection ${e} changed`,c),this.webRtc.onicecandidate=this._OnIceCandidate.bind(this),this.webRtc.onconnectionstatechange=()=>this._OnWebRtcStatusChanged(),this.webRtc.ontrack=this.onTrack.bind(this),this._SendOffer(),f.MainConnectionPingInterval!==void 0&&(this._PingIntervalId=setInterval(async()=>{this.connectionStats.rtt=await this.ping(f.HealthCheckTimeout)},f.MainConnectionPingInterval)),this._readWebRtcUpdate()}static async create(e,t,n,r){if(r.aborted)return(0,h.failure)("aborted");let o;try{o=new URL(`wss://${t}`)}catch(y){return(0,h.fail)(`address in token(${t}) can not be parsed; ${y}`)}let a=(0,h.extendUrl)(o,`main?uid=${Y}`);if(a.type!=="Success")return a;let c=await xe(a.value,r);if(c.type!=="Success")return c;let d=new K(new L(c.value),h.MainCommandsRpc,h.MainNotificationsRpc);try{return(0,h.success)(new i(e,t,o,n,d,new RTCPeerConnection))}catch(y){return d.close(),(0,h.failure)(`unable to create webrtc connection; ${y}`)}}get connectionStats(){return this._ConnectionStats}get transport(){return"WebRTC"}get closePromise(){return this._ClosePromise}get closeSignal(){return this.connection.closeSignal}get closed(){return this.connection.closed}get status(){switch(this.webRtc.connectionState){case"connected":return 1;case"failed":case"closed":return 2;default:return 0}}async waitForConnected(){return new Promise((e,t)=>{let n=()=>{let r=this.status;r!==0&&(this.webRtc.removeEventListener("connectionstatechange",n),r===1?e():t(new Error("connection closed")))};this.webRtc.addEventListener("connectionstatechange",n),n()})}close(){m(`closing webrtc connection to '${this.address}' with id ${this.id}`),this.connection.close()}async joinRoom(e,t,n,r){let o=await this.connection.request("JoinRoom",{token:String(e),room_id:t,user_data:n,position:r});if(o==="closed"||o.type==="Failure")return o;let a=o.value;if(!("token"in a))return(0,h.failure)("JoinRoom responded without token");let c=(0,h.extendUrl)(this.url,`room?token=${a.token}`);if(c.type==="Failure")return c;let d=await xe(c.value,this.closeSignal);return d.type==="Success"?(this._PeerIdMap.set(d.value,a.peer_id),(0,h.success)(new L(d.value))):d}async getReconnectToken(e){let t=await this._Request("RequestReconnectToken",{peer_id:e});return t.type==="Failure"?t:P.parse(t.value)}sendDatagram(e){if(this._AudioChannel.readyState==="open"){let t=e.byteLength;this._AudioChannel.send(e),this._ConnectionStats.packetsSent+=1,this._ConnectionStats.bytesSent+=t}}async healthCheck(){let e=this._RequestPing(f.HealthCheckTimeout);return e||this.close(),e}async ping(e=3e4){let t=Date.now();return await this._RequestPing(e)?Date.now()-t:0}async _RequestPing(e=3e4){let t=await this._Request("Ping",{},e);return t!=="timeout"&&t.type==="Success"}async dump(){return{type:"WebRtc",id:this.id,address:this.address,url:this.url.toString(),closed:this.closed,audioChannel:{readyState:this._AudioChannel.readyState,bufferedAmount:this._AudioChannel.bufferedAmount},datagrams:{sent:this.connectionStats.packetsSent,received:this.connectionStats.packetsReceived},webRtc:{signalingState:this.webRtc.signalingState,connectionState:this.webRtc.connectionState,iceGatheringState:this.webRtc.iceGatheringState,iceConnectionState:this.webRtc.iceConnectionState}}}async _Request(e,t,n){let r=Date.now(),o=await(n===void 0?this.connection.request(e,t):this.connection.request(e,t,n)),a=(Date.now()-r)/1e3;return e!=="Ping"&&u(`sent main rpc '${e}'; took: ${a}s`,{properties:t,timeout:n,result:o}),o==="closed"?(0,h.failure)("connection lost"):o}async _readWebRtcUpdate(){for(;;){let e=await this.connection.recv();if(e==="closed")break;e.name==="WebRtcUpdate"&&this._OnWebRtcUpdate(e.properties)}this.close()}async _SendOffer(){try{let e=await this.webRtc.createOffer();(0,h.assert)(e.type==="offer"&&e.sdp,"generated broken webrtc offer"),await this.webRtc.setLocalDescription(e);let t=e.sdp??"",n=[];for(let[r,{transceiver:o}]of this._LocalVideos)n.push([r,o.mid]);await this._Request("WebRtcUpdate",{kind:"Sdp",type:"Offer",sdp:t,media_map:n})}catch{g("failed to create sdp offer")}}_OnWebRtcStatusChanged(){switch(u(`${this.webRtc.connectionState}`),this.webRtc.connectionState){case"closed":case"failed":this.close();break;case"connected":clearTimeout(this._WebRtcIceTimeout),this._WebRtcIceTimeout=void 0,clearTimeout(this._WebRtcTimeout),this._WebRtcTimeout=void 0;break;case"disconnected":f.MainConnectionPingInterval!==void 0&&this.healthCheck().then();default:this._WebRtcIceTimeout===void 0&&(this._WebRtcIceTimeout=setTimeout(()=>{u("_WebRtcIceTimeout was reached"),this.webRtc.restartIce(),this._WebRtcIceTimeout=void 0},f.WebRtcIceTimeout)),this._WebRtcTimeout===void 0&&(this._WebRtcTimeout=setTimeout(()=>{u("_WebRtcTimeout was reached"),this.close()},f.WebRtcTimeout));break}}_OnWebRtcUpdate(e){switch(e.kind){case"Sdp":this._UpdateRemoteSdp(e);break;case"Trickle":this._AddIceCandidate(e);break;case"TrickleFinished":this.webRtc.addIceCandidate(void 0);break}}async _UpdateRemoteSdp(e){for(let[n,r]of e.media_map){let o=this._RemoteVideos.get(n);o?.state==="Requested"&&this._RemoteVideos.set(n,{state:"Mapped",mid:r,promise:o.promise})}let t=e.type==="Answer"?"answer":"offer";this._ResolveOffer?.(!0),await this.webRtc.setRemoteDescription({type:t,sdp:e.sdp})}_AddIceCandidate(e){let t={candidate:e.candidate,sdpMid:e.spd_mid??void 0,sdpMLineIndex:e.spd_mline_index??void 0,usernameFragment:e.username_fragment??void 0};t.sdpMid===void 0&&t.sdpMLineIndex===void 0&&(t.sdpMid="0"),this.webRtc.addIceCandidate(t)}async _OnIceCandidate(e){if(e.candidate){let{candidate:t,sdpMid:n,sdpMLineIndex:r,usernameFragment:o}=e.candidate;this._Request("WebRtcUpdate",{kind:"Trickle",candidate:t,spd_mid:n??void 0,spd_mline_index:r??void 0,username_fragment:o??void 0})}else this._Request("WebRtcUpdate",{kind:"TrickleFinished"})}ensureTransceivers(){let e=this._LocalVideos.size+this._RemoteVideos.size,t=this.webRtc.getTransceivers().length;for(let n=t;n<e;n++)this.webRtc.addTransceiver("video")}async startLocalVideo(e,t,n="H264"){(0,h.assert)(!this._LocalVideos.has(e),`No LocalVideo with mediaID ${e}`),u("mediaId",e),u("webRtc.getTransceivers()",this.webRtc.getTransceivers());let r=this.webRtc.getTransceivers().find(c=>c.sender.track===t);if(r)u("Transceiver found -> replaceTrack"),await r.sender.replaceTrack(t);else{let c=this.webRtc.addTrack(t);r=this.webRtc.getTransceivers().find(d=>d.sender===c),(0,h.assert)(r,"")}let a=new h.VideoCodec(n).isSupported();a&&r.setCodecPreferences([a]),this._LocalVideos.set(e,{transceiver:r}),this.requestNegotiation(!1)}stopLocalVideo(e){let t=this._LocalVideos.get(e);t&&(t.transceiver.sender.track.enabled=!1,this._LocalVideos.delete(e),this.requestNegotiation(!1))}startRemoteVideo(e,t="H264"){m(`starting remote video for media ${e}`);let n=this.webRtc.addTransceiver("video"),o=new h.VideoCodec(t).isSupported();return o&&n.setCodecPreferences([o]),this.requestNegotiation(!0),new Promise((a,c)=>{this._RemoteVideos.set(e,{state:"Requested",promise:{resolve:a,reject:c}})})}stopRemoteVideo(e){m(`stopping remote video for media ${e}`);let t=this._RemoteVideos.get(e);t&&(this._RemoteVideos.delete(e),t.state!=="Active"&&t.promise.reject(new Error("video stopped")),this.requestNegotiation(!1))}onTrack(e){u("event.transceiver",e.transceiver),u("webRtc.getTransceivers()",this.webRtc.getTransceivers()),u("_RemoteVideos",this._RemoteVideos);let t=Array.from(this._RemoteVideos).find(o=>o[1].state==="Mapped"&&o[1].mid===e.transceiver.mid);if(u("Found the RemoteVideo",t),t===void 0){v(`Unexpected track(mid: ${e.transceiver.mid})`);return}let[n,r]=t;(0,h.assert)(r.state==="Mapped",""),this._RemoteVideos.set(n,{state:"Active",transceiver:e.transceiver}),e.track.onended=()=>u(`Track ended ${e.transceiver.mid}`,t),r.promise.resolve(e.track)}requestNegotiation(e){this._Negotiation.requested=!0,!this._Negotiation.running&&(this._Negotiation.running=!0,setTimeout(async()=>{this._Negotiation.requested=!1;try{await this._SendOffer(),e&&await new Promise(t=>{this._ResolveOffer=t})}finally{this._Negotiation.running=!1;let t=Array.from(this._RemoteVideos).find(n=>n[1].state==="Requested");this._Negotiation.requested&&this.requestNegotiation(e)}}))}_CleanUp(){u(`performing cleanup for connection ${this.id} to '${this.url}'`),this.connection.close(),this.webRtc.close(),clearInterval(this._PingIntervalId),clearTimeout(this._WebRtcIceTimeout),clearTimeout(this._WebRtcTimeout)}addDataChannelToGlobal(e){typeof globalThis.dataChannels>"u"&&(globalThis.dataChannels=[]),Array.isArray(globalThis.dataChannels)&&globalThis.dataChannels.length<=100&&globalThis.dataChannels.push(e)}addWebRtcToGlobal(e){typeof globalThis.webRtcConnections>"u"&&(globalThis.webRtcConnections=[]),Array.isArray(globalThis.webRtcConnections)&&globalThis.webRtcConnections.length<=100&&globalThis.webRtcConnections.push(e)}};function Je(i,e,t,n,r){switch(m(`creating ${e} main connection to '${t}'`),e){case"H3":return ae.create(i,t,n,r);case"WebRTC":return se.create(i,t,n,r);default:throw new Error(`transport ${e} is not supported`)}}var x=require("@4players/odin-common");var ce=class{constructor(e,t,n,r){this.id=e;this.connection=t;this.cipher=n;this.mediaMap=r;this._DatagramReceived=0;this._DatagramSent=0;this._DatagramReceivedInvalid=0;this._DatagramSentInvalid=0;t.closeSignal.addEventListener("abort",()=>this.close())}get connectionStats(){return this.connection.connectionStats}get transport(){return this.connection.transport}get address(){return this.connection.address}get status(){return this.closed?2:this.connection.status}get closePromise(){return this.connection.closePromise}get closeSignal(){return this.connection.closeSignal}get closed(){return this.connection.closed}waitForConnected(){return this.connection.waitForConnected()}close(){this.connection.close()}async joinRoom(e,t,n,r){this.cipher.reset();let o=await this.cipher.encryptUserData(n);if(o===s)return(0,x.failure)("unable to encrypt initial user-data");let a=await this.connection.joinRoom(e,t,o,r);return a==="closed"||a.type==="Failure"?a:(0,x.success)(new Se(a.value,this.cipher,this))}getReconnectToken(e){return this.connection.getReconnectToken(e)}healthCheck(){return this.connection.healthCheck()}async sendDatagram(e){let t=await this.cipher.encryptDatagram(e);t!==s?(this._DatagramSent+=1,this.connection.sendDatagram(t)):this._DatagramSentInvalid+=1}async decryptDatagram(e,t){let n=await this.cipher.decryptDatagram(e,t);return n!==s?this._DatagramReceived+=1:this._DatagramReceivedInvalid+=1,n}startLocalVideo(e,t){return this.connection.startLocalVideo(e,t)}stopLocalVideo(e){return this.connection.stopLocalVideo(e)}startRemoteVideo(e){return this.connection.startRemoteVideo(e)}stopRemoteVideo(e){return this.connection.stopRemoteVideo(e)}async dump(){return{type:"Encrypted",id:this.id,address:this.address,connectionId:this.connection.id,closed:this.closed,transport:this.transport,datagrams:{sent:this._DatagramSent,received:this._DatagramReceived,sentInvalid:this._DatagramSentInvalid,receivedInvalid:this._DatagramReceivedInvalid},cipher:await this.cipher.dump()}}},de=class{constructor(e){this.room=e}resendUserData(){this.room.roomConnection?.notify("UpdatePeer",{user_data:this.room.roomConnection.userData})}setUserData(e,t){let n={kind:"UserDataChanged",peer_id:e,user_data:t};this.room.onEvent("PeerUpdated",n)}},Se=class{constructor(e,t,n){this.rpc=e;this.cipher=t;this.connection=n;this.peers=new Map;this.closeSignal.addEventListener("abort",this.cleanup.bind(this),{once:!0})}get closed(){return this.rpc.closed}get closeSignal(){return this.rpc.closeSignal}async request(e,t,n){let r=await this.outgoing(e,t);return r===s?(0,x.failure)("can not be encrypted"):await this.rpc.request(e,r,n)}async notify(e,t){let n=await this.outgoing(e,t);if(n!==s)return this.rpc.notify(e,n)}async recv(){for(;;){let e=await this.rpc.recv();if(e==="closed")return e;let t=await this.incoming(e.name,e.properties);if(t!==s)return{name:e.name,properties:t}}}close(){return this.rpc.close()}async outgoing(e,t){if(!(e in x.RoomCommandsRpc))return t;let n=zt(e,t,x.RoomCommandsRpc);if(n===void 0)return s;switch(n.name){case"UpdatePeer":{let r=await this.cipher.encryptUserData(n.request.user_data);if(r===s)return s;n.request.user_data=r}break;case"SendMessage":{let r=await this.cipher.encryptMessage(n.request.message);if(r===s)return s;n.request.message=r}break}return n.request}async incoming(e,t){if(!(e in x.RoomNotificationsRpc))return t;let n=jt(e,t,x.RoomNotificationsRpc);if(n===s)return s;switch(n.name){case"RoomUpdated":for(let r=0;r<n.properties.updates.length;r++){let o=await this.roomUpdate(n.properties.updates[r]);if(o===s)return s;n.properties.updates[r]=o}break;case"PeerUpdated":{let r=await this.peerUpdate(n.properties);if(r===s)return s;n.properties=r}break;case"MessageReceived":{let r=await this.cipher.decryptMessage(n.properties.sender_peer_id,n.properties.message);if(r===s)return s;n.properties.message=r}break}return n.properties}async peerUpdate(e){switch(e.kind){case"UserDataChanged":{let t=await this.cipher.decryptUserData(e.peer_id,e.user_data);if(t===s)return s;e.user_data=t}break;case"MediaStarted":this.mediaStarted(e.peer_id,e.media.id);break;case"MediaStopped":this.mediaStopped(e.peer_id,e.media_id);break}return e}async roomUpdate(e){switch(e.kind){case"Joined":for(let t of e.room.peers){for(let r of t.medias)this.mediaStarted(t.id,r.id);let n=await this.cipher.decryptUserData(t.id,t.user_data);n!==s?t.user_data=n:t.user_data=new Uint8Array}break;case"PeerJoined":{for(let n of e.peer.medias)this.mediaStarted(e.peer.id,n.id);let t=await this.cipher.decryptUserData(e.peer.id,e.peer.user_data);t!==s?e.peer.user_data=t:e.peer.user_data=new Uint8Array}break;case"PeerLeft":{let t=this.peers.get(e.peer_id);this.peers.delete(e.peer_id);for(let n of t??[])this.mediaStopped(e.peer_id,n)}break}return e}mediaStarted(e,t){let n=this.peers.get(e);n===void 0&&(n=new Set,this.peers.set(e,n)),n.add(t);let r=O({connectionId:this.connection.connection.id,mediaId:t});this.connection.mediaMap.set(r,{peerId:e,connection:this.connection})}mediaStopped(e,t){this.peers.get(e)?.delete(t);let r=O({connectionId:this.connection.connection.id,mediaId:t});this.connection.mediaMap.delete(r)}cleanup(){for(let[e,t]of this.peers)for(let n of t)this.mediaStopped(e,n)}};function jt(i,e,t){if(!(i in t))return s;let n=t[i].safeParse(e);return n.success?{name:i,properties:n.data}:s}function zt(i,e,t){if(!(i in t))return;let n=t[i].request.safeParse(e);return n.success?{name:i,request:n.data}:void 0}var ue=class{constructor(e){this.onDatagram=e;this._LastConnectionId=0;this._Connections=new Map;this._Close=new AbortController;this._MediaMap=new Map;let t=Date.now(),n=setInterval(()=>{Math.abs(Date.now()-t)>f.ClockJumpThreshold&&this.checkHealth(),t=Date.now()},f.ClockJumpCheckInterval);addEventListener("offline",()=>this.checkHealth(),{signal:this._Close.signal}),this._Close.signal.addEventListener("abort",()=>{clearInterval(n);for(let r of this._Connections.values())r.type==="establishing"?r.abort.abort():r.connection.close();this._Connections.clear()},{once:!0})}get closed(){return this._Close.signal.aborted}sendDatagram(e,t){let n=this._Connections.get(e);(n?.type==="established"||n?.type==="virtual")&&n.connection.sendDatagram(t)}async recvDatagram(e,t){let n=Yt(t);if(n===void 0||n.length===0)return;let r=this._MediaMap.get(O({connectionId:e,mediaId:n[0]}));if(r===void 0)this.onDatagram(e,t);else{let o=await r.connection.decryptDatagram(r.peerId,t);if(o===s)return;this.onDatagram(r.connection.id,o)}}async acquireConnection(e,t,n,r){if(await(0,Q.nextTick)(),this.closed)return"closed";let o=this.findConnection(e,t),a;switch(o?.type){case"established":u(`reusing existing connection ${o.id}`),a=o;break;case"establishing":{u(`waiting for connecting to '${t}' finish`);let y=await o.finished;if(y.type!=="Success")return y;a=y.value}break;case void 0:{u(`connecting to '${t}'`);let y=await this.createConnection(e,t,r);if(y.type!=="Success")return y;a=y.value}break}let c=a.id,d=new Ie(a.connection,()=>this.releaseConnection(c));return a.references+=1,n&&(d=new ce(++this._LastConnectionId,d,n,this._MediaMap),this.addConnection({type:"virtual",id:d.id,connection:d,createdAt:Date.now()})),(0,Q.success)(d)}async createConnection(e,t,n){let r=++this._LastConnectionId,o=Date.now(),a=new AbortController;n.addEventListener("abort",()=>a.abort(),{once:!0}),u(`creating new connection ${r}`);let c=Je(r,e,t,this.recvDatagram.bind(this),n),{promise:d,resolve:y}=Promise.withResolvers();this.addConnection({type:"establishing",id:r,address:t,transport:e,finished:d,createdAt:Date.now(),abort:a});let A=await c;if(this._Connections.delete(r),A.type!=="Success")return y(A),A;let Te={type:"established",id:r,address:t,transport:e,createdAt:o,references:0,connection:A.value};return this.addConnection(Te),(0,Q.success)(Te)}addConnection(e){(e.type==="established"||e.type==="virtual")&&e.connection.closeSignal.addEventListener("abort",()=>{m(`connection ${e.id} has been closed`),this._Connections.delete(e.id)},{once:!0}),this._Connections.set(e.id,e)}releaseConnection(e){let t=this._Connections.get(e);t?.type==="established"&&(t.references-=1,t.references<=0&&t.connection.close())}checkHealth(){for(let e of this._Connections.values())e.type==="established"&&e.connection.healthCheck()}close(){this._Close.abort()}async dump(){let e=[];for(let n of this._Connections.values()){let r;switch(n.type){case"established":r={transport:n.transport,address:n.address,connection:await n.connection.dump(),references:n.references};break;case"establishing":r={transport:n.transport,address:n.address,aborted:n.abort.signal.aborted};break;case"virtual":r={connection:await n.connection.dump()};break}e.push({type:n.type,id:n.id,createdAt:n.createdAt,...r})}let t=[];for(let[n,r]of this._MediaMap)t.push(`${n} -> ${r.peerId} @${r.connection.id} `);return{lastConnectionId:this._LastConnectionId,connections:e,mediaMap:t,closed:this.closed}}findConnection(e,t){for(let n of this._Connections.values())if(n.type!=="virtual"&&n.transport===e&&n.address===t)return n}},Ie=class{constructor(e,t){this.connection=e;this.release=t;this.closeController=new AbortController;this.closePromise=new Promise(e=>this.closeController.signal.addEventListener("abort",()=>e("closed"),{once:!0}));e.closeSignal.addEventListener("abort",()=>this.close(),{once:!0})}get closeSignal(){return this.closeController.signal}get closed(){return this.closeSignal.aborted}close(){this.release(),this.closeController.abort("closed")}get connectionStats(){return this.connection.connectionStats}get id(){return this.connection.id}get transport(){return this.connection.transport}get address(){return this.connection.address}get status(){return this.connection.status}waitForConnected(){return this.connection.waitForConnected()}joinRoom(e,t,n,r){return this.connection.joinRoom(e,t,n,r)}getReconnectToken(e){return this.connection.getReconnectToken(e)}healthCheck(){return this.connection.healthCheck()}sendDatagram(e){return this.connection.sendDatagram(e)}startLocalVideo(e,t){return this.connection.startLocalVideo(e,t)}stopLocalVideo(e){return this.connection.stopLocalVideo(e)}startRemoteVideo(e){return this.connection.startRemoteVideo(e)}stopRemoteVideo(e){return this.connection.stopRemoteVideo(e)}async dump(){let e=await this.connection.dump();return typeof e=="object"&&(e={...e,isShared:!0,referenceClosed:this.closed}),e}};function Yt(i){let e;try{e=(0,Ge.decode)(i)}catch{return}if(!Array.isArray(e)||e.length<2)return;let t=e[1];if(Array.isArray(t))return t.map(Number)}var E=class{constructor(e,t){this.uid=e;this.customType=t}setMediaStream(e){this._mediaStream||(this._mediaStream=e)}get mediaStream(){return this._mediaStream}close(){throw new Error("Method not implemented.")}};var C=require("@4players/odin-common");var l=require("@4players/odin-common");var le=class{constructor(e,t,n,r,o,a,c,d,y){this.connectionPool=e;this.transport=t;this.onEvent=n;this.gateway=r;this.roomId=a;this.cipher=y;this._Close=new AbortController;this._TokenUsed=!1;this._WasConnected=!1;this._FailedConnections=0;this._LastTokenRenew=performance.now();this._LastReconnectStartedAt=0;this._Closed=new Promise(e=>this._Close.signal.addEventListener("abort",()=>e(this._Close.signal.reason?"disconnected":"closed"),{once:!0}));this._Token=o,this._UserData=c,this._Position=d,this._Main().then(({reason:A})=>u(`_Main() resolved; ${A}`))}get token(){return this._Token}get userData(){return this._UserData}get position(){return this._Position}get closed(){return this._Close.signal.aborted}get mainConnection(){return this._MainConnection}async requestUnsafe(e,t){if(this._RoomConnection===void 0)return(0,l.failure)("currently not connected");let n=await this._RoomConnection.request(e,t,void 0);return(0,l.assert)(n!=="timeout","request should not timeout"),n!=="closed"&&n.type==="Success"&&this._HandleRequests(e,t,n.value),n}async request(e,t){let n=Date.now(),r=l.RoomCommandsRpc[e],o=await this.requestUnsafe(e,t),a=(Date.now()-n)/1e3;if(u(`sent room rpc '${e}'; took: ${a}s`,{properties:t,result:o}),o==="closed"||o.type==="Failure")return o;let c=r.response.safeParse(o.value);return c.success?(0,l.success)(c.data):(0,l.failure)(`received invalid response from ${e}; ${c.error}`)}notify(e,t){this._RoomConnection!==void 0&&this._RoomConnection.notify(e,t)}close(){this._Close.abort(!0)}get renewTokenIn(){let e=this._LastTokenRenew-performance.now()+f.MinTokenRefreshInterval,t=this._Token.timeLeft-f.MinTokenLifetime;return Math.max(e,t)}async _Main(){let e="unable to establish connection";for(let t=0;;this._FailedConnections++){if(await(0,l.sleep)(Math.max(t-performance.now(),0)),t=performance.now()+1e3,this._TokenUsed=!0,this._Token.hasAudience("sfu")){let n=await this._ConnectToServer();if(n.type==="Success"&&n.value==="shutdown"){u("connection closed by user request; shutting down"),e="user requested";break}if(this._LastReconnectStartedAt===0&&(this._LastReconnectStartedAt=performance.now()),n.type==="Failure"&&!this._WasConnected&&this._FailedConnections>=3){e=n.reason;break}if(n.type==="Failure"&&!Xt(this._LastReconnectStartedAt))continue}if(this.reset(),this.gateway!==void 0&&this._Token.hasAudience("gateway")){this._ChangeStatus("Joining",`requesting new token from ${this.gateway}`);let n=await Qt(this.gateway,this._Token,this._Close.signal);if(n.type==="Success")if(n.value instanceof P)t=0,this._Token=n.value,this._TokenUsed=!1;else{e=`gateway error; ${n.value}`;break}else if(!this._WasConnected&&this._FailedConnections>=3){e="unable to contact the gateway";break}}if(this._TokenUsed&&this._Token.timeLeft<=0){e="token has expired";break}if(this._Close.signal.aborted){e="user requested";break}}return this._ChangeStatus("Closed",e),this.close(),(0,l.failure)(e)}async _ConnectToServer(){let e=this._Token;for(;e.upstream!==void 0;)e=e.upstream;let t=e.claims.adr??this.gateway?.toString();if(!t)return(0,l.success)("continue");if(this._ChangeStatus("Joining",`connecting to ${e.claims.adr}`),this._MainConnection===void 0||this._MainConnection.closed||this._MainConnection.address!==e.claims.adr){this._MainConnection?.close();let o=await this.connectionPool.acquireConnection(this.transport,t,this.cipher,this._Close.signal);if(o==="closed")return(0,l.failure)("connection pool closed");if(o.type==="Failure")return this._ChangeStatus("Joining",`connection failed; ${o.reason}`),(0,l.failure)(`connection failed; ${o.reason}`);this._MainConnection=o.value,this._OwnPeerId=void 0}let n=await Promise.race([Promise.all([this._MainConnection.joinRoom(e,this.roomId,this.userData,this.position)]),(0,l.sleep)(f.ConnectTimeout,"timeout"),this._Closed]);if(n==="closed"||n==="disconnected")return(0,l.success)("continue");if(n==="timeout")return this._ChangeStatus("Joining","timed out"),(0,l.success)("continue");let[r]=n;if(r==="closed")return(0,l.success)("continue");if(r.type==="Failure")return(0,l.failure)(`unable to join room; ${r.reason}`);this._RoomConnection=r.value,this._WasConnected=!0,this._FailedConnections=0,this._ChangeStatus("Joined",`joined ${this.roomId}`);try{await this._MainConnection.waitForConnected();let o=await this._HandleConnection(this._MainConnection);return(0,l.success)(o?"shutdown":"continue")}catch(o){return(0,l.failure)(`unable to handle connection; ${o}`)}}_ChangeStatus(e,t){let n={status:e,message:t};this.onEvent("RoomStatusChanged",n)}async _HandleConnection(e){if(this._RoomConnection===void 0)return!1;let t=new l.Selector([()=>(0,l.sleep)(this.renewTokenIn,"renewToken"),()=>(0,l.sleep)(500,"tick"),async()=>await this._RoomConnection?.recv(),()=>this._Closed,()=>e.closePromise]);for(;;){let n=await t.next();switch(n){case void 0:case"closed":return this._RoomConnection?.close(),this._MainConnection?.close(),!1;case"disconnected":return this._RoomConnection?.close(),this._MainConnection?.close(),!0;case"renewToken":this._RenewToken();break;case"tick":break;default:this._HandleEvent(n);break}}}_HandleEvent(e){if(e.name==="RoomUpdated"){let t=l.RoomNotificationsRpc.RoomUpdated.safeParse(e.properties);if(!t.success)return;for(let n of t.data.updates)switch(n.kind){case"Joined":this._OwnPeerId=n.own_peer_id;break}}this.onEvent(e.name,e.properties)}_HandleRequests(e,t,n){switch(e){case"SetPeerPosition":{let r=l.RoomCommandsRpc.SetPeerPosition.request.safeParse(t);if(r.success){let o=r.data.position;this._Position=o.length===2?[o[0],o[1],0]:o}else v("unable to process SetPeerPosition command");break}case"UpdatePeer":{let r=l.RoomCommandsRpc.UpdatePeer.request.safeParse(t);r.success?this._UserData=r.data.user_data:v("unable to process UpdatePeer command");break}}}async _RenewToken(e){if(this._LastTokenRenew=performance.now(),!e&&this.token.timeLeft>f.MinTokenLifetime)return;if(this._MainConnection===void 0||this._OwnPeerId===void 0)return(0,l.failure)("connection is not established");let t=await this._MainConnection.getReconnectToken(this._OwnPeerId);return t.type==="Success"?(this._Token=t.value,(0,l.success)()):(0,l.failure)(`${this._MainConnection.address} rejected reconnect token request; ${t.reason}`)}reset(){this._LastReconnectStartedAt=0,this._RoomConnection?.close(),this._RoomConnection=void 0,this._MainConnection?.close(),this._MainConnection=void 0,this._OwnPeerId=void 0}};async function Qt(i,e,t){try{let n={jsonrpc:"2.0",id:1,method:"Connect",params:{}};m(`sending authentication request to '${i}'`,n);let o=await(await fetch(i,{body:JSON.stringify(n),method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${e}`},signal:t})).json();if(m(`received authentication response fom '${i}'`,o),"result"in o)return P.parse(o.result.token);{let a=o.error?.message??"malformed response";return g(`gateway rejected token; ${a}`),(0,l.success)(a)}}catch(n){return(0,l.failure)(String(n))}}function Xt(i,e=8e3){return performance.now()-i>e}var ct=require("@4players/odin-common");var q=require("@4players/odin-common");var rt=require("@4players/odin-common");var tt=require("@msgpack/msgpack");var je={name:"PBKDF2",hash:"SHA-256",iterations:6e5,salt:new Uint8Array([111,100,105,110,95,99,114,121,112,116,111,95,109,97,115,116,101,114,95,107,101,121])},ze=i=>({name:"PBKDF2",hash:"SHA-256",iterations:600,salt:i}),Ye=32,Qe=1e6,Xe=2147483648,Ze=32,W=[79,68,73,78,1,75,105,109],H=16,J=64,et=16;function nt(i,e){if(i.length!==e.length)return!1;for(let t=0;t<i.length;t++)if(i[t]!==e[t])return!1;return!0}function Zt(i){return isFinite(i)}function en(i){return Array.isArray(i)}function me(i){return Zt(i)?i:s}function X(i){return i instanceof Uint8Array?i:s}function tn(i,...e){let t;try{t=(0,tt.decode)(i)}catch{return s}if(!en(t))return s;let n=new Array(t.length);for(let r in e)if(n[r]=e[r](t[r]),n[r]===s)return s;return n}function pe(i,...e){if(i.length<W.length)return _;for(let t=0;t<W.length;t++)if(i[t]!==W[t])return _;return tn(i.subarray(W.length),...e)}var G=class{constructor(e){this.first=1;this.offset=0;this.window=Array(e)}isReplay(e){let t=e-this.first;if(t<0)return!0;if(t>=this.window.length)if(t<this.window.length*2){let o=t-this.window.length+1;t-=o;for(let a=0;a<o;a++)this.advance()}else return this.first=e+1-self.window.length,this.offset=0,this.window.fill(!1),!1;let n=(this.offset+t)%this.window.length,r=this.window[n]===!0;for(this.window[n]=!0;this.window[this.offset]===!0;)this.advance();return r}advance(){this.window[this.offset]=!1,this.first+=1,this.offset=(this.offset+1)%this.window.length}};var fe=class i{constructor(e){this.key=e}static async create(e){typeof e=="string"&&(e=new TextEncoder().encode(e));let t=await globalThis.crypto.subtle.importKey("raw",e,"PBKDF2",!1,["deriveBits"]),n=await globalThis.crypto.subtle.deriveBits(je,t,256),r=await globalThis.crypto.subtle.importKey("raw",n,"PBKDF2",!1,["deriveKey"]);return new i(r)}async generatePeerKey(e,t){return await globalThis.crypto.subtle.deriveKey(ze(e),this.key,{name:"AES-GCM",length:256},!1,[t])}},he=class i{constructor(e,t){this.seed=e;this.key=t;this.sequenceNumbers={datagram:1,message:1,userData:1};this.number_of_packets=0;this.number_of_bytes=0}static async create(e){let t=globalThis.crypto.getRandomValues(new Uint8Array(Ye)),n=await e.generatePeerKey(t,"encrypt");return new i(t,n)}get limitReached(){return this.key===void 0?!1:this.number_of_bytes>=Xe||this.number_of_packets>=Qe}nextSequenceNumber(e){return this.sequenceNumbers[e]++}};function Me(i,e){return{kind:"encrypted",key:i,material:e,antiReplays:{datagram:new G(Ze),userData:new G(0),message:new G(0)}}}var ye=class i{constructor(e,t,n,r){this.additionalData=e;this.sequenceNumber=t;this.seed=n;this.encrypted=r}static parse(e){let t=pe(e,X,me,X);if(t===s||t===_)return t;let[n,r,o]=t,a=e.subarray(0,e.length-o.length);return new i(a,r,n,o)}isSameSeed(e){return nt(this.seed,e.seed)}async generateKey(e){return await e.generatePeerKey(this.seed,"decrypt")}};function be(i,e){let t=new Uint8Array(12);switch(t[0]=e&255,t[1]=e>>8&255,t[2]=e>>16&255,t[3]=e>>24&255,i){case"datagram":t[5]=1;break;case"userData":t[5]=2;break;case"message":t[5]=3;break;default:(0,rt.assert)(!1,"missing content implementation")}return t}var Ae=require("@msgpack/msgpack"),ot=require("@4players/odin-common");var ge=class{constructor(e){this.actions=e}changeKey(e){this.masterKey!==e&&(this.masterKey=e,this.ownKey=s,this.actions.resendUserData())}async encryptDatagram(e){let t=await this.getKey("datagram");return t===void 0?e:t===N?s:await on(t,e)}async encryptMessage(e){let t=await this.getKey("message");return t===void 0?e:t===N?N:await it(t,e,"message")}async encryptUserData(e){let t=await this.getKey("userData");return t===void 0?e:((0,ot.assert)(t!==N,"key unavailable for user data encryption"),await it(t,e,"userData",t.seed))}async getKey(e){if(this.ownKey===s||this.ownKey?.limitReached)if(e==="userData")this.ownKey=this.masterKey?await he.create(this.masterKey):void 0;else return this.ownKey!==s&&(this.ownKey=s,this.actions.resendUserData()),N;return this.ownKey}reset(){this.ownKey=this.masterKey?s:void 0}};async function it(i,e,t,...n){let r=i.nextSequenceNumber(t),o=e.length+H,a=rn(r,n,o),c=a.length-o,d=await at(i,t,r,a.subarray(0,c),e);return a.set(new Uint8Array(d),c),a}function rn(i,e,t){let n=[];e.forEach(a=>n.push(a)),n.push(i),n.push(new Uint8Array(t));let r=(0,Ae.encode)(n),o=new Uint8Array(W.length+r.length);return o.set(W,0),o.set(r,W.length),o}async function on(i,e){let t=an(e);if(t===s)return s;if((t.kind&J)!==0)return s;let n=t.body.length+H,r=i.nextSequenceNumber("datagram"),o=(0,Ae.encode)([t.kind|J,t.mediaIds,r,new Uint8Array(n)]),a=o.length-n,c=await at(i,"datagram",r,o.subarray(0,a),t.body);return o.set(c,a),o}async function at(i,e,t,n,r){return new Uint8Array(await globalThis.crypto.subtle.encrypt({name:"AES-GCM",iv:be(e,t),additionalData:n,tagLength:H*8},i.key,r))}function an(i){let e=new B(i),t=e.readArrayLength();if(p(t)||t<2)return s;let n=e.readNumber(),r=e.readArrayLength();if(p(n)||p(r))return s;if(r>et)return s;let o=Array(r);for(let c=0;c<o.length;c++){let d=e.readNumber();if(p(d))return s;o[c]=d}let a=i.subarray(e.position);return{kind:n,mediaIds:o,body:a}}var st=require("@msgpack/msgpack");var ve=class{constructor(e){this.actions=e;this.peerKeys=new Map}async changeKey(e){this.masterKey=e;for(let[t,{kind:n,material:r}]of this.peerKeys)if(e!==void 0){let o=await r.generateKey(e),a=Me(o,r),c=await ke(a,"userData",r.additionalData,r.sequenceNumber,r.encrypted);c!==s?(this.peerKeys.set(t,a),this.actions.setUserData(t,c)):(this.peerKeys.set(t,{kind:"broken",material:r}),this.actions.setUserData(t,new Uint8Array))}else n==="encrypted"&&this.peerKeys.set(t,{kind:"broken",material:r})}async decryptDatagram(e,t){let n=sn(t);if(this.masterKey===void 0)return n===_?t:s;if(n===s||n===_)return s;let r=this.peerKeys.get(e);if(r?.kind!=="encrypted")return s;let o=await ke(r,"datagram",n.additionalData,n.sequenceNumber,n.encrypted);return o===s?s:cn(n.kind&~J,n.mediaIds,o)}async decryptMessage(e,t){let n=pe(t,me,X);if(this.masterKey===void 0)return n===_?t:s;if(n===s||n===_)return s;let r=this.peerKeys.get(e);if(r?.kind!=="encrypted")return s;let[o,a]=n,c=t.subarray(0,t.length-a.length);return ke(r,"message",c,o,a)}async decryptUserData(e,t){let n=ye.parse(t);if(n===s)return s;if(n===_)return this.peerKeys.delete(e),this.masterKey===void 0?t:s;if(this.masterKey===void 0)return this.peerKeys.set(e,{kind:"broken",material:n}),s;let r=this.peerKeys.get(e);if(r?.kind==="encrypted"&&r.material.isSameSeed(n))r.material=n;else{let a=await n.generateKey(this.masterKey);r=Me(a,n)}let o=await ke(r,"userData",n.additionalData,n.sequenceNumber,n.encrypted);return o===s?this.peerKeys.set(e,{kind:"broken",material:n}):r!==void 0&&this.peerKeys.set(e,r),o}reset(){this.peerKeys.clear()}};function sn(i){let e=new B(i),t=e.readArrayLength();if(p(t)||t<1)return s;let n=e.readNumber();if(p(n))return s;if((n&J)===0)return _;if(t!==4)return s;let r=e.position;e.readObjects(1);let o=i.subarray(r,e.position),a=e.readNumber(),c=e.readBytes();if(p(a)||p(c))return s;let d=i.subarray(0,i.length-c.length);return{kind:n,mediaIds:o,additionalData:d,sequenceNumber:a,encrypted:c}}async function ke(i,e,t,n,r){let o=await globalThis.crypto.subtle.decrypt({name:"AES-GCM",iv:be(e,n),additionalData:t,tagLength:H*8},i.key,r).catch(()=>s);return o===s?s:i.antiReplays[e].isReplay(n)?s:new Uint8Array(o)}function cn(i,e,t){let n=2;try{let a=new B(t);for(a.position=0;a.position<t.length;)a.readObjects(1),n+=1}catch{return s}let r=(0,st.encode)(i),o=new Uint8Array(3+r.length+e.length+t.length);return o[0]=220,o[1]=n>>8&255,o[2]=n&255,o.set(r,3),o.set(e,3+r.length),o.set(t,3+r.length+e.length),o}var Ce=class{constructor(e,t){this.active=!1;let n=new ve(t),r=new ge(t);this.crypto=new q.Strand({encryption:r,decryption:n}),this.peerKeys=n.peerKeys,this.configure(e)}encryptUserData(e){return this.crypto.enqueue(({encryption:t})=>t.encryptUserData(e))}async encryptMessage(e){let t;do t=await this.crypto.enqueue(({encryption:n})=>n.encryptMessage(e));while(t===N);return t}async encryptDatagram(e){return await this.crypto.enqueue(({encryption:t})=>t.encryptDatagram(e))}decryptUserData(e,t){return this.crypto.enqueue(({decryption:n})=>n.decryptUserData(e,t))}decryptMessage(e,t){return this.crypto.enqueue(({decryption:n})=>n.decryptMessage(e,t))}decryptDatagram(e,t){return this.crypto.enqueue(({decryption:n})=>n.decryptDatagram(e,t))}configure(e){return this.crypto.enqueue(async t=>{let n=e.password?await fe.create(e.password):void 0;this.active=n!==void 0,await t.encryption.changeKey(n),await t.decryption.changeKey(n)})}getPeerStatus(e){switch(this.peerKeys.get(e)?.kind??"unencrypted"){case"broken":return q.Backend.PeerCipherStatus.InvalidPassword;case"encrypted":return q.Backend.PeerCipherStatus.Encrypted;case"unencrypted":return typeof this.active>"u"?q.Backend.PeerCipherStatus.Unencrypted:q.Backend.PeerCipherStatus.InvalidPassword;default:return q.Backend.PeerCipherStatus.Unknown}}reset(){return this.crypto.enqueue(async({encryption:e,decryption:t})=>{e.reset(),t.reset()})}async dump(){return this.crypto.enqueue(async()=>({active:this.active,peers:Array.from(this.peerKeys).map(([e,t])=>`${e} => ${t.kind}`)}))}};function dt(i,e){return(0,ct.assert)(i.type==="OdinCrypto","only OdinCrypto supported"),new Ce(i,e)}var _e=class{constructor(e,t){this._Captures=new Map;this._Playbacks=new Map;this._Peers=new Map;this._AvailableMediaIds=[];let{url:n,onEvent:r}=t,o=(0,C.unwrap)(P.parse(t.token)),a=t.roomId??o.defaultRoomId,c=t.userData??new Uint8Array(0),d=t.position??[0,0,0];(0,C.assert)(a!==void 0,"room id required for multi-room-tokens"),this.cipher=t.cipher?dt(t.cipher,new de(this)):void 0,this.onEvent=r,m(`creating new room '${a}' ${this.cipher?"with":"without"} cipher`,o,c,d),this._Connection=new le(e,t.transport??"H3",this._HandleEvent.bind(this),n?new URL(n):void 0,o,a,c,d,this.cipher)}get connectionStats(){let e={...C.CONNECTION_STATS_INITIAL};return this.mainConnection?this.mainConnection.connectionStats:e}get roomConnection(){return this._Connection}get mainConnection(){return this.roomConnection?.mainConnection}get token(){return this._Connection.token.text}request(e,t){return this._Connection.requestUnsafe(e,t)}async link(e){if(e instanceof I){let t=this._Playbacks.get(e.uid)??{media:void 0,playback:void 0};t.playback!==void 0&&this.unlink(t.playback),t.media!==void 0&&e.start(t.media),t.playback=e,m(`linking audio playback media '${e.uid}' with volume ${e.volume} to room '${this._Connection.roomId}'`),this._Playbacks.set(e.uid,t)}else if(e instanceof D){let t=this._Captures.get(e);t&&this.unlink(e);let n=this.mainConnection!==void 0&&this._AvailableMediaIds.shift();t={media:n?{connectionId:this.mainConnection.id,mediaId:n}:void 0,uid:(0,C.generateUUID)()},m(`linking audio capture media '${t.uid}' with volume ${e.volume} to room '${this._Connection.roomId}'`),this._Captures.set(e,t),t.media!==void 0&&e.start(this._Connection,t.media,t.uid)}else if(e instanceof E){let t=this._Playbacks.get(e.uid)??{media:void 0,playback:void 0};if(t.playback!==void 0&&this.unlink(t.playback),t.media!==void 0){let n=await this.mainConnection?.startRemoteVideo(t.media.mediaId);n&&e.setMediaStream(new MediaStream([n]))}t.playback=e,m(`linking video playback media '${e.uid}' with type '${e.customType}' to room '${this._Connection.roomId}'`),this._Playbacks.set(e.uid,t)}else if(e instanceof S){let t=this._Captures.get(e);if(t&&this.unlink(e),e.mediaStream){let n=this.mainConnection!==void 0&&this._AvailableMediaIds.shift();t={media:n?{connectionId:this.mainConnection.id,mediaId:n}:void 0,uid:(0,C.generateUUID)()},m(`linking video capture media '${t.uid}' with type '${e.options?.customType}' to room '${this._Connection.roomId}'`),this._Captures.set(e,t),t.media!==void 0&&(this.mainConnection?.startLocalVideo(t.media.mediaId,e.mediaStream.getTracks()[0],e.options?.codec),e.start(this._Connection,t.media,t.uid))}}else throw new Error("invalid media")}unlink(e){if(e instanceof I||e instanceof E){let t=this._Playbacks.get(e.uid);if(t?.playback!==e)return;t.playback=void 0,t.media!==void 0&&(e instanceof I?e.stop(t.media):(this.mainConnection?.stopRemoteVideo(t.media.mediaId),e.setMediaStream(void 0))),m(`unlinking playback media '${e.uid}' from room '${this._Connection.roomId}'`),this._Playbacks.set(e.uid,t)}else if(e instanceof D||e instanceof S){let t=this._Captures.get(e);if(t===void 0)return;t.media!==void 0&&(e.stop(this._Connection,t.media),this._AvailableMediaIds.push(t.media.mediaId),e instanceof S&&this.mainConnection?.stopLocalVideo(t.media.mediaId)),m(`unlinking capture media '${t.uid}' from room '${this._Connection.roomId}'`),this._Captures.delete(e)}else throw new Error("invalid media")}close(){m(`closing room '${this._Connection.roomId}'`),this._Connection.close()}_HandleEvent(e,t){let n=Pe(C.RoomNotificationsRpc,{name:e,properties:t});n!==void 0&&(this._HandleKnownEvent(n),t=n.properties),this.onEvent(e,t)}_HandleKnownEvent(e){switch(u(`processing event '${e.name}'`,e.properties),e.name){case"RoomStatusChanged":e.properties.status!=="Joined"&&this._HandleConnectionLost();break;case"RoomUpdated":for(let t of e.properties.updates)this._HandleRoomUpdate(t);break;case"PeerUpdated":this._HandlePeerUpdate(e.properties);break}}_HandleConnectionLost(){for(let e of this._Peers.keys())this._HandlePeerLeft(e);for(let[e,t]of this._Captures)t.media!==void 0&&(e instanceof S,e instanceof D&&(e.stop(this._Connection,t.media),t.media=void 0));this._AvailableMediaIds.length=0}_HandleRoomUpdate(e){if(e.kind==="Joined"){(0,C.assert)(this._AvailableMediaIds.length===0,"left over media ids"),this._AvailableMediaIds.push(...e.media_ids),this._HandleRoomJoined();for(let t of e.room.peers)this._HandlePeerJoined(t)}else e.kind==="PeerJoined"?this._HandlePeerJoined(e.peer):e.kind==="PeerLeft"&&this._HandlePeerLeft(e.peer_id)}_HandleRoomJoined(){for(let[e,t]of this._Captures)this._AvailableMediaIds.length===0||this.mainConnection===void 0||(t.media={connectionId:this.mainConnection.id,mediaId:this._AvailableMediaIds.shift()},e instanceof D&&e.start(this._Connection,t.media,t.uid),e instanceof S)}_HandlePeerUpdate(e){e.kind==="MediaStarted"?this._HandleMediaStart(e.peer_id,e.media):e.kind==="MediaStopped"&&this._HandleMediaStopped(e.peer_id,e.media_id)}_HandlePeerJoined(e){this._Peers.has(e.id)||this._Peers.set(e.id,{mediaIds:new Map});for(let t of e.medias)this._HandleMediaStart(e.id,t)}_HandlePeerLeft(e){let t=this._Peers.get(e);if(t!==void 0){for(let n of t.mediaIds.keys())this._HandleMediaStopped(e,n);this._Peers.delete(e)}}_HandleMediaStart(e,t){if(this.mainConnection===void 0)return;let n=this._Peers.get(e);if(n===void 0||t.properties.kind!=="audio"&&t.properties.kind!=="video")return;let r=String(t.properties.uid??(0,C.generateUUID)());t.properties.uid=r;let o=this._Playbacks.get(r)??{media:void 0,playback:void 0};this._Playbacks.set(r,o),n.mediaIds.set(t.id,r),o.media={connectionId:this.mainConnection.id,mediaId:t.id},o.playback!==void 0&&(o.playback instanceof I&&o.playback.start(o.media),o.playback instanceof E&&this.mainConnection.startRemoteVideo(o.media.mediaId).then())}_HandleMediaStopped(e,t){let n=this._Peers.get(e);if(n===void 0)return;let r=n?.mediaIds.get(t);if(r===void 0)return;let o=this._Playbacks.get(r);o!==void 0&&(o.media!==void 0&&o.playback!==void 0&&(o.playback instanceof I&&o.playback.stop(o.media),o.playback instanceof E&&this.mainConnection?.stopRemoteVideo(o.media.mediaId)),o.media=void 0,n.mediaIds.delete(t))}};var dn=[],un=[];async function lt(){return{workers:await Promise.all(dn.map(ut)),worklets:await Promise.all(un.map(i=>ut(i.port)))}}function ut(i){let{port1:e,port2:t}=new MessageChannel,{promise:n,resolve:r}=Promise.withResolvers();return t.onmessage=o=>r(o.data),setTimeout(()=>r("timeout"),100),We(i,["dump",{result:e}],[e]),n}function ln(i,e){Le(e?.logger,e?.onLogMessage);let t,n=new ue((o,a)=>t.receivePacket(o,a)),r=new re(i);return t=new ie(r,n.sendDatagram.bind(n)),new Ue(n,t)}var Y=(0,Jo.generateUUID)(),mt=[],Ue=class{constructor(e,t){this.version="7";this.supportedTransports=["H3","WebRTC"];this._PlaybackVolume=[1,1];this._CreatedAt=Date.now();this._ConnectionPool=e,this._MediaController=t,mt.push(new WeakRef(this)),m("plugin initialized")}get playbackVolume(){return this._PlaybackVolume}joinRoom(e){return new _e(this._ConnectionPool,e)}async createAudioPlayback(e){return this._MediaController.createAudioPlayback(e)}async createAudioCapture(e){return this._MediaController.createAudioCapture(e)}async createVideoCapture(e,t){return this._MediaController.createVideoCapture(e,t)}async createVideoPlayback(e){return new E(e.uid,e.customType)}enumerateDevices(){return te()}setOutputVolume(e){this._MediaController.playbackSetGlobalVolume(e)}async setOutputDevice(e){await this._MediaController.playbackSetDevice(e.device)}close(){this._MediaController.close(),this._ConnectionPool.close(),m("plugin closed")}async dump(){return{createdAt:this._CreatedAt,connections:await this._ConnectionPool.dump(),medias:await this._MediaController.dump()}}};async function pt(){let i=[];for(let e of mt){let t=e.deref();t!==void 0&&i.push(await t.dump())}return JSON.stringify({timestamp:Date.now(),instances:i,background:await lt()})}globalThis.odin_debug_dump_state=pt;window!==void 0&&(window.odin_debug_dump_state=pt);0&&(module.exports={SESSION_ID,createPlugin});