@signalwire/js 4.0.0-dev-20260515133934 → 4.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.mjs +801 -463
- package/dist/browser.mjs.map +1 -1
- package/dist/browser.umd.js +801 -463
- package/dist/browser.umd.js.map +1 -1
- package/dist/index.cjs +791 -453
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +198 -32
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +198 -32
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +791 -453
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/browser.umd.js
CHANGED
|
@@ -9470,9 +9470,9 @@ var require_loglevel = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
9470
9470
|
defaultLogger$1 = new Logger();
|
|
9471
9471
|
defaultLogger$1.getLogger = function getLogger$1(name) {
|
|
9472
9472
|
if (typeof name !== "symbol" && typeof name !== "string" || name === "") throw new TypeError("You must supply a name when creating a logger.");
|
|
9473
|
-
var logger$
|
|
9474
|
-
if (!logger$
|
|
9475
|
-
return logger$
|
|
9473
|
+
var logger$33 = _loggersByName[name];
|
|
9474
|
+
if (!logger$33) logger$33 = _loggersByName[name] = new Logger(name, defaultLogger$1.methodFactory);
|
|
9475
|
+
return logger$33;
|
|
9476
9476
|
};
|
|
9477
9477
|
var _log = typeof window !== undefinedType ? window.log : void 0;
|
|
9478
9478
|
defaultLogger$1.noConflict = function() {
|
|
@@ -9508,8 +9508,8 @@ const defaultLoggerLevel = defaultLogger.levels.WARN;
|
|
|
9508
9508
|
defaultLogger.setLevel(defaultLoggerLevel);
|
|
9509
9509
|
let userLogger = null;
|
|
9510
9510
|
/** Replace the built-in logger with a custom implementation. Pass `null` to restore defaults. */
|
|
9511
|
-
const setLogger = (logger$
|
|
9512
|
-
userLogger = logger$
|
|
9511
|
+
const setLogger = (logger$33) => {
|
|
9512
|
+
userLogger = logger$33;
|
|
9513
9513
|
};
|
|
9514
9514
|
let debugOptions = {};
|
|
9515
9515
|
/** Configure debug options (e.g., `{ logWsTraffic: true }`). */
|
|
@@ -9553,8 +9553,8 @@ const wsTraffic = (options) => {
|
|
|
9553
9553
|
loggerInstance.debug(`${options.type.toUpperCase()}: \n`, msg, "\n");
|
|
9554
9554
|
};
|
|
9555
9555
|
const getLogger = () => {
|
|
9556
|
-
const logger$
|
|
9557
|
-
return new Proxy(logger$
|
|
9556
|
+
const logger$33 = getLoggerInstance();
|
|
9557
|
+
return new Proxy(logger$33, { get(_target, prop, _receiver) {
|
|
9558
9558
|
if (prop === "wsTraffic") return wsTraffic;
|
|
9559
9559
|
const instance = getLoggerInstance();
|
|
9560
9560
|
const value = Reflect.get(instance, prop);
|
|
@@ -9606,7 +9606,7 @@ const asyncRetry = async ({ asyncCallable, maxRetries: retries = DEFAULT_MAX_RET
|
|
|
9606
9606
|
|
|
9607
9607
|
//#endregion
|
|
9608
9608
|
//#region src/controllers/HTTPRequestController.ts
|
|
9609
|
-
const logger$
|
|
9609
|
+
const logger$32 = getLogger();
|
|
9610
9610
|
const GET_PARAMS = {
|
|
9611
9611
|
method: "GET",
|
|
9612
9612
|
headers: { Accept: "application/json" }
|
|
@@ -9670,7 +9670,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9670
9670
|
this._responses$.next(response);
|
|
9671
9671
|
return response;
|
|
9672
9672
|
} catch (error) {
|
|
9673
|
-
logger$
|
|
9673
|
+
logger$32.error("[HTTPRequestController] Request error:", error);
|
|
9674
9674
|
this._status$.next("error");
|
|
9675
9675
|
const err = error instanceof Error ? error : new Error("HTTP request failed", { cause: error });
|
|
9676
9676
|
this._errors$.next(err);
|
|
@@ -9697,7 +9697,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9697
9697
|
const url = this.buildURL(request.url);
|
|
9698
9698
|
const headers = this.buildHeaders(request.headers);
|
|
9699
9699
|
const timeout$5 = request.timeout ?? this.requestTimeout;
|
|
9700
|
-
logger$
|
|
9700
|
+
logger$32.debug("[HTTPRequestController] Executing request:", {
|
|
9701
9701
|
method: request.method,
|
|
9702
9702
|
url,
|
|
9703
9703
|
headers: Object.keys(headers).reduce((acc, key) => {
|
|
@@ -9717,7 +9717,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9717
9717
|
});
|
|
9718
9718
|
clearTimeout(timeoutId);
|
|
9719
9719
|
const httpResponse = await this.convertResponse(response);
|
|
9720
|
-
logger$
|
|
9720
|
+
logger$32.debug("[HTTPRequestController] Response received:", {
|
|
9721
9721
|
status: response.status,
|
|
9722
9722
|
statusText: response.statusText,
|
|
9723
9723
|
headers: [...response.headers.entries()],
|
|
@@ -9727,7 +9727,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9727
9727
|
} catch (error) {
|
|
9728
9728
|
clearTimeout(timeoutId);
|
|
9729
9729
|
if (error instanceof Error && error.name === "AbortError") throw new RequestTimeoutError(`Request timeout after ${timeout$5}ms`, { cause: error });
|
|
9730
|
-
logger$
|
|
9730
|
+
logger$32.error("[HTTPRequestController] Request failed:", error);
|
|
9731
9731
|
throw error;
|
|
9732
9732
|
}
|
|
9733
9733
|
}
|
|
@@ -9741,8 +9741,8 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9741
9741
|
const credential = this.getCredential();
|
|
9742
9742
|
if (credential.token) {
|
|
9743
9743
|
headers.Authorization = `Bearer ${credential.token}`;
|
|
9744
|
-
logger$
|
|
9745
|
-
} else logger$
|
|
9744
|
+
logger$32.debug("[HTTPRequestController] Using Bearer token auth, token length:", credential.token.length);
|
|
9745
|
+
} else logger$32.warn("[HTTPRequestController] No credentials available for authentication");
|
|
9746
9746
|
return headers;
|
|
9747
9747
|
}
|
|
9748
9748
|
/**
|
|
@@ -9912,6 +9912,13 @@ const CREDENTIAL_REFRESH_RETRY_BASE_MS = 1e3;
|
|
|
9912
9912
|
const CREDENTIAL_REFRESH_MAX_DELAY_MS = 3e4;
|
|
9913
9913
|
/** Buffer in milliseconds before token expiry to trigger refresh. */
|
|
9914
9914
|
const CREDENTIAL_REFRESH_BUFFER_MS = 5e3;
|
|
9915
|
+
/**
|
|
9916
|
+
* Maximum time the coordinator will wait for `DeviceTokenManager.activate()`
|
|
9917
|
+
* to resolve before treating the activation as failed and falling back to
|
|
9918
|
+
* the developer-provided refresh path. Prevents a wedged HTTP layer from
|
|
9919
|
+
* leaving the session with no active refresh mechanism.
|
|
9920
|
+
*/
|
|
9921
|
+
const CREDENTIAL_ACTIVATE_TIMEOUT_MS = 3e4;
|
|
9915
9922
|
/** JSON-RPC error code for requester validation failure (corrupted auth state). */
|
|
9916
9923
|
const RPC_ERROR_REQUESTER_VALIDATION_FAILED = -32003;
|
|
9917
9924
|
/** JSON-RPC error code for invalid params (e.g., missing authentication block). */
|
|
@@ -10014,7 +10021,7 @@ function fromMsToSec(milliseconds) {
|
|
|
10014
10021
|
|
|
10015
10022
|
//#endregion
|
|
10016
10023
|
//#region src/containers/PreferencesContainer.ts
|
|
10017
|
-
const logger$
|
|
10024
|
+
const logger$31 = getLogger();
|
|
10018
10025
|
var PreferencesContainer = class PreferencesContainer {
|
|
10019
10026
|
static get instance() {
|
|
10020
10027
|
this._instance ??= new PreferencesContainer();
|
|
@@ -10676,7 +10683,7 @@ var ClientPreferences = class {
|
|
|
10676
10683
|
if (!this._storage) return;
|
|
10677
10684
|
const data = collectStoredPreferences();
|
|
10678
10685
|
this._storage.setItem(PREFERENCES_STORAGE_KEY, data, "local").catch((error) => {
|
|
10679
|
-
logger$
|
|
10686
|
+
logger$31.error(`[ClientPreferences] Failed to save preferences: ${String(error)}`);
|
|
10680
10687
|
});
|
|
10681
10688
|
}
|
|
10682
10689
|
/** Loads preferences from storage and applies them to the container. */
|
|
@@ -10685,7 +10692,7 @@ var ClientPreferences = class {
|
|
|
10685
10692
|
this._storage.getItem(PREFERENCES_STORAGE_KEY, "local").then((stored) => {
|
|
10686
10693
|
if (stored) applyStoredPreferences(stored);
|
|
10687
10694
|
}).catch((error) => {
|
|
10688
|
-
logger$
|
|
10695
|
+
logger$31.error(`[ClientPreferences] Failed to load preferences: ${String(error)}`);
|
|
10689
10696
|
});
|
|
10690
10697
|
}
|
|
10691
10698
|
};
|
|
@@ -10707,7 +10714,7 @@ function toError(value) {
|
|
|
10707
10714
|
//#endregion
|
|
10708
10715
|
//#region src/controllers/NavigatorDeviceController.ts
|
|
10709
10716
|
var import_cjs$29 = require_cjs();
|
|
10710
|
-
const logger$
|
|
10717
|
+
const logger$30 = getLogger();
|
|
10711
10718
|
/** Maps a device kind to its storage key. */
|
|
10712
10719
|
const DEVICE_STORAGE_KEYS = {
|
|
10713
10720
|
audioinput: DEVICE_STORAGE_KEY_AUDIO_INPUT,
|
|
@@ -10729,7 +10736,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10729
10736
|
super();
|
|
10730
10737
|
this.webRTCApiProvider = webRTCApiProvider;
|
|
10731
10738
|
this.deviceChangeHandler = () => {
|
|
10732
|
-
logger$
|
|
10739
|
+
logger$30.debug("[DeviceController] Device change detected");
|
|
10733
10740
|
this.enumerateDevices();
|
|
10734
10741
|
};
|
|
10735
10742
|
this._devicesState$ = this.createBehaviorSubject(initialDevicesState);
|
|
@@ -10794,13 +10801,13 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10794
10801
|
return this.cachedObservable("videoInputDevices$", () => this._devicesState$.pipe((0, import_cjs$29.map)((state) => state.videoinput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$)));
|
|
10795
10802
|
}
|
|
10796
10803
|
get selectedAudioInputDevice$() {
|
|
10797
|
-
return this.cachedObservable("selectedAudioInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.audioinput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$
|
|
10804
|
+
return this.cachedObservable("selectedAudioInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.audioinput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$30.debug("[DeviceController] Selected audio input device changed:", info))));
|
|
10798
10805
|
}
|
|
10799
10806
|
get selectedAudioOutputDevice$() {
|
|
10800
|
-
return this.cachedObservable("selectedAudioOutputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.audiooutput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$
|
|
10807
|
+
return this.cachedObservable("selectedAudioOutputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.audiooutput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$30.debug("[DeviceController] Selected audio output device changed:", info))));
|
|
10801
10808
|
}
|
|
10802
10809
|
get selectedVideoInputDevice$() {
|
|
10803
|
-
return this.cachedObservable("selectedVideoInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.videoinput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$
|
|
10810
|
+
return this.cachedObservable("selectedVideoInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.videoinput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$30.debug("[DeviceController] Selected video input device changed:", info))));
|
|
10804
10811
|
}
|
|
10805
10812
|
get selectedAudioInputDevice() {
|
|
10806
10813
|
if (this._audioInputDisabled$.value) return null;
|
|
@@ -10875,7 +10882,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10875
10882
|
if (device) this.persistDeviceSelection("audioinput", device);
|
|
10876
10883
|
}
|
|
10877
10884
|
selectVideoInputDevice(device) {
|
|
10878
|
-
logger$
|
|
10885
|
+
logger$30.debug("[DeviceController] Setting selected video input device:", device);
|
|
10879
10886
|
if (this._videoInputDisabled$.value && device) this._videoInputDisabled$.next(false);
|
|
10880
10887
|
const previous = this._selectedDevicesState$.value.videoinput;
|
|
10881
10888
|
if (previous && previous.deviceId !== device?.deviceId) this._deviceHistory.push("videoinput", previous);
|
|
@@ -10932,7 +10939,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10932
10939
|
}
|
|
10933
10940
|
const fromHistory = this._deviceHistory.findInHistory(kind, devices);
|
|
10934
10941
|
if (fromHistory) {
|
|
10935
|
-
logger$
|
|
10942
|
+
logger$30.debug(`[DeviceController] Device disappeared, falling back to history: ${fromHistory.label}`);
|
|
10936
10943
|
this.emitDeviceRecovered(kind, selected, fromHistory, "device_disconnected");
|
|
10937
10944
|
return fromHistory;
|
|
10938
10945
|
}
|
|
@@ -10985,7 +10992,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10985
10992
|
try {
|
|
10986
10993
|
await this._storageManager.setItem(DEVICE_STORAGE_KEYS[kind], stored, "local");
|
|
10987
10994
|
} catch (error) {
|
|
10988
|
-
logger$
|
|
10995
|
+
logger$30.error(`[DeviceController] Failed to persist device selection for ${kind}:`, error);
|
|
10989
10996
|
}
|
|
10990
10997
|
}
|
|
10991
10998
|
async loadPersistedDevices() {
|
|
@@ -11001,7 +11008,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
11001
11008
|
[kind]: stored
|
|
11002
11009
|
};
|
|
11003
11010
|
} catch (error) {
|
|
11004
|
-
logger$
|
|
11011
|
+
logger$30.error(`[DeviceController] Failed to load persisted device for ${kind}:`, error);
|
|
11005
11012
|
}
|
|
11006
11013
|
}
|
|
11007
11014
|
/** Clears device history, persisted selections, and re-enumerates devices. */
|
|
@@ -11019,7 +11026,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
11019
11026
|
this.disableDeviceMonitoring();
|
|
11020
11027
|
this.webRTCApiProvider.mediaDevices.addEventListener("devicechange", this.deviceChangeHandler);
|
|
11021
11028
|
if (PreferencesContainer.instance.devicePollingInterval > 0) this._devicesPoolingSubscription = (0, import_cjs$29.interval)(PreferencesContainer.instance.devicePollingInterval).subscribe(() => {
|
|
11022
|
-
logger$
|
|
11029
|
+
logger$30.debug("[DeviceController] Polling devices due to interval");
|
|
11023
11030
|
this.enumerateDevices();
|
|
11024
11031
|
});
|
|
11025
11032
|
this.enumerateDevices();
|
|
@@ -11045,13 +11052,13 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
11045
11052
|
videoinput: []
|
|
11046
11053
|
});
|
|
11047
11054
|
this._devicesState$.next(devicesByKind);
|
|
11048
|
-
logger$
|
|
11055
|
+
logger$30.debug("[DeviceController] Devices enumerated:", {
|
|
11049
11056
|
audioInputs: devicesByKind.audioinput.length,
|
|
11050
11057
|
audioOutputs: devicesByKind.audiooutput.length,
|
|
11051
11058
|
videoInputs: devicesByKind.videoinput.length
|
|
11052
11059
|
});
|
|
11053
11060
|
} catch (error) {
|
|
11054
|
-
logger$
|
|
11061
|
+
logger$30.error("[DeviceController] Failed to enumerate devices:", error);
|
|
11055
11062
|
this._errors$.next(toError(error));
|
|
11056
11063
|
}
|
|
11057
11064
|
}
|
|
@@ -11067,7 +11074,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
11067
11074
|
stream.getTracks().forEach((t) => t.stop());
|
|
11068
11075
|
return capabilities;
|
|
11069
11076
|
} catch (error) {
|
|
11070
|
-
logger$
|
|
11077
|
+
logger$30.error("[DeviceController] Failed to get device capabilities:", error);
|
|
11071
11078
|
this._errors$.next(toError(error));
|
|
11072
11079
|
throw error;
|
|
11073
11080
|
}
|
|
@@ -11318,7 +11325,7 @@ var DependencyContainer = class {
|
|
|
11318
11325
|
|
|
11319
11326
|
//#endregion
|
|
11320
11327
|
//#region src/controllers/CryptoController.ts
|
|
11321
|
-
const logger$
|
|
11328
|
+
const logger$29 = getLogger();
|
|
11322
11329
|
const DPOP_DB_NAME = "sw-dpop";
|
|
11323
11330
|
const DPOP_DB_VERSION = 1;
|
|
11324
11331
|
const DPOP_STORE_NAME = "keys";
|
|
@@ -11377,7 +11384,7 @@ async function loadKeyPairFromDB() {
|
|
|
11377
11384
|
tx.oncomplete = () => db.close();
|
|
11378
11385
|
});
|
|
11379
11386
|
} catch (error) {
|
|
11380
|
-
logger$
|
|
11387
|
+
logger$29.warn("[DPoP] Failed to load key pair from IndexedDB:", error);
|
|
11381
11388
|
return null;
|
|
11382
11389
|
}
|
|
11383
11390
|
}
|
|
@@ -11397,7 +11404,7 @@ async function saveKeyPairToDB(keyPair) {
|
|
|
11397
11404
|
};
|
|
11398
11405
|
});
|
|
11399
11406
|
} catch (error) {
|
|
11400
|
-
logger$
|
|
11407
|
+
logger$29.warn("[DPoP] Failed to save key pair to IndexedDB:", error);
|
|
11401
11408
|
}
|
|
11402
11409
|
}
|
|
11403
11410
|
async function deleteKeyPairFromDB() {
|
|
@@ -11416,7 +11423,7 @@ async function deleteKeyPairFromDB() {
|
|
|
11416
11423
|
};
|
|
11417
11424
|
});
|
|
11418
11425
|
} catch (error) {
|
|
11419
|
-
logger$
|
|
11426
|
+
logger$29.warn("[DPoP] Failed to delete key pair from IndexedDB:", error);
|
|
11420
11427
|
}
|
|
11421
11428
|
}
|
|
11422
11429
|
/**
|
|
@@ -11476,13 +11483,13 @@ var CryptoController = class {
|
|
|
11476
11483
|
this._publicJwk = await crypto.subtle.exportKey("jwk", stored.publicKey);
|
|
11477
11484
|
this._fingerprint = await computeJwkThumbprint(this._publicJwk);
|
|
11478
11485
|
this._initialized = true;
|
|
11479
|
-
logger$
|
|
11486
|
+
logger$29.debug("[DPoP] Key pair restored from IndexedDB, fingerprint:", this._fingerprint);
|
|
11480
11487
|
return this._fingerprint;
|
|
11481
11488
|
} catch (error) {
|
|
11482
|
-
logger$
|
|
11489
|
+
logger$29.warn("[DPoP] Stored key pair unusable, generating new one:", error);
|
|
11483
11490
|
await deleteKeyPairFromDB();
|
|
11484
11491
|
}
|
|
11485
|
-
logger$
|
|
11492
|
+
logger$29.debug("[DPoP] Generating RSA key pair");
|
|
11486
11493
|
this._keyPair = await crypto.subtle.generateKey({
|
|
11487
11494
|
name: "RSASSA-PKCS1-v1_5",
|
|
11488
11495
|
modulusLength: 2048,
|
|
@@ -11497,7 +11504,7 @@ var CryptoController = class {
|
|
|
11497
11504
|
this._fingerprint = await computeJwkThumbprint(this._publicJwk);
|
|
11498
11505
|
this._initialized = true;
|
|
11499
11506
|
await saveKeyPairToDB(this._keyPair);
|
|
11500
|
-
logger$
|
|
11507
|
+
logger$29.debug("[DPoP] Key pair generated and persisted, fingerprint:", this._fingerprint);
|
|
11501
11508
|
return this._fingerprint;
|
|
11502
11509
|
}
|
|
11503
11510
|
/**
|
|
@@ -11563,7 +11570,7 @@ var CryptoController = class {
|
|
|
11563
11570
|
this._fingerprint = null;
|
|
11564
11571
|
this._initialized = false;
|
|
11565
11572
|
deleteKeyPairFromDB();
|
|
11566
|
-
logger$
|
|
11573
|
+
logger$29.debug("[DPoP] Controller destroyed");
|
|
11567
11574
|
}
|
|
11568
11575
|
get publicJwk() {
|
|
11569
11576
|
if (!this._publicJwk) throw new DPoPInitError("CryptoController not initialized. Call init() first.");
|
|
@@ -11587,7 +11594,7 @@ var CryptoController = class {
|
|
|
11587
11594
|
//#endregion
|
|
11588
11595
|
//#region src/controllers/NetworkMonitor.ts
|
|
11589
11596
|
var import_cjs$28 = require_cjs();
|
|
11590
|
-
const logger$
|
|
11597
|
+
const logger$28 = getLogger();
|
|
11591
11598
|
/**
|
|
11592
11599
|
* Safely check whether we are running in a browser environment
|
|
11593
11600
|
* with `window` and the relevant event targets.
|
|
@@ -11644,7 +11651,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11644
11651
|
}
|
|
11645
11652
|
attachListeners() {
|
|
11646
11653
|
if (!hasBrowserNetworkEvents()) {
|
|
11647
|
-
logger$
|
|
11654
|
+
logger$28.debug("NetworkMonitor: no browser environment detected, skipping event listeners");
|
|
11648
11655
|
return;
|
|
11649
11656
|
}
|
|
11650
11657
|
window.addEventListener("online", this._onOnline);
|
|
@@ -11652,7 +11659,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11652
11659
|
const connection = getNetworkConnection();
|
|
11653
11660
|
if (connection) connection.addEventListener("change", this._onConnectionChange);
|
|
11654
11661
|
this._listenersAttached = true;
|
|
11655
|
-
logger$
|
|
11662
|
+
logger$28.debug("NetworkMonitor: event listeners attached");
|
|
11656
11663
|
}
|
|
11657
11664
|
removeListeners() {
|
|
11658
11665
|
if (!this._listenersAttached) return;
|
|
@@ -11663,10 +11670,10 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11663
11670
|
if (connection) connection.removeEventListener("change", this._onConnectionChange);
|
|
11664
11671
|
}
|
|
11665
11672
|
this._listenersAttached = false;
|
|
11666
|
-
logger$
|
|
11673
|
+
logger$28.debug("NetworkMonitor: event listeners removed");
|
|
11667
11674
|
}
|
|
11668
11675
|
handleOnline() {
|
|
11669
|
-
logger$
|
|
11676
|
+
logger$28.info("NetworkMonitor: browser went online");
|
|
11670
11677
|
this._isOnline$.next(true);
|
|
11671
11678
|
this._networkChange$.next({
|
|
11672
11679
|
type: "online",
|
|
@@ -11675,7 +11682,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11675
11682
|
});
|
|
11676
11683
|
}
|
|
11677
11684
|
handleOffline() {
|
|
11678
|
-
logger$
|
|
11685
|
+
logger$28.info("NetworkMonitor: browser went offline");
|
|
11679
11686
|
this._isOnline$.next(false);
|
|
11680
11687
|
this._networkChange$.next({
|
|
11681
11688
|
type: "offline",
|
|
@@ -11684,7 +11691,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11684
11691
|
}
|
|
11685
11692
|
handleConnectionChange() {
|
|
11686
11693
|
const networkType = getNetworkType();
|
|
11687
|
-
logger$
|
|
11694
|
+
logger$28.info(`NetworkMonitor: connection changed — effectiveType=${networkType ?? "unknown"}`);
|
|
11688
11695
|
this._networkChange$.next({
|
|
11689
11696
|
type: "connection_change",
|
|
11690
11697
|
timestamp: Date.now(),
|
|
@@ -11800,7 +11807,7 @@ function getNavigatorMediaDevices() {
|
|
|
11800
11807
|
//#endregion
|
|
11801
11808
|
//#region src/controllers/PreflightRunner.ts
|
|
11802
11809
|
var import_cjs$27 = require_cjs();
|
|
11803
|
-
const logger$
|
|
11810
|
+
const logger$27 = getLogger();
|
|
11804
11811
|
const DEFAULT_MEDIA_TEST_DURATION_S = 10;
|
|
11805
11812
|
const ICE_GATHERING_TIMEOUT_MS = 1e4;
|
|
11806
11813
|
const SIGNALING_RTT_TIMEOUT_MS = 5e3;
|
|
@@ -11849,7 +11856,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11849
11856
|
if (!this._options.skipMediaTest) try {
|
|
11850
11857
|
bandwidth = await this.testMediaBandwidth(destination);
|
|
11851
11858
|
} catch (error) {
|
|
11852
|
-
logger$
|
|
11859
|
+
logger$27.warn("[PreflightRunner] Media bandwidth test failed:", error);
|
|
11853
11860
|
warnings.push("Media bandwidth test failed");
|
|
11854
11861
|
}
|
|
11855
11862
|
return {
|
|
@@ -11861,7 +11868,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11861
11868
|
warnings
|
|
11862
11869
|
};
|
|
11863
11870
|
} catch (error) {
|
|
11864
|
-
logger$
|
|
11871
|
+
logger$27.error("[PreflightRunner] Preflight test failed:", error);
|
|
11865
11872
|
throw new PreflightError("preflight", error instanceof Error ? error : new Error(String(error)));
|
|
11866
11873
|
} finally {
|
|
11867
11874
|
this.destroy();
|
|
@@ -11892,7 +11899,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11892
11899
|
if (track.kind === "video" && track.readyState === "live") videoWorking = true;
|
|
11893
11900
|
}
|
|
11894
11901
|
} catch (error) {
|
|
11895
|
-
logger$
|
|
11902
|
+
logger$27.warn("[PreflightRunner] Device test failed:", error);
|
|
11896
11903
|
} finally {
|
|
11897
11904
|
if (audioStream) audioStream.getTracks().forEach((t) => t.stop());
|
|
11898
11905
|
}
|
|
@@ -11950,7 +11957,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11950
11957
|
rttMs
|
|
11951
11958
|
};
|
|
11952
11959
|
} catch (error) {
|
|
11953
|
-
logger$
|
|
11960
|
+
logger$27.warn("[PreflightRunner] ICE connectivity test failed:", error);
|
|
11954
11961
|
return {
|
|
11955
11962
|
type: "failed",
|
|
11956
11963
|
turnReachable: false,
|
|
@@ -11998,7 +12005,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11998
12005
|
//#endregion
|
|
11999
12006
|
//#region src/controllers/VisibilityController.ts
|
|
12000
12007
|
var import_cjs$26 = require_cjs();
|
|
12001
|
-
const logger$
|
|
12008
|
+
const logger$26 = getLogger();
|
|
12002
12009
|
/**
|
|
12003
12010
|
* Checks whether the document visibility API is available.
|
|
12004
12011
|
*/
|
|
@@ -12035,8 +12042,8 @@ var VisibilityController = class extends Destroyable {
|
|
|
12035
12042
|
this._boundHandler = this._handleVisibilityChange.bind(this);
|
|
12036
12043
|
if (this._hasVisibilityApi) {
|
|
12037
12044
|
document.addEventListener("visibilitychange", this._boundHandler);
|
|
12038
|
-
logger$
|
|
12039
|
-
} else logger$
|
|
12045
|
+
logger$26.debug("VisibilityController: listening for visibilitychange events");
|
|
12046
|
+
} else logger$26.debug("VisibilityController: document visibility API not available, defaulting to visible");
|
|
12040
12047
|
}
|
|
12041
12048
|
/**
|
|
12042
12049
|
* Observable of the current visibility state.
|
|
@@ -12061,7 +12068,7 @@ var VisibilityController = class extends Destroyable {
|
|
|
12061
12068
|
destroy() {
|
|
12062
12069
|
if (this._hasVisibilityApi) {
|
|
12063
12070
|
document.removeEventListener("visibilitychange", this._boundHandler);
|
|
12064
|
-
logger$
|
|
12071
|
+
logger$26.debug("VisibilityController: removed visibilitychange listener");
|
|
12065
12072
|
}
|
|
12066
12073
|
super.destroy();
|
|
12067
12074
|
}
|
|
@@ -12079,7 +12086,7 @@ var VisibilityController = class extends Destroyable {
|
|
|
12079
12086
|
timestamp: Date.now()
|
|
12080
12087
|
};
|
|
12081
12088
|
this._visibilityChange$.next(changeEvent);
|
|
12082
|
-
logger$
|
|
12089
|
+
logger$26.debug("VisibilityController: visibility changed", {
|
|
12083
12090
|
from: previousState,
|
|
12084
12091
|
to: newState
|
|
12085
12092
|
});
|
|
@@ -12320,7 +12327,7 @@ const RPCEventAckResponse = (id) => makeRPCResponse({
|
|
|
12320
12327
|
|
|
12321
12328
|
//#endregion
|
|
12322
12329
|
//#region src/managers/AttachManager.ts
|
|
12323
|
-
const logger$
|
|
12330
|
+
const logger$25 = getLogger();
|
|
12324
12331
|
var AttachManager = class {
|
|
12325
12332
|
constructor(storage, deviceController, reconnectCallsTimeout, attachKey) {
|
|
12326
12333
|
this.storage = storage;
|
|
@@ -12341,7 +12348,7 @@ var AttachManager = class {
|
|
|
12341
12348
|
try {
|
|
12342
12349
|
return await this.storage.getItem(this.attachKey) ?? {};
|
|
12343
12350
|
} catch (error) {
|
|
12344
|
-
logger$
|
|
12351
|
+
logger$25.warn("[AttachManager] Failed to retrieve attached calls from storage", error);
|
|
12345
12352
|
return {};
|
|
12346
12353
|
}
|
|
12347
12354
|
}
|
|
@@ -12349,7 +12356,7 @@ var AttachManager = class {
|
|
|
12349
12356
|
try {
|
|
12350
12357
|
await this.storage.setItem(this.attachKey, attached);
|
|
12351
12358
|
} catch (error) {
|
|
12352
|
-
logger$
|
|
12359
|
+
logger$25.warn("[AttachManager] Failed to write attached calls to storage", error);
|
|
12353
12360
|
}
|
|
12354
12361
|
}
|
|
12355
12362
|
/**
|
|
@@ -12368,7 +12375,7 @@ var AttachManager = class {
|
|
|
12368
12375
|
}
|
|
12369
12376
|
async attach(call) {
|
|
12370
12377
|
if (!call.to) {
|
|
12371
|
-
logger$
|
|
12378
|
+
logger$25.warn("[AttachManager] Skip attach for calls with no destination");
|
|
12372
12379
|
return;
|
|
12373
12380
|
}
|
|
12374
12381
|
const destination = call.to;
|
|
@@ -12421,15 +12428,15 @@ var AttachManager = class {
|
|
|
12421
12428
|
callId,
|
|
12422
12429
|
...options
|
|
12423
12430
|
});
|
|
12424
|
-
logger$
|
|
12431
|
+
logger$25.info(`[AttachManager] Reattached call ${callId} (attempt ${attempt})`);
|
|
12425
12432
|
succeeded = true;
|
|
12426
12433
|
break;
|
|
12427
12434
|
} catch (error) {
|
|
12428
|
-
logger$
|
|
12435
|
+
logger$25.warn(`[AttachManager] Reattach attempt ${attempt}/3 failed for call ${callId}:`, error);
|
|
12429
12436
|
if (attempt < 3) await new Promise((r) => setTimeout(r, (attempt + 1) * 1e3));
|
|
12430
12437
|
}
|
|
12431
12438
|
if (!succeeded) {
|
|
12432
|
-
logger$
|
|
12439
|
+
logger$25.warn(`[AttachManager] Reattach failed after 3 attempts for call ${callId}, removing reference`);
|
|
12433
12440
|
await this.detach({
|
|
12434
12441
|
id: callId,
|
|
12435
12442
|
mediaDirections: attachment.mediaDirections
|
|
@@ -13366,7 +13373,9 @@ const DEFAULT_MEMBER_CAPABILITIES = {
|
|
|
13366
13373
|
position: false,
|
|
13367
13374
|
meta: false,
|
|
13368
13375
|
remove: false,
|
|
13369
|
-
audioFlags: false
|
|
13376
|
+
audioFlags: false,
|
|
13377
|
+
denoise: false,
|
|
13378
|
+
lowbitrate: false
|
|
13370
13379
|
};
|
|
13371
13380
|
/**
|
|
13372
13381
|
* Default call capabilities with no permissions
|
|
@@ -13439,7 +13448,9 @@ function computeMemberCapabilities(flags, memberType) {
|
|
|
13439
13448
|
position: hasBooleanCapability(flags, memberType, null, "position"),
|
|
13440
13449
|
meta: hasBooleanCapability(flags, memberType, null, "meta"),
|
|
13441
13450
|
remove: hasBooleanCapability(flags, memberType, null, "remove"),
|
|
13442
|
-
audioFlags: hasBooleanCapability(flags, memberType, null, "audioflags")
|
|
13451
|
+
audioFlags: hasBooleanCapability(flags, memberType, null, "audioflags"),
|
|
13452
|
+
denoise: hasBooleanCapability(flags, memberType, null, "denoise"),
|
|
13453
|
+
lowbitrate: hasBooleanCapability(flags, memberType, null, "lowbitrate")
|
|
13443
13454
|
};
|
|
13444
13455
|
}
|
|
13445
13456
|
/**
|
|
@@ -13596,7 +13607,7 @@ function toggleHandraiseMethod(is) {
|
|
|
13596
13607
|
|
|
13597
13608
|
//#endregion
|
|
13598
13609
|
//#region src/core/entities/Participant.ts
|
|
13599
|
-
const logger$
|
|
13610
|
+
const logger$24 = getLogger();
|
|
13600
13611
|
const initialState = {};
|
|
13601
13612
|
/**
|
|
13602
13613
|
* Represents a participant in a call.
|
|
@@ -13822,6 +13833,10 @@ var Participant = class extends Destroyable {
|
|
|
13822
13833
|
get nodeId() {
|
|
13823
13834
|
return this._state$.value.node_id;
|
|
13824
13835
|
}
|
|
13836
|
+
/** Call ID for this participant's leg, or `undefined` if not available. */
|
|
13837
|
+
get callId() {
|
|
13838
|
+
return this._state$.value.call_id;
|
|
13839
|
+
}
|
|
13825
13840
|
/** @internal */
|
|
13826
13841
|
get value() {
|
|
13827
13842
|
return this._state$.value;
|
|
@@ -13883,8 +13898,9 @@ var Participant = class extends Destroyable {
|
|
|
13883
13898
|
noise_suppression: !this.noiseSuppression
|
|
13884
13899
|
});
|
|
13885
13900
|
}
|
|
13901
|
+
/** Toggles low-bitrate mode for this participant's media. */
|
|
13886
13902
|
async toggleLowbitrate() {
|
|
13887
|
-
|
|
13903
|
+
await this.executeMethod(this.id, "call.lowbitrate.set", { lowbitrate: !this.lowbitrate });
|
|
13888
13904
|
}
|
|
13889
13905
|
/**
|
|
13890
13906
|
* Adjusts the **conference-only** microphone energy gate / sensitivity level
|
|
@@ -13931,10 +13947,27 @@ var Participant = class extends Destroyable {
|
|
|
13931
13947
|
}
|
|
13932
13948
|
/**
|
|
13933
13949
|
* Sets the participant's position in the video layout.
|
|
13950
|
+
*
|
|
13951
|
+
* Requires the `member.position` capability. The gateway keys positions by the
|
|
13952
|
+
* **target member's own** `call_id`/`node_id` (see issue #19400 and the legacy
|
|
13953
|
+
* `setPositions` implementation), so this sends the participant's own call
|
|
13954
|
+
* context — matching {@link Participant.remove}. A resolved promise does not
|
|
13955
|
+
* guarantee a visible change: the backend silently returns `200` (no-op) for
|
|
13956
|
+
* non-conference targets.
|
|
13957
|
+
*
|
|
13934
13958
|
* @param value - The {@link VideoPosition} to assign (e.g. `'auto'`, `'reserved-0'`).
|
|
13935
13959
|
*/
|
|
13936
13960
|
async setPosition(value) {
|
|
13937
|
-
|
|
13961
|
+
const state = this._state$.value;
|
|
13962
|
+
const target = {
|
|
13963
|
+
member_id: this.id,
|
|
13964
|
+
call_id: state.call_id ?? "",
|
|
13965
|
+
node_id: state.node_id ?? ""
|
|
13966
|
+
};
|
|
13967
|
+
await this.executeMethod(target, "call.member.position.set", { targets: [{
|
|
13968
|
+
target,
|
|
13969
|
+
position: value
|
|
13970
|
+
}] });
|
|
13938
13971
|
}
|
|
13939
13972
|
/** Removes this participant from the call. */
|
|
13940
13973
|
async remove() {
|
|
@@ -14029,7 +14062,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14029
14062
|
try {
|
|
14030
14063
|
await this.vertoManager.addScreenMedia();
|
|
14031
14064
|
} catch (error) {
|
|
14032
|
-
logger$
|
|
14065
|
+
logger$24.error("[Participant.startScreenShare] Screen share error:", error);
|
|
14033
14066
|
}
|
|
14034
14067
|
}
|
|
14035
14068
|
/** Observable of the current screen share status. */
|
|
@@ -14049,7 +14082,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14049
14082
|
try {
|
|
14050
14083
|
await this.vertoManager.addInputDevice(options);
|
|
14051
14084
|
} catch (error) {
|
|
14052
|
-
logger$
|
|
14085
|
+
logger$24.error("[Participant.startScreenShare] Screen share error:", error);
|
|
14053
14086
|
}
|
|
14054
14087
|
}
|
|
14055
14088
|
/** Removes an additional media input device by ID. */
|
|
@@ -14111,7 +14144,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14111
14144
|
*/
|
|
14112
14145
|
exitStudioModeIfActive() {
|
|
14113
14146
|
if (this._studioAudio$.value) {
|
|
14114
|
-
logger$
|
|
14147
|
+
logger$24.debug("[SelfParticipant] Exiting studio audio mode due to individual flag toggle");
|
|
14115
14148
|
this._studioAudio$.next(false);
|
|
14116
14149
|
}
|
|
14117
14150
|
}
|
|
@@ -14135,7 +14168,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14135
14168
|
try {
|
|
14136
14169
|
await super.mute();
|
|
14137
14170
|
} catch (error) {
|
|
14138
|
-
logger$
|
|
14171
|
+
logger$24.warn("[Participant.toggleAudioInput] Server Error while muting audio input, proceeding with local toggle anyway", error);
|
|
14139
14172
|
} finally {
|
|
14140
14173
|
this.vertoManager.muteMainAudioInputDevice();
|
|
14141
14174
|
}
|
|
@@ -14145,7 +14178,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14145
14178
|
try {
|
|
14146
14179
|
await super.unmute();
|
|
14147
14180
|
} catch (error) {
|
|
14148
|
-
logger$
|
|
14181
|
+
logger$24.warn("[Participant.toggleAudioInput] Server Error while unmuting audio input, proceeding with local toggle anyway", error);
|
|
14149
14182
|
} finally {
|
|
14150
14183
|
await this.vertoManager.unmuteMainAudioInputDevice();
|
|
14151
14184
|
}
|
|
@@ -14155,7 +14188,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14155
14188
|
try {
|
|
14156
14189
|
await super.muteVideo();
|
|
14157
14190
|
} catch (error) {
|
|
14158
|
-
logger$
|
|
14191
|
+
logger$24.warn("[Participant.toggleVideoInput] Server Error while muting video input, proceeding with local toggle anyway", error);
|
|
14159
14192
|
} finally {
|
|
14160
14193
|
this.vertoManager.muteMainVideoInputDevice();
|
|
14161
14194
|
}
|
|
@@ -14165,7 +14198,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14165
14198
|
try {
|
|
14166
14199
|
await super.unmuteVideo();
|
|
14167
14200
|
} catch (error) {
|
|
14168
|
-
logger$
|
|
14201
|
+
logger$24.warn("[Participant.toggleVideoInput] Server Error while unmuting video input, proceeding with local toggle anyway", error);
|
|
14169
14202
|
} finally {
|
|
14170
14203
|
await this.vertoManager.unmuteMainVideoInputDevice();
|
|
14171
14204
|
}
|
|
@@ -14370,7 +14403,7 @@ function filterAs(predicate, resultPath) {
|
|
|
14370
14403
|
//#endregion
|
|
14371
14404
|
//#region src/operators/throwOnRPCError.ts
|
|
14372
14405
|
var import_cjs$21 = require_cjs();
|
|
14373
|
-
const logger$
|
|
14406
|
+
const logger$23 = getLogger();
|
|
14374
14407
|
/**
|
|
14375
14408
|
* RxJS operator that throws a {@link JSONRPCError} when the RPC response contains an error.
|
|
14376
14409
|
* Passes successful responses through unchanged.
|
|
@@ -14378,14 +14411,14 @@ const logger$22 = getLogger();
|
|
|
14378
14411
|
function throwOnRPCError() {
|
|
14379
14412
|
return (0, import_cjs$21.map)((response) => {
|
|
14380
14413
|
if (response.error) {
|
|
14381
|
-
logger$
|
|
14414
|
+
logger$23.error("[throwOnRPCError] RPC error response:", {
|
|
14382
14415
|
code: response.error.code,
|
|
14383
14416
|
message: response.error.message,
|
|
14384
14417
|
data: response.error.data
|
|
14385
14418
|
});
|
|
14386
14419
|
throw new JSONRPCError(response.error.code, response.error.message, response.error.data);
|
|
14387
14420
|
}
|
|
14388
|
-
logger$
|
|
14421
|
+
logger$23.debug("[throwOnRPCError] RPC successful response:", response);
|
|
14389
14422
|
return response;
|
|
14390
14423
|
});
|
|
14391
14424
|
}
|
|
@@ -14393,7 +14426,7 @@ function throwOnRPCError() {
|
|
|
14393
14426
|
//#endregion
|
|
14394
14427
|
//#region src/managers/CallEventsManager.ts
|
|
14395
14428
|
var import_cjs$20 = require_cjs();
|
|
14396
|
-
const logger$
|
|
14429
|
+
const logger$22 = getLogger();
|
|
14397
14430
|
const initialSessionState = {};
|
|
14398
14431
|
/** @internal */
|
|
14399
14432
|
var CallEventsManager = class extends Destroyable {
|
|
@@ -14497,7 +14530,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14497
14530
|
}
|
|
14498
14531
|
initSubscriptions() {
|
|
14499
14532
|
this.subscribeTo(this.callJoinedEvent$, (callJoinedEvent) => {
|
|
14500
|
-
logger$
|
|
14533
|
+
logger$22.debug("[CallEventsManager] Handling call.joined event for call/session IDs:", {
|
|
14501
14534
|
callId: callJoinedEvent.call_id,
|
|
14502
14535
|
roomSessionId: callJoinedEvent.room_session_id
|
|
14503
14536
|
});
|
|
@@ -14524,19 +14557,19 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14524
14557
|
if (this._self$.value?.capabilities.setLayout) this.updateLayouts();
|
|
14525
14558
|
});
|
|
14526
14559
|
this.subscribeTo(this.memberUpdates$, (member) => {
|
|
14527
|
-
logger$
|
|
14560
|
+
logger$22.debug("[CallEventsManager] Handling member update event for member ID:", member);
|
|
14528
14561
|
this.upsertParticipant(member);
|
|
14529
14562
|
});
|
|
14530
14563
|
this.subscribeTo(this.webRtcCallSession.memberLeft$, (memberLeftEvent) => {
|
|
14531
|
-
logger$
|
|
14564
|
+
logger$22.debug("[CallEventsManager] Handling member.left event for member ID:", memberLeftEvent.member.member_id);
|
|
14532
14565
|
const participants = { ...this._participants$.value };
|
|
14533
14566
|
if (memberLeftEvent.member.member_id in participants) {
|
|
14534
14567
|
delete participants[memberLeftEvent.member.member_id];
|
|
14535
14568
|
this._participants$.next(participants);
|
|
14536
|
-
} else logger$
|
|
14569
|
+
} else logger$22.warn(`[CallEventsManager] Received member.left event for unknown member ID: ${memberLeftEvent.member.member_id}`);
|
|
14537
14570
|
});
|
|
14538
14571
|
this.subscribeTo(this.webRtcCallSession.callUpdated$, (callUpdatedEvent) => {
|
|
14539
|
-
logger$
|
|
14572
|
+
logger$22.debug("[CallEventsManager] Handling call.updated event:", callUpdatedEvent);
|
|
14540
14573
|
const roomSession = callUpdatedEvent.room_session;
|
|
14541
14574
|
this._sessionState$.next({
|
|
14542
14575
|
...this._sessionState$.value,
|
|
@@ -14551,7 +14584,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14551
14584
|
});
|
|
14552
14585
|
});
|
|
14553
14586
|
this.subscribeTo(this.layoutChangedEvent$, (layoutChangedEvent) => {
|
|
14554
|
-
logger$
|
|
14587
|
+
logger$22.debug("[CallEventsManager] Handling layout.changed event:", layoutChangedEvent);
|
|
14555
14588
|
this._sessionState$.next({
|
|
14556
14589
|
...this._sessionState$.value,
|
|
14557
14590
|
layout_name: layoutChangedEvent.id,
|
|
@@ -14561,10 +14594,10 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14561
14594
|
});
|
|
14562
14595
|
}
|
|
14563
14596
|
updateParticipantPositions(layoutChangedEvent) {
|
|
14564
|
-
if (Object.keys(this._participants$.value).length > 0 && !layoutChangedEvent.layers.some((layer) => !!layer.member_id)) logger$
|
|
14597
|
+
if (Object.keys(this._participants$.value).length > 0 && !layoutChangedEvent.layers.some((layer) => !!layer.member_id)) logger$22.warn("[CallEventsManager] No layers with member_id found in layout.changed event. Nothing to update.");
|
|
14565
14598
|
layoutChangedEvent.layers.filter((layer) => !!layer.member_id).filter((layer) => {
|
|
14566
14599
|
if (!(layer.member_id in this._participants$.value)) {
|
|
14567
|
-
logger$
|
|
14600
|
+
logger$22.warn(`[CallEventsManager] Skipping layout layer for unknown member_id: ${layer.member_id}`);
|
|
14568
14601
|
return false;
|
|
14569
14602
|
}
|
|
14570
14603
|
return true;
|
|
@@ -14587,7 +14620,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14587
14620
|
layouts: response.result.layouts
|
|
14588
14621
|
});
|
|
14589
14622
|
}).catch((error) => {
|
|
14590
|
-
logger$
|
|
14623
|
+
logger$22.error("[CallEventsManager] Error fetching layouts:", error);
|
|
14591
14624
|
});
|
|
14592
14625
|
}
|
|
14593
14626
|
updateParticipants(members) {
|
|
@@ -14603,7 +14636,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14603
14636
|
}
|
|
14604
14637
|
const participant = this._participants$.value[member.member_id];
|
|
14605
14638
|
const oldValue = participant.value;
|
|
14606
|
-
logger$
|
|
14639
|
+
logger$22.debug("[CallEventsManager] Updating participant:", member.member_id, {
|
|
14607
14640
|
oldValue,
|
|
14608
14641
|
newValue: member
|
|
14609
14642
|
});
|
|
@@ -14616,17 +14649,17 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14616
14649
|
}
|
|
14617
14650
|
get callJoinedEvent$() {
|
|
14618
14651
|
return this.cachedObservable("callJoinedEvent$", () => this.webRtcCallSession.callEvent$.pipe((0, import_cjs$20.filter)(isCallJoinedPayload), (0, import_cjs$20.tap)((event) => {
|
|
14619
|
-
logger$
|
|
14652
|
+
logger$22.debug("[CallEventsManager] Call joined event:", event);
|
|
14620
14653
|
})));
|
|
14621
14654
|
}
|
|
14622
14655
|
get layoutChangedEvent$() {
|
|
14623
14656
|
return this.cachedObservable("layoutChangedEvent$", () => this.webRtcCallSession.callEvent$.pipe(filterAs(isLayoutChangedPayload, "layout"), (0, import_cjs$20.tap)((event) => {
|
|
14624
|
-
logger$
|
|
14657
|
+
logger$22.debug("[CallEventsManager] Layout changed event:", event);
|
|
14625
14658
|
})));
|
|
14626
14659
|
}
|
|
14627
14660
|
get memberUpdates$() {
|
|
14628
14661
|
return this.cachedObservable("memberUpdates$", () => (0, import_cjs$20.merge)(this.webRtcCallSession.memberJoined$, this.webRtcCallSession.memberUpdated$, this.webRtcCallSession.memberTalking$).pipe((0, import_cjs$20.map)((event) => event.member), (0, import_cjs$20.tap)((event) => {
|
|
14629
|
-
logger$
|
|
14662
|
+
logger$22.debug("[CallEventsManager] Member update event:", event);
|
|
14630
14663
|
})));
|
|
14631
14664
|
}
|
|
14632
14665
|
destroy() {
|
|
@@ -14883,7 +14916,7 @@ function appendStereoParams(fmtpLine, maxBitrate) {
|
|
|
14883
14916
|
//#endregion
|
|
14884
14917
|
//#region src/controllers/ICEGatheringController.ts
|
|
14885
14918
|
var import_cjs$19 = require_cjs();
|
|
14886
|
-
const logger$
|
|
14919
|
+
const logger$21 = getLogger();
|
|
14887
14920
|
var ICEGatheringController = class extends Destroyable {
|
|
14888
14921
|
constructor(peerConnection, peerConnectionControllerNegotiating$, options = {}) {
|
|
14889
14922
|
super();
|
|
@@ -14891,23 +14924,23 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14891
14924
|
this.peerConnectionControllerNegotiating$ = peerConnectionControllerNegotiating$;
|
|
14892
14925
|
this.onicegatheringstatechangeHandler = () => {
|
|
14893
14926
|
const { iceGatheringState } = this.peerConnection;
|
|
14894
|
-
logger$
|
|
14927
|
+
logger$21.debug(`[ICEGatheringController] ICE gathering state changed to: ${iceGatheringState}`);
|
|
14895
14928
|
if (iceGatheringState === "gathering") this._iceCandidatesState.next({
|
|
14896
14929
|
state: "gathering",
|
|
14897
14930
|
validSDP: false
|
|
14898
14931
|
});
|
|
14899
14932
|
};
|
|
14900
14933
|
this.onicecandidateHandler = (event) => {
|
|
14901
|
-
logger$
|
|
14934
|
+
logger$21.debug("[ICEGatheringController] ICE candidate event received:", event.candidate);
|
|
14902
14935
|
this.removeTimer("iceCandidateTimer");
|
|
14903
14936
|
if (event.candidate) this.iceCandidateTimer = setTimeout(() => {
|
|
14904
14937
|
if (this.peerConnection.iceGatheringState !== "complete") {
|
|
14905
|
-
logger$
|
|
14938
|
+
logger$21.warn("[ICEGatheringController] ICE candidate timeout, using current SDP");
|
|
14906
14939
|
this.handleICECandidateTimeout();
|
|
14907
14940
|
}
|
|
14908
14941
|
}, this.iceCandidateTimeout);
|
|
14909
14942
|
else {
|
|
14910
|
-
logger$
|
|
14943
|
+
logger$21.debug("[ICEGatheringController] ICE gathering completed: null candidate received");
|
|
14911
14944
|
this.removeTimer("iceGatheringTimer");
|
|
14912
14945
|
this.handleICEGatheringComplete();
|
|
14913
14946
|
}
|
|
@@ -14925,7 +14958,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14925
14958
|
this.setupEventListeners();
|
|
14926
14959
|
this.iceGatheringTimer = setTimeout(() => {
|
|
14927
14960
|
if (this.peerConnection.iceGatheringState !== "complete") {
|
|
14928
|
-
logger$
|
|
14961
|
+
logger$21.warn("[ICEGatheringController] ICE gathering timeout, using current SDP");
|
|
14929
14962
|
this.handleICEGatheringTimeout();
|
|
14930
14963
|
}
|
|
14931
14964
|
}, this.iceGatheringTimeout);
|
|
@@ -14952,9 +14985,9 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14952
14985
|
this.relayOnly = value;
|
|
14953
14986
|
}
|
|
14954
14987
|
handleICEGatheringComplete() {
|
|
14955
|
-
logger$
|
|
14956
|
-
logger$
|
|
14957
|
-
logger$
|
|
14988
|
+
logger$21.debug("[ICEGatheringController] Handling ICE gathering complete");
|
|
14989
|
+
logger$21.debug(`[ICEGatheringController] Checking ICE gathering state: ${this.peerConnection.iceGatheringState}`);
|
|
14990
|
+
logger$21.debug("[ICEGatheringController] ICE gathering complete");
|
|
14958
14991
|
this._iceCandidatesState.next({
|
|
14959
14992
|
state: "complete",
|
|
14960
14993
|
validSDP: this.hasValidLocalDescriptionSDP
|
|
@@ -14970,21 +15003,21 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14970
15003
|
this.removeTimer("iceGatheringTimer");
|
|
14971
15004
|
const validSDP = this.hasValidLocalDescriptionSDP;
|
|
14972
15005
|
if (validSDP) {
|
|
14973
|
-
logger$
|
|
15006
|
+
logger$21.debug("[ICEGatheringController] Local SDP is valid");
|
|
14974
15007
|
this._iceCandidatesState.next({
|
|
14975
15008
|
state: "timeout",
|
|
14976
15009
|
validSDP
|
|
14977
15010
|
});
|
|
14978
15011
|
this.stopGathering();
|
|
14979
|
-
} else logger$
|
|
15012
|
+
} else logger$21.debug("### ICE gathering timeout\n", this.peerConnection.localDescription?.sdp);
|
|
14980
15013
|
}
|
|
14981
15014
|
handleICECandidateTimeout() {
|
|
14982
15015
|
if (this.iceCandidateTimer) this.removeTimer("iceCandidateTimer");
|
|
14983
|
-
logger$
|
|
15016
|
+
logger$21.warn("[ICEGatheringController] ICE candidate timeout");
|
|
14984
15017
|
const validSDP = this.hasValidLocalDescriptionSDP;
|
|
14985
15018
|
if (!validSDP && !this.relayOnly) this.restartICEGatheringWithRelayOnly();
|
|
14986
15019
|
else {
|
|
14987
|
-
logger$
|
|
15020
|
+
logger$21.debug("[ICEGatheringController] Using current SDP due to ICE candidate timeout");
|
|
14988
15021
|
this._iceCandidatesState.next({
|
|
14989
15022
|
state: "timeout",
|
|
14990
15023
|
validSDP
|
|
@@ -14993,7 +15026,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14993
15026
|
}
|
|
14994
15027
|
}
|
|
14995
15028
|
restartICEGatheringWithRelayOnly() {
|
|
14996
|
-
logger$
|
|
15029
|
+
logger$21.debug("[ICEGatheringController] Restarting ICE gathering with relay-only candidates");
|
|
14997
15030
|
this.relayOnly = true;
|
|
14998
15031
|
this.peerConnection.setConfiguration({
|
|
14999
15032
|
...this.peerConnection.getConfiguration(),
|
|
@@ -15008,7 +15041,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
15008
15041
|
}
|
|
15009
15042
|
}
|
|
15010
15043
|
clearAllTimers() {
|
|
15011
|
-
logger$
|
|
15044
|
+
logger$21.debug("[ICEGatheringController] Clearing all timers");
|
|
15012
15045
|
this.removeTimer("iceGatheringTimer");
|
|
15013
15046
|
this.removeTimer("iceCandidateTimer");
|
|
15014
15047
|
}
|
|
@@ -15017,7 +15050,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
15017
15050
|
this.peerConnection.removeEventListener("icecandidate", this.onicecandidateHandler);
|
|
15018
15051
|
}
|
|
15019
15052
|
destroy() {
|
|
15020
|
-
logger$
|
|
15053
|
+
logger$21.debug("[ICEGatheringController] Destroying ICEGatheringController");
|
|
15021
15054
|
this.clearAllTimers();
|
|
15022
15055
|
this.removeEventListeners();
|
|
15023
15056
|
super.destroy();
|
|
@@ -15027,7 +15060,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
15027
15060
|
//#endregion
|
|
15028
15061
|
//#region src/controllers/LocalAudioPipeline.ts
|
|
15029
15062
|
var import_cjs$18 = require_cjs();
|
|
15030
|
-
const logger$
|
|
15063
|
+
const logger$20 = getLogger();
|
|
15031
15064
|
/**
|
|
15032
15065
|
* Web Audio pipeline for the local microphone stream.
|
|
15033
15066
|
*
|
|
@@ -15139,7 +15172,7 @@ var LocalAudioPipeline = class extends Destroyable {
|
|
|
15139
15172
|
try {
|
|
15140
15173
|
this._inputSource.disconnect();
|
|
15141
15174
|
} catch (error) {
|
|
15142
|
-
logger$
|
|
15175
|
+
logger$20.debug("[LocalAudioPipeline] input disconnect warning:", error);
|
|
15143
15176
|
}
|
|
15144
15177
|
this._inputSource = null;
|
|
15145
15178
|
}
|
|
@@ -15149,7 +15182,7 @@ var LocalAudioPipeline = class extends Destroyable {
|
|
|
15149
15182
|
this._inputSource = this._audioContext.createMediaStreamSource(this._inputStream);
|
|
15150
15183
|
this._inputSource.connect(this._gainNode);
|
|
15151
15184
|
if (this._audioContext.state === "suspended") this._audioContext.resume().catch((error) => {
|
|
15152
|
-
logger$
|
|
15185
|
+
logger$20.warn("[LocalAudioPipeline] AudioContext resume failed:", error);
|
|
15153
15186
|
});
|
|
15154
15187
|
}
|
|
15155
15188
|
destroy() {
|
|
@@ -15164,7 +15197,7 @@ var LocalAudioPipeline = class extends Destroyable {
|
|
|
15164
15197
|
this._analyser.disconnect();
|
|
15165
15198
|
} catch {}
|
|
15166
15199
|
this._audioContext.close().catch((error) => {
|
|
15167
|
-
logger$
|
|
15200
|
+
logger$20.debug("[LocalAudioPipeline] audio context close warning:", error);
|
|
15168
15201
|
});
|
|
15169
15202
|
super.destroy();
|
|
15170
15203
|
}
|
|
@@ -15191,7 +15224,7 @@ var LocalAudioPipeline = class extends Destroyable {
|
|
|
15191
15224
|
//#endregion
|
|
15192
15225
|
//#region src/controllers/LocalStreamController.ts
|
|
15193
15226
|
var import_cjs$17 = require_cjs();
|
|
15194
|
-
const logger$
|
|
15227
|
+
const logger$19 = getLogger();
|
|
15195
15228
|
var LocalStreamController = class extends Destroyable {
|
|
15196
15229
|
constructor(options) {
|
|
15197
15230
|
super();
|
|
@@ -15229,26 +15262,26 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15229
15262
|
* Build the local media stream based on the provided options.
|
|
15230
15263
|
*/
|
|
15231
15264
|
async buildLocalStream() {
|
|
15232
|
-
logger$
|
|
15265
|
+
logger$19.debug("[LocalStreamController] Building local media stream.");
|
|
15233
15266
|
let stream;
|
|
15234
15267
|
if (this.options.inputAudioStream ?? this.options.inputVideoStream) {
|
|
15235
15268
|
const tracks = [...this.options.inputAudioStream?.getTracks() ?? [], ...this.options.inputVideoStream?.getTracks() ?? []];
|
|
15236
15269
|
stream = new MediaStream(tracks);
|
|
15237
15270
|
} else if (this.options.propose === "screenshare") {
|
|
15238
|
-
logger$
|
|
15271
|
+
logger$19.debug("[LocalStreamController] Requesting display media for screen sharing with audio:", Boolean(this.options.inputAudioDeviceConstraints));
|
|
15239
15272
|
stream = await this.options.getDisplayMedia({
|
|
15240
15273
|
video: true,
|
|
15241
15274
|
audio: Boolean(this.options.inputAudioDeviceConstraints)
|
|
15242
15275
|
});
|
|
15243
|
-
logger$
|
|
15276
|
+
logger$19.debug("[LocalStreamController] Screen share media obtained:", stream);
|
|
15244
15277
|
} else {
|
|
15245
15278
|
const constraints = {
|
|
15246
15279
|
audio: this.options.inputAudioDeviceConstraints,
|
|
15247
15280
|
video: this.options.inputVideoDeviceConstraints
|
|
15248
15281
|
};
|
|
15249
|
-
logger$
|
|
15282
|
+
logger$19.debug("[LocalStreamController] Requesting user media with constraints:", constraints);
|
|
15250
15283
|
stream = await this.options.getUserMedia(constraints);
|
|
15251
|
-
logger$
|
|
15284
|
+
logger$19.debug("[LocalStreamController] User media obtained:", stream);
|
|
15252
15285
|
}
|
|
15253
15286
|
this._localStream$.next(stream);
|
|
15254
15287
|
this._localAudioTracks$.next(stream.getAudioTracks());
|
|
@@ -15267,7 +15300,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15267
15300
|
this._localStream$.next(localStream);
|
|
15268
15301
|
if (track.kind === "video") this._localVideoTracks$.next(localStream.getVideoTracks());
|
|
15269
15302
|
else this._localAudioTracks$.next(localStream.getAudioTracks());
|
|
15270
|
-
logger$
|
|
15303
|
+
logger$19.debug(`[LocalStreamController] ${track.kind} track added:`, track.id);
|
|
15271
15304
|
return localStream;
|
|
15272
15305
|
}
|
|
15273
15306
|
/**
|
|
@@ -15279,7 +15312,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15279
15312
|
const stream = this._localStream$.value;
|
|
15280
15313
|
const track = stream?.getTracks().find((t) => t.id === trackId);
|
|
15281
15314
|
if (!track) {
|
|
15282
|
-
logger$
|
|
15315
|
+
logger$19.debug(`[LocalStreamController] track not found: ${trackId}`);
|
|
15283
15316
|
return;
|
|
15284
15317
|
}
|
|
15285
15318
|
track.removeEventListener("ended", this.mediaTrackEndedHandler);
|
|
@@ -15288,7 +15321,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15288
15321
|
this._localStream$.next(stream);
|
|
15289
15322
|
if (track.kind === "video") this._localVideoTracks$.next(stream?.getVideoTracks() ?? []);
|
|
15290
15323
|
else this._localAudioTracks$.next(stream?.getAudioTracks() ?? []);
|
|
15291
|
-
logger$
|
|
15324
|
+
logger$19.debug(`[LocalStreamController] ${track.kind} track removed:`, trackId);
|
|
15292
15325
|
return track;
|
|
15293
15326
|
}
|
|
15294
15327
|
/**
|
|
@@ -15323,7 +15356,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15323
15356
|
*/
|
|
15324
15357
|
stopAllTracks() {
|
|
15325
15358
|
this._localStream$.value?.getTracks().forEach((track) => {
|
|
15326
|
-
logger$
|
|
15359
|
+
logger$19.debug(`[LocalStreamController] Stopping local track: ${track.kind}`);
|
|
15327
15360
|
track.removeEventListener("ended", this.mediaTrackEndedHandler);
|
|
15328
15361
|
track.stop();
|
|
15329
15362
|
});
|
|
@@ -15339,7 +15372,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15339
15372
|
|
|
15340
15373
|
//#endregion
|
|
15341
15374
|
//#region src/controllers/TransceiverController.ts
|
|
15342
|
-
const logger$
|
|
15375
|
+
const logger$18 = getLogger();
|
|
15343
15376
|
const getDirection = (send, recv) => {
|
|
15344
15377
|
if (send && recv) return "sendrecv";
|
|
15345
15378
|
else if (send && !recv) return "sendonly";
|
|
@@ -15447,7 +15480,7 @@ var TransceiverController = class extends Destroyable {
|
|
|
15447
15480
|
sendEncodings: isAudio ? void 0 : this.sendEncodings,
|
|
15448
15481
|
streams: direction === "recvonly" ? void 0 : [localStream]
|
|
15449
15482
|
};
|
|
15450
|
-
logger$
|
|
15483
|
+
logger$18.debug(`[TransceiverController] Setting up transceiver sender for local ${track.kind} track:`, {
|
|
15451
15484
|
transceiver,
|
|
15452
15485
|
transceiverParams
|
|
15453
15486
|
});
|
|
@@ -15455,11 +15488,11 @@ var TransceiverController = class extends Destroyable {
|
|
|
15455
15488
|
await transceiver.sender.replaceTrack(track);
|
|
15456
15489
|
transceiver.direction = transceiverParams.direction;
|
|
15457
15490
|
if (transceiverParams.streams?.some((stream) => Boolean(stream))) {
|
|
15458
|
-
logger$
|
|
15491
|
+
logger$18.debug(`[TransceiverController] Setting streams for transceiver sender for local ${track.kind} track:`, transceiverParams.streams);
|
|
15459
15492
|
transceiver.sender.setStreams(...transceiverParams.streams);
|
|
15460
15493
|
}
|
|
15461
15494
|
} else {
|
|
15462
|
-
logger$
|
|
15495
|
+
logger$18.debug(`[TransceiverController] Adding new transceiver for local ${track.kind} track:`, track.id);
|
|
15463
15496
|
this.peerConnection.addTransceiver(track, transceiverParams);
|
|
15464
15497
|
}
|
|
15465
15498
|
}
|
|
@@ -15473,13 +15506,13 @@ var TransceiverController = class extends Destroyable {
|
|
|
15473
15506
|
if (options.updateTransceiverDirection) transceiver.direction = "inactive";
|
|
15474
15507
|
}
|
|
15475
15508
|
} catch (error) {
|
|
15476
|
-
logger$
|
|
15509
|
+
logger$18.error("[TransceiverController] stopTrackSender error", kind, error);
|
|
15477
15510
|
this.options.onError?.(new MediaTrackError("stopTrackSender", kind, error));
|
|
15478
15511
|
}
|
|
15479
15512
|
}
|
|
15480
15513
|
async restoreTrackSender(kind) {
|
|
15481
15514
|
try {
|
|
15482
|
-
logger$
|
|
15515
|
+
logger$18.debug("[TransceiverController] restoreTrackSender called", kind);
|
|
15483
15516
|
const constraints = {};
|
|
15484
15517
|
const transceivers = this.transceiverByKind(kind);
|
|
15485
15518
|
for (const transceiver of transceivers) {
|
|
@@ -15489,23 +15522,23 @@ var TransceiverController = class extends Destroyable {
|
|
|
15489
15522
|
if (trackKind === "audio" || trackKind === "video") constraints[trackKind] = this.getConstraintsFor(trackKind);
|
|
15490
15523
|
}
|
|
15491
15524
|
}
|
|
15492
|
-
logger$
|
|
15525
|
+
logger$18.debug("[TransceiverController] restoreTrackSender constraints:", constraints);
|
|
15493
15526
|
if (Object.keys(constraints).length === 0) {
|
|
15494
|
-
logger$
|
|
15527
|
+
logger$18.warn("[TransceiverController] restoreTrackSender: no tracks need restoration", kind);
|
|
15495
15528
|
return;
|
|
15496
15529
|
}
|
|
15497
15530
|
const newTracks = (await this.options.getUserMedia(constraints)).getTracks();
|
|
15498
|
-
logger$
|
|
15531
|
+
logger$18.debug("[TransceiverController] restoreTrackSender new tracks:", newTracks);
|
|
15499
15532
|
for (const newTrack of newTracks) {
|
|
15500
15533
|
this.options.localStreamController.addTrack(newTrack);
|
|
15501
15534
|
const trackKind = newTrack.kind;
|
|
15502
15535
|
const transceiverOfKind = this.transceiverByKind(trackKind)[0];
|
|
15503
15536
|
transceiverOfKind.direction = trackKind === "audio" ? this.audioDirection : this.videoDirection;
|
|
15504
|
-
logger$
|
|
15537
|
+
logger$18.debug("[TransceiverController] restoreTrackSender setting direction for", trackKind, transceiverOfKind.direction);
|
|
15505
15538
|
await transceiverOfKind.sender.replaceTrack(newTrack);
|
|
15506
15539
|
}
|
|
15507
15540
|
} catch (error) {
|
|
15508
|
-
logger$
|
|
15541
|
+
logger$18.error("[TransceiverController] restoreTrackSender error", kind, error);
|
|
15509
15542
|
this.options.onError?.(new MediaTrackError("restoreTrackSender", kind, error));
|
|
15510
15543
|
}
|
|
15511
15544
|
}
|
|
@@ -15546,14 +15579,14 @@ var TransceiverController = class extends Destroyable {
|
|
|
15546
15579
|
};
|
|
15547
15580
|
try {
|
|
15548
15581
|
await track.applyConstraints(constraintsToApply);
|
|
15549
|
-
logger$
|
|
15550
|
-
logger$
|
|
15582
|
+
logger$18.debug(`[TransceiverController] Updated ${kind} sender constraints:`, constraintsToApply);
|
|
15583
|
+
logger$18.debug(`[TransceiverController] Updated ${kind} sender constraints:`, track.getConstraints());
|
|
15551
15584
|
} catch (error) {
|
|
15552
|
-
logger$
|
|
15585
|
+
logger$18.warn(`[TransceiverController] applyConstraints failed for ${kind} track ${track.id}, attempting track replacement fallback:`, error);
|
|
15553
15586
|
try {
|
|
15554
15587
|
await this.replaceTrackFallback(sender, track, kind, constraintsToApply);
|
|
15555
15588
|
} catch (fallbackError) {
|
|
15556
|
-
logger$
|
|
15589
|
+
logger$18.warn(`[TransceiverController] Track replacement fallback also failed for ${kind} track:`, fallbackError);
|
|
15557
15590
|
this.options.onError?.(new MediaTrackError("updateSendersConstraints", kind, fallbackError));
|
|
15558
15591
|
}
|
|
15559
15592
|
}
|
|
@@ -15581,7 +15614,7 @@ var TransceiverController = class extends Destroyable {
|
|
|
15581
15614
|
if (!newTrack) throw new MediaTrackError("replaceTrackFallback", kind, /* @__PURE__ */ new Error("getUserMedia returned no track of the requested kind"));
|
|
15582
15615
|
await sender.replaceTrack(newTrack);
|
|
15583
15616
|
this.options.localStreamController.addTrack(newTrack);
|
|
15584
|
-
logger$
|
|
15617
|
+
logger$18.debug(`[TransceiverController] Track replacement fallback succeeded for ${kind}. New track: ${newTrack.id}`);
|
|
15585
15618
|
}
|
|
15586
15619
|
getMediaDirections() {
|
|
15587
15620
|
if (this.peerConnection.connectionState === "connected") return this.peerConnection.getTransceivers().reduce((acc, transceiver) => {
|
|
@@ -15612,7 +15645,7 @@ var TransceiverController = class extends Destroyable {
|
|
|
15612
15645
|
//#endregion
|
|
15613
15646
|
//#region src/controllers/RTCPeerConnectionController.ts
|
|
15614
15647
|
var import_cjs$16 = require_cjs();
|
|
15615
|
-
const logger$
|
|
15648
|
+
const logger$17 = getLogger();
|
|
15616
15649
|
var RTCPeerConnectionController = class extends Destroyable {
|
|
15617
15650
|
constructor(options = {}, remoteSessionDescription, deviceController) {
|
|
15618
15651
|
super();
|
|
@@ -15628,43 +15661,43 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15628
15661
|
this.oniceconnectionstatechangeHandler = () => {
|
|
15629
15662
|
if (this.peerConnection) {
|
|
15630
15663
|
const { iceConnectionState } = this.peerConnection;
|
|
15631
|
-
logger$
|
|
15664
|
+
logger$17.debug(`[RTCPeerConnectionController] ICE connection state changed to: ${iceConnectionState}`);
|
|
15632
15665
|
this._iceConnectionState$.next(this.peerConnection.iceConnectionState);
|
|
15633
15666
|
}
|
|
15634
15667
|
};
|
|
15635
15668
|
this.onconnectionstatechangeHandler = () => {
|
|
15636
15669
|
if (this.peerConnection) {
|
|
15637
15670
|
const { connectionState } = this.peerConnection;
|
|
15638
|
-
logger$
|
|
15671
|
+
logger$17.debug(`[RTCPeerConnectionController] Connection state changed to: ${connectionState}`);
|
|
15639
15672
|
if (connectionState === "connected") this.removeConnectionTimer();
|
|
15640
15673
|
this._connectionState$.next(this.peerConnection.connectionState);
|
|
15641
15674
|
}
|
|
15642
15675
|
};
|
|
15643
15676
|
this.onsignalingstatechangeHandler = () => {
|
|
15644
|
-
logger$
|
|
15677
|
+
logger$17.debug(`[RTCPeerConnectionController] Signaling state changed to: ${this.peerConnection?.signalingState}`);
|
|
15645
15678
|
};
|
|
15646
15679
|
this.onicegatheringstatechangeHandler = () => {
|
|
15647
15680
|
if (this.peerConnection) this._iceGatheringState$.next(this.peerConnection.iceGatheringState);
|
|
15648
15681
|
};
|
|
15649
15682
|
this.onnegotiationneededHandler = (event) => {
|
|
15650
|
-
logger$
|
|
15683
|
+
logger$17.debug("[RTCPeerConnectionController] Negotiation needed event received.", event);
|
|
15651
15684
|
this.negotiationNeeded$.next();
|
|
15652
15685
|
};
|
|
15653
15686
|
this.updateSelectedInputDevice = async (kind, deviceInfo) => {
|
|
15654
15687
|
try {
|
|
15655
15688
|
const { localStream } = this;
|
|
15656
15689
|
if (!localStream) {
|
|
15657
|
-
logger$
|
|
15690
|
+
logger$17.warn("[RTCPeerConnectionController] No local stream available to update input device.");
|
|
15658
15691
|
return;
|
|
15659
15692
|
}
|
|
15660
|
-
logger$
|
|
15693
|
+
logger$17.debug(`[RTCPeerConnectionController] Updating selected ${kind} input device:`, localStream.getTracks());
|
|
15661
15694
|
const track = localStream.getTracks().find((track$1) => track$1.kind === kind);
|
|
15662
15695
|
if (track) {
|
|
15663
15696
|
this.transceiverController?.stopTrackSender(kind);
|
|
15664
15697
|
this.localStreamController.removeTrack(track.id);
|
|
15665
|
-
logger$
|
|
15698
|
+
logger$17.debug(`[RTCPeerConnectionController] Stopped existing ${kind} track: ${track.id}`, localStream.getTracks());
|
|
15666
15699
|
if (!deviceInfo) {
|
|
15667
|
-
logger$
|
|
15700
|
+
logger$17.debug(`[RTCPeerConnectionController] ${kind} input device selected: none`);
|
|
15668
15701
|
return;
|
|
15669
15702
|
}
|
|
15670
15703
|
const streamTrack = (await this.getUserMedia({ [kind]: {
|
|
@@ -15672,15 +15705,15 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15672
15705
|
...this.deviceController.deviceInfoToConstraints(deviceInfo)
|
|
15673
15706
|
} })).getTracks().find((t) => t.kind === kind);
|
|
15674
15707
|
if (streamTrack) {
|
|
15675
|
-
logger$
|
|
15708
|
+
logger$17.debug(`[RTCPeerConnectionController] Adding new ${kind} track: ${streamTrack.id}`);
|
|
15676
15709
|
this.localStreamController.addTrack(streamTrack);
|
|
15677
15710
|
await this.transceiverController?.replaceSenderTrack(kind, streamTrack);
|
|
15678
|
-
logger$
|
|
15711
|
+
logger$17.debug(`[RTCPeerConnectionController] Added new ${kind} track: ${streamTrack.id}`, this.localStream?.getTracks());
|
|
15679
15712
|
}
|
|
15680
15713
|
}
|
|
15681
|
-
logger$
|
|
15714
|
+
logger$17.debug(`[RTCPeerConnectionController] ${kind} input device selected:`, deviceInfo?.label);
|
|
15682
15715
|
} catch (error) {
|
|
15683
|
-
logger$
|
|
15716
|
+
logger$17.error(`[RTCPeerConnectionController] Failed to select ${kind} input device:`, error);
|
|
15684
15717
|
this._errors$.next(toError(error));
|
|
15685
15718
|
throw error;
|
|
15686
15719
|
}
|
|
@@ -15930,15 +15963,15 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15930
15963
|
this.setupPeerConnection();
|
|
15931
15964
|
this.subscribeTo(this.negotiationNeeded$.pipe((0, import_cjs$16.auditTime)(0), (0, import_cjs$16.exhaustMap)(async () => this.startNegotiation())), {
|
|
15932
15965
|
next: () => {
|
|
15933
|
-
logger$
|
|
15966
|
+
logger$17.debug("[RTCPeerConnectionController] Start Negotiation completed successfully");
|
|
15934
15967
|
},
|
|
15935
15968
|
error: (error) => {
|
|
15936
|
-
logger$
|
|
15969
|
+
logger$17.error("[RTCPeerConnectionController] Start Negotiation error:", error);
|
|
15937
15970
|
this._errors$.next(toError(error));
|
|
15938
15971
|
}
|
|
15939
15972
|
});
|
|
15940
15973
|
this.subscribeTo((0, import_cjs$16.merge)(this.deviceController.selectedAudioInputDevice$.pipe((0, import_cjs$16.map)((deviceInfo) => ["audio", deviceInfo])), this.deviceController.selectedVideoInputDevice$.pipe((0, import_cjs$16.map)((deviceInfo) => ["video", deviceInfo]))).pipe((0, import_cjs$16.skipWhile)(() => !this.localStreamController.localStream)), async ([kind, deviceInfo]) => {
|
|
15941
|
-
logger$
|
|
15974
|
+
logger$17.debug(`[RTCPeerConnectionController] Selected input device changed for:`, {
|
|
15942
15975
|
kind,
|
|
15943
15976
|
deviceInfo
|
|
15944
15977
|
});
|
|
@@ -15955,7 +15988,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15955
15988
|
this._initialized$.next(true);
|
|
15956
15989
|
}
|
|
15957
15990
|
} catch (error) {
|
|
15958
|
-
logger$
|
|
15991
|
+
logger$17.error("[RTCPeerConnectionController] Initialization error:", error);
|
|
15959
15992
|
this._errors$.next(toError(error));
|
|
15960
15993
|
this.destroy();
|
|
15961
15994
|
}
|
|
@@ -15987,22 +16020,22 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15987
16020
|
}
|
|
15988
16021
|
async startNegotiation() {
|
|
15989
16022
|
if (this.isNegotiating) {
|
|
15990
|
-
logger$
|
|
16023
|
+
logger$17.debug("[RTCPeerConnectionController] Negotiation already in progress, skipping.");
|
|
15991
16024
|
return;
|
|
15992
16025
|
}
|
|
15993
16026
|
this.setupEventListeners();
|
|
15994
16027
|
if (this.type === "answer") {
|
|
15995
|
-
logger$
|
|
16028
|
+
logger$17.debug("[RTCPeerConnectionController] This is an answer type still, skipping offer creation.");
|
|
15996
16029
|
return;
|
|
15997
16030
|
}
|
|
15998
16031
|
this._isNegotiating$.next(true);
|
|
15999
|
-
logger$
|
|
16032
|
+
logger$17.debug("[RTCPeerConnectionController] Starting negotiation.");
|
|
16000
16033
|
try {
|
|
16001
16034
|
const { offerOptions } = this;
|
|
16002
|
-
logger$
|
|
16035
|
+
logger$17.debug("[RTCPeerConnectionController] Creating offer with options:", offerOptions);
|
|
16003
16036
|
await this.createOffer(offerOptions);
|
|
16004
16037
|
} catch (error) {
|
|
16005
|
-
logger$
|
|
16038
|
+
logger$17.error("[RTCPeerConnectionController] Error during negotiation:", error);
|
|
16006
16039
|
this._errors$.next(toError(error));
|
|
16007
16040
|
}
|
|
16008
16041
|
}
|
|
@@ -16018,14 +16051,14 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16018
16051
|
let readyToConnect = status !== "failed";
|
|
16019
16052
|
try {
|
|
16020
16053
|
if (status === "received" && sdp) {
|
|
16021
|
-
logger$
|
|
16054
|
+
logger$17.debug("[RTCPeerConnectionController] Received answer SDP:", sdp);
|
|
16022
16055
|
await this._setRemoteDescription({
|
|
16023
16056
|
type: "answer",
|
|
16024
16057
|
sdp
|
|
16025
16058
|
});
|
|
16026
16059
|
}
|
|
16027
16060
|
} catch (error) {
|
|
16028
|
-
logger$
|
|
16061
|
+
logger$17.error("[RTCPeerConnectionController] Error updating answer status:", error);
|
|
16029
16062
|
this._errors$.next(toError(error));
|
|
16030
16063
|
readyToConnect = false;
|
|
16031
16064
|
} finally {
|
|
@@ -16044,7 +16077,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16044
16077
|
await this.handleOfferReceived();
|
|
16045
16078
|
break;
|
|
16046
16079
|
case "failed":
|
|
16047
|
-
logger$
|
|
16080
|
+
logger$17.error("[RTCPeerConnectionController] Offer failed to be processed by remote.");
|
|
16048
16081
|
break;
|
|
16049
16082
|
case "sent":
|
|
16050
16083
|
default:
|
|
@@ -16076,7 +16109,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16076
16109
|
}
|
|
16077
16110
|
await this.setupLocalTracks();
|
|
16078
16111
|
const { answerOptions } = this;
|
|
16079
|
-
logger$
|
|
16112
|
+
logger$17.debug("[RTCPeerConnectionController] Creating inbound answer with options:", answerOptions);
|
|
16080
16113
|
await this.createAnswer(answerOptions);
|
|
16081
16114
|
}
|
|
16082
16115
|
async handleOfferReceived() {
|
|
@@ -16084,7 +16117,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16084
16117
|
this._isNegotiating$.next(true);
|
|
16085
16118
|
await this._setRemoteDescription(this.sdpInit);
|
|
16086
16119
|
const { answerOptions } = this;
|
|
16087
|
-
logger$
|
|
16120
|
+
logger$17.debug("[RTCPeerConnectionController] Creating answer with options:", answerOptions);
|
|
16088
16121
|
await this.createAnswer(answerOptions);
|
|
16089
16122
|
}
|
|
16090
16123
|
readyToConnect() {
|
|
@@ -16092,7 +16125,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16092
16125
|
this.connectionTimer = setTimeout(() => {
|
|
16093
16126
|
this.removeConnectionTimer();
|
|
16094
16127
|
if (this.peerConnection?.connectionState !== "connected") {
|
|
16095
|
-
logger$
|
|
16128
|
+
logger$17.debug("[RTCPeerConnectionController] Connection timeout, restarting ICE gathering with relay only.");
|
|
16096
16129
|
this.iceGatheringController.restartICEGatheringWithRelayOnly();
|
|
16097
16130
|
}
|
|
16098
16131
|
}, this.connectionTimeout);
|
|
@@ -16114,14 +16147,14 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16114
16147
|
const stereo = this.options.stereo ?? PreferencesContainer.instance.stereoAudio;
|
|
16115
16148
|
if (preferredAudioCodecs.length > 0 || preferredVideoCodecs.length > 0) {
|
|
16116
16149
|
result = setCodecPreferences(result, preferredAudioCodecs, preferredVideoCodecs);
|
|
16117
|
-
logger$
|
|
16150
|
+
logger$17.debug("[RTCPeerConnectionController] Applied codec preferences to SDP", {
|
|
16118
16151
|
preferredAudioCodecs,
|
|
16119
16152
|
preferredVideoCodecs
|
|
16120
16153
|
});
|
|
16121
16154
|
}
|
|
16122
16155
|
if (stereo) {
|
|
16123
16156
|
result = enableStereoOpus(result);
|
|
16124
|
-
logger$
|
|
16157
|
+
logger$17.debug("[RTCPeerConnectionController] Applied stereo Opus to SDP");
|
|
16125
16158
|
}
|
|
16126
16159
|
return Promise.resolve(result);
|
|
16127
16160
|
}
|
|
@@ -16177,25 +16210,25 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16177
16210
|
...this.peerConnection.getConfiguration(),
|
|
16178
16211
|
iceTransportPolicy: "relay"
|
|
16179
16212
|
});
|
|
16180
|
-
logger$
|
|
16213
|
+
logger$17.debug("[RTCPeerConnectionController] ICE transport policy set to relay-only");
|
|
16181
16214
|
} catch (error) {
|
|
16182
|
-
logger$
|
|
16215
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to set relay-only policy:", error);
|
|
16183
16216
|
}
|
|
16184
16217
|
this.setupEventListeners();
|
|
16185
16218
|
this._isNegotiating$.next(true);
|
|
16186
|
-
logger$
|
|
16219
|
+
logger$17.debug(`[RTCPeerConnectionController] Triggering ICE restart${relayOnly ? " (relay-only)" : ""}.`);
|
|
16187
16220
|
try {
|
|
16188
16221
|
const offer = await this.peerConnection.createOffer({ iceRestart: true });
|
|
16189
16222
|
await this.setLocalDescription(offer);
|
|
16190
16223
|
} catch (error) {
|
|
16191
|
-
logger$
|
|
16224
|
+
logger$17.error("[RTCPeerConnectionController] ICE restart offer failed:", error);
|
|
16192
16225
|
this._errors$.next(toError(error));
|
|
16193
16226
|
this.negotiationEnded();
|
|
16194
16227
|
if (policyChanged) this.restoreIceTransportPolicy();
|
|
16195
16228
|
throw error;
|
|
16196
16229
|
}
|
|
16197
16230
|
if (policyChanged) (0, import_cjs$16.firstValueFrom)((0, import_cjs$16.race)(this._iceGatheringState$.pipe((0, import_cjs$16.filter)((state) => state === "complete"), (0, import_cjs$16.take)(1)), (0, import_cjs$16.timer)(ICE_GATHERING_COMPLETE_TIMEOUT_MS).pipe((0, import_cjs$16.map)(() => "timeout")))).then(() => this.restoreIceTransportPolicy()).catch((error) => {
|
|
16198
|
-
logger$
|
|
16231
|
+
logger$17.warn("[RTCPeerConnectionController] Error waiting for ICE gathering to complete:", error);
|
|
16199
16232
|
this.restoreIceTransportPolicy();
|
|
16200
16233
|
});
|
|
16201
16234
|
}
|
|
@@ -16205,9 +16238,9 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16205
16238
|
...this.peerConnection.getConfiguration(),
|
|
16206
16239
|
iceTransportPolicy: this.options.relayOnly ? "relay" : "all"
|
|
16207
16240
|
});
|
|
16208
|
-
logger$
|
|
16241
|
+
logger$17.debug("[RTCPeerConnectionController] ICE transport policy restored");
|
|
16209
16242
|
} catch (error) {
|
|
16210
|
-
logger$
|
|
16243
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to restore ICE transport policy:", error);
|
|
16211
16244
|
}
|
|
16212
16245
|
}
|
|
16213
16246
|
/**
|
|
@@ -16219,13 +16252,13 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16219
16252
|
await this.setupRemoteTracks();
|
|
16220
16253
|
}
|
|
16221
16254
|
async setupLocalTracks() {
|
|
16222
|
-
logger$
|
|
16255
|
+
logger$17.debug("[RTCPeerConnectionController] Setting up local tracks/transceivers.");
|
|
16223
16256
|
const localStream = this.localStream ?? await this.localStreamController.buildLocalStream();
|
|
16224
16257
|
if (this.transceiverController?.useAddStream ?? false) {
|
|
16225
|
-
logger$
|
|
16258
|
+
logger$17.warn("[RTCPeerConnectionController] Using deprecated addStream API to add local stream.");
|
|
16226
16259
|
this.peerConnection?.addStream(localStream);
|
|
16227
16260
|
if (!this.isNegotiating) {
|
|
16228
|
-
logger$
|
|
16261
|
+
logger$17.debug("[RTCPeerConnectionController] Forcing negotiationneeded after local tracks setup.");
|
|
16229
16262
|
this.negotiationNeeded$.next();
|
|
16230
16263
|
}
|
|
16231
16264
|
return;
|
|
@@ -16241,7 +16274,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16241
16274
|
const transceivers = (kind === "audio" ? this.transceiverController?.audioTransceivers : this.transceiverController?.videoTransceivers) ?? [];
|
|
16242
16275
|
await this.transceiverController?.setupTransceiverSender(track, localStream, transceivers[index]);
|
|
16243
16276
|
} else {
|
|
16244
|
-
logger$
|
|
16277
|
+
logger$17.debug(`[RTCPeerConnectionController] Using addTrack for local ${kind} track:`, track.id);
|
|
16245
16278
|
this.peerConnection?.addTrack(track, localStream);
|
|
16246
16279
|
}
|
|
16247
16280
|
}
|
|
@@ -16258,7 +16291,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16258
16291
|
async setupRemoteTracks() {
|
|
16259
16292
|
if (!this.peerConnection) throw new DependencyError("RTCPeerConnection is not initialized");
|
|
16260
16293
|
this.peerConnection.ontrack = (event) => {
|
|
16261
|
-
logger$
|
|
16294
|
+
logger$17.debug("[RTCPeerConnectionController] Remote track received:", event.track.kind);
|
|
16262
16295
|
if (event.streams[0]) this._remoteStream$.next(event.streams[0]);
|
|
16263
16296
|
else {
|
|
16264
16297
|
const existingTracks = this._remoteStream$.value?.getTracks() ?? [];
|
|
@@ -16282,7 +16315,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16282
16315
|
try {
|
|
16283
16316
|
stream = await this.getUserMedia({ audio: constraints });
|
|
16284
16317
|
} catch (error) {
|
|
16285
|
-
logger$
|
|
16318
|
+
logger$17.error("[RTCPeerConnectionController] Failed to re-acquire mic for pipeline restore:", error);
|
|
16286
16319
|
this._errors$.next(toError(error));
|
|
16287
16320
|
return;
|
|
16288
16321
|
}
|
|
@@ -16304,7 +16337,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16304
16337
|
try {
|
|
16305
16338
|
this._localAudioPipeline = new LocalAudioPipeline();
|
|
16306
16339
|
} catch (error) {
|
|
16307
|
-
logger$
|
|
16340
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to create LocalAudioPipeline:", error);
|
|
16308
16341
|
return null;
|
|
16309
16342
|
}
|
|
16310
16343
|
this.subscribeTo(this.localStreamController.localAudioTracks$, () => {
|
|
@@ -16326,7 +16359,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16326
16359
|
try {
|
|
16327
16360
|
await sender.replaceTrack(this._localAudioPipeline.outputTrack);
|
|
16328
16361
|
} catch (error) {
|
|
16329
|
-
logger$
|
|
16362
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to route audio sender through pipeline:", error);
|
|
16330
16363
|
}
|
|
16331
16364
|
}
|
|
16332
16365
|
/**
|
|
@@ -16342,9 +16375,9 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16342
16375
|
try {
|
|
16343
16376
|
const localStream = this.localStreamController.addTrack(track);
|
|
16344
16377
|
this.peerConnection.addTrack(track, localStream);
|
|
16345
|
-
logger$
|
|
16378
|
+
logger$17.debug(`[RTCPeerConnectionController] ${track.kind} track added:`, track.id);
|
|
16346
16379
|
} catch (error) {
|
|
16347
|
-
logger$
|
|
16380
|
+
logger$17.error(`[RTCPeerConnectionController] Failed to add ${track.kind} track:`, error);
|
|
16348
16381
|
this._errors$.next(toError(error));
|
|
16349
16382
|
throw error;
|
|
16350
16383
|
}
|
|
@@ -16361,15 +16394,15 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16361
16394
|
}
|
|
16362
16395
|
const sender = this.peerConnection.getSenders().find((sender$1) => sender$1.track?.id === trackId);
|
|
16363
16396
|
if (!sender) {
|
|
16364
|
-
logger$
|
|
16397
|
+
logger$17.debug(`[RTCPeerConnectionController] track not found: ${trackId}`);
|
|
16365
16398
|
return;
|
|
16366
16399
|
}
|
|
16367
16400
|
try {
|
|
16368
16401
|
this.peerConnection.removeTrack(sender);
|
|
16369
16402
|
this.localStreamController.removeTrack(trackId);
|
|
16370
|
-
logger$
|
|
16403
|
+
logger$17.debug(`[RTCPeerConnectionController] ${sender.track?.kind} track removed:`, trackId);
|
|
16371
16404
|
} catch (error) {
|
|
16372
|
-
logger$
|
|
16405
|
+
logger$17.error(`[RTCPeerConnectionController] Failed to remove ${sender.track?.kind} track:`, error);
|
|
16373
16406
|
this._errors$.next(toError(error));
|
|
16374
16407
|
throw error;
|
|
16375
16408
|
}
|
|
@@ -16396,7 +16429,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16396
16429
|
async replaceAudioTrackWithConstraints(constraints) {
|
|
16397
16430
|
const senders = this.peerConnection?.getSenders().filter((s) => s.track?.kind === "audio" && s.track.readyState === "live");
|
|
16398
16431
|
if (!senders || senders.length === 0) {
|
|
16399
|
-
logger$
|
|
16432
|
+
logger$17.warn("[RTCPeerConnectionController] No live audio sender to replace");
|
|
16400
16433
|
return;
|
|
16401
16434
|
}
|
|
16402
16435
|
for (const sender of senders) {
|
|
@@ -16414,7 +16447,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16414
16447
|
const newTrack = (await this.getUserMedia({ audio: mergedConstraints })).getAudioTracks()[0];
|
|
16415
16448
|
await sender.replaceTrack(newTrack);
|
|
16416
16449
|
this.localStreamController.addTrack(newTrack);
|
|
16417
|
-
logger$
|
|
16450
|
+
logger$17.debug(`[RTCPeerConnectionController] Audio track replaced for server-pushed params. New track: ${newTrack.id}`);
|
|
16418
16451
|
}
|
|
16419
16452
|
}
|
|
16420
16453
|
/**
|
|
@@ -16422,7 +16455,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16422
16455
|
* Completes all observables to prevent memory leaks.
|
|
16423
16456
|
*/
|
|
16424
16457
|
destroy() {
|
|
16425
|
-
logger$
|
|
16458
|
+
logger$17.debug(`[RTCPeerConnectionController] Destroying RTCPeerConnectionController. ${this.propose}`);
|
|
16426
16459
|
this.removeConnectionTimer();
|
|
16427
16460
|
this._iceGatheringController?.destroy();
|
|
16428
16461
|
this._localAudioPipeline?.destroy();
|
|
@@ -16448,7 +16481,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16448
16481
|
}
|
|
16449
16482
|
stopRemoteTracks() {
|
|
16450
16483
|
this._remoteStream$.value?.getTracks().forEach((track) => {
|
|
16451
|
-
logger$
|
|
16484
|
+
logger$17.debug(`[RTCPeerConnectionController] Stopping remote track: ${track.kind}`);
|
|
16452
16485
|
track.stop();
|
|
16453
16486
|
});
|
|
16454
16487
|
}
|
|
@@ -16465,7 +16498,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16465
16498
|
...params,
|
|
16466
16499
|
sdp: finalRemote
|
|
16467
16500
|
};
|
|
16468
|
-
logger$
|
|
16501
|
+
logger$17.debug("[RTCPeerConnectionController] Setting remote description:", answer);
|
|
16469
16502
|
return this.peerConnection.setRemoteDescription(answer);
|
|
16470
16503
|
}
|
|
16471
16504
|
};
|
|
@@ -16480,7 +16513,11 @@ function isVertoInviteMessage(value) {
|
|
|
16480
16513
|
const msg = value;
|
|
16481
16514
|
return msg.method === "verto.invite" && isObject(msg.params) && hasProperty(msg.params, "sdp") && hasProperty(msg.params, "callID");
|
|
16482
16515
|
}
|
|
16483
|
-
|
|
16516
|
+
/**
|
|
16517
|
+
* Guards inbound `verto.bye` frames (server → client). Outbound bye messages are
|
|
16518
|
+
* built locally and never type-guarded, so only the inbound shape is asserted.
|
|
16519
|
+
*/
|
|
16520
|
+
function isVertoByeInboundMessage(value) {
|
|
16484
16521
|
if (!isVertoMethodMessage(value)) return false;
|
|
16485
16522
|
return value.method === "verto.bye";
|
|
16486
16523
|
}
|
|
@@ -16500,11 +16537,19 @@ function isVertoMediaParamsInnerParams(value) {
|
|
|
16500
16537
|
function isVertoPingInnerParams(value) {
|
|
16501
16538
|
return isObject(value) && hasProperty(value, "jsonrpc") && value.jsonrpc === "2.0" && hasProperty(value, "method") && value.method === "verto.ping";
|
|
16502
16539
|
}
|
|
16540
|
+
/**
|
|
16541
|
+
* Guards the params of an inbound `verto.bye` frame (top-level `callID`). Use
|
|
16542
|
+
* this to recognise the bye payload extracted by `vertoBye$`, e.g. when racing
|
|
16543
|
+
* it against a boolean answer/reject signal.
|
|
16544
|
+
*/
|
|
16545
|
+
function isVertoByeInboundParamsGuard(value) {
|
|
16546
|
+
return isObject(value) && hasProperty(value, "callID") && typeof value.callID === "string";
|
|
16547
|
+
}
|
|
16503
16548
|
|
|
16504
16549
|
//#endregion
|
|
16505
16550
|
//#region src/managers/VertoManager.ts
|
|
16506
16551
|
var import_cjs$15 = require_cjs();
|
|
16507
|
-
const logger$
|
|
16552
|
+
const logger$16 = getLogger();
|
|
16508
16553
|
/**
|
|
16509
16554
|
* Decide what value goes on the `node_id` field of a `webrtc.verto` envelope.
|
|
16510
16555
|
*
|
|
@@ -16560,7 +16605,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16560
16605
|
try {
|
|
16561
16606
|
await this.executeVerto(vertoModifyMessage);
|
|
16562
16607
|
} catch (error) {
|
|
16563
|
-
logger$
|
|
16608
|
+
logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto hold:", error);
|
|
16564
16609
|
throw error;
|
|
16565
16610
|
}
|
|
16566
16611
|
}
|
|
@@ -16573,7 +16618,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16573
16618
|
try {
|
|
16574
16619
|
await this.executeVerto(vertoModifyMessage);
|
|
16575
16620
|
} catch (error) {
|
|
16576
|
-
logger$
|
|
16621
|
+
logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto unhold:", error);
|
|
16577
16622
|
throw error;
|
|
16578
16623
|
}
|
|
16579
16624
|
}
|
|
@@ -16626,7 +16671,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16626
16671
|
if (event.member_id) this.setSelfIdIfNull(event.member_id);
|
|
16627
16672
|
});
|
|
16628
16673
|
this.subscribeTo(this.vertoMedia$, (event) => {
|
|
16629
|
-
logger$
|
|
16674
|
+
logger$16.debug("[WebRTCManager] Received Verto media event (early media SDP):", event);
|
|
16630
16675
|
const { sdp, callID } = event;
|
|
16631
16676
|
this.emitMainSignalingStatus(callID, "ringing");
|
|
16632
16677
|
this._rtcPeerConnectionsMap.get(callID)?.updateAnswerStatus({
|
|
@@ -16635,7 +16680,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16635
16680
|
});
|
|
16636
16681
|
});
|
|
16637
16682
|
this.subscribeTo(this.vertoAnswer$, (event) => {
|
|
16638
|
-
logger$
|
|
16683
|
+
logger$16.debug("[WebRTCManager] Received Verto answer event:", event);
|
|
16639
16684
|
const { sdp, callID } = event;
|
|
16640
16685
|
this.emitMainSignalingStatus(callID, "connecting");
|
|
16641
16686
|
this._rtcPeerConnectionsMap.get(callID)?.updateAnswerStatus({
|
|
@@ -16644,7 +16689,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16644
16689
|
});
|
|
16645
16690
|
});
|
|
16646
16691
|
this.subscribeTo(this.vertoMediaParams$, (event) => {
|
|
16647
|
-
logger$
|
|
16692
|
+
logger$16.debug("[WebRTCManager] Received Verto mediaParams event:", event);
|
|
16648
16693
|
const { mediaParams, callID } = event;
|
|
16649
16694
|
const rtcPeerConnController = this._rtcPeerConnectionsMap.get(callID);
|
|
16650
16695
|
const { audio, video } = mediaParams;
|
|
@@ -16658,7 +16703,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16658
16703
|
timestamp: Date.now()
|
|
16659
16704
|
});
|
|
16660
16705
|
} catch (error) {
|
|
16661
|
-
logger$
|
|
16706
|
+
logger$16.warn("[WebRTCManager] Error applying server-pushed media params:", error);
|
|
16662
16707
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
16663
16708
|
}
|
|
16664
16709
|
})();
|
|
@@ -16680,13 +16725,13 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16680
16725
|
*/
|
|
16681
16726
|
setNodeIdIfNull(nodeId) {
|
|
16682
16727
|
if (!this._nodeId$.value && nodeId) {
|
|
16683
|
-
logger$
|
|
16728
|
+
logger$16.debug(`[WebRTCManager] Early node_id set: ${nodeId}`);
|
|
16684
16729
|
this._nodeId$.next(nodeId);
|
|
16685
16730
|
}
|
|
16686
16731
|
}
|
|
16687
16732
|
setSelfIdIfNull(selfId) {
|
|
16688
16733
|
if (!this._selfId$.value && selfId) {
|
|
16689
|
-
logger$
|
|
16734
|
+
logger$16.debug(`[WebRTCManager] Early selfId set: ${selfId}`);
|
|
16690
16735
|
this._selfId$.next(selfId);
|
|
16691
16736
|
}
|
|
16692
16737
|
}
|
|
@@ -16695,7 +16740,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16695
16740
|
const vertoPongMessage = VertoPong({ ...vertoPing });
|
|
16696
16741
|
await this.executeVerto(vertoPongMessage);
|
|
16697
16742
|
} catch (error) {
|
|
16698
|
-
logger$
|
|
16743
|
+
logger$16.warn("[WebRTCManager] Call might disconnect, error sending Verto pong:", error);
|
|
16699
16744
|
this.onError?.(new VertoPongError(error));
|
|
16700
16745
|
}
|
|
16701
16746
|
}
|
|
@@ -16705,7 +16750,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16705
16750
|
if (audio) await this.mainPeerConnection.updateSendersConstraints("audio", audio);
|
|
16706
16751
|
if (video) await this.mainPeerConnection.updateSendersConstraints("video", video);
|
|
16707
16752
|
} catch (error) {
|
|
16708
|
-
logger$
|
|
16753
|
+
logger$16.warn("[WebRTCManager] Error updating media constraints:", error);
|
|
16709
16754
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
16710
16755
|
throw error;
|
|
16711
16756
|
}
|
|
@@ -16735,20 +16780,20 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16735
16780
|
try {
|
|
16736
16781
|
const pc = this.mainPeerConnection.peerConnection;
|
|
16737
16782
|
if (!pc) {
|
|
16738
|
-
logger$
|
|
16783
|
+
logger$16.warn("[WebRTCManager] No peer connection for keyframe request");
|
|
16739
16784
|
return;
|
|
16740
16785
|
}
|
|
16741
16786
|
const videoReceiver = pc.getReceivers().find((r) => r.track.kind === "video");
|
|
16742
16787
|
if (!videoReceiver) {
|
|
16743
|
-
logger$
|
|
16788
|
+
logger$16.warn("[WebRTCManager] No video receiver for keyframe request");
|
|
16744
16789
|
return;
|
|
16745
16790
|
}
|
|
16746
16791
|
if (typeof videoReceiver.requestKeyFrame === "function") {
|
|
16747
16792
|
videoReceiver.requestKeyFrame();
|
|
16748
|
-
logger$
|
|
16749
|
-
} else logger$
|
|
16793
|
+
logger$16.debug("[WebRTCManager] Keyframe requested via RTCRtpReceiver.requestKeyFrame()");
|
|
16794
|
+
} else logger$16.debug("[WebRTCManager] requestKeyFrame() not supported, skipping");
|
|
16750
16795
|
} catch (error) {
|
|
16751
|
-
logger$
|
|
16796
|
+
logger$16.warn("[WebRTCManager] Keyframe request failed (non-fatal):", error);
|
|
16752
16797
|
}
|
|
16753
16798
|
}
|
|
16754
16799
|
/**
|
|
@@ -16766,13 +16811,13 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16766
16811
|
try {
|
|
16767
16812
|
const controller = this.mainPeerConnection;
|
|
16768
16813
|
if (!controller.peerConnection) {
|
|
16769
|
-
logger$
|
|
16814
|
+
logger$16.warn("[WebRTCManager] No peer connection for ICE restart");
|
|
16770
16815
|
return;
|
|
16771
16816
|
}
|
|
16772
16817
|
await controller.triggerIceRestart(relayOnly);
|
|
16773
|
-
logger$
|
|
16818
|
+
logger$16.info(`[WebRTCManager] ICE restart initiated${relayOnly ? " (relay-only)" : ""}`);
|
|
16774
16819
|
} catch (error) {
|
|
16775
|
-
logger$
|
|
16820
|
+
logger$16.error("[WebRTCManager] ICE restart failed:", error);
|
|
16776
16821
|
throw error;
|
|
16777
16822
|
}
|
|
16778
16823
|
}
|
|
@@ -16790,13 +16835,13 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16790
16835
|
const entries = Array.from(this._rtcPeerConnectionsMap.entries());
|
|
16791
16836
|
for (const [id, controller] of entries) try {
|
|
16792
16837
|
if (!controller.peerConnection) {
|
|
16793
|
-
logger$
|
|
16838
|
+
logger$16.debug(`[WebRTCManager] No peer connection for leg ${id}, skipping ICE restart`);
|
|
16794
16839
|
continue;
|
|
16795
16840
|
}
|
|
16796
16841
|
await controller.triggerIceRestart(relayOnly);
|
|
16797
|
-
logger$
|
|
16842
|
+
logger$16.info(`[WebRTCManager] ICE restart initiated for leg ${id}${relayOnly ? " (relay-only)" : ""}`);
|
|
16798
16843
|
} catch (error) {
|
|
16799
|
-
logger$
|
|
16844
|
+
logger$16.warn(`[WebRTCManager] ICE restart failed for leg ${id}:`, error);
|
|
16800
16845
|
}
|
|
16801
16846
|
}
|
|
16802
16847
|
/**
|
|
@@ -16808,7 +16853,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16808
16853
|
requestKeyframeAll() {
|
|
16809
16854
|
for (const [id, controller] of this._rtcPeerConnectionsMap) {
|
|
16810
16855
|
if (controller.isScreenShare) {
|
|
16811
|
-
logger$
|
|
16856
|
+
logger$16.debug(`[WebRTCManager] Skipping keyframe for send-only screen share leg ${id}`);
|
|
16812
16857
|
continue;
|
|
16813
16858
|
}
|
|
16814
16859
|
try {
|
|
@@ -16818,10 +16863,10 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16818
16863
|
if (!videoReceiver) continue;
|
|
16819
16864
|
if (typeof videoReceiver.requestKeyFrame === "function") {
|
|
16820
16865
|
videoReceiver.requestKeyFrame();
|
|
16821
|
-
logger$
|
|
16866
|
+
logger$16.debug(`[WebRTCManager] Keyframe requested for leg ${id}`);
|
|
16822
16867
|
}
|
|
16823
16868
|
} catch (error) {
|
|
16824
|
-
logger$
|
|
16869
|
+
logger$16.warn(`[WebRTCManager] Keyframe request failed for leg ${id} (non-fatal):`, error);
|
|
16825
16870
|
}
|
|
16826
16871
|
}
|
|
16827
16872
|
}
|
|
@@ -16838,7 +16883,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16838
16883
|
return this.cachedObservable("vertoMediaParams$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoMediaParamsInnerParams, "params"), (0, import_cjs$15.takeUntil)(this.destroyed$)));
|
|
16839
16884
|
}
|
|
16840
16885
|
get vertoBye$() {
|
|
16841
|
-
return this.cachedObservable("vertoBye$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(
|
|
16886
|
+
return this.cachedObservable("vertoBye$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoByeInboundMessage, "params"), (0, import_cjs$15.takeUntil)(this.destroyed$)));
|
|
16842
16887
|
}
|
|
16843
16888
|
get vertoAttach$() {
|
|
16844
16889
|
return this.cachedObservable("vertoAttach$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoAttachMessage, "params"), (0, import_cjs$15.takeUntil)(this.destroyed$)));
|
|
@@ -16882,7 +16927,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16882
16927
|
default:
|
|
16883
16928
|
}
|
|
16884
16929
|
} catch (error) {
|
|
16885
|
-
logger$
|
|
16930
|
+
logger$16.error(`[WebRTCManager] Error sending Verto ${vertoMethod}:`, error);
|
|
16886
16931
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
16887
16932
|
if (vertoMethod === "verto.modify") this.onModifyFailed?.();
|
|
16888
16933
|
}
|
|
@@ -16897,7 +16942,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16897
16942
|
sdp
|
|
16898
16943
|
});
|
|
16899
16944
|
} catch (error) {
|
|
16900
|
-
logger$
|
|
16945
|
+
logger$16.warn("[WebRTCManager] Error processing modify response:", error);
|
|
16901
16946
|
const modifyError = error instanceof Error ? error : new Error(String(error), { cause: error });
|
|
16902
16947
|
this.onError?.(modifyError);
|
|
16903
16948
|
}
|
|
@@ -16907,7 +16952,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16907
16952
|
const rtcPeerConnController = this._rtcPeerConnectionsMap.get(callId);
|
|
16908
16953
|
if (!rtcPeerConnController) {
|
|
16909
16954
|
const signalingError = new DependencyError(`Cannot emit signaling status, RTCPeerConnectionController not found for callID: ${callId}`);
|
|
16910
|
-
logger$
|
|
16955
|
+
logger$16.error("[WebRTCManager] Failed to emit signaling status:", {
|
|
16911
16956
|
callId,
|
|
16912
16957
|
status,
|
|
16913
16958
|
signalingError
|
|
@@ -16923,7 +16968,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16923
16968
|
this._nodeId$.next(getValueFrom(response, "result.node_id") ?? null);
|
|
16924
16969
|
const memberId = getValueFrom(response, "result.result.result.memberID") ?? null;
|
|
16925
16970
|
const callId = getValueFrom(response, "result.result.result.callID") ?? null;
|
|
16926
|
-
logger$
|
|
16971
|
+
logger$16.debug("[WebRTCManager] Verto invite response:", {
|
|
16927
16972
|
callId,
|
|
16928
16973
|
memberId,
|
|
16929
16974
|
response
|
|
@@ -16933,14 +16978,14 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16933
16978
|
if (callId) {
|
|
16934
16979
|
this.webRtcCallSession.addCallId(callId);
|
|
16935
16980
|
this.attachManager.attach(this.buildAttachableCall(callId));
|
|
16936
|
-
} else logger$
|
|
16981
|
+
} else logger$16.warn("[WebRTCManager] Cannot attach call, missing callId:", {
|
|
16937
16982
|
nodeId: this.nodeId,
|
|
16938
16983
|
callId
|
|
16939
16984
|
});
|
|
16940
|
-
logger$
|
|
16941
|
-
logger$
|
|
16985
|
+
logger$16.info("[WebRTCManager] Verto invite successful");
|
|
16986
|
+
logger$16.debug(`[WebRTCManager] nodeid: ${this._nodeId$.value}, selfId: ${this._selfId$.value}`);
|
|
16942
16987
|
} else {
|
|
16943
|
-
logger$
|
|
16988
|
+
logger$16.error("[WebRTCManager] Verto invite failed:", response);
|
|
16944
16989
|
const inviteError = response.error ? new JSONRPCError(response.error.code, response.error.message, response.error.data) : /* @__PURE__ */ new Error("Verto invite failed: unexpected response");
|
|
16945
16990
|
this.onError?.(inviteError);
|
|
16946
16991
|
}
|
|
@@ -16985,17 +17030,17 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16985
17030
|
if (options.initOffer) this.handleInboundAnswer(rtcPeerConnController);
|
|
16986
17031
|
}
|
|
16987
17032
|
async handleInboundAnswer(rtcPeerConnController) {
|
|
16988
|
-
logger$
|
|
17033
|
+
logger$16.debug("[WebRTCManager] Waiting for inbound call to be accepted or rejected");
|
|
16989
17034
|
const vertoByeOrAccepted = await (0, import_cjs$15.firstValueFrom)((0, import_cjs$15.race)(this.vertoBye$, this.webRtcCallSession.answered$).pipe((0, import_cjs$15.takeUntil)(this.destroyed$))).catch(() => null);
|
|
16990
17035
|
if (vertoByeOrAccepted === null) {
|
|
16991
|
-
logger$
|
|
17036
|
+
logger$16.debug("[WebRTCManager] Inbound answer handler aborted (destroyed).");
|
|
16992
17037
|
return;
|
|
16993
17038
|
}
|
|
16994
|
-
if (
|
|
16995
|
-
logger$
|
|
17039
|
+
if (isVertoByeInboundParamsGuard(vertoByeOrAccepted)) {
|
|
17040
|
+
logger$16.info("[WebRTCManager] Inbound call ended by remote before answer.");
|
|
16996
17041
|
this.callSession?.destroy();
|
|
16997
17042
|
} else if (!vertoByeOrAccepted) {
|
|
16998
|
-
logger$
|
|
17043
|
+
logger$16.info("[WebRTCManager] Inbound call rejected by user.");
|
|
16999
17044
|
try {
|
|
17000
17045
|
await this.bye("USER_BUSY");
|
|
17001
17046
|
} finally {
|
|
@@ -17003,19 +17048,19 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17003
17048
|
this.callSession?.destroy();
|
|
17004
17049
|
}
|
|
17005
17050
|
} else {
|
|
17006
|
-
logger$
|
|
17051
|
+
logger$16.debug("[WebRTCManager] Inbound call accepted, creating SDP answer");
|
|
17007
17052
|
const answerOptions = this.webRtcCallSession.answerMediaOptions;
|
|
17008
17053
|
try {
|
|
17009
17054
|
await rtcPeerConnController.acceptInbound(answerOptions);
|
|
17010
17055
|
} catch (error) {
|
|
17011
|
-
logger$
|
|
17056
|
+
logger$16.error("[WebRTCManager] Error creating inbound answer:", error);
|
|
17012
17057
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
17013
17058
|
}
|
|
17014
17059
|
}
|
|
17015
17060
|
}
|
|
17016
17061
|
setupVertoAttachHandler() {
|
|
17017
17062
|
this.subscribeTo(this.vertoAttach$, async (vertoAttach) => {
|
|
17018
|
-
logger$
|
|
17063
|
+
logger$16.debug("[WebRTCManager] Received Verto attach event for existing call:", vertoAttach);
|
|
17019
17064
|
const { callID } = vertoAttach;
|
|
17020
17065
|
await this.attachManager.attach({
|
|
17021
17066
|
nodeId: this.nodeId ?? void 0,
|
|
@@ -17087,17 +17132,17 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17087
17132
|
};
|
|
17088
17133
|
}
|
|
17089
17134
|
async sendLocalDescriptionOnceAccepted(vertoMessageRequest, rtcPeerConnectionController) {
|
|
17090
|
-
logger$
|
|
17135
|
+
logger$16.debug("[WebRTCManager] Waiting for call to be accepted or ended before sending answer");
|
|
17091
17136
|
const vertoByeOrAccepted = await (0, import_cjs$15.firstValueFrom)((0, import_cjs$15.race)(this.vertoBye$, this.webRtcCallSession.answered$).pipe((0, import_cjs$15.takeUntil)(this.destroyed$))).catch(() => null);
|
|
17092
17137
|
if (vertoByeOrAccepted === null) {
|
|
17093
|
-
logger$
|
|
17138
|
+
logger$16.debug("[WebRTCManager] Destroyed while waiting for call acceptance");
|
|
17094
17139
|
return;
|
|
17095
17140
|
}
|
|
17096
|
-
if (
|
|
17097
|
-
logger$
|
|
17141
|
+
if (isVertoByeInboundParamsGuard(vertoByeOrAccepted)) {
|
|
17142
|
+
logger$16.info("[WebRTCManager] Call ended before answer was sent.");
|
|
17098
17143
|
this.callSession?.destroy();
|
|
17099
17144
|
} else if (!vertoByeOrAccepted) {
|
|
17100
|
-
logger$
|
|
17145
|
+
logger$16.info("[WebRTCManager] Call was not accepted, sending verto.bye.");
|
|
17101
17146
|
try {
|
|
17102
17147
|
await this.bye("USER_BUSY");
|
|
17103
17148
|
} finally {
|
|
@@ -17105,14 +17150,14 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17105
17150
|
this.callSession?.destroy();
|
|
17106
17151
|
}
|
|
17107
17152
|
} else {
|
|
17108
|
-
logger$
|
|
17153
|
+
logger$16.debug("[WebRTCManager] Call accepted, sending answer");
|
|
17109
17154
|
try {
|
|
17110
17155
|
this.emitMainSignalingStatus(rtcPeerConnectionController.id, "connecting");
|
|
17111
17156
|
await this.sendLocalDescription(vertoMessageRequest, rtcPeerConnectionController);
|
|
17112
17157
|
await rtcPeerConnectionController.updateAnswerStatus({ status: "sent" });
|
|
17113
17158
|
await this.attachManager.attach(this.buildAttachableCall());
|
|
17114
17159
|
} catch (error) {
|
|
17115
|
-
logger$
|
|
17160
|
+
logger$16.error("[WebRTCManager] Error sending Verto answer:", error);
|
|
17116
17161
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
17117
17162
|
await rtcPeerConnectionController.updateAnswerStatus({ status: "failed" });
|
|
17118
17163
|
}
|
|
@@ -17211,10 +17256,10 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17211
17256
|
});
|
|
17212
17257
|
await (0, import_cjs$15.firstValueFrom)(rtcPeerConnController.connectionState$.pipe((0, import_cjs$15.filter)((state) => state === "connected"), (0, import_cjs$15.take)(1), (0, import_cjs$15.timeout)(this._screenShareTimeoutMs), (0, import_cjs$15.takeUntil)(this.destroyed$)));
|
|
17213
17258
|
this._screenShareStatus$.next("started");
|
|
17214
|
-
logger$
|
|
17259
|
+
logger$16.info("[WebRTCManager] Screen share started successfully.");
|
|
17215
17260
|
return rtcPeerConnController.id;
|
|
17216
17261
|
} catch (error) {
|
|
17217
|
-
logger$
|
|
17262
|
+
logger$16.warn("[WebRTCManager] Error initializing additional peer connection:", error);
|
|
17218
17263
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
17219
17264
|
if (rtcPeerConnController) rtcPeerConnController.destroy();
|
|
17220
17265
|
this._screenShareStatus$.next("none");
|
|
@@ -17233,9 +17278,9 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17233
17278
|
if (removeTrack) return this.mainPeerConnection.stopTrackSender(removeTrack, { updateTransceiverDirection: true });
|
|
17234
17279
|
}
|
|
17235
17280
|
async removeScreenMedia() {
|
|
17236
|
-
if (!["starting", "started"].includes(this._screenShareStatus$.value)) logger$
|
|
17281
|
+
if (!["starting", "started"].includes(this._screenShareStatus$.value)) logger$16.warn("[WebRTCManager] No active screen share to stop.");
|
|
17237
17282
|
if (!this._screenShareId) {
|
|
17238
|
-
logger$
|
|
17283
|
+
logger$16.debug("[WebRTCManager] No screen share peer connection found.");
|
|
17239
17284
|
return;
|
|
17240
17285
|
}
|
|
17241
17286
|
this._screenShareStatus$.next("stopping");
|
|
@@ -17264,7 +17309,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17264
17309
|
dialogParams: this.dialogParams(rtcPeerConnController)
|
|
17265
17310
|
}));
|
|
17266
17311
|
} catch (error) {
|
|
17267
|
-
logger$
|
|
17312
|
+
logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto bye:", error);
|
|
17268
17313
|
throw error;
|
|
17269
17314
|
}
|
|
17270
17315
|
}
|
|
@@ -17282,7 +17327,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17282
17327
|
try {
|
|
17283
17328
|
await this.executeVerto(vertoInfoMessage);
|
|
17284
17329
|
} catch (error) {
|
|
17285
|
-
logger$
|
|
17330
|
+
logger$16.warn("[WebRTCManager] Error sending DTMF digits:", error);
|
|
17286
17331
|
throw error;
|
|
17287
17332
|
}
|
|
17288
17333
|
}
|
|
@@ -17293,10 +17338,10 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17293
17338
|
action: "transfer"
|
|
17294
17339
|
});
|
|
17295
17340
|
try {
|
|
17296
|
-
logger$
|
|
17341
|
+
logger$16.debug("[WebRTCManager] Transferring call with options:", options);
|
|
17297
17342
|
await this.executeVerto(message);
|
|
17298
17343
|
} catch (error) {
|
|
17299
|
-
logger$
|
|
17344
|
+
logger$16.error("[WebRTCManager] Error transferring call:", error);
|
|
17300
17345
|
throw error;
|
|
17301
17346
|
}
|
|
17302
17347
|
}
|
|
@@ -17313,7 +17358,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17313
17358
|
//#endregion
|
|
17314
17359
|
//#region src/controllers/RemoteAudioMeter.ts
|
|
17315
17360
|
var import_cjs$14 = require_cjs();
|
|
17316
|
-
const logger$
|
|
17361
|
+
const logger$15 = getLogger();
|
|
17317
17362
|
/**
|
|
17318
17363
|
* Read-only audio level meter for a remote MediaStream. Attaches an
|
|
17319
17364
|
* AnalyserNode to a MediaStreamAudioSourceNode so it observes the stream
|
|
@@ -17348,7 +17393,7 @@ var RemoteAudioMeter = class extends Destroyable {
|
|
|
17348
17393
|
try {
|
|
17349
17394
|
this._source.disconnect();
|
|
17350
17395
|
} catch (error) {
|
|
17351
|
-
logger$
|
|
17396
|
+
logger$15.debug("[RemoteAudioMeter] source disconnect warning:", error);
|
|
17352
17397
|
}
|
|
17353
17398
|
this._source = null;
|
|
17354
17399
|
this._stream = null;
|
|
@@ -17365,7 +17410,7 @@ var RemoteAudioMeter = class extends Destroyable {
|
|
|
17365
17410
|
this._source = null;
|
|
17366
17411
|
}
|
|
17367
17412
|
this._audioContext.close().catch((error) => {
|
|
17368
|
-
logger$
|
|
17413
|
+
logger$15.debug("[RemoteAudioMeter] audio context close warning:", error);
|
|
17369
17414
|
});
|
|
17370
17415
|
super.destroy();
|
|
17371
17416
|
}
|
|
@@ -17384,7 +17429,7 @@ var RemoteAudioMeter = class extends Destroyable {
|
|
|
17384
17429
|
//#endregion
|
|
17385
17430
|
//#region src/controllers/RTCStatsMonitor.ts
|
|
17386
17431
|
var import_cjs$13 = require_cjs();
|
|
17387
|
-
const logger$
|
|
17432
|
+
const logger$14 = getLogger();
|
|
17388
17433
|
const DEFAULT_POLLING_INTERVAL_MS = 1e3;
|
|
17389
17434
|
const DEFAULT_BASELINE_SAMPLES = 10;
|
|
17390
17435
|
const DEFAULT_NO_AUDIO_PACKET_THRESHOLD_MS = 2e3;
|
|
@@ -17474,9 +17519,9 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
17474
17519
|
const now = Date.now();
|
|
17475
17520
|
this.lastAudioPacketChangeTime = now;
|
|
17476
17521
|
this.lastVideoPacketChangeTime = now;
|
|
17477
|
-
logger$
|
|
17522
|
+
logger$14.debug("[RTCStatsMonitor] Starting stats monitoring");
|
|
17478
17523
|
this.subscribeTo((0, import_cjs$13.interval)(this.pollingIntervalMs).pipe((0, import_cjs$13.filter)(() => this.running), (0, import_cjs$13.switchMap)(() => (0, import_cjs$13.from)(this.peerConnection.getStats()).pipe((0, import_cjs$13.catchError)((err) => {
|
|
17479
|
-
logger$
|
|
17524
|
+
logger$14.warn("[RTCStatsMonitor] Failed to get stats:", err);
|
|
17480
17525
|
return import_cjs$13.EMPTY;
|
|
17481
17526
|
}))), (0, import_cjs$13.filter)(() => this.running), (0, import_cjs$13.map)((report) => this.extractSample(report))), (sample$1) => this._sample$.next(sample$1));
|
|
17482
17527
|
this.subscribeTo(this._sample$.pipe((0, import_cjs$13.take)(this.baselineSampleCount), (0, import_cjs$13.toArray)(), (0, import_cjs$13.map)((samples) => ({
|
|
@@ -17484,7 +17529,7 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
17484
17529
|
jitter: samples.reduce((s, b) => s + b.audioJitter, 0) / samples.length,
|
|
17485
17530
|
ready: true
|
|
17486
17531
|
}))), (baseline) => {
|
|
17487
|
-
logger$
|
|
17532
|
+
logger$14.debug(`[RTCStatsMonitor] Baseline established: rtt=${baseline.rtt.toFixed(1)}ms, jitter=${baseline.jitter.toFixed(1)}ms`);
|
|
17488
17533
|
this._baseline$.next(baseline);
|
|
17489
17534
|
});
|
|
17490
17535
|
this.subscribeTo(this._sample$.pipe((0, import_cjs$13.scan)((acc, sample$1) => ({
|
|
@@ -17521,10 +17566,10 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
17521
17566
|
stop() {
|
|
17522
17567
|
if (!this.running) return;
|
|
17523
17568
|
this.running = false;
|
|
17524
|
-
logger$
|
|
17569
|
+
logger$14.debug("[RTCStatsMonitor] Stopping stats monitoring");
|
|
17525
17570
|
}
|
|
17526
17571
|
destroy() {
|
|
17527
|
-
logger$
|
|
17572
|
+
logger$14.debug("[RTCStatsMonitor] Destroying RTCStatsMonitor");
|
|
17528
17573
|
this.stop();
|
|
17529
17574
|
super.destroy();
|
|
17530
17575
|
}
|
|
@@ -17653,7 +17698,7 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
17653
17698
|
//#endregion
|
|
17654
17699
|
//#region src/managers/CallRecoveryManager.ts
|
|
17655
17700
|
var import_cjs$12 = require_cjs();
|
|
17656
|
-
const logger$
|
|
17701
|
+
const logger$13 = getLogger();
|
|
17657
17702
|
const DEFAULT_DEBOUNCE_TIME_MS = 2e3;
|
|
17658
17703
|
const DEFAULT_COOLDOWN_MS = 1e4;
|
|
17659
17704
|
const DEFAULT_ICE_GRACE_PERIOD_MS = 3e3;
|
|
@@ -17750,10 +17795,10 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17750
17795
|
*/
|
|
17751
17796
|
async requestIceRestart() {
|
|
17752
17797
|
if (this._recoveryState$.value === "recovering") {
|
|
17753
|
-
logger$
|
|
17798
|
+
logger$13.info("CallRecoveryManager: manual ICE restart skipped — recovery already in progress");
|
|
17754
17799
|
return;
|
|
17755
17800
|
}
|
|
17756
|
-
logger$
|
|
17801
|
+
logger$13.info("CallRecoveryManager: manual ICE restart requested");
|
|
17757
17802
|
this.transitionTo("recovering");
|
|
17758
17803
|
await this.executeIceRestart(false);
|
|
17759
17804
|
this.startCooldown();
|
|
@@ -17769,7 +17814,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17769
17814
|
* WebSocket reconnect or call state recovers to 'connected'.
|
|
17770
17815
|
*/
|
|
17771
17816
|
reset() {
|
|
17772
|
-
logger$
|
|
17817
|
+
logger$13.info("CallRecoveryManager: resetting counters");
|
|
17773
17818
|
this._attemptCount = 0;
|
|
17774
17819
|
this._keyframeBurstCount = 0;
|
|
17775
17820
|
this._keyframeBurstStart = 0;
|
|
@@ -17784,7 +17829,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17784
17829
|
*/
|
|
17785
17830
|
notifyModifyFailed() {
|
|
17786
17831
|
if (this._recoveryState$.value === "cooldown" || this._recoveryState$.value === "idle") {
|
|
17787
|
-
logger$
|
|
17832
|
+
logger$13.info("CallRecoveryManager: verto.modify failed — re-entering recovery");
|
|
17788
17833
|
this._cooldownUntil = 0;
|
|
17789
17834
|
this.transitionTo("idle");
|
|
17790
17835
|
this.pushTrigger({
|
|
@@ -17808,7 +17853,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17808
17853
|
reason: `bandwidth ${bitrateKbps}kbps below threshold ${this._config.degradationBitrateThreshold}kbps`,
|
|
17809
17854
|
timestamp: Date.now()
|
|
17810
17855
|
});
|
|
17811
|
-
logger$
|
|
17856
|
+
logger$13.warn(`CallRecoveryManager: disabling video — bandwidth ${bitrateKbps}kbps < ${this._config.degradationBitrateThreshold}kbps`);
|
|
17812
17857
|
} else if (wasConstrained && bitrateKbps >= this._config.degradationRecoveryThreshold) {
|
|
17813
17858
|
this._bandwidthConstrained$.next(false);
|
|
17814
17859
|
this._callbacks.enableVideo();
|
|
@@ -17817,7 +17862,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17817
17862
|
reason: `bandwidth ${bitrateKbps}kbps recovered above ${this._config.degradationRecoveryThreshold}kbps`,
|
|
17818
17863
|
timestamp: Date.now()
|
|
17819
17864
|
});
|
|
17820
|
-
logger$
|
|
17865
|
+
logger$13.info(`CallRecoveryManager: restoring video — bandwidth ${bitrateKbps}kbps >= ${this._config.degradationRecoveryThreshold}kbps`);
|
|
17821
17866
|
}
|
|
17822
17867
|
}
|
|
17823
17868
|
/**
|
|
@@ -17836,14 +17881,14 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17836
17881
|
handleWebSocketReconnect() {
|
|
17837
17882
|
const pcState = this._callbacks.getPeerConnectionState();
|
|
17838
17883
|
if (pcState === "connected" || pcState === "completed") {
|
|
17839
|
-
logger$
|
|
17884
|
+
logger$13.info("CallRecoveryManager: signal-only reconnect — peer connection still alive");
|
|
17840
17885
|
this.emitEvent({
|
|
17841
17886
|
action: "signal_reconnect",
|
|
17842
17887
|
reason: "WebSocket reconnected, peer connection still connected",
|
|
17843
17888
|
timestamp: Date.now()
|
|
17844
17889
|
});
|
|
17845
17890
|
} else {
|
|
17846
|
-
logger$
|
|
17891
|
+
logger$13.info("CallRecoveryManager: full reconnect — peer connection also down");
|
|
17847
17892
|
this.emitEvent({
|
|
17848
17893
|
action: "full_reconnect",
|
|
17849
17894
|
reason: "WebSocket reconnected, peer connection not connected — ICE restart needed",
|
|
@@ -17867,7 +17912,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17867
17912
|
}), (0, import_cjs$12.debounceTime)(this._config.debounceTimeMs), (0, import_cjs$12.withLatestFrom)(this._inputs.signalingReady$), (0, import_cjs$12.filter)(([, signalingReady]) => this.passGateChecks(signalingReady)), (0, import_cjs$12.map)(([trigger]) => trigger), (0, import_cjs$12.exhaustMap)((trigger) => this.executeTieredRecovery(trigger)), (0, import_cjs$12.takeUntil)((0, import_cjs$12.merge)(this._destroyed$, this._pipelineStop$))), {
|
|
17868
17913
|
next: () => {},
|
|
17869
17914
|
error: (err) => {
|
|
17870
|
-
logger$
|
|
17915
|
+
logger$13.error("CallRecoveryManager: pipeline error", err);
|
|
17871
17916
|
this.transitionTo("idle");
|
|
17872
17917
|
}
|
|
17873
17918
|
});
|
|
@@ -17894,27 +17939,27 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17894
17939
|
reason: `no packet loss for ${this._config.packetLossRecoveryDelaySec}s — restoring video`,
|
|
17895
17940
|
timestamp: Date.now()
|
|
17896
17941
|
});
|
|
17897
|
-
logger$
|
|
17942
|
+
logger$13.info(`CallRecoveryManager: restoring video — no packet loss for ${this._config.packetLossRecoveryDelaySec}s`);
|
|
17898
17943
|
});
|
|
17899
17944
|
}
|
|
17900
17945
|
passGateChecks(signalingReady) {
|
|
17901
17946
|
if (this._callbacks.isNegotiating()) {
|
|
17902
|
-
logger$
|
|
17947
|
+
logger$13.debug("CallRecoveryManager: gate blocked — negotiation in progress");
|
|
17903
17948
|
this.transitionTo("idle");
|
|
17904
17949
|
return false;
|
|
17905
17950
|
}
|
|
17906
17951
|
if (!signalingReady) {
|
|
17907
|
-
logger$
|
|
17952
|
+
logger$13.debug("CallRecoveryManager: gate blocked — signaling not ready");
|
|
17908
17953
|
this.transitionTo("idle");
|
|
17909
17954
|
return false;
|
|
17910
17955
|
}
|
|
17911
17956
|
if (!this._callbacks.isCallConnected()) {
|
|
17912
|
-
logger$
|
|
17957
|
+
logger$13.debug("CallRecoveryManager: gate blocked — call not connected");
|
|
17913
17958
|
this.transitionTo("idle");
|
|
17914
17959
|
return false;
|
|
17915
17960
|
}
|
|
17916
17961
|
if (this.isCooldownActive()) {
|
|
17917
|
-
logger$
|
|
17962
|
+
logger$13.debug("CallRecoveryManager: gate blocked — cooldown active");
|
|
17918
17963
|
this.transitionTo("cooldown");
|
|
17919
17964
|
return false;
|
|
17920
17965
|
}
|
|
@@ -17925,9 +17970,9 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17925
17970
|
}
|
|
17926
17971
|
executeTieredRecovery(trigger) {
|
|
17927
17972
|
this.transitionTo("recovering");
|
|
17928
|
-
logger$
|
|
17973
|
+
logger$13.info(`CallRecoveryManager: starting tiered recovery — source=${trigger.source} detail=${trigger.detail}`);
|
|
17929
17974
|
return (0, import_cjs$12.from)(this.runTiers(trigger)).pipe((0, import_cjs$12.tap)(() => this.startCooldown()), (0, import_cjs$12.catchError)((err) => {
|
|
17930
|
-
logger$
|
|
17975
|
+
logger$13.error("CallRecoveryManager: tiered recovery failed", err);
|
|
17931
17976
|
this.startCooldown();
|
|
17932
17977
|
return import_cjs$12.EMPTY;
|
|
17933
17978
|
}));
|
|
@@ -17935,7 +17980,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17935
17980
|
async runTiers(trigger) {
|
|
17936
17981
|
this.executeKeyframe(trigger.detail);
|
|
17937
17982
|
if (trigger.issueType && DEGRADATION_ONLY_ISSUES.has(trigger.issueType)) {
|
|
17938
|
-
logger$
|
|
17983
|
+
logger$13.debug(`CallRecoveryManager: degradation-only issue (${trigger.issueType}) — Tier 1 only, skipping ICE restart`);
|
|
17939
17984
|
return;
|
|
17940
17985
|
}
|
|
17941
17986
|
if (this._attemptCount < this._config.maxAttempts) {
|
|
@@ -17952,13 +17997,13 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17952
17997
|
maxAttempts: this._config.maxAttempts,
|
|
17953
17998
|
timestamp: Date.now()
|
|
17954
17999
|
});
|
|
17955
|
-
logger$
|
|
18000
|
+
logger$13.warn("CallRecoveryManager: max recovery attempts reached");
|
|
17956
18001
|
}
|
|
17957
18002
|
}
|
|
17958
18003
|
executeKeyframe(reason) {
|
|
17959
18004
|
const now = Date.now();
|
|
17960
18005
|
if (now < this._keyframeCooldownUntil) {
|
|
17961
|
-
logger$
|
|
18006
|
+
logger$13.debug("CallRecoveryManager: keyframe request skipped — cooldown active");
|
|
17962
18007
|
return;
|
|
17963
18008
|
}
|
|
17964
18009
|
if (now - this._keyframeBurstStart > this._config.keyframeBurstWindowMs) {
|
|
@@ -17967,7 +18012,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17967
18012
|
}
|
|
17968
18013
|
if (this._keyframeBurstCount >= this._config.keyframeMaxBurst) {
|
|
17969
18014
|
this._keyframeCooldownUntil = now + this._config.keyframeCooldownMs;
|
|
17970
|
-
logger$
|
|
18015
|
+
logger$13.debug(`CallRecoveryManager: keyframe burst limit reached (${this._config.keyframeMaxBurst}), cooldown until ${this._keyframeCooldownUntil}`);
|
|
17971
18016
|
return;
|
|
17972
18017
|
}
|
|
17973
18018
|
this._keyframeBurstCount += 1;
|
|
@@ -17977,12 +18022,12 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17977
18022
|
reason,
|
|
17978
18023
|
timestamp: now
|
|
17979
18024
|
});
|
|
17980
|
-
logger$
|
|
18025
|
+
logger$13.debug(`CallRecoveryManager: keyframe requested (burst ${this._keyframeBurstCount}/${this._config.keyframeMaxBurst})`);
|
|
17981
18026
|
}
|
|
17982
18027
|
async executeIceRestart(relayOnly) {
|
|
17983
18028
|
this._attemptCount += 1;
|
|
17984
18029
|
const tier = relayOnly ? "Tier 3 (relay-only)" : "Tier 2 (standard)";
|
|
17985
|
-
logger$
|
|
18030
|
+
logger$13.info(`CallRecoveryManager: ${tier} ICE restart — attempt ${this._attemptCount}/${this._config.maxAttempts}`);
|
|
17986
18031
|
this.emitEvent({
|
|
17987
18032
|
action: "reinvite_started",
|
|
17988
18033
|
reason: `${tier} ICE restart`,
|
|
@@ -17999,7 +18044,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17999
18044
|
maxAttempts: this._config.maxAttempts,
|
|
18000
18045
|
timestamp: Date.now()
|
|
18001
18046
|
});
|
|
18002
|
-
logger$
|
|
18047
|
+
logger$13.info(`CallRecoveryManager: ${tier} ICE restart succeeded`);
|
|
18003
18048
|
this._attemptCount = 0;
|
|
18004
18049
|
return true;
|
|
18005
18050
|
}
|
|
@@ -18010,7 +18055,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
18010
18055
|
maxAttempts: this._config.maxAttempts,
|
|
18011
18056
|
timestamp: Date.now()
|
|
18012
18057
|
});
|
|
18013
|
-
logger$
|
|
18058
|
+
logger$13.warn(`CallRecoveryManager: ${tier} ICE restart failed`);
|
|
18014
18059
|
return false;
|
|
18015
18060
|
} catch {
|
|
18016
18061
|
this.emitEvent({
|
|
@@ -18020,7 +18065,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
18020
18065
|
maxAttempts: this._config.maxAttempts,
|
|
18021
18066
|
timestamp: Date.now()
|
|
18022
18067
|
});
|
|
18023
|
-
logger$
|
|
18068
|
+
logger$13.warn(`CallRecoveryManager: ${tier} ICE restart timed out`);
|
|
18024
18069
|
return false;
|
|
18025
18070
|
}
|
|
18026
18071
|
}
|
|
@@ -18041,7 +18086,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
18041
18086
|
transitionTo(state) {
|
|
18042
18087
|
const prev = this._recoveryState$.value;
|
|
18043
18088
|
if (prev !== state) {
|
|
18044
|
-
logger$
|
|
18089
|
+
logger$13.debug(`CallRecoveryManager: state ${prev} -> ${state}`);
|
|
18045
18090
|
this._recoveryState$.next(state);
|
|
18046
18091
|
}
|
|
18047
18092
|
}
|
|
@@ -18144,7 +18189,13 @@ function mosToQualityLevel(mos) {
|
|
|
18144
18189
|
//#endregion
|
|
18145
18190
|
//#region src/core/entities/Call.ts
|
|
18146
18191
|
var import_cjs$11 = require_cjs();
|
|
18147
|
-
const logger$
|
|
18192
|
+
const logger$12 = getLogger();
|
|
18193
|
+
/**
|
|
18194
|
+
* Verto method for setting member layout positions. Its gateway DTO requires a
|
|
18195
|
+
* `targets` array whose entries are `{ target, position }` (NOT bare targets),
|
|
18196
|
+
* so {@link WebRTCCall.buildMethodParams} special-cases it. See issue #19400.
|
|
18197
|
+
*/
|
|
18198
|
+
const POSITION_SET_METHOD = "call.member.position.set";
|
|
18148
18199
|
/**
|
|
18149
18200
|
* Ratio between the critical and warning RTT spike multipliers.
|
|
18150
18201
|
* Warning threshold = baseline * warningMultiplier (default 3x)
|
|
@@ -18162,7 +18213,7 @@ const fromDestinationParams = (destination) => {
|
|
|
18162
18213
|
});
|
|
18163
18214
|
return params;
|
|
18164
18215
|
} catch (error) {
|
|
18165
|
-
logger$
|
|
18216
|
+
logger$12.warn(`Failed to parse destination URI: ${destination}`, error);
|
|
18166
18217
|
return {};
|
|
18167
18218
|
}
|
|
18168
18219
|
};
|
|
@@ -18352,7 +18403,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18352
18403
|
* @throws {JSONRPCError} If the RPC call returns an error.
|
|
18353
18404
|
*/
|
|
18354
18405
|
async executeMethod(target, method, args) {
|
|
18355
|
-
const params = this.buildMethodParams(target, args);
|
|
18406
|
+
const params = this.buildMethodParams(target, args, method);
|
|
18356
18407
|
const request = buildRPCRequest({
|
|
18357
18408
|
method,
|
|
18358
18409
|
params
|
|
@@ -18362,16 +18413,20 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18362
18413
|
if (isJSONRPCErrorResponse(response)) throw new JSONRPCError(parseInt(response.result?.code ?? "0"), `Error response from method ${method}: ${response.result?.code} ${response.result?.message}`, void 0, void 0, request.id);
|
|
18363
18414
|
return response;
|
|
18364
18415
|
} catch (error) {
|
|
18365
|
-
logger$
|
|
18416
|
+
logger$12.error(`[Call] Error executing method ${method} with params`, params, error);
|
|
18366
18417
|
throw error;
|
|
18367
18418
|
}
|
|
18368
18419
|
}
|
|
18369
|
-
buildMethodParams(target, args) {
|
|
18420
|
+
buildMethodParams(target, args, method) {
|
|
18370
18421
|
const self = {
|
|
18371
18422
|
node_id: this.nodeId ?? "",
|
|
18372
18423
|
call_id: this.id,
|
|
18373
18424
|
member_id: this.vertoManager.selfId ?? ""
|
|
18374
18425
|
};
|
|
18426
|
+
if (method === POSITION_SET_METHOD) return {
|
|
18427
|
+
...args,
|
|
18428
|
+
self
|
|
18429
|
+
};
|
|
18375
18430
|
if (typeof target === "object") return {
|
|
18376
18431
|
...args,
|
|
18377
18432
|
self,
|
|
@@ -18565,9 +18620,9 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18565
18620
|
*/
|
|
18566
18621
|
initResilienceSubsystems() {
|
|
18567
18622
|
const pc = this.rtcPeerConnection;
|
|
18568
|
-
logger$
|
|
18623
|
+
logger$12.debug(`[Call] initResilienceSubsystems: pc=${pc ? "exists" : "undefined"}, connectionState=${pc?.connectionState}`);
|
|
18569
18624
|
if (!pc) {
|
|
18570
|
-
logger$
|
|
18625
|
+
logger$12.warn("[Call] No peer connection available, skipping resilience init");
|
|
18571
18626
|
return;
|
|
18572
18627
|
}
|
|
18573
18628
|
try {
|
|
@@ -18602,14 +18657,14 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18602
18657
|
disableVideo: () => {
|
|
18603
18658
|
try {
|
|
18604
18659
|
this.vertoManager.muteMainVideoInputDevice();
|
|
18605
|
-
logger$
|
|
18660
|
+
logger$12.debug("[Call] Recovery manager disabled video");
|
|
18606
18661
|
} catch {
|
|
18607
|
-
logger$
|
|
18662
|
+
logger$12.debug("[Call] Recovery manager failed to disable video");
|
|
18608
18663
|
}
|
|
18609
18664
|
},
|
|
18610
18665
|
enableVideo: () => {
|
|
18611
18666
|
this.vertoManager.unmuteMainVideoInputDevice().catch(() => {
|
|
18612
|
-
logger$
|
|
18667
|
+
logger$12.debug("[Call] Recovery manager failed to enable video");
|
|
18613
18668
|
});
|
|
18614
18669
|
},
|
|
18615
18670
|
isNegotiating: () => this.vertoManager.mainPeerConnection.isNegotiating,
|
|
@@ -18659,7 +18714,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18659
18714
|
this.subscribeTo(this._recoveryManager.recoveryEvent$, (event) => {
|
|
18660
18715
|
this._recoveryEvent$.next(event);
|
|
18661
18716
|
if (event.action === "max_attempts_reached") {
|
|
18662
|
-
logger$
|
|
18717
|
+
logger$12.warn("[Call] All recovery attempts exhausted, terminating call");
|
|
18663
18718
|
this.emitError({
|
|
18664
18719
|
kind: "network",
|
|
18665
18720
|
fatal: true,
|
|
@@ -18679,13 +18734,13 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18679
18734
|
else if (event.type === "online") this._recoveryManager?.handleWebSocketReconnect();
|
|
18680
18735
|
});
|
|
18681
18736
|
this.subscribeTo(this.clientSession.authenticated$.pipe((0, import_cjs$11.skip)(1), (0, import_cjs$11.filter)(Boolean)), () => {
|
|
18682
|
-
logger$
|
|
18737
|
+
logger$12.debug("[Call] WebSocket reconnected — notifying recovery manager");
|
|
18683
18738
|
this._recoveryManager?.handleWebSocketReconnect();
|
|
18684
18739
|
});
|
|
18685
18740
|
this._statsMonitor.start();
|
|
18686
|
-
logger$
|
|
18741
|
+
logger$12.debug("[Call] Resilience subsystems initialized for call", this.id);
|
|
18687
18742
|
} catch (error) {
|
|
18688
|
-
logger$
|
|
18743
|
+
logger$12.warn("[Call] Failed to initialize resilience subsystems:", error);
|
|
18689
18744
|
}
|
|
18690
18745
|
}
|
|
18691
18746
|
/**
|
|
@@ -18768,19 +18823,19 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18768
18823
|
}
|
|
18769
18824
|
isCallSessionEvent(event) {
|
|
18770
18825
|
try {
|
|
18771
|
-
logger$
|
|
18826
|
+
logger$12.debug("[Call] Checking if event is for this call session:", event);
|
|
18772
18827
|
const callId = getValueFrom(event, "params.params.callID") ?? getValueFrom(event, "params.call_id");
|
|
18773
18828
|
const roomSessionId = getValueFrom(event, "params.room_session_id");
|
|
18774
|
-
logger$
|
|
18829
|
+
logger$12.debug(`[Call] Extracted session identifiers callID: ${callId} and roomSessionID: ${roomSessionId} from event:`);
|
|
18775
18830
|
return callId === this.id || !!callId && this.callEventsManager.isCallIdValid(callId) || !!roomSessionId && this.callEventsManager.isRoomSessionIdValid(roomSessionId);
|
|
18776
18831
|
} catch (error) {
|
|
18777
|
-
logger$
|
|
18832
|
+
logger$12.error("[Call] Error checking if event is for this call session:", error);
|
|
18778
18833
|
return false;
|
|
18779
18834
|
}
|
|
18780
18835
|
}
|
|
18781
18836
|
get callSessionEvents$() {
|
|
18782
18837
|
return this.cachedObservable("callSessionEvents$", () => this.clientSession.signalingEvent$.pipe((0, import_cjs$11.filter)((event) => this.isCallSessionEvent(event)), (0, import_cjs$11.tap)((event) => {
|
|
18783
|
-
logger$
|
|
18838
|
+
logger$12.debug("[Call] Received call session event:", event);
|
|
18784
18839
|
}), (0, import_cjs$11.takeUntil)(this.destroyed$), (0, import_cjs$11.share)()));
|
|
18785
18840
|
}
|
|
18786
18841
|
/** Observable of call-updated events. */
|
|
@@ -18850,16 +18905,16 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18850
18905
|
this._customSubscriptions.set(eventType, filtered$);
|
|
18851
18906
|
}, (error) => {
|
|
18852
18907
|
this._customSubscriptions.delete(eventType);
|
|
18853
|
-
logger$
|
|
18908
|
+
logger$12.warn(`[Call] verto.subscribe for '${eventType}' failed, not caching:`, error);
|
|
18854
18909
|
});
|
|
18855
18910
|
this._customSubscriptions.set(eventType, filtered$);
|
|
18856
18911
|
return filtered$;
|
|
18857
18912
|
}
|
|
18858
18913
|
get webrtcMessages$() {
|
|
18859
|
-
return this.cachedObservable("webrtcMessages$", () => this.callSessionEvents$.pipe(filterAs(isWebrtcMessageMetadata, "params"), (0, import_cjs$11.tap)((event) => logger$
|
|
18914
|
+
return this.cachedObservable("webrtcMessages$", () => this.callSessionEvents$.pipe(filterAs(isWebrtcMessageMetadata, "params"), (0, import_cjs$11.tap)((event) => logger$12.debug("[Call] Event is a WebRTC message event:", event)), (0, import_cjs$11.takeUntil)(this.destroyed$), (0, import_cjs$11.share)()));
|
|
18860
18915
|
}
|
|
18861
18916
|
get callEvent$() {
|
|
18862
|
-
return this.cachedObservable("callEvent$", () => this.callSessionEvents$.pipe(filterAs(isSignalwireCallMetadata, "params"), (0, import_cjs$11.tap)((event) => logger$
|
|
18917
|
+
return this.cachedObservable("callEvent$", () => this.callSessionEvents$.pipe(filterAs(isSignalwireCallMetadata, "params"), (0, import_cjs$11.tap)((event) => logger$12.debug("[Call] Event is a call event:", event)), (0, import_cjs$11.takeUntil)(this.destroyed$), (0, import_cjs$11.share)()));
|
|
18863
18918
|
}
|
|
18864
18919
|
get layoutEvent$() {
|
|
18865
18920
|
return this.cachedObservable("layoutEvent$", () => this.callEvent$.pipe(filterAs(isLayoutChangedMetadata, "params")));
|
|
@@ -18937,10 +18992,21 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18937
18992
|
return this.deferEmission(this._answered$.asObservable());
|
|
18938
18993
|
}
|
|
18939
18994
|
/**
|
|
18940
|
-
* Sets the call layout and participant positions.
|
|
18995
|
+
* Sets the call layout and, optionally, individual participant positions.
|
|
18996
|
+
*
|
|
18997
|
+
* The gateway `call.layout.set` DTO has **no** `positions` member, so when
|
|
18998
|
+
* `positions` is provided this method issues a `call.member.position.set`
|
|
18999
|
+
* request per member (via {@link Participant.setPosition}, which keys each
|
|
19000
|
+
* position by that member's own call context) alongside `call.layout.set`
|
|
19001
|
+
* (issue #19400, Flag #6).
|
|
19002
|
+
*
|
|
19003
|
+
* **These operations are NOT atomic.** The layout is applied first, then each
|
|
19004
|
+
* member position sequentially, so members may briefly flash into their
|
|
19005
|
+
* default slots before being moved to the requested positions.
|
|
18941
19006
|
*
|
|
18942
19007
|
* @param layout - Layout name (must be one of {@link layouts}).
|
|
18943
|
-
* @param positions -
|
|
19008
|
+
* @param positions - Optional map of member IDs to {@link VideoPosition} values.
|
|
19009
|
+
* When omitted or empty, only the layout is changed.
|
|
18944
19010
|
* @throws {InvalidParams} If the layout is not in the available {@link layouts}.
|
|
18945
19011
|
*
|
|
18946
19012
|
* @example
|
|
@@ -18953,10 +19019,17 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18953
19019
|
async setLayout(layout, positions) {
|
|
18954
19020
|
if (!this.layouts.includes(layout)) throw new InvalidParams(`Layout ${layout} is not available in the current call layouts: ${this.layouts.join(", ")}`);
|
|
18955
19021
|
const selfId = await (0, import_cjs$11.firstValueFrom)(this.selfId$.pipe((0, import_cjs$11.filter)((id) => id !== null)));
|
|
18956
|
-
await this.executeMethod(selfId, "call.layout.set", {
|
|
18957
|
-
|
|
18958
|
-
|
|
18959
|
-
|
|
19022
|
+
await this.executeMethod(selfId, "call.layout.set", { layout });
|
|
19023
|
+
const positionEntries = Object.entries(positions ?? {});
|
|
19024
|
+
if (positionEntries.length === 0) return;
|
|
19025
|
+
for (const [memberId, position] of positionEntries) {
|
|
19026
|
+
const participant = this.participants.find((p) => p.id === memberId);
|
|
19027
|
+
if (!participant) {
|
|
19028
|
+
logger$12.warn(`[Call] setLayout: member ${memberId} not found in participants; skipping position ${position}`);
|
|
19029
|
+
continue;
|
|
19030
|
+
}
|
|
19031
|
+
await participant.setPosition(position);
|
|
19032
|
+
}
|
|
18960
19033
|
}
|
|
18961
19034
|
/**
|
|
18962
19035
|
* Transfers the call to another destination.
|
|
@@ -18988,7 +19061,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18988
19061
|
setLocalMicrophoneGain(value) {
|
|
18989
19062
|
const pipeline = this.vertoManager.ensureLocalAudioPipeline();
|
|
18990
19063
|
if (!pipeline) {
|
|
18991
|
-
logger$
|
|
19064
|
+
logger$12.warn("[Call] setLocalMicrophoneGain: audio pipeline unavailable");
|
|
18992
19065
|
return;
|
|
18993
19066
|
}
|
|
18994
19067
|
const percent = Math.max(0, Math.min(200, value));
|
|
@@ -19033,7 +19106,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
19033
19106
|
enablePushToTalk() {
|
|
19034
19107
|
const pipeline = this.vertoManager.ensureLocalAudioPipeline();
|
|
19035
19108
|
if (!pipeline) {
|
|
19036
|
-
logger$
|
|
19109
|
+
logger$12.warn("[Call] enablePushToTalk: audio pipeline unavailable");
|
|
19037
19110
|
return;
|
|
19038
19111
|
}
|
|
19039
19112
|
pipeline.setPTTActive(false);
|
|
@@ -19196,7 +19269,7 @@ var CallFactory = class {
|
|
|
19196
19269
|
//#endregion
|
|
19197
19270
|
//#region src/behaviors/Collection.ts
|
|
19198
19271
|
var import_cjs$10 = require_cjs();
|
|
19199
|
-
const logger$
|
|
19272
|
+
const logger$11 = getLogger();
|
|
19200
19273
|
var Fetcher = class {
|
|
19201
19274
|
constructor(endpoint, params, http) {
|
|
19202
19275
|
this.endpoint = endpoint;
|
|
@@ -19220,7 +19293,7 @@ var Fetcher = class {
|
|
|
19220
19293
|
this.hasMore = !!this.nextUrl;
|
|
19221
19294
|
return result.data.filter(this.filter).map(this.mapper);
|
|
19222
19295
|
}
|
|
19223
|
-
logger$
|
|
19296
|
+
logger$11.error("Failed to fetch entity");
|
|
19224
19297
|
return [];
|
|
19225
19298
|
}
|
|
19226
19299
|
async id(v) {
|
|
@@ -19296,7 +19369,7 @@ var EntityCollection = class extends Destroyable {
|
|
|
19296
19369
|
this._hasMore$.next(this.fetchController.hasMore ?? false);
|
|
19297
19370
|
this._loading$.next(false);
|
|
19298
19371
|
} catch (error) {
|
|
19299
|
-
logger$
|
|
19372
|
+
logger$11.error(`Failed to fetch initial collection data`, error);
|
|
19300
19373
|
this._hasMore$.next(this.fetchController.hasMore ?? false);
|
|
19301
19374
|
this._loading$.next(false);
|
|
19302
19375
|
this.onError?.(new CollectionFetchError("fetchMore", error));
|
|
@@ -19310,7 +19383,7 @@ var EntityCollection = class extends Destroyable {
|
|
|
19310
19383
|
if (data) this.upsertData(data);
|
|
19311
19384
|
return data;
|
|
19312
19385
|
} catch (error) {
|
|
19313
|
-
logger$
|
|
19386
|
+
logger$11.error(`Failed to fetch data for (${String(key)}:${String(value)}) :`, error);
|
|
19314
19387
|
this._loading$.next(false);
|
|
19315
19388
|
this.onError?.(new CollectionFetchError(`tryFetch(${String(key)})`, error));
|
|
19316
19389
|
}
|
|
@@ -19559,13 +19632,13 @@ var Address = class extends Destroyable {
|
|
|
19559
19632
|
//#endregion
|
|
19560
19633
|
//#region src/core/utils.ts
|
|
19561
19634
|
var import_cjs$8 = require_cjs();
|
|
19562
|
-
const logger$
|
|
19635
|
+
const logger$10 = getLogger();
|
|
19563
19636
|
const isRPCConnectResult = (e) => {
|
|
19564
|
-
logger$
|
|
19637
|
+
logger$10.debug("isRPCConnectResult check:", e);
|
|
19565
19638
|
if (!e || typeof e !== "object") return false;
|
|
19566
19639
|
const result = e;
|
|
19567
19640
|
const is = typeof result.identity === "string" && typeof result.protocol === "string" && typeof result.authorization === "object" && typeof result.authorization.jti === "string" && typeof result.authorization.project_id === "string" && typeof result.authorization.fabric_subscriber === "object";
|
|
19568
|
-
logger$
|
|
19641
|
+
logger$10.debug("isRPCConnectResult check result:", is);
|
|
19569
19642
|
return is;
|
|
19570
19643
|
};
|
|
19571
19644
|
var PendingRPC = class PendingRPC {
|
|
@@ -19574,7 +19647,7 @@ var PendingRPC = class PendingRPC {
|
|
|
19574
19647
|
}
|
|
19575
19648
|
constructor(request, responses$, options) {
|
|
19576
19649
|
this.id = v4_default();
|
|
19577
|
-
logger$
|
|
19650
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}: method:${request.method}] Creating PendingRPC`);
|
|
19578
19651
|
this.request = request;
|
|
19579
19652
|
const timeoutMs = options?.timeoutMs ?? PendingRPC.defaultTimeoutMs;
|
|
19580
19653
|
const signal = options?.signal;
|
|
@@ -19600,22 +19673,22 @@ var PendingRPC = class PendingRPC {
|
|
|
19600
19673
|
isSettled = true;
|
|
19601
19674
|
if (response.error) {
|
|
19602
19675
|
const rpcError = new JSONRPCError(response.error.code, response.error.message, response.error.data, void 0, request.id);
|
|
19603
|
-
logger$
|
|
19676
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with RPC error:`, rpcError);
|
|
19604
19677
|
reject(rpcError);
|
|
19605
19678
|
} else {
|
|
19606
|
-
logger$
|
|
19679
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Resolving promise with response:`, response);
|
|
19607
19680
|
resolve(response);
|
|
19608
19681
|
}
|
|
19609
19682
|
subscription.unsubscribe();
|
|
19610
19683
|
},
|
|
19611
19684
|
error: (error) => {
|
|
19612
|
-
logger$
|
|
19685
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with error:`, error);
|
|
19613
19686
|
isSettled = true;
|
|
19614
19687
|
reject(error);
|
|
19615
19688
|
subscription.unsubscribe();
|
|
19616
19689
|
},
|
|
19617
19690
|
complete: () => {
|
|
19618
|
-
logger$
|
|
19691
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Observable completed`);
|
|
19619
19692
|
if (!isSettled) reject(new RPCTimeoutError(request.id, timeoutMs));
|
|
19620
19693
|
subscription.unsubscribe();
|
|
19621
19694
|
}
|
|
@@ -19636,7 +19709,7 @@ var PendingRPC = class PendingRPC {
|
|
|
19636
19709
|
//#endregion
|
|
19637
19710
|
//#region src/managers/ClientSessionManager.ts
|
|
19638
19711
|
var import_cjs$7 = require_cjs();
|
|
19639
|
-
const logger$
|
|
19712
|
+
const logger$9 = getLogger();
|
|
19640
19713
|
const getAddressSearchURI = (options) => {
|
|
19641
19714
|
const to = options.to?.split("?")[0];
|
|
19642
19715
|
const from$9 = options.from?.startsWith("subscriber://") ? options.from.replace("subscriber://", "") : options.from;
|
|
@@ -19734,7 +19807,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19734
19807
|
try {
|
|
19735
19808
|
return await this.transport.execute(request, options);
|
|
19736
19809
|
} catch (error) {
|
|
19737
|
-
logger$
|
|
19810
|
+
logger$9.debug("[Session] Execute Error", error);
|
|
19738
19811
|
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
19739
19812
|
throw error;
|
|
19740
19813
|
}
|
|
@@ -19748,13 +19821,13 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19748
19821
|
return true;
|
|
19749
19822
|
}
|
|
19750
19823
|
setupMessageHandlers() {
|
|
19751
|
-
logger$
|
|
19824
|
+
logger$9.debug("[Session] Setting up message handlers");
|
|
19752
19825
|
this.subscribeTo(this.authStateEvent$, async (authStateEvent) => {
|
|
19753
|
-
logger$
|
|
19826
|
+
logger$9.debug("[Session] Authorization state event received:", authStateEvent);
|
|
19754
19827
|
try {
|
|
19755
19828
|
await this.updateAuthorizationStateInStorage(authStateEvent.authorization_state);
|
|
19756
19829
|
} catch (error) {
|
|
19757
|
-
logger$
|
|
19830
|
+
logger$9.error("[Session] Failed to handle authorization state update:", error);
|
|
19758
19831
|
this._errors$.next(new AuthStateHandlerError(error));
|
|
19759
19832
|
}
|
|
19760
19833
|
});
|
|
@@ -19762,29 +19835,29 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19762
19835
|
if (this._authState$.value.kind === "authenticated") this._authState$.next({ kind: "unauthenticated" });
|
|
19763
19836
|
});
|
|
19764
19837
|
this.subscribeTo(this.transport.connectionStatus$.pipe((0, import_cjs$7.filter)((status) => status === "connected"), (0, import_cjs$7.exhaustMap)(() => {
|
|
19765
|
-
logger$
|
|
19838
|
+
logger$9.debug("[Session] Connection established, initiating authentication");
|
|
19766
19839
|
return (0, import_cjs$7.from)(this.authenticate()).pipe((0, import_cjs$7.catchError)((error) => {
|
|
19767
19840
|
this.handleAuthenticationError(error).catch((err) => {
|
|
19768
|
-
logger$
|
|
19841
|
+
logger$9.error("[Session] Error handling authentication failure:", err);
|
|
19769
19842
|
});
|
|
19770
19843
|
return import_cjs$7.EMPTY;
|
|
19771
19844
|
}));
|
|
19772
19845
|
})), void 0);
|
|
19773
19846
|
this.subscribeTo(this.vertoInvite$, async (invite) => {
|
|
19774
|
-
logger$
|
|
19847
|
+
logger$9.debug("[Session] Verto invite received:", invite);
|
|
19775
19848
|
try {
|
|
19776
19849
|
await this.createInboundCall(invite);
|
|
19777
19850
|
} catch (error) {
|
|
19778
|
-
logger$
|
|
19851
|
+
logger$9.error("[Session] Error handling Verto invite:", error);
|
|
19779
19852
|
this._errors$.next(new VertoInviteHandlerError(error));
|
|
19780
19853
|
}
|
|
19781
19854
|
});
|
|
19782
19855
|
this.subscribeTo(this.vertoAttach$, async (attach) => {
|
|
19783
|
-
logger$
|
|
19856
|
+
logger$9.debug("[Session] Verto attach received:", attach);
|
|
19784
19857
|
try {
|
|
19785
19858
|
await this.handleVertoAttach(attach);
|
|
19786
19859
|
} catch (error) {
|
|
19787
|
-
logger$
|
|
19860
|
+
logger$9.error("[Session] Error handling Verto attach:", error);
|
|
19788
19861
|
this._errors$.next(new VertoAttachHandlerError(error));
|
|
19789
19862
|
}
|
|
19790
19863
|
});
|
|
@@ -19794,36 +19867,36 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19794
19867
|
const storedState = await this.storage.getItem(this.authorizationStateKey);
|
|
19795
19868
|
this.authorizationState$.next(storedState ?? void 0);
|
|
19796
19869
|
} catch (error) {
|
|
19797
|
-
logger$
|
|
19870
|
+
logger$9.error("Failed to retrieve authorization state from storage:", error);
|
|
19798
19871
|
this.authorizationState$.next(void 0);
|
|
19799
19872
|
}
|
|
19800
19873
|
}
|
|
19801
19874
|
async updateAuthorizationStateInStorage(authorizationState) {
|
|
19802
19875
|
if (!authorizationState) {
|
|
19803
|
-
logger$
|
|
19876
|
+
logger$9.debug("[Session] Removing authorization state from storage");
|
|
19804
19877
|
try {
|
|
19805
19878
|
await this.storage.removeItem(this.authorizationStateKey);
|
|
19806
19879
|
this.authorizationState$.next(void 0);
|
|
19807
19880
|
} catch (error) {
|
|
19808
|
-
logger$
|
|
19881
|
+
logger$9.error("Failed to remove authorization state from storage:", error);
|
|
19809
19882
|
throw error;
|
|
19810
19883
|
}
|
|
19811
19884
|
return;
|
|
19812
19885
|
}
|
|
19813
19886
|
try {
|
|
19814
|
-
logger$
|
|
19887
|
+
logger$9.debug("[Session] Updating authorization state in storage");
|
|
19815
19888
|
await this.storage.setItem(this.authorizationStateKey, authorizationState);
|
|
19816
19889
|
this.authorizationState$.next(authorizationState);
|
|
19817
19890
|
} catch (error) {
|
|
19818
|
-
logger$
|
|
19891
|
+
logger$9.error("Failed to retrieve authorization state from storage:", error);
|
|
19819
19892
|
throw error;
|
|
19820
19893
|
}
|
|
19821
19894
|
}
|
|
19822
19895
|
get authStateEvent$() {
|
|
19823
19896
|
return this.cachedObservable("authStateEvent$", () => this.signalingEvent$.pipe((0, import_cjs$7.tap)((msg) => {
|
|
19824
|
-
logger$
|
|
19897
|
+
logger$9.debug("[Session] Received incoming message:", msg);
|
|
19825
19898
|
}), filterAs(isSignalwireAuthorizationStateMetadata, "params"), (0, import_cjs$7.tap)((event) => {
|
|
19826
|
-
logger$
|
|
19899
|
+
logger$9.debug("[Session] Authorization state event received:", event.authorization_state);
|
|
19827
19900
|
})));
|
|
19828
19901
|
}
|
|
19829
19902
|
get signalingEvent$() {
|
|
@@ -19861,42 +19934,72 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19861
19934
|
await (0, import_cjs$7.firstValueFrom)(this.authenticated$.pipe((0, import_cjs$7.takeUntil)(this.destroyed$), (0, import_cjs$7.filter)(Boolean), (0, import_cjs$7.take)(1), (0, import_cjs$7.timeout)({ first: 15e3 })));
|
|
19862
19935
|
}
|
|
19863
19936
|
async handleAuthenticationError(error) {
|
|
19864
|
-
logger$
|
|
19937
|
+
logger$9.error("Authentication error:", error);
|
|
19865
19938
|
const isRecoverableAuthError = error instanceof JSONRPCError && (error.code === RPC_ERROR_REQUESTER_VALIDATION_FAILED || error.code === RPC_ERROR_INVALID_PARAMS || error.code === RPC_ERROR_AUTHENTICATION_FAILED);
|
|
19866
19939
|
const hasStoredState = await (0, import_cjs$7.firstValueFrom)(this.authorizationState$.pipe((0, import_cjs$7.take)(1))) !== void 0;
|
|
19867
19940
|
if (isRecoverableAuthError && hasStoredState) {
|
|
19868
|
-
logger$
|
|
19941
|
+
logger$9.debug("[Session] Recoverable auth error — cleaning up stored state and reconnecting fresh");
|
|
19869
19942
|
try {
|
|
19870
19943
|
await this.cleanupStoredConnectionParams();
|
|
19871
19944
|
} catch (cleanupError) {
|
|
19872
|
-
logger$
|
|
19945
|
+
logger$9.error("Failed to cleanup stored connection params:", cleanupError);
|
|
19873
19946
|
} finally {
|
|
19874
19947
|
this.transport.reconnect();
|
|
19875
19948
|
}
|
|
19876
19949
|
} else this._errors$.next(error);
|
|
19877
19950
|
}
|
|
19951
|
+
/**
|
|
19952
|
+
* Clear the resume state (authorization_state + protocol) only.
|
|
19953
|
+
*
|
|
19954
|
+
* This is the stale-auth-state recovery helper used by handleAuthError:
|
|
19955
|
+
* the server rejected a reconnect, so the resume state is discarded and a
|
|
19956
|
+
* fresh connect follows. Attach records are deliberately preserved — the
|
|
19957
|
+
* session lives on through the reconnect and reattachCalls() needs the
|
|
19958
|
+
* stored call references afterwards. Do NOT add detachAll() here.
|
|
19959
|
+
*
|
|
19960
|
+
* For public teardown (disconnect/destroy), use {@link teardownSessionState}
|
|
19961
|
+
* instead, which clears the attach records as well.
|
|
19962
|
+
*/
|
|
19878
19963
|
async cleanupStoredConnectionParams() {
|
|
19879
19964
|
await this.transport.setProtocol(void 0);
|
|
19880
19965
|
await this.updateAuthorizationStateInStorage(void 0);
|
|
19881
19966
|
this._authorization$.next(void 0);
|
|
19882
19967
|
}
|
|
19968
|
+
/**
|
|
19969
|
+
* Public-teardown helper for disconnect()/destroy(). Clears the resume
|
|
19970
|
+
* state (authorization_state + protocol) AND the attach records as one
|
|
19971
|
+
* atomic unit.
|
|
19972
|
+
*
|
|
19973
|
+
* The two stores are coupled: the backend only honors attach records
|
|
19974
|
+
* within the session identified by the resume state, so ending the
|
|
19975
|
+
* session must clear both. Clearing one without the other strands records
|
|
19976
|
+
* no future session can honor (disconnect) or revives a session the
|
|
19977
|
+
* developer explicitly ended (destroy).
|
|
19978
|
+
*
|
|
19979
|
+
* Distinct from {@link cleanupStoredConnectionParams}, which keeps the
|
|
19980
|
+
* attach records for the stale-auth-state recovery path.
|
|
19981
|
+
*/
|
|
19982
|
+
async teardownSessionState() {
|
|
19983
|
+
await this.cleanupStoredConnectionParams();
|
|
19984
|
+
await this.attachManager.detachAll();
|
|
19985
|
+
}
|
|
19883
19986
|
async updateAuthState(authorization_state) {
|
|
19884
19987
|
try {
|
|
19885
19988
|
await this.storage.setItem(this.authorizationStateKey, authorization_state);
|
|
19886
19989
|
} catch (error) {
|
|
19887
|
-
logger$
|
|
19990
|
+
logger$9.error("Failed to update authorization state in storage:", error);
|
|
19888
19991
|
this._errors$.next(new AuthStateHandlerError(error));
|
|
19889
19992
|
}
|
|
19890
19993
|
}
|
|
19891
19994
|
async reauthenticate(token, dpopToken, options) {
|
|
19892
|
-
logger$
|
|
19995
|
+
logger$9.debug("[Session] Re-authenticating session");
|
|
19893
19996
|
try {
|
|
19894
19997
|
let resolvedDpopToken = dpopToken;
|
|
19895
19998
|
if (!resolvedDpopToken && this.dpopManager?.initialized) try {
|
|
19896
19999
|
resolvedDpopToken = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
|
|
19897
20000
|
} catch (error) {
|
|
19898
20001
|
if (this.clientBound) throw error;
|
|
19899
|
-
logger$
|
|
20002
|
+
logger$9.warn("[Session] Failed to create DPoP proof for reauthenticate:", error);
|
|
19900
20003
|
}
|
|
19901
20004
|
const request = RPCReauthenticate({
|
|
19902
20005
|
project: this._authorization$.value?.project_id ?? "",
|
|
@@ -19904,24 +20007,24 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19904
20007
|
...resolvedDpopToken ? { dpop_token: resolvedDpopToken } : {}
|
|
19905
20008
|
});
|
|
19906
20009
|
await (0, import_cjs$7.lastValueFrom)((0, import_cjs$7.from)(this.transport.execute(request)).pipe(throwOnRPCError(), (0, import_cjs$7.take)(1), (0, import_cjs$7.catchError)((err) => {
|
|
19907
|
-
logger$
|
|
20010
|
+
logger$9.error("[Session] Re-authentication RPC failed:", err);
|
|
19908
20011
|
throw err;
|
|
19909
20012
|
})));
|
|
19910
20013
|
if (options?.clientBound) this._wasClientBound = true;
|
|
19911
|
-
logger$
|
|
20014
|
+
logger$9.debug("[Session] Re-authentication successful, updating stored auth state");
|
|
19912
20015
|
} catch (error) {
|
|
19913
|
-
logger$
|
|
20016
|
+
logger$9.error("[Session] Re-authentication failed:", error);
|
|
19914
20017
|
this._errors$.next(new AuthStateHandlerError(error));
|
|
19915
20018
|
throw error;
|
|
19916
20019
|
}
|
|
19917
20020
|
}
|
|
19918
20021
|
async authenticate() {
|
|
19919
|
-
logger$
|
|
20022
|
+
logger$9.debug("[Session] Starting authentication process");
|
|
19920
20023
|
const persistedParams = await (0, import_cjs$7.firstValueFrom)((0, import_cjs$7.combineLatest)({
|
|
19921
20024
|
protocol: this.transport.protocol$,
|
|
19922
20025
|
authorization_state: this.authorizationState$
|
|
19923
20026
|
}).pipe((0, import_cjs$7.take)(1)));
|
|
19924
|
-
logger$
|
|
20027
|
+
logger$9.debug("[Session] Persisted params:\n", {
|
|
19925
20028
|
protocol: persistedParams.protocol,
|
|
19926
20029
|
authStateLength: persistedParams.authorization_state?.length
|
|
19927
20030
|
});
|
|
@@ -19929,16 +20032,16 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19929
20032
|
const storedToken = this.getCredential().token;
|
|
19930
20033
|
const isReconnect = hasReconnectState && storedToken;
|
|
19931
20034
|
let dpopToken;
|
|
19932
|
-
if (isReconnect) logger$
|
|
20035
|
+
if (isReconnect) logger$9.debug("[Session] Reconnecting with stored jwt_token + authorization_state");
|
|
19933
20036
|
else if (this.onBeforeReconnect && this.clientBound) {
|
|
19934
|
-
logger$
|
|
20037
|
+
logger$9.debug("[Session] Refreshing credentials before fresh connect");
|
|
19935
20038
|
await this.onBeforeReconnect();
|
|
19936
20039
|
}
|
|
19937
20040
|
if ((!isReconnect || this.clientBound) && this.dpopManager?.initialized) try {
|
|
19938
20041
|
dpopToken = await this.dpopManager.createRpcProof({ method: "signalwire.connect" });
|
|
19939
20042
|
} catch (error) {
|
|
19940
20043
|
if (this.clientBound) throw error;
|
|
19941
|
-
logger$
|
|
20044
|
+
logger$9.warn("[Session] Failed to create DPoP proof for connect, proceeding without:", error);
|
|
19942
20045
|
}
|
|
19943
20046
|
const rpcConnectRequest = RPCConnect({
|
|
19944
20047
|
authentication: isReconnect ? { jwt_token: storedToken } : this.authentication,
|
|
@@ -19955,12 +20058,12 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19955
20058
|
} : {}
|
|
19956
20059
|
});
|
|
19957
20060
|
const response = await (0, import_cjs$7.lastValueFrom)((0, import_cjs$7.from)(this.transport.execute(rpcConnectRequest)).pipe(throwOnRPCError(), (0, import_cjs$7.map)((res) => res.result), (0, import_cjs$7.filter)(isRPCConnectResult), (0, import_cjs$7.tap)(() => {
|
|
19958
|
-
logger$
|
|
20061
|
+
logger$9.debug("[Session] Response passed filter, processing authentication result");
|
|
19959
20062
|
}), (0, import_cjs$7.take)(1), (0, import_cjs$7.catchError)((err) => {
|
|
19960
|
-
logger$
|
|
20063
|
+
logger$9.error("[Session] Authentication RPC failed:", err);
|
|
19961
20064
|
throw err;
|
|
19962
20065
|
})));
|
|
19963
|
-
logger$
|
|
20066
|
+
logger$9.debug("[Session] Processing authentication result:", {
|
|
19964
20067
|
hasProtocol: !!response.protocol,
|
|
19965
20068
|
hasAuthorization: !!response.authorization,
|
|
19966
20069
|
hasIceServers: !!response.ice_servers
|
|
@@ -19969,12 +20072,12 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19969
20072
|
this._authorization$.next(response.authorization);
|
|
19970
20073
|
this._iceServers$.next(response.ice_servers ?? []);
|
|
19971
20074
|
this._authState$.next({ kind: "authenticated" });
|
|
19972
|
-
logger$
|
|
20075
|
+
logger$9.debug("[Session] Authentication completed successfully");
|
|
19973
20076
|
}
|
|
19974
20077
|
async disconnect() {
|
|
19975
20078
|
this.transport.disconnect();
|
|
19976
20079
|
this._authState$.next({ kind: "unauthenticated" });
|
|
19977
|
-
await this.
|
|
20080
|
+
await this.teardownSessionState();
|
|
19978
20081
|
}
|
|
19979
20082
|
async createInboundCall(invite) {
|
|
19980
20083
|
const callSession = await this.createCall({
|
|
@@ -20005,11 +20108,11 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
20005
20108
|
async handleVertoAttach(attach) {
|
|
20006
20109
|
const { callID } = attach;
|
|
20007
20110
|
if (callID in this._calls$.value) {
|
|
20008
|
-
logger$
|
|
20111
|
+
logger$9.debug(`[Session] Verto attach for existing call ${callID}, deferring to per-call handler`);
|
|
20009
20112
|
return;
|
|
20010
20113
|
}
|
|
20011
20114
|
const storedOptions = await this.attachManager.consumePendingAttachment(callID);
|
|
20012
|
-
logger$
|
|
20115
|
+
logger$9.debug(`[Session] Creating reattached call for callID: ${callID}`);
|
|
20013
20116
|
const callSession = await this.createCall({
|
|
20014
20117
|
nodeId: attach.node_id,
|
|
20015
20118
|
callId: callID,
|
|
@@ -20040,7 +20143,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
20040
20143
|
});
|
|
20041
20144
|
return callSession;
|
|
20042
20145
|
} catch (error) {
|
|
20043
|
-
logger$
|
|
20146
|
+
logger$9.error("[Session] Error creating outbound call:", error);
|
|
20044
20147
|
callSession?.destroy();
|
|
20045
20148
|
const callError = new CallCreateError(error instanceof import_cjs$7.TimeoutError ? "Call create timeout" : "Call creation failed", error, "outbound");
|
|
20046
20149
|
this._errors$.next(callError);
|
|
@@ -20058,7 +20161,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
20058
20161
|
address = this._directory.get(addressId);
|
|
20059
20162
|
if (!address) throw new DependencyError(`Address ID: ${addressId} not found`);
|
|
20060
20163
|
} catch {
|
|
20061
|
-
logger$
|
|
20164
|
+
logger$9.warn(`[Session] Directory lookup failed for ${addressURI}, proceeding with raw URI`);
|
|
20062
20165
|
}
|
|
20063
20166
|
const callSession = this.callFactory.createCall(address, { ...options });
|
|
20064
20167
|
this.subscribeTo(callSession.status$.pipe((0, import_cjs$7.filter)((status) => status === "destroyed"), (0, import_cjs$7.take)(1)), () => {
|
|
@@ -20067,7 +20170,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
20067
20170
|
});
|
|
20068
20171
|
return callSession;
|
|
20069
20172
|
} catch (error) {
|
|
20070
|
-
logger$
|
|
20173
|
+
logger$9.error("[Session] Error creating call session:", error);
|
|
20071
20174
|
throw new CallCreateError("Call create error", error, options.initOffer ? "inbound" : "outbound");
|
|
20072
20175
|
}
|
|
20073
20176
|
}
|
|
@@ -20116,7 +20219,7 @@ const isString = (obj) => typeof obj === "string";
|
|
|
20116
20219
|
//#endregion
|
|
20117
20220
|
//#region src/managers/ConversationsManager.ts
|
|
20118
20221
|
var import_cjs$6 = require_cjs();
|
|
20119
|
-
const logger$
|
|
20222
|
+
const logger$8 = getLogger();
|
|
20120
20223
|
var ConversationMessagesFetcher = class extends Fetcher {
|
|
20121
20224
|
constructor(groupId, http) {
|
|
20122
20225
|
super(`/api/fabric/conversations/${groupId}/messages`, "page_size=100", http);
|
|
@@ -20156,13 +20259,13 @@ var ConversationsManager = class {
|
|
|
20156
20259
|
}
|
|
20157
20260
|
throw new ConversationError("Join Failed - Unexpected response");
|
|
20158
20261
|
} catch (error) {
|
|
20159
|
-
logger$
|
|
20262
|
+
logger$8.error("[ConversationsManager] Failed to join conversation:", error);
|
|
20160
20263
|
throw error;
|
|
20161
20264
|
}
|
|
20162
20265
|
}
|
|
20163
20266
|
async getConversationMessageCollection(addressId) {
|
|
20164
20267
|
const groupId = this.groupIds.get(addressId) ?? await this.join(addressId);
|
|
20165
|
-
return Promise.resolve(new ConversationMessageCollection(groupId, this.clientSession.signalingEvent$.pipe(filterAs(isConversationMessageMetadata, "params"), (0, import_cjs$6.tap)((event) => logger$
|
|
20268
|
+
return Promise.resolve(new ConversationMessageCollection(groupId, this.clientSession.signalingEvent$.pipe(filterAs(isConversationMessageMetadata, "params"), (0, import_cjs$6.tap)((event) => logger$8.debug("[ConversationsManager ] Conversation Event:", event)), (0, import_cjs$6.map)((params) => ({ ...params }))), this.http, this.onError));
|
|
20166
20269
|
}
|
|
20167
20270
|
async sendText(text, destinationAddressId) {
|
|
20168
20271
|
const groupId = this.groupIds.get(destinationAddressId) ?? await this.join(destinationAddressId);
|
|
@@ -20179,7 +20282,7 @@ var ConversationsManager = class {
|
|
|
20179
20282
|
})).ok) return;
|
|
20180
20283
|
throw new ConversationError("Send Text Failed - Unexpected response");
|
|
20181
20284
|
} catch (error) {
|
|
20182
|
-
logger$
|
|
20285
|
+
logger$8.error("[ConversationsManager] Failed to send text message:", error);
|
|
20183
20286
|
throw error;
|
|
20184
20287
|
}
|
|
20185
20288
|
}
|
|
@@ -20188,7 +20291,7 @@ var ConversationsManager = class {
|
|
|
20188
20291
|
//#endregion
|
|
20189
20292
|
//#region src/managers/DeviceTokenManager.ts
|
|
20190
20293
|
var import_cjs$5 = require_cjs();
|
|
20191
|
-
const logger$
|
|
20294
|
+
const logger$7 = getLogger();
|
|
20192
20295
|
/**
|
|
20193
20296
|
* Resolves the token expiry timestamp (epoch seconds) using a 3-tier priority chain:
|
|
20194
20297
|
* 1. `data.expires_at` — server-provided absolute timestamp
|
|
@@ -20198,7 +20301,7 @@ const logger$6 = getLogger();
|
|
|
20198
20301
|
function resolveExpiresAt(data) {
|
|
20199
20302
|
if (data.expires_at) return data.expires_at;
|
|
20200
20303
|
if (data.expires_in) return Math.floor(Date.now() / 1e3) + data.expires_in;
|
|
20201
|
-
logger$
|
|
20304
|
+
logger$7.warn("[DeviceToken] Could not determine token expiry, using default");
|
|
20202
20305
|
return Math.floor(Date.now() / 1e3) + DEVICE_TOKEN_DEFAULT_EXPIRE_IN;
|
|
20203
20306
|
}
|
|
20204
20307
|
/**
|
|
@@ -20231,11 +20334,12 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20231
20334
|
this.getCredential = getCredential;
|
|
20232
20335
|
this._currentToken$ = this.createBehaviorSubject(null);
|
|
20233
20336
|
this._refreshInProgress = false;
|
|
20337
|
+
this._paused = false;
|
|
20234
20338
|
this._effectiveExpireIn = DEVICE_TOKEN_DEFAULT_EXPIRE_IN;
|
|
20235
20339
|
this.subscribeTo(this._currentToken$.pipe((0, import_cjs$5.filter)(Boolean), (0, import_cjs$5.switchMap)((tokenData) => {
|
|
20236
20340
|
const expiresAt = resolveExpiresAt(tokenData);
|
|
20237
20341
|
const refreshIn = Math.max(expiresAt * 1e3 - Date.now() - DEVICE_TOKEN_REFRESH_BUFFER_MS, 1e3);
|
|
20238
|
-
logger$
|
|
20342
|
+
logger$7.debug(`[DeviceToken] Scheduling Client Bound SAT refresh in ${refreshIn}ms`);
|
|
20239
20343
|
return (0, import_cjs$5.timer)(refreshIn);
|
|
20240
20344
|
})), () => {
|
|
20241
20345
|
this.executeRefresh();
|
|
@@ -20249,7 +20353,12 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20249
20353
|
* Activates the Client Bound SAT flow when the user's token has
|
|
20250
20354
|
* `sat:refresh` scope.
|
|
20251
20355
|
*
|
|
20252
|
-
*
|
|
20356
|
+
* Returns an {@link ActivationResult} indicating whether the manager
|
|
20357
|
+
* took ownership of refresh duties. The caller must use the `activated`
|
|
20358
|
+
* boolean to decide whether to keep its own refresh path armed — when
|
|
20359
|
+
* `activated` is `false`, the caller is responsible for refresh.
|
|
20360
|
+
*
|
|
20361
|
+
* Steps on success:
|
|
20253
20362
|
* 1. Check user's `sat_claims` for `sat:refresh` scope
|
|
20254
20363
|
* 2. Call `/api/fabric/subscriber/devices/token` with a DPoP proof
|
|
20255
20364
|
* 3. Reauthenticate the session with the Client Bound SAT + DPoP proof
|
|
@@ -20258,32 +20367,51 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20258
20367
|
async activate(user, session, updateCredential) {
|
|
20259
20368
|
const { satClaims } = user;
|
|
20260
20369
|
if (!satClaims?.scope?.includes(SAT_REFRESH_SCOPE)) {
|
|
20261
|
-
logger$
|
|
20262
|
-
return
|
|
20370
|
+
logger$7.debug("[DeviceToken] No sat:refresh scope, skipping Client Bound SAT activation");
|
|
20371
|
+
return {
|
|
20372
|
+
activated: false,
|
|
20373
|
+
reason: "no-scope"
|
|
20374
|
+
};
|
|
20263
20375
|
}
|
|
20264
20376
|
this._session = session;
|
|
20265
20377
|
this._updateCredential = updateCredential;
|
|
20266
20378
|
try {
|
|
20379
|
+
const cached = this._currentToken$.value;
|
|
20380
|
+
if (cached && this.isTokenFresh(cached)) {
|
|
20381
|
+
logger$7.debug("[DeviceToken] Reusing cached Client Bound SAT — skipping /devices/token");
|
|
20382
|
+
const rpcProof$1 = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
|
|
20383
|
+
await session.reauthenticate(cached.token, rpcProof$1, { clientBound: true });
|
|
20384
|
+
updateCredential({ token: cached.token });
|
|
20385
|
+
return { activated: true };
|
|
20386
|
+
}
|
|
20267
20387
|
const tokenData = await this.obtainToken();
|
|
20268
20388
|
if (!tokenData.expires_at && !tokenData.expires_in && satClaims.expires_at) tokenData.expires_at = satClaims.expires_at;
|
|
20269
20389
|
this._effectiveExpireIn = resolveExpireIn(tokenData);
|
|
20270
20390
|
const rpcProof = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
|
|
20271
20391
|
await session.reauthenticate(tokenData.token, rpcProof, { clientBound: true });
|
|
20272
20392
|
updateCredential({ token: tokenData.token });
|
|
20273
|
-
logger$
|
|
20393
|
+
logger$7.info("[DeviceToken] Client Bound SAT activated successfully");
|
|
20274
20394
|
this._currentToken$.next(tokenData);
|
|
20395
|
+
return { activated: true };
|
|
20275
20396
|
} catch (error) {
|
|
20276
|
-
logger$
|
|
20397
|
+
logger$7.error("[DeviceToken] Failed to activate Client Bound SAT:", error);
|
|
20277
20398
|
this.errorHandler(new DPoPInitError(error, "Failed to activate Client Bound SAT"));
|
|
20278
|
-
|
|
20279
|
-
|
|
20280
|
-
|
|
20281
|
-
|
|
20282
|
-
expires_at: expiresAt
|
|
20283
|
-
});
|
|
20399
|
+
return {
|
|
20400
|
+
activated: false,
|
|
20401
|
+
reason: "endpoint-failed"
|
|
20402
|
+
};
|
|
20284
20403
|
}
|
|
20285
20404
|
}
|
|
20286
20405
|
/**
|
|
20406
|
+
* Returns true when the cached token has enough headroom before expiry to
|
|
20407
|
+
* be safely reused on reactivation. The headroom matches the refresh
|
|
20408
|
+
* buffer, so a token within the refresh window is treated as stale (the
|
|
20409
|
+
* reactive pipeline is about to refresh it anyway).
|
|
20410
|
+
*/
|
|
20411
|
+
isTokenFresh(token) {
|
|
20412
|
+
return resolveExpiresAt(token) * 1e3 - Date.now() > DEVICE_TOKEN_REFRESH_BUFFER_MS;
|
|
20413
|
+
}
|
|
20414
|
+
/**
|
|
20287
20415
|
* Obtains a Client Bound SAT from `/api/fabric/subscriber/devices/token`.
|
|
20288
20416
|
* Returns the full {@link DeviceTokenResponse} including expiry metadata.
|
|
20289
20417
|
*/
|
|
@@ -20313,7 +20441,7 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20313
20441
|
* handled by the reactive pipeline).
|
|
20314
20442
|
*/
|
|
20315
20443
|
async refreshToken(session, currentToken, updateCredential) {
|
|
20316
|
-
logger$
|
|
20444
|
+
logger$7.debug("[DeviceToken] Refreshing Client Bound SAT");
|
|
20317
20445
|
const dpopProof = await this.dpopManager.createHttpProof({
|
|
20318
20446
|
method: "POST",
|
|
20319
20447
|
uri: DEVICE_REFRESH_ENDPOINT,
|
|
@@ -20335,7 +20463,7 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20335
20463
|
const rpcProof = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
|
|
20336
20464
|
await session.reauthenticate(data.token, rpcProof);
|
|
20337
20465
|
updateCredential({ token: data.token });
|
|
20338
|
-
logger$
|
|
20466
|
+
logger$7.info("[DeviceToken] Client Bound SAT refreshed successfully");
|
|
20339
20467
|
return data;
|
|
20340
20468
|
}
|
|
20341
20469
|
/**
|
|
@@ -20344,18 +20472,22 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20344
20472
|
* On all retries exhausted, emits to `errorHandler`.
|
|
20345
20473
|
*/
|
|
20346
20474
|
async executeRefresh() {
|
|
20475
|
+
if (this._paused) {
|
|
20476
|
+
logger$7.debug("[DeviceToken] Manager paused, skipping refresh");
|
|
20477
|
+
return;
|
|
20478
|
+
}
|
|
20347
20479
|
if (this._refreshInProgress) {
|
|
20348
|
-
logger$
|
|
20480
|
+
logger$7.debug("[DeviceToken] Refresh already in progress, skipping");
|
|
20349
20481
|
return;
|
|
20350
20482
|
}
|
|
20351
20483
|
const session = this._session;
|
|
20352
20484
|
const updateCredential = this._updateCredential;
|
|
20353
20485
|
if (!session || !updateCredential) {
|
|
20354
|
-
logger$
|
|
20486
|
+
logger$7.warn("[DeviceToken] Cannot refresh: session or updateCredential not set");
|
|
20355
20487
|
return;
|
|
20356
20488
|
}
|
|
20357
20489
|
if (!session.authenticated) {
|
|
20358
|
-
logger$
|
|
20490
|
+
logger$7.debug("[DeviceToken] Session not authenticated, deferring refresh");
|
|
20359
20491
|
return;
|
|
20360
20492
|
}
|
|
20361
20493
|
this._refreshInProgress = true;
|
|
@@ -20365,7 +20497,7 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20365
20497
|
const newTokenData = await this.retryRefresh(session, currentToken, updateCredential);
|
|
20366
20498
|
this._currentToken$.next(newTokenData);
|
|
20367
20499
|
} catch (error) {
|
|
20368
|
-
logger$
|
|
20500
|
+
logger$7.error("[DeviceToken] Automatic Client Bound SAT refresh failed:", error);
|
|
20369
20501
|
this.errorHandler(error instanceof TokenRefreshError ? error : new TokenRefreshError("Automatic token refresh failed", error));
|
|
20370
20502
|
} finally {
|
|
20371
20503
|
this._refreshInProgress = false;
|
|
@@ -20383,18 +20515,215 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20383
20515
|
lastError = error;
|
|
20384
20516
|
if (attempt < DEVICE_TOKEN_REFRESH_MAX_RETRIES - 1) {
|
|
20385
20517
|
const delay$1 = DEVICE_TOKEN_REFRESH_RETRY_BASE_MS * Math.pow(2, attempt);
|
|
20386
|
-
logger$
|
|
20518
|
+
logger$7.warn(`[DeviceToken] Refresh attempt ${attempt + 1} failed, retrying in ${delay$1}ms`);
|
|
20387
20519
|
await new Promise((resolve) => setTimeout(resolve, delay$1));
|
|
20388
20520
|
}
|
|
20389
20521
|
}
|
|
20390
20522
|
throw lastError instanceof Error ? lastError : new TokenRefreshError("All refresh retries exhausted", lastError);
|
|
20391
20523
|
}
|
|
20524
|
+
/**
|
|
20525
|
+
* Stops the reactive refresh pipeline from firing. Use when the underlying
|
|
20526
|
+
* session is being torn down (e.g., during {@link SignalWire.disconnect})
|
|
20527
|
+
* so a scheduled refresh cannot fire against a destroyed session.
|
|
20528
|
+
*
|
|
20529
|
+
* The manager's state (cached token, effective TTL, subscriptions) is
|
|
20530
|
+
* preserved — call {@link resume} to re-enable firing after reconnect.
|
|
20531
|
+
*/
|
|
20532
|
+
pause() {
|
|
20533
|
+
this._paused = true;
|
|
20534
|
+
}
|
|
20535
|
+
/** Re-enables the reactive refresh pipeline after a previous {@link pause}. */
|
|
20536
|
+
resume() {
|
|
20537
|
+
this._paused = false;
|
|
20538
|
+
}
|
|
20392
20539
|
/** Cleans up the manager, cancelling the reactive pipeline and all subscriptions. */
|
|
20393
20540
|
destroy() {
|
|
20394
20541
|
super.destroy();
|
|
20395
20542
|
}
|
|
20396
20543
|
};
|
|
20397
20544
|
|
|
20545
|
+
//#endregion
|
|
20546
|
+
//#region src/managers/CredentialRefreshCoordinator.ts
|
|
20547
|
+
const logger$6 = getLogger();
|
|
20548
|
+
const defaultDeviceTokenManagerFactory = (dpopManager, http, errorHandler, getCredential) => new DeviceTokenManager(dpopManager, http, errorHandler, getCredential);
|
|
20549
|
+
/**
|
|
20550
|
+
* Centralizes credential-refresh ownership across the two competing
|
|
20551
|
+
* mechanisms — developer-provided `CredentialProvider.refresh()` and the
|
|
20552
|
+
* Client Bound SAT path via {@link DeviceTokenManager}.
|
|
20553
|
+
*
|
|
20554
|
+
* Maintains the invariant: **at most one refresh mechanism is armed at a
|
|
20555
|
+
* time, and at least one is armed whenever the current credential has an
|
|
20556
|
+
* `expiry_at`**.
|
|
20557
|
+
*
|
|
20558
|
+
* Replaces the previous design where refresh state was distributed across
|
|
20559
|
+
* `SignalWire` (timer field, scheduler method, activation helper) and
|
|
20560
|
+
* `DeviceTokenManager` (reactive pipeline). Centralizing the invariant in
|
|
20561
|
+
* one component eliminates the bug class that produced issue #19074.
|
|
20562
|
+
*
|
|
20563
|
+
* Race-safety:
|
|
20564
|
+
* - `_activating` flag prevents overlapping `activate()` calls from racing.
|
|
20565
|
+
* - `_activationGeneration` lets late resolutions detect they've been
|
|
20566
|
+
* preempted by a newer activation (e.g., reconnect during in-flight
|
|
20567
|
+
* `obtainToken`).
|
|
20568
|
+
*/
|
|
20569
|
+
var CredentialRefreshCoordinator = class extends Destroyable {
|
|
20570
|
+
constructor(dpopManager, deps) {
|
|
20571
|
+
super();
|
|
20572
|
+
this.deps = deps;
|
|
20573
|
+
this._activating = false;
|
|
20574
|
+
this._activationGeneration = 0;
|
|
20575
|
+
if (dpopManager?.initialized) this._deviceTokenManager = (deps.deviceTokenManagerFactory ?? defaultDeviceTokenManagerFactory)(dpopManager, deps.http, (error) => deps.notifier.onError(error), () => deps.store.read());
|
|
20576
|
+
}
|
|
20577
|
+
/** True when the Client Bound SAT path is available (DPoP initialized). */
|
|
20578
|
+
get clientBoundSATAvailable() {
|
|
20579
|
+
return this._deviceTokenManager !== void 0;
|
|
20580
|
+
}
|
|
20581
|
+
/** True when the developer-provided refresh timer is currently armed. */
|
|
20582
|
+
get developerRefreshArmed() {
|
|
20583
|
+
return this._developerTimerId !== void 0;
|
|
20584
|
+
}
|
|
20585
|
+
/**
|
|
20586
|
+
* Arms the developer-provided refresh timer to fire shortly before
|
|
20587
|
+
* `expiresAt`. Replaces any previously scheduled developer refresh.
|
|
20588
|
+
*
|
|
20589
|
+
* Idempotent — multiple calls just reschedule. On retry exhaustion,
|
|
20590
|
+
* invokes `deps.onRefreshExhausted` so the orchestrator can disconnect.
|
|
20591
|
+
*/
|
|
20592
|
+
scheduleDeveloperRefresh(provider, expiresAt, attempt = 0) {
|
|
20593
|
+
if (this._developerTimerId !== void 0) clearTimeout(this._developerTimerId);
|
|
20594
|
+
const refreshInterval = attempt === 0 ? Math.max(expiresAt - Date.now() - CREDENTIAL_REFRESH_BUFFER_MS, 1e3) : Math.min(CREDENTIAL_REFRESH_RETRY_BASE_MS * Math.pow(2, attempt) * (.5 + Math.random() * .5), CREDENTIAL_REFRESH_MAX_DELAY_MS);
|
|
20595
|
+
this._developerTimerId = setTimeout(async () => {
|
|
20596
|
+
try {
|
|
20597
|
+
if (!provider.refresh) throw new InvalidCredentialsError("Credential provider does not support refresh");
|
|
20598
|
+
const newCredentials = await provider.refresh();
|
|
20599
|
+
this.deps.store.write(newCredentials);
|
|
20600
|
+
this.deps.store.persist(newCredentials);
|
|
20601
|
+
logger$6.info("[Coordinator] Credentials refreshed successfully.");
|
|
20602
|
+
if (newCredentials.expiry_at) this.scheduleDeveloperRefresh(provider, newCredentials.expiry_at, 0);
|
|
20603
|
+
} catch (error) {
|
|
20604
|
+
const nextAttempt = attempt + 1;
|
|
20605
|
+
logger$6.error(`[Coordinator] Credential refresh failed (attempt ${nextAttempt}/${CREDENTIAL_REFRESH_MAX_RETRIES}):`, error);
|
|
20606
|
+
this.deps.notifier.onError(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
20607
|
+
if (nextAttempt < CREDENTIAL_REFRESH_MAX_RETRIES) this.scheduleDeveloperRefresh(provider, expiresAt, nextAttempt);
|
|
20608
|
+
else {
|
|
20609
|
+
logger$6.error("[Coordinator] Credential refresh exhausted all retries. Disconnecting.");
|
|
20610
|
+
this.deps.notifier.onError(new TokenRefreshError("Credential refresh failed after max retries"));
|
|
20611
|
+
this.deps.notifier.onRefreshExhausted();
|
|
20612
|
+
}
|
|
20613
|
+
}
|
|
20614
|
+
}, refreshInterval);
|
|
20615
|
+
}
|
|
20616
|
+
/**
|
|
20617
|
+
* Cancels any scheduled developer-provided refresh. Idempotent.
|
|
20618
|
+
*
|
|
20619
|
+
* @internal Used by the coordinator's own activation flow. External
|
|
20620
|
+
* callers should use {@link suspend} for disconnect-time quiescence —
|
|
20621
|
+
* `suspend()` also pauses the internal Client Bound SAT pipeline.
|
|
20622
|
+
*/
|
|
20623
|
+
cancelDeveloperRefresh() {
|
|
20624
|
+
if (this._developerTimerId !== void 0) {
|
|
20625
|
+
clearTimeout(this._developerTimerId);
|
|
20626
|
+
this._developerTimerId = void 0;
|
|
20627
|
+
}
|
|
20628
|
+
}
|
|
20629
|
+
/**
|
|
20630
|
+
* Suspends both refresh paths — cancels the developer timer and pauses
|
|
20631
|
+
* the internal reactive pipeline. Use when the underlying session is
|
|
20632
|
+
* being torn down (e.g., {@link SignalWire.disconnect}). The next
|
|
20633
|
+
* {@link activate} call re-enables the internal pipeline.
|
|
20634
|
+
*
|
|
20635
|
+
* The internal manager's cached token survives — see
|
|
20636
|
+
* {@link DeviceTokenManager.pause} — so a subsequent reconnect can
|
|
20637
|
+
* skip the `/devices/token` exchange entirely.
|
|
20638
|
+
*/
|
|
20639
|
+
suspend() {
|
|
20640
|
+
this.cancelDeveloperRefresh();
|
|
20641
|
+
this._deviceTokenManager?.pause();
|
|
20642
|
+
}
|
|
20643
|
+
/**
|
|
20644
|
+
* Asks the Client Bound SAT path to take over refresh. If it accepts,
|
|
20645
|
+
* the developer-provided timer (if any) is cancelled. If it declines, the
|
|
20646
|
+
* developer timer remains armed and a `credential_refresh_fallback`
|
|
20647
|
+
* warning is emitted.
|
|
20648
|
+
*
|
|
20649
|
+
* **Idempotent** — re-entrant calls during an in-flight activation are
|
|
20650
|
+
* dropped. Use `_activationGeneration` to detect stale resolutions
|
|
20651
|
+
* (e.g., a reconnect-triggered activate() that races with an earlier one).
|
|
20652
|
+
*/
|
|
20653
|
+
async activate(user, session) {
|
|
20654
|
+
if (this._activating) {
|
|
20655
|
+
logger$6.debug("[Coordinator] activate() in flight; ignoring re-entrant call");
|
|
20656
|
+
return;
|
|
20657
|
+
}
|
|
20658
|
+
if (!this._deviceTokenManager) return;
|
|
20659
|
+
this._deviceTokenManager.resume();
|
|
20660
|
+
const generation = ++this._activationGeneration;
|
|
20661
|
+
this._activating = true;
|
|
20662
|
+
try {
|
|
20663
|
+
const result = await this.withActivationTimeout(this._deviceTokenManager.activate(user, session, (cred) => this.deps.store.merge(cred)));
|
|
20664
|
+
if (generation !== this._activationGeneration) {
|
|
20665
|
+
logger$6.debug("[Coordinator] activate() result discarded (preempted by newer activation)");
|
|
20666
|
+
return;
|
|
20667
|
+
}
|
|
20668
|
+
if (result.activated) {
|
|
20669
|
+
this.cancelDeveloperRefresh();
|
|
20670
|
+
logger$6.debug("[Coordinator] Developer refresh disabled — Client Bound SAT owns refresh");
|
|
20671
|
+
return;
|
|
20672
|
+
}
|
|
20673
|
+
logger$6.warn(`[SignalWire] [SW-REFRESH-FALLBACK] Client Bound SAT declined (reason=${result.reason}); using developer-provided refresh handler.`);
|
|
20674
|
+
this.deps.notifier.onWarning({
|
|
20675
|
+
code: "credential_refresh_fallback",
|
|
20676
|
+
source: "CredentialProvider",
|
|
20677
|
+
reason: result.reason,
|
|
20678
|
+
message: `Client Bound SAT activation declined (${result.reason}); using developer-provided refresh.`
|
|
20679
|
+
});
|
|
20680
|
+
} finally {
|
|
20681
|
+
this._activating = false;
|
|
20682
|
+
}
|
|
20683
|
+
}
|
|
20684
|
+
destroy() {
|
|
20685
|
+
this.cancelDeveloperRefresh();
|
|
20686
|
+
this._deviceTokenManager?.destroy();
|
|
20687
|
+
this._deviceTokenManager = void 0;
|
|
20688
|
+
super.destroy();
|
|
20689
|
+
}
|
|
20690
|
+
/**
|
|
20691
|
+
* Races the manager's `activate()` against a hard timeout. A wedged HTTP
|
|
20692
|
+
* layer (e.g., proxy issues) could otherwise hang the activation
|
|
20693
|
+
* indefinitely, leaving the session with no refresh mechanism while the
|
|
20694
|
+
* `_activating` guard blocks subsequent retries.
|
|
20695
|
+
*
|
|
20696
|
+
* On timeout, the manager is paused immediately. The inner `activate()`
|
|
20697
|
+
* may still complete in the background and emit to `_currentToken$`,
|
|
20698
|
+
* which would normally arm the reactive refresh pipeline; pausing
|
|
20699
|
+
* prevents that pipeline from firing while the developer refresh path
|
|
20700
|
+
* is the active mechanism — preserving the "at most one mechanism
|
|
20701
|
+
* armed" invariant. The next `activate()` call resumes the manager.
|
|
20702
|
+
*/
|
|
20703
|
+
async withActivationTimeout(inner) {
|
|
20704
|
+
return new Promise((resolve) => {
|
|
20705
|
+
const timer$4 = setTimeout(() => {
|
|
20706
|
+
this._deviceTokenManager?.pause();
|
|
20707
|
+
resolve({
|
|
20708
|
+
activated: false,
|
|
20709
|
+
reason: "activation-timeout"
|
|
20710
|
+
});
|
|
20711
|
+
}, CREDENTIAL_ACTIVATE_TIMEOUT_MS);
|
|
20712
|
+
inner.then((result) => {
|
|
20713
|
+
clearTimeout(timer$4);
|
|
20714
|
+
resolve(result);
|
|
20715
|
+
}, (error) => {
|
|
20716
|
+
clearTimeout(timer$4);
|
|
20717
|
+
this.deps.notifier.onError(error instanceof Error ? error : new Error(String(error)));
|
|
20718
|
+
resolve({
|
|
20719
|
+
activated: false,
|
|
20720
|
+
reason: "endpoint-failed"
|
|
20721
|
+
});
|
|
20722
|
+
});
|
|
20723
|
+
});
|
|
20724
|
+
}
|
|
20725
|
+
};
|
|
20726
|
+
|
|
20398
20727
|
//#endregion
|
|
20399
20728
|
//#region src/managers/DiagnosticsCollector.ts
|
|
20400
20729
|
const logger$5 = getLogger();
|
|
@@ -20915,6 +21244,10 @@ var TransportManager = class extends Destroyable {
|
|
|
20915
21244
|
if (!isSignalwireRequest(message)) return true;
|
|
20916
21245
|
const eventChannel = message.params.event_channel;
|
|
20917
21246
|
if (!eventChannel) return true;
|
|
21247
|
+
if (message.params.event_type.startsWith("conversation.")) {
|
|
21248
|
+
logger$2.debug(`[Transport] Received conversation event: ${message.params.event_type} (event_channel: ${eventChannel})`);
|
|
21249
|
+
return true;
|
|
21250
|
+
}
|
|
20918
21251
|
const currentProtocol = this._currentProtocol;
|
|
20919
21252
|
if (!currentProtocol) return true;
|
|
20920
21253
|
if (!eventChannel.includes(currentProtocol)) {
|
|
@@ -21108,6 +21441,7 @@ var SignalWire = class extends Destroyable {
|
|
|
21108
21441
|
this._isConnected$ = this.createBehaviorSubject(false);
|
|
21109
21442
|
this._isRegistered$ = this.createBehaviorSubject(false);
|
|
21110
21443
|
this._errors$ = this.createReplaySubject(1);
|
|
21444
|
+
this._warnings$ = this.createReplaySubject(10);
|
|
21111
21445
|
this._options = {};
|
|
21112
21446
|
this._deps = new DependencyContainer();
|
|
21113
21447
|
this._credentialProvider = credentialProvider;
|
|
@@ -21167,6 +21501,27 @@ var SignalWire = class extends Destroyable {
|
|
|
21167
21501
|
*/
|
|
21168
21502
|
async resolveCredentials() {
|
|
21169
21503
|
const fingerprint = await this.initDPoP();
|
|
21504
|
+
this._refreshCoordinator = new CredentialRefreshCoordinator(this._dpopManager, {
|
|
21505
|
+
http: this._deps.http,
|
|
21506
|
+
notifier: {
|
|
21507
|
+
onError: (error) => this._errors$.next(error),
|
|
21508
|
+
onWarning: (warning) => this._warnings$.next(warning),
|
|
21509
|
+
onRefreshExhausted: () => void this.disconnect()
|
|
21510
|
+
},
|
|
21511
|
+
store: {
|
|
21512
|
+
read: () => this._deps.credential,
|
|
21513
|
+
write: (credential) => {
|
|
21514
|
+
this._deps.credential = credential;
|
|
21515
|
+
},
|
|
21516
|
+
merge: (partial) => {
|
|
21517
|
+
this._deps.credential = {
|
|
21518
|
+
...this._deps.credential,
|
|
21519
|
+
...partial
|
|
21520
|
+
};
|
|
21521
|
+
},
|
|
21522
|
+
persist: (credential) => this.persistCredential(credential)
|
|
21523
|
+
}
|
|
21524
|
+
});
|
|
21170
21525
|
if (this._credentialProvider) return this.validateCredentials(this._credentialProvider, void 0, fingerprint);
|
|
21171
21526
|
for (const scope of this._deps.persistSession ? ["local", "session"] : ["session"]) try {
|
|
21172
21527
|
const cached = await this._deps.storage.getItem("sw:cached_credential", scope);
|
|
@@ -21196,7 +21551,16 @@ var SignalWire = class extends Destroyable {
|
|
|
21196
21551
|
logger$1.error("[SignalWire] Provided credentials have expired.");
|
|
21197
21552
|
throw new InvalidCredentialsError("Provided credentials have expired.");
|
|
21198
21553
|
}
|
|
21199
|
-
if (_credentials.expiry_at && credentialProvider?.refresh) this.
|
|
21554
|
+
if (_credentials.expiry_at && credentialProvider?.refresh) this._refreshCoordinator?.scheduleDeveloperRefresh(credentialProvider, _credentials.expiry_at);
|
|
21555
|
+
else if (_credentials.expiry_at && !credentialProvider?.refresh) {
|
|
21556
|
+
logger$1.warn(`[SignalWire] [SW-NO-REFRESH-HANDLER] Credential has expiry_at=${_credentials.expiry_at} but no refresh handler. Session will terminate at expiry unless the SAT has 'sat:refresh' scope.`);
|
|
21557
|
+
this._warnings$.next({
|
|
21558
|
+
code: "credential_no_refresh_handler",
|
|
21559
|
+
source: "CredentialProvider",
|
|
21560
|
+
message: "Credential has expiry_at but no refresh handler. Session will terminate at expiry unless the SAT carries 'sat:refresh' scope.",
|
|
21561
|
+
expiresAt: _credentials.expiry_at
|
|
21562
|
+
});
|
|
21563
|
+
}
|
|
21200
21564
|
this._deps.credential = _credentials;
|
|
21201
21565
|
this.persistCredential(_credentials);
|
|
21202
21566
|
if (this.isConnected && this._clientSession.authenticated && _credentials.token) try {
|
|
@@ -21207,35 +21571,6 @@ var SignalWire = class extends Destroyable {
|
|
|
21207
21571
|
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
21208
21572
|
}
|
|
21209
21573
|
}
|
|
21210
|
-
/**
|
|
21211
|
-
* Schedules credential refresh with exponential backoff retry on failure.
|
|
21212
|
-
* On success, resets attempt counter and schedules the next refresh.
|
|
21213
|
-
* After exhausting retries, emits TokenRefreshError and disconnects.
|
|
21214
|
-
*/
|
|
21215
|
-
scheduleCredentialRefresh(credentialProvider, expiresAt, attempt = 0) {
|
|
21216
|
-
if (this._refreshTimerId !== void 0) clearTimeout(this._refreshTimerId);
|
|
21217
|
-
const refreshInterval = attempt === 0 ? Math.max(expiresAt - Date.now() - CREDENTIAL_REFRESH_BUFFER_MS, 1e3) : Math.min(CREDENTIAL_REFRESH_RETRY_BASE_MS * Math.pow(2, attempt) * (.5 + Math.random() * .5), CREDENTIAL_REFRESH_MAX_DELAY_MS);
|
|
21218
|
-
this._refreshTimerId = setTimeout(async () => {
|
|
21219
|
-
try {
|
|
21220
|
-
if (!credentialProvider.refresh) throw new InvalidCredentialsError("Credential provider does not support refresh");
|
|
21221
|
-
const newCredentials = await credentialProvider.refresh();
|
|
21222
|
-
this._deps.credential = newCredentials;
|
|
21223
|
-
this.persistCredential(newCredentials);
|
|
21224
|
-
logger$1.info("[SignalWire] Credentials refreshed successfully.");
|
|
21225
|
-
if (newCredentials.expiry_at) this.scheduleCredentialRefresh(credentialProvider, newCredentials.expiry_at, 0);
|
|
21226
|
-
} catch (error) {
|
|
21227
|
-
const nextAttempt = attempt + 1;
|
|
21228
|
-
logger$1.error(`[SignalWire] Credential refresh failed (attempt ${nextAttempt}/${CREDENTIAL_REFRESH_MAX_RETRIES}):`, error);
|
|
21229
|
-
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
21230
|
-
if (nextAttempt < CREDENTIAL_REFRESH_MAX_RETRIES) this.scheduleCredentialRefresh(credentialProvider, expiresAt, nextAttempt);
|
|
21231
|
-
else {
|
|
21232
|
-
logger$1.error("[SignalWire] Credential refresh exhausted all retries. Disconnecting.");
|
|
21233
|
-
this._errors$.next(new TokenRefreshError("Credential refresh failed after max retries"));
|
|
21234
|
-
this.disconnect();
|
|
21235
|
-
}
|
|
21236
|
-
}
|
|
21237
|
-
}, refreshInterval);
|
|
21238
|
-
}
|
|
21239
21574
|
/** Persist credential to localStorage when persistSession is enabled. */
|
|
21240
21575
|
persistCredential(credential) {
|
|
21241
21576
|
if (!credential.token) return;
|
|
@@ -21329,6 +21664,7 @@ var SignalWire = class extends Destroyable {
|
|
|
21329
21664
|
logger$1.debug("[SignalWire] Credential expired, refreshing before reconnect");
|
|
21330
21665
|
const newCredentials = await this._credentialProvider.authenticate(fingerprint ? { fingerprint } : void 0);
|
|
21331
21666
|
this._deps.credential = newCredentials;
|
|
21667
|
+
if (newCredentials.expiry_at && this._credentialProvider.refresh) this._refreshCoordinator?.scheduleDeveloperRefresh(this._credentialProvider, newCredentials.expiry_at);
|
|
21332
21668
|
logger$1.debug("[SignalWire] Credential refreshed successfully for reconnect");
|
|
21333
21669
|
} catch (error) {
|
|
21334
21670
|
logger$1.error("[SignalWire] Failed to refresh credentials for reconnect:", error);
|
|
@@ -21340,33 +21676,12 @@ var SignalWire = class extends Destroyable {
|
|
|
21340
21676
|
this._errors$.next(error);
|
|
21341
21677
|
});
|
|
21342
21678
|
await this._clientSession.connect();
|
|
21343
|
-
|
|
21344
|
-
if (this._refreshTimerId) {
|
|
21345
|
-
clearTimeout(this._refreshTimerId);
|
|
21346
|
-
this._refreshTimerId = void 0;
|
|
21347
|
-
logger$1.debug("[SignalWire] Developer refresh disabled — Client Bound SAT activation starting");
|
|
21348
|
-
}
|
|
21349
|
-
this._deviceTokenManager = new DeviceTokenManager(this._dpopManager, this._deps.http, (error) => this._errors$.next(error), () => this._deps.credential);
|
|
21350
|
-
await this._deviceTokenManager.activate(this._deps.user, this._clientSession, (cred) => {
|
|
21351
|
-
this._deps.credential = {
|
|
21352
|
-
...this._deps.credential,
|
|
21353
|
-
...cred
|
|
21354
|
-
};
|
|
21355
|
-
});
|
|
21356
|
-
}
|
|
21679
|
+
await this._refreshCoordinator?.activate(this._deps.user, this._clientSession);
|
|
21357
21680
|
this.subscribeTo(this._clientSession.authenticated$.pipe((0, import_cjs$1.skip)(1), (0, import_cjs$1.filter)(Boolean)), async () => {
|
|
21358
21681
|
try {
|
|
21359
|
-
|
|
21360
|
-
await this._deviceTokenManager.activate(this._deps.user, this._clientSession, (cred) => {
|
|
21361
|
-
this._deps.credential = {
|
|
21362
|
-
...this._deps.credential,
|
|
21363
|
-
...cred
|
|
21364
|
-
};
|
|
21365
|
-
});
|
|
21366
|
-
logger$1.debug("[SignalWire] Client Bound SAT re-activated after reconnect");
|
|
21367
|
-
}
|
|
21682
|
+
await this._refreshCoordinator?.activate(this._deps.user, this._clientSession);
|
|
21368
21683
|
} catch (error) {
|
|
21369
|
-
logger$1.error("[SignalWire]
|
|
21684
|
+
logger$1.error("[SignalWire] Refresh re-arm after reconnect failed (non-fatal):", error);
|
|
21370
21685
|
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
21371
21686
|
}
|
|
21372
21687
|
try {
|
|
@@ -21452,6 +21767,19 @@ var SignalWire = class extends Destroyable {
|
|
|
21452
21767
|
get errors$() {
|
|
21453
21768
|
return this.deferEmission(this._errors$.asObservable());
|
|
21454
21769
|
}
|
|
21770
|
+
/**
|
|
21771
|
+
* Observable stream of non-fatal SDK warnings.
|
|
21772
|
+
*
|
|
21773
|
+
* Subscribe to detect SDK behaviors that affect session liveness or developer-facing
|
|
21774
|
+
* contracts but do not warrant disconnection — e.g., a fallback from Client Bound SAT
|
|
21775
|
+
* refresh to the developer-provided `refresh()` because the SAT lacks `sat:refresh`
|
|
21776
|
+
* scope. Discriminated by `code`.
|
|
21777
|
+
*
|
|
21778
|
+
* Independent from {@link errors$}: existing error consumers are not notified.
|
|
21779
|
+
*/
|
|
21780
|
+
get warnings$() {
|
|
21781
|
+
return this.deferEmission(this._warnings$.asObservable());
|
|
21782
|
+
}
|
|
21455
21783
|
/** Platform WebRTC capabilities detected at construction time. */
|
|
21456
21784
|
get platformCapabilities() {
|
|
21457
21785
|
this._platformCapabilities ??= detectPlatformCapabilities(this._options.webRTCApiProvider);
|
|
@@ -21520,7 +21848,7 @@ var SignalWire = class extends Destroyable {
|
|
|
21520
21848
|
logger$1.warn("[SignalWire] Failed to initialize VisibilityController:", error);
|
|
21521
21849
|
}
|
|
21522
21850
|
try {
|
|
21523
|
-
this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "
|
|
21851
|
+
this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "4.0.0-rc.1" });
|
|
21524
21852
|
} catch (error) {
|
|
21525
21853
|
logger$1.warn("[SignalWire] Failed to initialize DiagnosticsCollector:", error);
|
|
21526
21854
|
}
|
|
@@ -21528,14 +21856,19 @@ var SignalWire = class extends Destroyable {
|
|
|
21528
21856
|
/**
|
|
21529
21857
|
* Disconnects the WebSocket and tears down the current session.
|
|
21530
21858
|
*
|
|
21859
|
+
* Ends the session identified by the protocol and clears its persisted
|
|
21860
|
+
* resume state (`authorization_state` + protocol) and attach records
|
|
21861
|
+
* together — a later {@link connect} with the same credentials starts a
|
|
21862
|
+
* fresh session and cannot reattach to the ended session's calls.
|
|
21863
|
+
* Credentials and device preferences are preserved. To temporarily stop
|
|
21864
|
+
* receiving inbound calls while keeping the session alive, use
|
|
21865
|
+
* `unregister()` instead.
|
|
21866
|
+
*
|
|
21531
21867
|
* The client can be reconnected by calling {@link connect} again,
|
|
21532
21868
|
* which creates a fresh transport and session.
|
|
21533
21869
|
*/
|
|
21534
21870
|
async disconnect() {
|
|
21535
|
-
|
|
21536
|
-
clearTimeout(this._refreshTimerId);
|
|
21537
|
-
this._refreshTimerId = void 0;
|
|
21538
|
-
}
|
|
21871
|
+
this._refreshCoordinator?.suspend();
|
|
21539
21872
|
this._diagnosticsCollector?.record("connection", "disconnected");
|
|
21540
21873
|
await this.teardownTransportAndSession();
|
|
21541
21874
|
this._isConnected$.next(false);
|
|
@@ -21922,15 +22255,20 @@ var SignalWire = class extends Destroyable {
|
|
|
21922
22255
|
prefs.preferredVideoInput = null;
|
|
21923
22256
|
await this._deviceController.clearDeviceState();
|
|
21924
22257
|
}
|
|
21925
|
-
/**
|
|
22258
|
+
/**
|
|
22259
|
+
* Destroys the client, clearing timers and releasing all resources.
|
|
22260
|
+
*
|
|
22261
|
+
* Intentionally destroying the client ends its session: the resume state
|
|
22262
|
+
* (`authorization_state` + protocol) and the attach records are both
|
|
22263
|
+
* cleared. Credentials and device preferences are preserved — use
|
|
22264
|
+
* {@link resetToDefaults} for a full wipe. To temporarily stop receiving
|
|
22265
|
+
* inbound calls while keeping the session alive, use `unregister()`.
|
|
22266
|
+
*/
|
|
21926
22267
|
destroy() {
|
|
21927
|
-
|
|
21928
|
-
|
|
21929
|
-
this._refreshTimerId = void 0;
|
|
21930
|
-
}
|
|
21931
|
-
this._deviceTokenManager?.destroy();
|
|
22268
|
+
this._refreshCoordinator?.destroy();
|
|
22269
|
+
this._refreshCoordinator = void 0;
|
|
21932
22270
|
this._dpopManager?.destroy();
|
|
21933
|
-
|
|
22271
|
+
this._clientSession.teardownSessionState();
|
|
21934
22272
|
this._transport.destroy();
|
|
21935
22273
|
this._clientSession.destroy();
|
|
21936
22274
|
try {
|
|
@@ -22045,7 +22383,7 @@ var StaticCredentialProvider = class {
|
|
|
22045
22383
|
/**
|
|
22046
22384
|
* Library version from package.json, injected at build time.
|
|
22047
22385
|
*/
|
|
22048
|
-
const version = "
|
|
22386
|
+
const version = "4.0.0-rc.1";
|
|
22049
22387
|
/**
|
|
22050
22388
|
* Flag indicating the library has been loaded and is ready to use.
|
|
22051
22389
|
* For UMD builds: `window.SignalWire.ready`
|
|
@@ -22067,7 +22405,7 @@ const ready = true;
|
|
|
22067
22405
|
*/
|
|
22068
22406
|
const emitReadyEvent = () => {
|
|
22069
22407
|
if (typeof window !== "undefined") {
|
|
22070
|
-
const event = new CustomEvent("signalwire:js:ready", { detail: { version: "
|
|
22408
|
+
const event = new CustomEvent("signalwire:js:ready", { detail: { version: "4.0.0-rc.1" } });
|
|
22071
22409
|
window.dispatchEvent(event);
|
|
22072
22410
|
}
|
|
22073
22411
|
};
|