@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.mjs
CHANGED
|
@@ -9464,9 +9464,9 @@ var require_loglevel = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
9464
9464
|
defaultLogger$1 = new Logger();
|
|
9465
9465
|
defaultLogger$1.getLogger = function getLogger$1(name) {
|
|
9466
9466
|
if (typeof name !== "symbol" && typeof name !== "string" || name === "") throw new TypeError("You must supply a name when creating a logger.");
|
|
9467
|
-
var logger$
|
|
9468
|
-
if (!logger$
|
|
9469
|
-
return logger$
|
|
9467
|
+
var logger$33 = _loggersByName[name];
|
|
9468
|
+
if (!logger$33) logger$33 = _loggersByName[name] = new Logger(name, defaultLogger$1.methodFactory);
|
|
9469
|
+
return logger$33;
|
|
9470
9470
|
};
|
|
9471
9471
|
var _log = typeof window !== undefinedType ? window.log : void 0;
|
|
9472
9472
|
defaultLogger$1.noConflict = function() {
|
|
@@ -9502,8 +9502,8 @@ const defaultLoggerLevel = defaultLogger.levels.WARN;
|
|
|
9502
9502
|
defaultLogger.setLevel(defaultLoggerLevel);
|
|
9503
9503
|
let userLogger = null;
|
|
9504
9504
|
/** Replace the built-in logger with a custom implementation. Pass `null` to restore defaults. */
|
|
9505
|
-
const setLogger = (logger$
|
|
9506
|
-
userLogger = logger$
|
|
9505
|
+
const setLogger = (logger$33) => {
|
|
9506
|
+
userLogger = logger$33;
|
|
9507
9507
|
};
|
|
9508
9508
|
let debugOptions = {};
|
|
9509
9509
|
/** Configure debug options (e.g., `{ logWsTraffic: true }`). */
|
|
@@ -9547,8 +9547,8 @@ const wsTraffic = (options) => {
|
|
|
9547
9547
|
loggerInstance.debug(`${options.type.toUpperCase()}: \n`, msg, "\n");
|
|
9548
9548
|
};
|
|
9549
9549
|
const getLogger = () => {
|
|
9550
|
-
const logger$
|
|
9551
|
-
return new Proxy(logger$
|
|
9550
|
+
const logger$33 = getLoggerInstance();
|
|
9551
|
+
return new Proxy(logger$33, { get(_target, prop, _receiver) {
|
|
9552
9552
|
if (prop === "wsTraffic") return wsTraffic;
|
|
9553
9553
|
const instance = getLoggerInstance();
|
|
9554
9554
|
const value = Reflect.get(instance, prop);
|
|
@@ -9600,7 +9600,7 @@ const asyncRetry = async ({ asyncCallable, maxRetries: retries = DEFAULT_MAX_RET
|
|
|
9600
9600
|
|
|
9601
9601
|
//#endregion
|
|
9602
9602
|
//#region src/controllers/HTTPRequestController.ts
|
|
9603
|
-
const logger$
|
|
9603
|
+
const logger$32 = getLogger();
|
|
9604
9604
|
const GET_PARAMS = {
|
|
9605
9605
|
method: "GET",
|
|
9606
9606
|
headers: { Accept: "application/json" }
|
|
@@ -9664,7 +9664,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9664
9664
|
this._responses$.next(response);
|
|
9665
9665
|
return response;
|
|
9666
9666
|
} catch (error) {
|
|
9667
|
-
logger$
|
|
9667
|
+
logger$32.error("[HTTPRequestController] Request error:", error);
|
|
9668
9668
|
this._status$.next("error");
|
|
9669
9669
|
const err = error instanceof Error ? error : new Error("HTTP request failed", { cause: error });
|
|
9670
9670
|
this._errors$.next(err);
|
|
@@ -9691,7 +9691,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9691
9691
|
const url = this.buildURL(request.url);
|
|
9692
9692
|
const headers = this.buildHeaders(request.headers);
|
|
9693
9693
|
const timeout$5 = request.timeout ?? this.requestTimeout;
|
|
9694
|
-
logger$
|
|
9694
|
+
logger$32.debug("[HTTPRequestController] Executing request:", {
|
|
9695
9695
|
method: request.method,
|
|
9696
9696
|
url,
|
|
9697
9697
|
headers: Object.keys(headers).reduce((acc, key) => {
|
|
@@ -9711,7 +9711,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9711
9711
|
});
|
|
9712
9712
|
clearTimeout(timeoutId);
|
|
9713
9713
|
const httpResponse = await this.convertResponse(response);
|
|
9714
|
-
logger$
|
|
9714
|
+
logger$32.debug("[HTTPRequestController] Response received:", {
|
|
9715
9715
|
status: response.status,
|
|
9716
9716
|
statusText: response.statusText,
|
|
9717
9717
|
headers: [...response.headers.entries()],
|
|
@@ -9721,7 +9721,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9721
9721
|
} catch (error) {
|
|
9722
9722
|
clearTimeout(timeoutId);
|
|
9723
9723
|
if (error instanceof Error && error.name === "AbortError") throw new RequestTimeoutError(`Request timeout after ${timeout$5}ms`, { cause: error });
|
|
9724
|
-
logger$
|
|
9724
|
+
logger$32.error("[HTTPRequestController] Request failed:", error);
|
|
9725
9725
|
throw error;
|
|
9726
9726
|
}
|
|
9727
9727
|
}
|
|
@@ -9735,8 +9735,8 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9735
9735
|
const credential = this.getCredential();
|
|
9736
9736
|
if (credential.token) {
|
|
9737
9737
|
headers.Authorization = `Bearer ${credential.token}`;
|
|
9738
|
-
logger$
|
|
9739
|
-
} else logger$
|
|
9738
|
+
logger$32.debug("[HTTPRequestController] Using Bearer token auth, token length:", credential.token.length);
|
|
9739
|
+
} else logger$32.warn("[HTTPRequestController] No credentials available for authentication");
|
|
9740
9740
|
return headers;
|
|
9741
9741
|
}
|
|
9742
9742
|
/**
|
|
@@ -9906,6 +9906,13 @@ const CREDENTIAL_REFRESH_RETRY_BASE_MS = 1e3;
|
|
|
9906
9906
|
const CREDENTIAL_REFRESH_MAX_DELAY_MS = 3e4;
|
|
9907
9907
|
/** Buffer in milliseconds before token expiry to trigger refresh. */
|
|
9908
9908
|
const CREDENTIAL_REFRESH_BUFFER_MS = 5e3;
|
|
9909
|
+
/**
|
|
9910
|
+
* Maximum time the coordinator will wait for `DeviceTokenManager.activate()`
|
|
9911
|
+
* to resolve before treating the activation as failed and falling back to
|
|
9912
|
+
* the developer-provided refresh path. Prevents a wedged HTTP layer from
|
|
9913
|
+
* leaving the session with no active refresh mechanism.
|
|
9914
|
+
*/
|
|
9915
|
+
const CREDENTIAL_ACTIVATE_TIMEOUT_MS = 3e4;
|
|
9909
9916
|
/** JSON-RPC error code for requester validation failure (corrupted auth state). */
|
|
9910
9917
|
const RPC_ERROR_REQUESTER_VALIDATION_FAILED = -32003;
|
|
9911
9918
|
/** JSON-RPC error code for invalid params (e.g., missing authentication block). */
|
|
@@ -10008,7 +10015,7 @@ function fromMsToSec(milliseconds) {
|
|
|
10008
10015
|
|
|
10009
10016
|
//#endregion
|
|
10010
10017
|
//#region src/containers/PreferencesContainer.ts
|
|
10011
|
-
const logger$
|
|
10018
|
+
const logger$31 = getLogger();
|
|
10012
10019
|
var PreferencesContainer = class PreferencesContainer {
|
|
10013
10020
|
static get instance() {
|
|
10014
10021
|
this._instance ??= new PreferencesContainer();
|
|
@@ -10670,7 +10677,7 @@ var ClientPreferences = class {
|
|
|
10670
10677
|
if (!this._storage) return;
|
|
10671
10678
|
const data = collectStoredPreferences();
|
|
10672
10679
|
this._storage.setItem(PREFERENCES_STORAGE_KEY, data, "local").catch((error) => {
|
|
10673
|
-
logger$
|
|
10680
|
+
logger$31.error(`[ClientPreferences] Failed to save preferences: ${String(error)}`);
|
|
10674
10681
|
});
|
|
10675
10682
|
}
|
|
10676
10683
|
/** Loads preferences from storage and applies them to the container. */
|
|
@@ -10679,7 +10686,7 @@ var ClientPreferences = class {
|
|
|
10679
10686
|
this._storage.getItem(PREFERENCES_STORAGE_KEY, "local").then((stored) => {
|
|
10680
10687
|
if (stored) applyStoredPreferences(stored);
|
|
10681
10688
|
}).catch((error) => {
|
|
10682
|
-
logger$
|
|
10689
|
+
logger$31.error(`[ClientPreferences] Failed to load preferences: ${String(error)}`);
|
|
10683
10690
|
});
|
|
10684
10691
|
}
|
|
10685
10692
|
};
|
|
@@ -10701,7 +10708,7 @@ function toError(value) {
|
|
|
10701
10708
|
//#endregion
|
|
10702
10709
|
//#region src/controllers/NavigatorDeviceController.ts
|
|
10703
10710
|
var import_cjs$29 = require_cjs();
|
|
10704
|
-
const logger$
|
|
10711
|
+
const logger$30 = getLogger();
|
|
10705
10712
|
/** Maps a device kind to its storage key. */
|
|
10706
10713
|
const DEVICE_STORAGE_KEYS = {
|
|
10707
10714
|
audioinput: DEVICE_STORAGE_KEY_AUDIO_INPUT,
|
|
@@ -10723,7 +10730,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10723
10730
|
super();
|
|
10724
10731
|
this.webRTCApiProvider = webRTCApiProvider;
|
|
10725
10732
|
this.deviceChangeHandler = () => {
|
|
10726
|
-
logger$
|
|
10733
|
+
logger$30.debug("[DeviceController] Device change detected");
|
|
10727
10734
|
this.enumerateDevices();
|
|
10728
10735
|
};
|
|
10729
10736
|
this._devicesState$ = this.createBehaviorSubject(initialDevicesState);
|
|
@@ -10788,13 +10795,13 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10788
10795
|
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$)));
|
|
10789
10796
|
}
|
|
10790
10797
|
get selectedAudioInputDevice$() {
|
|
10791
|
-
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$
|
|
10798
|
+
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))));
|
|
10792
10799
|
}
|
|
10793
10800
|
get selectedAudioOutputDevice$() {
|
|
10794
|
-
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$
|
|
10801
|
+
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))));
|
|
10795
10802
|
}
|
|
10796
10803
|
get selectedVideoInputDevice$() {
|
|
10797
|
-
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$
|
|
10804
|
+
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))));
|
|
10798
10805
|
}
|
|
10799
10806
|
get selectedAudioInputDevice() {
|
|
10800
10807
|
if (this._audioInputDisabled$.value) return null;
|
|
@@ -10869,7 +10876,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10869
10876
|
if (device) this.persistDeviceSelection("audioinput", device);
|
|
10870
10877
|
}
|
|
10871
10878
|
selectVideoInputDevice(device) {
|
|
10872
|
-
logger$
|
|
10879
|
+
logger$30.debug("[DeviceController] Setting selected video input device:", device);
|
|
10873
10880
|
if (this._videoInputDisabled$.value && device) this._videoInputDisabled$.next(false);
|
|
10874
10881
|
const previous = this._selectedDevicesState$.value.videoinput;
|
|
10875
10882
|
if (previous && previous.deviceId !== device?.deviceId) this._deviceHistory.push("videoinput", previous);
|
|
@@ -10926,7 +10933,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10926
10933
|
}
|
|
10927
10934
|
const fromHistory = this._deviceHistory.findInHistory(kind, devices);
|
|
10928
10935
|
if (fromHistory) {
|
|
10929
|
-
logger$
|
|
10936
|
+
logger$30.debug(`[DeviceController] Device disappeared, falling back to history: ${fromHistory.label}`);
|
|
10930
10937
|
this.emitDeviceRecovered(kind, selected, fromHistory, "device_disconnected");
|
|
10931
10938
|
return fromHistory;
|
|
10932
10939
|
}
|
|
@@ -10979,7 +10986,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10979
10986
|
try {
|
|
10980
10987
|
await this._storageManager.setItem(DEVICE_STORAGE_KEYS[kind], stored, "local");
|
|
10981
10988
|
} catch (error) {
|
|
10982
|
-
logger$
|
|
10989
|
+
logger$30.error(`[DeviceController] Failed to persist device selection for ${kind}:`, error);
|
|
10983
10990
|
}
|
|
10984
10991
|
}
|
|
10985
10992
|
async loadPersistedDevices() {
|
|
@@ -10995,7 +11002,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10995
11002
|
[kind]: stored
|
|
10996
11003
|
};
|
|
10997
11004
|
} catch (error) {
|
|
10998
|
-
logger$
|
|
11005
|
+
logger$30.error(`[DeviceController] Failed to load persisted device for ${kind}:`, error);
|
|
10999
11006
|
}
|
|
11000
11007
|
}
|
|
11001
11008
|
/** Clears device history, persisted selections, and re-enumerates devices. */
|
|
@@ -11013,7 +11020,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
11013
11020
|
this.disableDeviceMonitoring();
|
|
11014
11021
|
this.webRTCApiProvider.mediaDevices.addEventListener("devicechange", this.deviceChangeHandler);
|
|
11015
11022
|
if (PreferencesContainer.instance.devicePollingInterval > 0) this._devicesPoolingSubscription = (0, import_cjs$29.interval)(PreferencesContainer.instance.devicePollingInterval).subscribe(() => {
|
|
11016
|
-
logger$
|
|
11023
|
+
logger$30.debug("[DeviceController] Polling devices due to interval");
|
|
11017
11024
|
this.enumerateDevices();
|
|
11018
11025
|
});
|
|
11019
11026
|
this.enumerateDevices();
|
|
@@ -11039,13 +11046,13 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
11039
11046
|
videoinput: []
|
|
11040
11047
|
});
|
|
11041
11048
|
this._devicesState$.next(devicesByKind);
|
|
11042
|
-
logger$
|
|
11049
|
+
logger$30.debug("[DeviceController] Devices enumerated:", {
|
|
11043
11050
|
audioInputs: devicesByKind.audioinput.length,
|
|
11044
11051
|
audioOutputs: devicesByKind.audiooutput.length,
|
|
11045
11052
|
videoInputs: devicesByKind.videoinput.length
|
|
11046
11053
|
});
|
|
11047
11054
|
} catch (error) {
|
|
11048
|
-
logger$
|
|
11055
|
+
logger$30.error("[DeviceController] Failed to enumerate devices:", error);
|
|
11049
11056
|
this._errors$.next(toError(error));
|
|
11050
11057
|
}
|
|
11051
11058
|
}
|
|
@@ -11061,7 +11068,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
11061
11068
|
stream.getTracks().forEach((t) => t.stop());
|
|
11062
11069
|
return capabilities;
|
|
11063
11070
|
} catch (error) {
|
|
11064
|
-
logger$
|
|
11071
|
+
logger$30.error("[DeviceController] Failed to get device capabilities:", error);
|
|
11065
11072
|
this._errors$.next(toError(error));
|
|
11066
11073
|
throw error;
|
|
11067
11074
|
}
|
|
@@ -11312,7 +11319,7 @@ var DependencyContainer = class {
|
|
|
11312
11319
|
|
|
11313
11320
|
//#endregion
|
|
11314
11321
|
//#region src/controllers/CryptoController.ts
|
|
11315
|
-
const logger$
|
|
11322
|
+
const logger$29 = getLogger();
|
|
11316
11323
|
const DPOP_DB_NAME = "sw-dpop";
|
|
11317
11324
|
const DPOP_DB_VERSION = 1;
|
|
11318
11325
|
const DPOP_STORE_NAME = "keys";
|
|
@@ -11371,7 +11378,7 @@ async function loadKeyPairFromDB() {
|
|
|
11371
11378
|
tx.oncomplete = () => db.close();
|
|
11372
11379
|
});
|
|
11373
11380
|
} catch (error) {
|
|
11374
|
-
logger$
|
|
11381
|
+
logger$29.warn("[DPoP] Failed to load key pair from IndexedDB:", error);
|
|
11375
11382
|
return null;
|
|
11376
11383
|
}
|
|
11377
11384
|
}
|
|
@@ -11391,7 +11398,7 @@ async function saveKeyPairToDB(keyPair) {
|
|
|
11391
11398
|
};
|
|
11392
11399
|
});
|
|
11393
11400
|
} catch (error) {
|
|
11394
|
-
logger$
|
|
11401
|
+
logger$29.warn("[DPoP] Failed to save key pair to IndexedDB:", error);
|
|
11395
11402
|
}
|
|
11396
11403
|
}
|
|
11397
11404
|
async function deleteKeyPairFromDB() {
|
|
@@ -11410,7 +11417,7 @@ async function deleteKeyPairFromDB() {
|
|
|
11410
11417
|
};
|
|
11411
11418
|
});
|
|
11412
11419
|
} catch (error) {
|
|
11413
|
-
logger$
|
|
11420
|
+
logger$29.warn("[DPoP] Failed to delete key pair from IndexedDB:", error);
|
|
11414
11421
|
}
|
|
11415
11422
|
}
|
|
11416
11423
|
/**
|
|
@@ -11470,13 +11477,13 @@ var CryptoController = class {
|
|
|
11470
11477
|
this._publicJwk = await crypto.subtle.exportKey("jwk", stored.publicKey);
|
|
11471
11478
|
this._fingerprint = await computeJwkThumbprint(this._publicJwk);
|
|
11472
11479
|
this._initialized = true;
|
|
11473
|
-
logger$
|
|
11480
|
+
logger$29.debug("[DPoP] Key pair restored from IndexedDB, fingerprint:", this._fingerprint);
|
|
11474
11481
|
return this._fingerprint;
|
|
11475
11482
|
} catch (error) {
|
|
11476
|
-
logger$
|
|
11483
|
+
logger$29.warn("[DPoP] Stored key pair unusable, generating new one:", error);
|
|
11477
11484
|
await deleteKeyPairFromDB();
|
|
11478
11485
|
}
|
|
11479
|
-
logger$
|
|
11486
|
+
logger$29.debug("[DPoP] Generating RSA key pair");
|
|
11480
11487
|
this._keyPair = await crypto.subtle.generateKey({
|
|
11481
11488
|
name: "RSASSA-PKCS1-v1_5",
|
|
11482
11489
|
modulusLength: 2048,
|
|
@@ -11491,7 +11498,7 @@ var CryptoController = class {
|
|
|
11491
11498
|
this._fingerprint = await computeJwkThumbprint(this._publicJwk);
|
|
11492
11499
|
this._initialized = true;
|
|
11493
11500
|
await saveKeyPairToDB(this._keyPair);
|
|
11494
|
-
logger$
|
|
11501
|
+
logger$29.debug("[DPoP] Key pair generated and persisted, fingerprint:", this._fingerprint);
|
|
11495
11502
|
return this._fingerprint;
|
|
11496
11503
|
}
|
|
11497
11504
|
/**
|
|
@@ -11557,7 +11564,7 @@ var CryptoController = class {
|
|
|
11557
11564
|
this._fingerprint = null;
|
|
11558
11565
|
this._initialized = false;
|
|
11559
11566
|
deleteKeyPairFromDB();
|
|
11560
|
-
logger$
|
|
11567
|
+
logger$29.debug("[DPoP] Controller destroyed");
|
|
11561
11568
|
}
|
|
11562
11569
|
get publicJwk() {
|
|
11563
11570
|
if (!this._publicJwk) throw new DPoPInitError("CryptoController not initialized. Call init() first.");
|
|
@@ -11581,7 +11588,7 @@ var CryptoController = class {
|
|
|
11581
11588
|
//#endregion
|
|
11582
11589
|
//#region src/controllers/NetworkMonitor.ts
|
|
11583
11590
|
var import_cjs$28 = require_cjs();
|
|
11584
|
-
const logger$
|
|
11591
|
+
const logger$28 = getLogger();
|
|
11585
11592
|
/**
|
|
11586
11593
|
* Safely check whether we are running in a browser environment
|
|
11587
11594
|
* with `window` and the relevant event targets.
|
|
@@ -11638,7 +11645,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11638
11645
|
}
|
|
11639
11646
|
attachListeners() {
|
|
11640
11647
|
if (!hasBrowserNetworkEvents()) {
|
|
11641
|
-
logger$
|
|
11648
|
+
logger$28.debug("NetworkMonitor: no browser environment detected, skipping event listeners");
|
|
11642
11649
|
return;
|
|
11643
11650
|
}
|
|
11644
11651
|
window.addEventListener("online", this._onOnline);
|
|
@@ -11646,7 +11653,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11646
11653
|
const connection = getNetworkConnection();
|
|
11647
11654
|
if (connection) connection.addEventListener("change", this._onConnectionChange);
|
|
11648
11655
|
this._listenersAttached = true;
|
|
11649
|
-
logger$
|
|
11656
|
+
logger$28.debug("NetworkMonitor: event listeners attached");
|
|
11650
11657
|
}
|
|
11651
11658
|
removeListeners() {
|
|
11652
11659
|
if (!this._listenersAttached) return;
|
|
@@ -11657,10 +11664,10 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11657
11664
|
if (connection) connection.removeEventListener("change", this._onConnectionChange);
|
|
11658
11665
|
}
|
|
11659
11666
|
this._listenersAttached = false;
|
|
11660
|
-
logger$
|
|
11667
|
+
logger$28.debug("NetworkMonitor: event listeners removed");
|
|
11661
11668
|
}
|
|
11662
11669
|
handleOnline() {
|
|
11663
|
-
logger$
|
|
11670
|
+
logger$28.info("NetworkMonitor: browser went online");
|
|
11664
11671
|
this._isOnline$.next(true);
|
|
11665
11672
|
this._networkChange$.next({
|
|
11666
11673
|
type: "online",
|
|
@@ -11669,7 +11676,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11669
11676
|
});
|
|
11670
11677
|
}
|
|
11671
11678
|
handleOffline() {
|
|
11672
|
-
logger$
|
|
11679
|
+
logger$28.info("NetworkMonitor: browser went offline");
|
|
11673
11680
|
this._isOnline$.next(false);
|
|
11674
11681
|
this._networkChange$.next({
|
|
11675
11682
|
type: "offline",
|
|
@@ -11678,7 +11685,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11678
11685
|
}
|
|
11679
11686
|
handleConnectionChange() {
|
|
11680
11687
|
const networkType = getNetworkType();
|
|
11681
|
-
logger$
|
|
11688
|
+
logger$28.info(`NetworkMonitor: connection changed — effectiveType=${networkType ?? "unknown"}`);
|
|
11682
11689
|
this._networkChange$.next({
|
|
11683
11690
|
type: "connection_change",
|
|
11684
11691
|
timestamp: Date.now(),
|
|
@@ -11794,7 +11801,7 @@ function getNavigatorMediaDevices() {
|
|
|
11794
11801
|
//#endregion
|
|
11795
11802
|
//#region src/controllers/PreflightRunner.ts
|
|
11796
11803
|
var import_cjs$27 = require_cjs();
|
|
11797
|
-
const logger$
|
|
11804
|
+
const logger$27 = getLogger();
|
|
11798
11805
|
const DEFAULT_MEDIA_TEST_DURATION_S = 10;
|
|
11799
11806
|
const ICE_GATHERING_TIMEOUT_MS = 1e4;
|
|
11800
11807
|
const SIGNALING_RTT_TIMEOUT_MS = 5e3;
|
|
@@ -11843,7 +11850,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11843
11850
|
if (!this._options.skipMediaTest) try {
|
|
11844
11851
|
bandwidth = await this.testMediaBandwidth(destination);
|
|
11845
11852
|
} catch (error) {
|
|
11846
|
-
logger$
|
|
11853
|
+
logger$27.warn("[PreflightRunner] Media bandwidth test failed:", error);
|
|
11847
11854
|
warnings.push("Media bandwidth test failed");
|
|
11848
11855
|
}
|
|
11849
11856
|
return {
|
|
@@ -11855,7 +11862,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11855
11862
|
warnings
|
|
11856
11863
|
};
|
|
11857
11864
|
} catch (error) {
|
|
11858
|
-
logger$
|
|
11865
|
+
logger$27.error("[PreflightRunner] Preflight test failed:", error);
|
|
11859
11866
|
throw new PreflightError("preflight", error instanceof Error ? error : new Error(String(error)));
|
|
11860
11867
|
} finally {
|
|
11861
11868
|
this.destroy();
|
|
@@ -11886,7 +11893,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11886
11893
|
if (track.kind === "video" && track.readyState === "live") videoWorking = true;
|
|
11887
11894
|
}
|
|
11888
11895
|
} catch (error) {
|
|
11889
|
-
logger$
|
|
11896
|
+
logger$27.warn("[PreflightRunner] Device test failed:", error);
|
|
11890
11897
|
} finally {
|
|
11891
11898
|
if (audioStream) audioStream.getTracks().forEach((t) => t.stop());
|
|
11892
11899
|
}
|
|
@@ -11944,7 +11951,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11944
11951
|
rttMs
|
|
11945
11952
|
};
|
|
11946
11953
|
} catch (error) {
|
|
11947
|
-
logger$
|
|
11954
|
+
logger$27.warn("[PreflightRunner] ICE connectivity test failed:", error);
|
|
11948
11955
|
return {
|
|
11949
11956
|
type: "failed",
|
|
11950
11957
|
turnReachable: false,
|
|
@@ -11992,7 +11999,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11992
11999
|
//#endregion
|
|
11993
12000
|
//#region src/controllers/VisibilityController.ts
|
|
11994
12001
|
var import_cjs$26 = require_cjs();
|
|
11995
|
-
const logger$
|
|
12002
|
+
const logger$26 = getLogger();
|
|
11996
12003
|
/**
|
|
11997
12004
|
* Checks whether the document visibility API is available.
|
|
11998
12005
|
*/
|
|
@@ -12029,8 +12036,8 @@ var VisibilityController = class extends Destroyable {
|
|
|
12029
12036
|
this._boundHandler = this._handleVisibilityChange.bind(this);
|
|
12030
12037
|
if (this._hasVisibilityApi) {
|
|
12031
12038
|
document.addEventListener("visibilitychange", this._boundHandler);
|
|
12032
|
-
logger$
|
|
12033
|
-
} else logger$
|
|
12039
|
+
logger$26.debug("VisibilityController: listening for visibilitychange events");
|
|
12040
|
+
} else logger$26.debug("VisibilityController: document visibility API not available, defaulting to visible");
|
|
12034
12041
|
}
|
|
12035
12042
|
/**
|
|
12036
12043
|
* Observable of the current visibility state.
|
|
@@ -12055,7 +12062,7 @@ var VisibilityController = class extends Destroyable {
|
|
|
12055
12062
|
destroy() {
|
|
12056
12063
|
if (this._hasVisibilityApi) {
|
|
12057
12064
|
document.removeEventListener("visibilitychange", this._boundHandler);
|
|
12058
|
-
logger$
|
|
12065
|
+
logger$26.debug("VisibilityController: removed visibilitychange listener");
|
|
12059
12066
|
}
|
|
12060
12067
|
super.destroy();
|
|
12061
12068
|
}
|
|
@@ -12073,7 +12080,7 @@ var VisibilityController = class extends Destroyable {
|
|
|
12073
12080
|
timestamp: Date.now()
|
|
12074
12081
|
};
|
|
12075
12082
|
this._visibilityChange$.next(changeEvent);
|
|
12076
|
-
logger$
|
|
12083
|
+
logger$26.debug("VisibilityController: visibility changed", {
|
|
12077
12084
|
from: previousState,
|
|
12078
12085
|
to: newState
|
|
12079
12086
|
});
|
|
@@ -12314,7 +12321,7 @@ const RPCEventAckResponse = (id) => makeRPCResponse({
|
|
|
12314
12321
|
|
|
12315
12322
|
//#endregion
|
|
12316
12323
|
//#region src/managers/AttachManager.ts
|
|
12317
|
-
const logger$
|
|
12324
|
+
const logger$25 = getLogger();
|
|
12318
12325
|
var AttachManager = class {
|
|
12319
12326
|
constructor(storage, deviceController, reconnectCallsTimeout, attachKey) {
|
|
12320
12327
|
this.storage = storage;
|
|
@@ -12335,7 +12342,7 @@ var AttachManager = class {
|
|
|
12335
12342
|
try {
|
|
12336
12343
|
return await this.storage.getItem(this.attachKey) ?? {};
|
|
12337
12344
|
} catch (error) {
|
|
12338
|
-
logger$
|
|
12345
|
+
logger$25.warn("[AttachManager] Failed to retrieve attached calls from storage", error);
|
|
12339
12346
|
return {};
|
|
12340
12347
|
}
|
|
12341
12348
|
}
|
|
@@ -12343,7 +12350,7 @@ var AttachManager = class {
|
|
|
12343
12350
|
try {
|
|
12344
12351
|
await this.storage.setItem(this.attachKey, attached);
|
|
12345
12352
|
} catch (error) {
|
|
12346
|
-
logger$
|
|
12353
|
+
logger$25.warn("[AttachManager] Failed to write attached calls to storage", error);
|
|
12347
12354
|
}
|
|
12348
12355
|
}
|
|
12349
12356
|
/**
|
|
@@ -12362,7 +12369,7 @@ var AttachManager = class {
|
|
|
12362
12369
|
}
|
|
12363
12370
|
async attach(call) {
|
|
12364
12371
|
if (!call.to) {
|
|
12365
|
-
logger$
|
|
12372
|
+
logger$25.warn("[AttachManager] Skip attach for calls with no destination");
|
|
12366
12373
|
return;
|
|
12367
12374
|
}
|
|
12368
12375
|
const destination = call.to;
|
|
@@ -12415,15 +12422,15 @@ var AttachManager = class {
|
|
|
12415
12422
|
callId,
|
|
12416
12423
|
...options
|
|
12417
12424
|
});
|
|
12418
|
-
logger$
|
|
12425
|
+
logger$25.info(`[AttachManager] Reattached call ${callId} (attempt ${attempt})`);
|
|
12419
12426
|
succeeded = true;
|
|
12420
12427
|
break;
|
|
12421
12428
|
} catch (error) {
|
|
12422
|
-
logger$
|
|
12429
|
+
logger$25.warn(`[AttachManager] Reattach attempt ${attempt}/3 failed for call ${callId}:`, error);
|
|
12423
12430
|
if (attempt < 3) await new Promise((r) => setTimeout(r, (attempt + 1) * 1e3));
|
|
12424
12431
|
}
|
|
12425
12432
|
if (!succeeded) {
|
|
12426
|
-
logger$
|
|
12433
|
+
logger$25.warn(`[AttachManager] Reattach failed after 3 attempts for call ${callId}, removing reference`);
|
|
12427
12434
|
await this.detach({
|
|
12428
12435
|
id: callId,
|
|
12429
12436
|
mediaDirections: attachment.mediaDirections
|
|
@@ -13360,7 +13367,9 @@ const DEFAULT_MEMBER_CAPABILITIES = {
|
|
|
13360
13367
|
position: false,
|
|
13361
13368
|
meta: false,
|
|
13362
13369
|
remove: false,
|
|
13363
|
-
audioFlags: false
|
|
13370
|
+
audioFlags: false,
|
|
13371
|
+
denoise: false,
|
|
13372
|
+
lowbitrate: false
|
|
13364
13373
|
};
|
|
13365
13374
|
/**
|
|
13366
13375
|
* Default call capabilities with no permissions
|
|
@@ -13433,7 +13442,9 @@ function computeMemberCapabilities(flags, memberType) {
|
|
|
13433
13442
|
position: hasBooleanCapability(flags, memberType, null, "position"),
|
|
13434
13443
|
meta: hasBooleanCapability(flags, memberType, null, "meta"),
|
|
13435
13444
|
remove: hasBooleanCapability(flags, memberType, null, "remove"),
|
|
13436
|
-
audioFlags: hasBooleanCapability(flags, memberType, null, "audioflags")
|
|
13445
|
+
audioFlags: hasBooleanCapability(flags, memberType, null, "audioflags"),
|
|
13446
|
+
denoise: hasBooleanCapability(flags, memberType, null, "denoise"),
|
|
13447
|
+
lowbitrate: hasBooleanCapability(flags, memberType, null, "lowbitrate")
|
|
13437
13448
|
};
|
|
13438
13449
|
}
|
|
13439
13450
|
/**
|
|
@@ -13590,7 +13601,7 @@ function toggleHandraiseMethod(is) {
|
|
|
13590
13601
|
|
|
13591
13602
|
//#endregion
|
|
13592
13603
|
//#region src/core/entities/Participant.ts
|
|
13593
|
-
const logger$
|
|
13604
|
+
const logger$24 = getLogger();
|
|
13594
13605
|
const initialState = {};
|
|
13595
13606
|
/**
|
|
13596
13607
|
* Represents a participant in a call.
|
|
@@ -13816,6 +13827,10 @@ var Participant = class extends Destroyable {
|
|
|
13816
13827
|
get nodeId() {
|
|
13817
13828
|
return this._state$.value.node_id;
|
|
13818
13829
|
}
|
|
13830
|
+
/** Call ID for this participant's leg, or `undefined` if not available. */
|
|
13831
|
+
get callId() {
|
|
13832
|
+
return this._state$.value.call_id;
|
|
13833
|
+
}
|
|
13819
13834
|
/** @internal */
|
|
13820
13835
|
get value() {
|
|
13821
13836
|
return this._state$.value;
|
|
@@ -13877,8 +13892,9 @@ var Participant = class extends Destroyable {
|
|
|
13877
13892
|
noise_suppression: !this.noiseSuppression
|
|
13878
13893
|
});
|
|
13879
13894
|
}
|
|
13895
|
+
/** Toggles low-bitrate mode for this participant's media. */
|
|
13880
13896
|
async toggleLowbitrate() {
|
|
13881
|
-
|
|
13897
|
+
await this.executeMethod(this.id, "call.lowbitrate.set", { lowbitrate: !this.lowbitrate });
|
|
13882
13898
|
}
|
|
13883
13899
|
/**
|
|
13884
13900
|
* Adjusts the **conference-only** microphone energy gate / sensitivity level
|
|
@@ -13925,10 +13941,27 @@ var Participant = class extends Destroyable {
|
|
|
13925
13941
|
}
|
|
13926
13942
|
/**
|
|
13927
13943
|
* Sets the participant's position in the video layout.
|
|
13944
|
+
*
|
|
13945
|
+
* Requires the `member.position` capability. The gateway keys positions by the
|
|
13946
|
+
* **target member's own** `call_id`/`node_id` (see issue #19400 and the legacy
|
|
13947
|
+
* `setPositions` implementation), so this sends the participant's own call
|
|
13948
|
+
* context — matching {@link Participant.remove}. A resolved promise does not
|
|
13949
|
+
* guarantee a visible change: the backend silently returns `200` (no-op) for
|
|
13950
|
+
* non-conference targets.
|
|
13951
|
+
*
|
|
13928
13952
|
* @param value - The {@link VideoPosition} to assign (e.g. `'auto'`, `'reserved-0'`).
|
|
13929
13953
|
*/
|
|
13930
13954
|
async setPosition(value) {
|
|
13931
|
-
|
|
13955
|
+
const state = this._state$.value;
|
|
13956
|
+
const target = {
|
|
13957
|
+
member_id: this.id,
|
|
13958
|
+
call_id: state.call_id ?? "",
|
|
13959
|
+
node_id: state.node_id ?? ""
|
|
13960
|
+
};
|
|
13961
|
+
await this.executeMethod(target, "call.member.position.set", { targets: [{
|
|
13962
|
+
target,
|
|
13963
|
+
position: value
|
|
13964
|
+
}] });
|
|
13932
13965
|
}
|
|
13933
13966
|
/** Removes this participant from the call. */
|
|
13934
13967
|
async remove() {
|
|
@@ -14023,7 +14056,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14023
14056
|
try {
|
|
14024
14057
|
await this.vertoManager.addScreenMedia();
|
|
14025
14058
|
} catch (error) {
|
|
14026
|
-
logger$
|
|
14059
|
+
logger$24.error("[Participant.startScreenShare] Screen share error:", error);
|
|
14027
14060
|
}
|
|
14028
14061
|
}
|
|
14029
14062
|
/** Observable of the current screen share status. */
|
|
@@ -14043,7 +14076,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14043
14076
|
try {
|
|
14044
14077
|
await this.vertoManager.addInputDevice(options);
|
|
14045
14078
|
} catch (error) {
|
|
14046
|
-
logger$
|
|
14079
|
+
logger$24.error("[Participant.startScreenShare] Screen share error:", error);
|
|
14047
14080
|
}
|
|
14048
14081
|
}
|
|
14049
14082
|
/** Removes an additional media input device by ID. */
|
|
@@ -14105,7 +14138,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14105
14138
|
*/
|
|
14106
14139
|
exitStudioModeIfActive() {
|
|
14107
14140
|
if (this._studioAudio$.value) {
|
|
14108
|
-
logger$
|
|
14141
|
+
logger$24.debug("[SelfParticipant] Exiting studio audio mode due to individual flag toggle");
|
|
14109
14142
|
this._studioAudio$.next(false);
|
|
14110
14143
|
}
|
|
14111
14144
|
}
|
|
@@ -14129,7 +14162,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14129
14162
|
try {
|
|
14130
14163
|
await super.mute();
|
|
14131
14164
|
} catch (error) {
|
|
14132
|
-
logger$
|
|
14165
|
+
logger$24.warn("[Participant.toggleAudioInput] Server Error while muting audio input, proceeding with local toggle anyway", error);
|
|
14133
14166
|
} finally {
|
|
14134
14167
|
this.vertoManager.muteMainAudioInputDevice();
|
|
14135
14168
|
}
|
|
@@ -14139,7 +14172,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14139
14172
|
try {
|
|
14140
14173
|
await super.unmute();
|
|
14141
14174
|
} catch (error) {
|
|
14142
|
-
logger$
|
|
14175
|
+
logger$24.warn("[Participant.toggleAudioInput] Server Error while unmuting audio input, proceeding with local toggle anyway", error);
|
|
14143
14176
|
} finally {
|
|
14144
14177
|
await this.vertoManager.unmuteMainAudioInputDevice();
|
|
14145
14178
|
}
|
|
@@ -14149,7 +14182,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14149
14182
|
try {
|
|
14150
14183
|
await super.muteVideo();
|
|
14151
14184
|
} catch (error) {
|
|
14152
|
-
logger$
|
|
14185
|
+
logger$24.warn("[Participant.toggleVideoInput] Server Error while muting video input, proceeding with local toggle anyway", error);
|
|
14153
14186
|
} finally {
|
|
14154
14187
|
this.vertoManager.muteMainVideoInputDevice();
|
|
14155
14188
|
}
|
|
@@ -14159,7 +14192,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14159
14192
|
try {
|
|
14160
14193
|
await super.unmuteVideo();
|
|
14161
14194
|
} catch (error) {
|
|
14162
|
-
logger$
|
|
14195
|
+
logger$24.warn("[Participant.toggleVideoInput] Server Error while unmuting video input, proceeding with local toggle anyway", error);
|
|
14163
14196
|
} finally {
|
|
14164
14197
|
await this.vertoManager.unmuteMainVideoInputDevice();
|
|
14165
14198
|
}
|
|
@@ -14364,7 +14397,7 @@ function filterAs(predicate, resultPath) {
|
|
|
14364
14397
|
//#endregion
|
|
14365
14398
|
//#region src/operators/throwOnRPCError.ts
|
|
14366
14399
|
var import_cjs$21 = require_cjs();
|
|
14367
|
-
const logger$
|
|
14400
|
+
const logger$23 = getLogger();
|
|
14368
14401
|
/**
|
|
14369
14402
|
* RxJS operator that throws a {@link JSONRPCError} when the RPC response contains an error.
|
|
14370
14403
|
* Passes successful responses through unchanged.
|
|
@@ -14372,14 +14405,14 @@ const logger$22 = getLogger();
|
|
|
14372
14405
|
function throwOnRPCError() {
|
|
14373
14406
|
return (0, import_cjs$21.map)((response) => {
|
|
14374
14407
|
if (response.error) {
|
|
14375
|
-
logger$
|
|
14408
|
+
logger$23.error("[throwOnRPCError] RPC error response:", {
|
|
14376
14409
|
code: response.error.code,
|
|
14377
14410
|
message: response.error.message,
|
|
14378
14411
|
data: response.error.data
|
|
14379
14412
|
});
|
|
14380
14413
|
throw new JSONRPCError(response.error.code, response.error.message, response.error.data);
|
|
14381
14414
|
}
|
|
14382
|
-
logger$
|
|
14415
|
+
logger$23.debug("[throwOnRPCError] RPC successful response:", response);
|
|
14383
14416
|
return response;
|
|
14384
14417
|
});
|
|
14385
14418
|
}
|
|
@@ -14387,7 +14420,7 @@ function throwOnRPCError() {
|
|
|
14387
14420
|
//#endregion
|
|
14388
14421
|
//#region src/managers/CallEventsManager.ts
|
|
14389
14422
|
var import_cjs$20 = require_cjs();
|
|
14390
|
-
const logger$
|
|
14423
|
+
const logger$22 = getLogger();
|
|
14391
14424
|
const initialSessionState = {};
|
|
14392
14425
|
/** @internal */
|
|
14393
14426
|
var CallEventsManager = class extends Destroyable {
|
|
@@ -14491,7 +14524,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14491
14524
|
}
|
|
14492
14525
|
initSubscriptions() {
|
|
14493
14526
|
this.subscribeTo(this.callJoinedEvent$, (callJoinedEvent) => {
|
|
14494
|
-
logger$
|
|
14527
|
+
logger$22.debug("[CallEventsManager] Handling call.joined event for call/session IDs:", {
|
|
14495
14528
|
callId: callJoinedEvent.call_id,
|
|
14496
14529
|
roomSessionId: callJoinedEvent.room_session_id
|
|
14497
14530
|
});
|
|
@@ -14518,19 +14551,19 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14518
14551
|
if (this._self$.value?.capabilities.setLayout) this.updateLayouts();
|
|
14519
14552
|
});
|
|
14520
14553
|
this.subscribeTo(this.memberUpdates$, (member) => {
|
|
14521
|
-
logger$
|
|
14554
|
+
logger$22.debug("[CallEventsManager] Handling member update event for member ID:", member);
|
|
14522
14555
|
this.upsertParticipant(member);
|
|
14523
14556
|
});
|
|
14524
14557
|
this.subscribeTo(this.webRtcCallSession.memberLeft$, (memberLeftEvent) => {
|
|
14525
|
-
logger$
|
|
14558
|
+
logger$22.debug("[CallEventsManager] Handling member.left event for member ID:", memberLeftEvent.member.member_id);
|
|
14526
14559
|
const participants = { ...this._participants$.value };
|
|
14527
14560
|
if (memberLeftEvent.member.member_id in participants) {
|
|
14528
14561
|
delete participants[memberLeftEvent.member.member_id];
|
|
14529
14562
|
this._participants$.next(participants);
|
|
14530
|
-
} else logger$
|
|
14563
|
+
} else logger$22.warn(`[CallEventsManager] Received member.left event for unknown member ID: ${memberLeftEvent.member.member_id}`);
|
|
14531
14564
|
});
|
|
14532
14565
|
this.subscribeTo(this.webRtcCallSession.callUpdated$, (callUpdatedEvent) => {
|
|
14533
|
-
logger$
|
|
14566
|
+
logger$22.debug("[CallEventsManager] Handling call.updated event:", callUpdatedEvent);
|
|
14534
14567
|
const roomSession = callUpdatedEvent.room_session;
|
|
14535
14568
|
this._sessionState$.next({
|
|
14536
14569
|
...this._sessionState$.value,
|
|
@@ -14545,7 +14578,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14545
14578
|
});
|
|
14546
14579
|
});
|
|
14547
14580
|
this.subscribeTo(this.layoutChangedEvent$, (layoutChangedEvent) => {
|
|
14548
|
-
logger$
|
|
14581
|
+
logger$22.debug("[CallEventsManager] Handling layout.changed event:", layoutChangedEvent);
|
|
14549
14582
|
this._sessionState$.next({
|
|
14550
14583
|
...this._sessionState$.value,
|
|
14551
14584
|
layout_name: layoutChangedEvent.id,
|
|
@@ -14555,10 +14588,10 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14555
14588
|
});
|
|
14556
14589
|
}
|
|
14557
14590
|
updateParticipantPositions(layoutChangedEvent) {
|
|
14558
|
-
if (Object.keys(this._participants$.value).length > 0 && !layoutChangedEvent.layers.some((layer) => !!layer.member_id)) logger$
|
|
14591
|
+
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.");
|
|
14559
14592
|
layoutChangedEvent.layers.filter((layer) => !!layer.member_id).filter((layer) => {
|
|
14560
14593
|
if (!(layer.member_id in this._participants$.value)) {
|
|
14561
|
-
logger$
|
|
14594
|
+
logger$22.warn(`[CallEventsManager] Skipping layout layer for unknown member_id: ${layer.member_id}`);
|
|
14562
14595
|
return false;
|
|
14563
14596
|
}
|
|
14564
14597
|
return true;
|
|
@@ -14581,7 +14614,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14581
14614
|
layouts: response.result.layouts
|
|
14582
14615
|
});
|
|
14583
14616
|
}).catch((error) => {
|
|
14584
|
-
logger$
|
|
14617
|
+
logger$22.error("[CallEventsManager] Error fetching layouts:", error);
|
|
14585
14618
|
});
|
|
14586
14619
|
}
|
|
14587
14620
|
updateParticipants(members) {
|
|
@@ -14597,7 +14630,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14597
14630
|
}
|
|
14598
14631
|
const participant = this._participants$.value[member.member_id];
|
|
14599
14632
|
const oldValue = participant.value;
|
|
14600
|
-
logger$
|
|
14633
|
+
logger$22.debug("[CallEventsManager] Updating participant:", member.member_id, {
|
|
14601
14634
|
oldValue,
|
|
14602
14635
|
newValue: member
|
|
14603
14636
|
});
|
|
@@ -14610,17 +14643,17 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14610
14643
|
}
|
|
14611
14644
|
get callJoinedEvent$() {
|
|
14612
14645
|
return this.cachedObservable("callJoinedEvent$", () => this.webRtcCallSession.callEvent$.pipe((0, import_cjs$20.filter)(isCallJoinedPayload), (0, import_cjs$20.tap)((event) => {
|
|
14613
|
-
logger$
|
|
14646
|
+
logger$22.debug("[CallEventsManager] Call joined event:", event);
|
|
14614
14647
|
})));
|
|
14615
14648
|
}
|
|
14616
14649
|
get layoutChangedEvent$() {
|
|
14617
14650
|
return this.cachedObservable("layoutChangedEvent$", () => this.webRtcCallSession.callEvent$.pipe(filterAs(isLayoutChangedPayload, "layout"), (0, import_cjs$20.tap)((event) => {
|
|
14618
|
-
logger$
|
|
14651
|
+
logger$22.debug("[CallEventsManager] Layout changed event:", event);
|
|
14619
14652
|
})));
|
|
14620
14653
|
}
|
|
14621
14654
|
get memberUpdates$() {
|
|
14622
14655
|
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) => {
|
|
14623
|
-
logger$
|
|
14656
|
+
logger$22.debug("[CallEventsManager] Member update event:", event);
|
|
14624
14657
|
})));
|
|
14625
14658
|
}
|
|
14626
14659
|
destroy() {
|
|
@@ -14877,7 +14910,7 @@ function appendStereoParams(fmtpLine, maxBitrate) {
|
|
|
14877
14910
|
//#endregion
|
|
14878
14911
|
//#region src/controllers/ICEGatheringController.ts
|
|
14879
14912
|
var import_cjs$19 = require_cjs();
|
|
14880
|
-
const logger$
|
|
14913
|
+
const logger$21 = getLogger();
|
|
14881
14914
|
var ICEGatheringController = class extends Destroyable {
|
|
14882
14915
|
constructor(peerConnection, peerConnectionControllerNegotiating$, options = {}) {
|
|
14883
14916
|
super();
|
|
@@ -14885,23 +14918,23 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14885
14918
|
this.peerConnectionControllerNegotiating$ = peerConnectionControllerNegotiating$;
|
|
14886
14919
|
this.onicegatheringstatechangeHandler = () => {
|
|
14887
14920
|
const { iceGatheringState } = this.peerConnection;
|
|
14888
|
-
logger$
|
|
14921
|
+
logger$21.debug(`[ICEGatheringController] ICE gathering state changed to: ${iceGatheringState}`);
|
|
14889
14922
|
if (iceGatheringState === "gathering") this._iceCandidatesState.next({
|
|
14890
14923
|
state: "gathering",
|
|
14891
14924
|
validSDP: false
|
|
14892
14925
|
});
|
|
14893
14926
|
};
|
|
14894
14927
|
this.onicecandidateHandler = (event) => {
|
|
14895
|
-
logger$
|
|
14928
|
+
logger$21.debug("[ICEGatheringController] ICE candidate event received:", event.candidate);
|
|
14896
14929
|
this.removeTimer("iceCandidateTimer");
|
|
14897
14930
|
if (event.candidate) this.iceCandidateTimer = setTimeout(() => {
|
|
14898
14931
|
if (this.peerConnection.iceGatheringState !== "complete") {
|
|
14899
|
-
logger$
|
|
14932
|
+
logger$21.warn("[ICEGatheringController] ICE candidate timeout, using current SDP");
|
|
14900
14933
|
this.handleICECandidateTimeout();
|
|
14901
14934
|
}
|
|
14902
14935
|
}, this.iceCandidateTimeout);
|
|
14903
14936
|
else {
|
|
14904
|
-
logger$
|
|
14937
|
+
logger$21.debug("[ICEGatheringController] ICE gathering completed: null candidate received");
|
|
14905
14938
|
this.removeTimer("iceGatheringTimer");
|
|
14906
14939
|
this.handleICEGatheringComplete();
|
|
14907
14940
|
}
|
|
@@ -14919,7 +14952,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14919
14952
|
this.setupEventListeners();
|
|
14920
14953
|
this.iceGatheringTimer = setTimeout(() => {
|
|
14921
14954
|
if (this.peerConnection.iceGatheringState !== "complete") {
|
|
14922
|
-
logger$
|
|
14955
|
+
logger$21.warn("[ICEGatheringController] ICE gathering timeout, using current SDP");
|
|
14923
14956
|
this.handleICEGatheringTimeout();
|
|
14924
14957
|
}
|
|
14925
14958
|
}, this.iceGatheringTimeout);
|
|
@@ -14946,9 +14979,9 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14946
14979
|
this.relayOnly = value;
|
|
14947
14980
|
}
|
|
14948
14981
|
handleICEGatheringComplete() {
|
|
14949
|
-
logger$
|
|
14950
|
-
logger$
|
|
14951
|
-
logger$
|
|
14982
|
+
logger$21.debug("[ICEGatheringController] Handling ICE gathering complete");
|
|
14983
|
+
logger$21.debug(`[ICEGatheringController] Checking ICE gathering state: ${this.peerConnection.iceGatheringState}`);
|
|
14984
|
+
logger$21.debug("[ICEGatheringController] ICE gathering complete");
|
|
14952
14985
|
this._iceCandidatesState.next({
|
|
14953
14986
|
state: "complete",
|
|
14954
14987
|
validSDP: this.hasValidLocalDescriptionSDP
|
|
@@ -14964,21 +14997,21 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14964
14997
|
this.removeTimer("iceGatheringTimer");
|
|
14965
14998
|
const validSDP = this.hasValidLocalDescriptionSDP;
|
|
14966
14999
|
if (validSDP) {
|
|
14967
|
-
logger$
|
|
15000
|
+
logger$21.debug("[ICEGatheringController] Local SDP is valid");
|
|
14968
15001
|
this._iceCandidatesState.next({
|
|
14969
15002
|
state: "timeout",
|
|
14970
15003
|
validSDP
|
|
14971
15004
|
});
|
|
14972
15005
|
this.stopGathering();
|
|
14973
|
-
} else logger$
|
|
15006
|
+
} else logger$21.debug("### ICE gathering timeout\n", this.peerConnection.localDescription?.sdp);
|
|
14974
15007
|
}
|
|
14975
15008
|
handleICECandidateTimeout() {
|
|
14976
15009
|
if (this.iceCandidateTimer) this.removeTimer("iceCandidateTimer");
|
|
14977
|
-
logger$
|
|
15010
|
+
logger$21.warn("[ICEGatheringController] ICE candidate timeout");
|
|
14978
15011
|
const validSDP = this.hasValidLocalDescriptionSDP;
|
|
14979
15012
|
if (!validSDP && !this.relayOnly) this.restartICEGatheringWithRelayOnly();
|
|
14980
15013
|
else {
|
|
14981
|
-
logger$
|
|
15014
|
+
logger$21.debug("[ICEGatheringController] Using current SDP due to ICE candidate timeout");
|
|
14982
15015
|
this._iceCandidatesState.next({
|
|
14983
15016
|
state: "timeout",
|
|
14984
15017
|
validSDP
|
|
@@ -14987,7 +15020,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14987
15020
|
}
|
|
14988
15021
|
}
|
|
14989
15022
|
restartICEGatheringWithRelayOnly() {
|
|
14990
|
-
logger$
|
|
15023
|
+
logger$21.debug("[ICEGatheringController] Restarting ICE gathering with relay-only candidates");
|
|
14991
15024
|
this.relayOnly = true;
|
|
14992
15025
|
this.peerConnection.setConfiguration({
|
|
14993
15026
|
...this.peerConnection.getConfiguration(),
|
|
@@ -15002,7 +15035,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
15002
15035
|
}
|
|
15003
15036
|
}
|
|
15004
15037
|
clearAllTimers() {
|
|
15005
|
-
logger$
|
|
15038
|
+
logger$21.debug("[ICEGatheringController] Clearing all timers");
|
|
15006
15039
|
this.removeTimer("iceGatheringTimer");
|
|
15007
15040
|
this.removeTimer("iceCandidateTimer");
|
|
15008
15041
|
}
|
|
@@ -15011,7 +15044,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
15011
15044
|
this.peerConnection.removeEventListener("icecandidate", this.onicecandidateHandler);
|
|
15012
15045
|
}
|
|
15013
15046
|
destroy() {
|
|
15014
|
-
logger$
|
|
15047
|
+
logger$21.debug("[ICEGatheringController] Destroying ICEGatheringController");
|
|
15015
15048
|
this.clearAllTimers();
|
|
15016
15049
|
this.removeEventListeners();
|
|
15017
15050
|
super.destroy();
|
|
@@ -15021,7 +15054,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
15021
15054
|
//#endregion
|
|
15022
15055
|
//#region src/controllers/LocalAudioPipeline.ts
|
|
15023
15056
|
var import_cjs$18 = require_cjs();
|
|
15024
|
-
const logger$
|
|
15057
|
+
const logger$20 = getLogger();
|
|
15025
15058
|
/**
|
|
15026
15059
|
* Web Audio pipeline for the local microphone stream.
|
|
15027
15060
|
*
|
|
@@ -15133,7 +15166,7 @@ var LocalAudioPipeline = class extends Destroyable {
|
|
|
15133
15166
|
try {
|
|
15134
15167
|
this._inputSource.disconnect();
|
|
15135
15168
|
} catch (error) {
|
|
15136
|
-
logger$
|
|
15169
|
+
logger$20.debug("[LocalAudioPipeline] input disconnect warning:", error);
|
|
15137
15170
|
}
|
|
15138
15171
|
this._inputSource = null;
|
|
15139
15172
|
}
|
|
@@ -15143,7 +15176,7 @@ var LocalAudioPipeline = class extends Destroyable {
|
|
|
15143
15176
|
this._inputSource = this._audioContext.createMediaStreamSource(this._inputStream);
|
|
15144
15177
|
this._inputSource.connect(this._gainNode);
|
|
15145
15178
|
if (this._audioContext.state === "suspended") this._audioContext.resume().catch((error) => {
|
|
15146
|
-
logger$
|
|
15179
|
+
logger$20.warn("[LocalAudioPipeline] AudioContext resume failed:", error);
|
|
15147
15180
|
});
|
|
15148
15181
|
}
|
|
15149
15182
|
destroy() {
|
|
@@ -15158,7 +15191,7 @@ var LocalAudioPipeline = class extends Destroyable {
|
|
|
15158
15191
|
this._analyser.disconnect();
|
|
15159
15192
|
} catch {}
|
|
15160
15193
|
this._audioContext.close().catch((error) => {
|
|
15161
|
-
logger$
|
|
15194
|
+
logger$20.debug("[LocalAudioPipeline] audio context close warning:", error);
|
|
15162
15195
|
});
|
|
15163
15196
|
super.destroy();
|
|
15164
15197
|
}
|
|
@@ -15185,7 +15218,7 @@ var LocalAudioPipeline = class extends Destroyable {
|
|
|
15185
15218
|
//#endregion
|
|
15186
15219
|
//#region src/controllers/LocalStreamController.ts
|
|
15187
15220
|
var import_cjs$17 = require_cjs();
|
|
15188
|
-
const logger$
|
|
15221
|
+
const logger$19 = getLogger();
|
|
15189
15222
|
var LocalStreamController = class extends Destroyable {
|
|
15190
15223
|
constructor(options) {
|
|
15191
15224
|
super();
|
|
@@ -15223,26 +15256,26 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15223
15256
|
* Build the local media stream based on the provided options.
|
|
15224
15257
|
*/
|
|
15225
15258
|
async buildLocalStream() {
|
|
15226
|
-
logger$
|
|
15259
|
+
logger$19.debug("[LocalStreamController] Building local media stream.");
|
|
15227
15260
|
let stream;
|
|
15228
15261
|
if (this.options.inputAudioStream ?? this.options.inputVideoStream) {
|
|
15229
15262
|
const tracks = [...this.options.inputAudioStream?.getTracks() ?? [], ...this.options.inputVideoStream?.getTracks() ?? []];
|
|
15230
15263
|
stream = new MediaStream(tracks);
|
|
15231
15264
|
} else if (this.options.propose === "screenshare") {
|
|
15232
|
-
logger$
|
|
15265
|
+
logger$19.debug("[LocalStreamController] Requesting display media for screen sharing with audio:", Boolean(this.options.inputAudioDeviceConstraints));
|
|
15233
15266
|
stream = await this.options.getDisplayMedia({
|
|
15234
15267
|
video: true,
|
|
15235
15268
|
audio: Boolean(this.options.inputAudioDeviceConstraints)
|
|
15236
15269
|
});
|
|
15237
|
-
logger$
|
|
15270
|
+
logger$19.debug("[LocalStreamController] Screen share media obtained:", stream);
|
|
15238
15271
|
} else {
|
|
15239
15272
|
const constraints = {
|
|
15240
15273
|
audio: this.options.inputAudioDeviceConstraints,
|
|
15241
15274
|
video: this.options.inputVideoDeviceConstraints
|
|
15242
15275
|
};
|
|
15243
|
-
logger$
|
|
15276
|
+
logger$19.debug("[LocalStreamController] Requesting user media with constraints:", constraints);
|
|
15244
15277
|
stream = await this.options.getUserMedia(constraints);
|
|
15245
|
-
logger$
|
|
15278
|
+
logger$19.debug("[LocalStreamController] User media obtained:", stream);
|
|
15246
15279
|
}
|
|
15247
15280
|
this._localStream$.next(stream);
|
|
15248
15281
|
this._localAudioTracks$.next(stream.getAudioTracks());
|
|
@@ -15261,7 +15294,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15261
15294
|
this._localStream$.next(localStream);
|
|
15262
15295
|
if (track.kind === "video") this._localVideoTracks$.next(localStream.getVideoTracks());
|
|
15263
15296
|
else this._localAudioTracks$.next(localStream.getAudioTracks());
|
|
15264
|
-
logger$
|
|
15297
|
+
logger$19.debug(`[LocalStreamController] ${track.kind} track added:`, track.id);
|
|
15265
15298
|
return localStream;
|
|
15266
15299
|
}
|
|
15267
15300
|
/**
|
|
@@ -15273,7 +15306,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15273
15306
|
const stream = this._localStream$.value;
|
|
15274
15307
|
const track = stream?.getTracks().find((t) => t.id === trackId);
|
|
15275
15308
|
if (!track) {
|
|
15276
|
-
logger$
|
|
15309
|
+
logger$19.debug(`[LocalStreamController] track not found: ${trackId}`);
|
|
15277
15310
|
return;
|
|
15278
15311
|
}
|
|
15279
15312
|
track.removeEventListener("ended", this.mediaTrackEndedHandler);
|
|
@@ -15282,7 +15315,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15282
15315
|
this._localStream$.next(stream);
|
|
15283
15316
|
if (track.kind === "video") this._localVideoTracks$.next(stream?.getVideoTracks() ?? []);
|
|
15284
15317
|
else this._localAudioTracks$.next(stream?.getAudioTracks() ?? []);
|
|
15285
|
-
logger$
|
|
15318
|
+
logger$19.debug(`[LocalStreamController] ${track.kind} track removed:`, trackId);
|
|
15286
15319
|
return track;
|
|
15287
15320
|
}
|
|
15288
15321
|
/**
|
|
@@ -15317,7 +15350,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15317
15350
|
*/
|
|
15318
15351
|
stopAllTracks() {
|
|
15319
15352
|
this._localStream$.value?.getTracks().forEach((track) => {
|
|
15320
|
-
logger$
|
|
15353
|
+
logger$19.debug(`[LocalStreamController] Stopping local track: ${track.kind}`);
|
|
15321
15354
|
track.removeEventListener("ended", this.mediaTrackEndedHandler);
|
|
15322
15355
|
track.stop();
|
|
15323
15356
|
});
|
|
@@ -15333,7 +15366,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15333
15366
|
|
|
15334
15367
|
//#endregion
|
|
15335
15368
|
//#region src/controllers/TransceiverController.ts
|
|
15336
|
-
const logger$
|
|
15369
|
+
const logger$18 = getLogger();
|
|
15337
15370
|
const getDirection = (send, recv) => {
|
|
15338
15371
|
if (send && recv) return "sendrecv";
|
|
15339
15372
|
else if (send && !recv) return "sendonly";
|
|
@@ -15441,7 +15474,7 @@ var TransceiverController = class extends Destroyable {
|
|
|
15441
15474
|
sendEncodings: isAudio ? void 0 : this.sendEncodings,
|
|
15442
15475
|
streams: direction === "recvonly" ? void 0 : [localStream]
|
|
15443
15476
|
};
|
|
15444
|
-
logger$
|
|
15477
|
+
logger$18.debug(`[TransceiverController] Setting up transceiver sender for local ${track.kind} track:`, {
|
|
15445
15478
|
transceiver,
|
|
15446
15479
|
transceiverParams
|
|
15447
15480
|
});
|
|
@@ -15449,11 +15482,11 @@ var TransceiverController = class extends Destroyable {
|
|
|
15449
15482
|
await transceiver.sender.replaceTrack(track);
|
|
15450
15483
|
transceiver.direction = transceiverParams.direction;
|
|
15451
15484
|
if (transceiverParams.streams?.some((stream) => Boolean(stream))) {
|
|
15452
|
-
logger$
|
|
15485
|
+
logger$18.debug(`[TransceiverController] Setting streams for transceiver sender for local ${track.kind} track:`, transceiverParams.streams);
|
|
15453
15486
|
transceiver.sender.setStreams(...transceiverParams.streams);
|
|
15454
15487
|
}
|
|
15455
15488
|
} else {
|
|
15456
|
-
logger$
|
|
15489
|
+
logger$18.debug(`[TransceiverController] Adding new transceiver for local ${track.kind} track:`, track.id);
|
|
15457
15490
|
this.peerConnection.addTransceiver(track, transceiverParams);
|
|
15458
15491
|
}
|
|
15459
15492
|
}
|
|
@@ -15467,13 +15500,13 @@ var TransceiverController = class extends Destroyable {
|
|
|
15467
15500
|
if (options.updateTransceiverDirection) transceiver.direction = "inactive";
|
|
15468
15501
|
}
|
|
15469
15502
|
} catch (error) {
|
|
15470
|
-
logger$
|
|
15503
|
+
logger$18.error("[TransceiverController] stopTrackSender error", kind, error);
|
|
15471
15504
|
this.options.onError?.(new MediaTrackError("stopTrackSender", kind, error));
|
|
15472
15505
|
}
|
|
15473
15506
|
}
|
|
15474
15507
|
async restoreTrackSender(kind) {
|
|
15475
15508
|
try {
|
|
15476
|
-
logger$
|
|
15509
|
+
logger$18.debug("[TransceiverController] restoreTrackSender called", kind);
|
|
15477
15510
|
const constraints = {};
|
|
15478
15511
|
const transceivers = this.transceiverByKind(kind);
|
|
15479
15512
|
for (const transceiver of transceivers) {
|
|
@@ -15483,23 +15516,23 @@ var TransceiverController = class extends Destroyable {
|
|
|
15483
15516
|
if (trackKind === "audio" || trackKind === "video") constraints[trackKind] = this.getConstraintsFor(trackKind);
|
|
15484
15517
|
}
|
|
15485
15518
|
}
|
|
15486
|
-
logger$
|
|
15519
|
+
logger$18.debug("[TransceiverController] restoreTrackSender constraints:", constraints);
|
|
15487
15520
|
if (Object.keys(constraints).length === 0) {
|
|
15488
|
-
logger$
|
|
15521
|
+
logger$18.warn("[TransceiverController] restoreTrackSender: no tracks need restoration", kind);
|
|
15489
15522
|
return;
|
|
15490
15523
|
}
|
|
15491
15524
|
const newTracks = (await this.options.getUserMedia(constraints)).getTracks();
|
|
15492
|
-
logger$
|
|
15525
|
+
logger$18.debug("[TransceiverController] restoreTrackSender new tracks:", newTracks);
|
|
15493
15526
|
for (const newTrack of newTracks) {
|
|
15494
15527
|
this.options.localStreamController.addTrack(newTrack);
|
|
15495
15528
|
const trackKind = newTrack.kind;
|
|
15496
15529
|
const transceiverOfKind = this.transceiverByKind(trackKind)[0];
|
|
15497
15530
|
transceiverOfKind.direction = trackKind === "audio" ? this.audioDirection : this.videoDirection;
|
|
15498
|
-
logger$
|
|
15531
|
+
logger$18.debug("[TransceiverController] restoreTrackSender setting direction for", trackKind, transceiverOfKind.direction);
|
|
15499
15532
|
await transceiverOfKind.sender.replaceTrack(newTrack);
|
|
15500
15533
|
}
|
|
15501
15534
|
} catch (error) {
|
|
15502
|
-
logger$
|
|
15535
|
+
logger$18.error("[TransceiverController] restoreTrackSender error", kind, error);
|
|
15503
15536
|
this.options.onError?.(new MediaTrackError("restoreTrackSender", kind, error));
|
|
15504
15537
|
}
|
|
15505
15538
|
}
|
|
@@ -15540,14 +15573,14 @@ var TransceiverController = class extends Destroyable {
|
|
|
15540
15573
|
};
|
|
15541
15574
|
try {
|
|
15542
15575
|
await track.applyConstraints(constraintsToApply);
|
|
15543
|
-
logger$
|
|
15544
|
-
logger$
|
|
15576
|
+
logger$18.debug(`[TransceiverController] Updated ${kind} sender constraints:`, constraintsToApply);
|
|
15577
|
+
logger$18.debug(`[TransceiverController] Updated ${kind} sender constraints:`, track.getConstraints());
|
|
15545
15578
|
} catch (error) {
|
|
15546
|
-
logger$
|
|
15579
|
+
logger$18.warn(`[TransceiverController] applyConstraints failed for ${kind} track ${track.id}, attempting track replacement fallback:`, error);
|
|
15547
15580
|
try {
|
|
15548
15581
|
await this.replaceTrackFallback(sender, track, kind, constraintsToApply);
|
|
15549
15582
|
} catch (fallbackError) {
|
|
15550
|
-
logger$
|
|
15583
|
+
logger$18.warn(`[TransceiverController] Track replacement fallback also failed for ${kind} track:`, fallbackError);
|
|
15551
15584
|
this.options.onError?.(new MediaTrackError("updateSendersConstraints", kind, fallbackError));
|
|
15552
15585
|
}
|
|
15553
15586
|
}
|
|
@@ -15575,7 +15608,7 @@ var TransceiverController = class extends Destroyable {
|
|
|
15575
15608
|
if (!newTrack) throw new MediaTrackError("replaceTrackFallback", kind, /* @__PURE__ */ new Error("getUserMedia returned no track of the requested kind"));
|
|
15576
15609
|
await sender.replaceTrack(newTrack);
|
|
15577
15610
|
this.options.localStreamController.addTrack(newTrack);
|
|
15578
|
-
logger$
|
|
15611
|
+
logger$18.debug(`[TransceiverController] Track replacement fallback succeeded for ${kind}. New track: ${newTrack.id}`);
|
|
15579
15612
|
}
|
|
15580
15613
|
getMediaDirections() {
|
|
15581
15614
|
if (this.peerConnection.connectionState === "connected") return this.peerConnection.getTransceivers().reduce((acc, transceiver) => {
|
|
@@ -15606,7 +15639,7 @@ var TransceiverController = class extends Destroyable {
|
|
|
15606
15639
|
//#endregion
|
|
15607
15640
|
//#region src/controllers/RTCPeerConnectionController.ts
|
|
15608
15641
|
var import_cjs$16 = require_cjs();
|
|
15609
|
-
const logger$
|
|
15642
|
+
const logger$17 = getLogger();
|
|
15610
15643
|
var RTCPeerConnectionController = class extends Destroyable {
|
|
15611
15644
|
constructor(options = {}, remoteSessionDescription, deviceController) {
|
|
15612
15645
|
super();
|
|
@@ -15622,43 +15655,43 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15622
15655
|
this.oniceconnectionstatechangeHandler = () => {
|
|
15623
15656
|
if (this.peerConnection) {
|
|
15624
15657
|
const { iceConnectionState } = this.peerConnection;
|
|
15625
|
-
logger$
|
|
15658
|
+
logger$17.debug(`[RTCPeerConnectionController] ICE connection state changed to: ${iceConnectionState}`);
|
|
15626
15659
|
this._iceConnectionState$.next(this.peerConnection.iceConnectionState);
|
|
15627
15660
|
}
|
|
15628
15661
|
};
|
|
15629
15662
|
this.onconnectionstatechangeHandler = () => {
|
|
15630
15663
|
if (this.peerConnection) {
|
|
15631
15664
|
const { connectionState } = this.peerConnection;
|
|
15632
|
-
logger$
|
|
15665
|
+
logger$17.debug(`[RTCPeerConnectionController] Connection state changed to: ${connectionState}`);
|
|
15633
15666
|
if (connectionState === "connected") this.removeConnectionTimer();
|
|
15634
15667
|
this._connectionState$.next(this.peerConnection.connectionState);
|
|
15635
15668
|
}
|
|
15636
15669
|
};
|
|
15637
15670
|
this.onsignalingstatechangeHandler = () => {
|
|
15638
|
-
logger$
|
|
15671
|
+
logger$17.debug(`[RTCPeerConnectionController] Signaling state changed to: ${this.peerConnection?.signalingState}`);
|
|
15639
15672
|
};
|
|
15640
15673
|
this.onicegatheringstatechangeHandler = () => {
|
|
15641
15674
|
if (this.peerConnection) this._iceGatheringState$.next(this.peerConnection.iceGatheringState);
|
|
15642
15675
|
};
|
|
15643
15676
|
this.onnegotiationneededHandler = (event) => {
|
|
15644
|
-
logger$
|
|
15677
|
+
logger$17.debug("[RTCPeerConnectionController] Negotiation needed event received.", event);
|
|
15645
15678
|
this.negotiationNeeded$.next();
|
|
15646
15679
|
};
|
|
15647
15680
|
this.updateSelectedInputDevice = async (kind, deviceInfo) => {
|
|
15648
15681
|
try {
|
|
15649
15682
|
const { localStream } = this;
|
|
15650
15683
|
if (!localStream) {
|
|
15651
|
-
logger$
|
|
15684
|
+
logger$17.warn("[RTCPeerConnectionController] No local stream available to update input device.");
|
|
15652
15685
|
return;
|
|
15653
15686
|
}
|
|
15654
|
-
logger$
|
|
15687
|
+
logger$17.debug(`[RTCPeerConnectionController] Updating selected ${kind} input device:`, localStream.getTracks());
|
|
15655
15688
|
const track = localStream.getTracks().find((track$1) => track$1.kind === kind);
|
|
15656
15689
|
if (track) {
|
|
15657
15690
|
this.transceiverController?.stopTrackSender(kind);
|
|
15658
15691
|
this.localStreamController.removeTrack(track.id);
|
|
15659
|
-
logger$
|
|
15692
|
+
logger$17.debug(`[RTCPeerConnectionController] Stopped existing ${kind} track: ${track.id}`, localStream.getTracks());
|
|
15660
15693
|
if (!deviceInfo) {
|
|
15661
|
-
logger$
|
|
15694
|
+
logger$17.debug(`[RTCPeerConnectionController] ${kind} input device selected: none`);
|
|
15662
15695
|
return;
|
|
15663
15696
|
}
|
|
15664
15697
|
const streamTrack = (await this.getUserMedia({ [kind]: {
|
|
@@ -15666,15 +15699,15 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15666
15699
|
...this.deviceController.deviceInfoToConstraints(deviceInfo)
|
|
15667
15700
|
} })).getTracks().find((t) => t.kind === kind);
|
|
15668
15701
|
if (streamTrack) {
|
|
15669
|
-
logger$
|
|
15702
|
+
logger$17.debug(`[RTCPeerConnectionController] Adding new ${kind} track: ${streamTrack.id}`);
|
|
15670
15703
|
this.localStreamController.addTrack(streamTrack);
|
|
15671
15704
|
await this.transceiverController?.replaceSenderTrack(kind, streamTrack);
|
|
15672
|
-
logger$
|
|
15705
|
+
logger$17.debug(`[RTCPeerConnectionController] Added new ${kind} track: ${streamTrack.id}`, this.localStream?.getTracks());
|
|
15673
15706
|
}
|
|
15674
15707
|
}
|
|
15675
|
-
logger$
|
|
15708
|
+
logger$17.debug(`[RTCPeerConnectionController] ${kind} input device selected:`, deviceInfo?.label);
|
|
15676
15709
|
} catch (error) {
|
|
15677
|
-
logger$
|
|
15710
|
+
logger$17.error(`[RTCPeerConnectionController] Failed to select ${kind} input device:`, error);
|
|
15678
15711
|
this._errors$.next(toError(error));
|
|
15679
15712
|
throw error;
|
|
15680
15713
|
}
|
|
@@ -15924,15 +15957,15 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15924
15957
|
this.setupPeerConnection();
|
|
15925
15958
|
this.subscribeTo(this.negotiationNeeded$.pipe((0, import_cjs$16.auditTime)(0), (0, import_cjs$16.exhaustMap)(async () => this.startNegotiation())), {
|
|
15926
15959
|
next: () => {
|
|
15927
|
-
logger$
|
|
15960
|
+
logger$17.debug("[RTCPeerConnectionController] Start Negotiation completed successfully");
|
|
15928
15961
|
},
|
|
15929
15962
|
error: (error) => {
|
|
15930
|
-
logger$
|
|
15963
|
+
logger$17.error("[RTCPeerConnectionController] Start Negotiation error:", error);
|
|
15931
15964
|
this._errors$.next(toError(error));
|
|
15932
15965
|
}
|
|
15933
15966
|
});
|
|
15934
15967
|
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]) => {
|
|
15935
|
-
logger$
|
|
15968
|
+
logger$17.debug(`[RTCPeerConnectionController] Selected input device changed for:`, {
|
|
15936
15969
|
kind,
|
|
15937
15970
|
deviceInfo
|
|
15938
15971
|
});
|
|
@@ -15949,7 +15982,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15949
15982
|
this._initialized$.next(true);
|
|
15950
15983
|
}
|
|
15951
15984
|
} catch (error) {
|
|
15952
|
-
logger$
|
|
15985
|
+
logger$17.error("[RTCPeerConnectionController] Initialization error:", error);
|
|
15953
15986
|
this._errors$.next(toError(error));
|
|
15954
15987
|
this.destroy();
|
|
15955
15988
|
}
|
|
@@ -15981,22 +16014,22 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15981
16014
|
}
|
|
15982
16015
|
async startNegotiation() {
|
|
15983
16016
|
if (this.isNegotiating) {
|
|
15984
|
-
logger$
|
|
16017
|
+
logger$17.debug("[RTCPeerConnectionController] Negotiation already in progress, skipping.");
|
|
15985
16018
|
return;
|
|
15986
16019
|
}
|
|
15987
16020
|
this.setupEventListeners();
|
|
15988
16021
|
if (this.type === "answer") {
|
|
15989
|
-
logger$
|
|
16022
|
+
logger$17.debug("[RTCPeerConnectionController] This is an answer type still, skipping offer creation.");
|
|
15990
16023
|
return;
|
|
15991
16024
|
}
|
|
15992
16025
|
this._isNegotiating$.next(true);
|
|
15993
|
-
logger$
|
|
16026
|
+
logger$17.debug("[RTCPeerConnectionController] Starting negotiation.");
|
|
15994
16027
|
try {
|
|
15995
16028
|
const { offerOptions } = this;
|
|
15996
|
-
logger$
|
|
16029
|
+
logger$17.debug("[RTCPeerConnectionController] Creating offer with options:", offerOptions);
|
|
15997
16030
|
await this.createOffer(offerOptions);
|
|
15998
16031
|
} catch (error) {
|
|
15999
|
-
logger$
|
|
16032
|
+
logger$17.error("[RTCPeerConnectionController] Error during negotiation:", error);
|
|
16000
16033
|
this._errors$.next(toError(error));
|
|
16001
16034
|
}
|
|
16002
16035
|
}
|
|
@@ -16012,14 +16045,14 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16012
16045
|
let readyToConnect = status !== "failed";
|
|
16013
16046
|
try {
|
|
16014
16047
|
if (status === "received" && sdp) {
|
|
16015
|
-
logger$
|
|
16048
|
+
logger$17.debug("[RTCPeerConnectionController] Received answer SDP:", sdp);
|
|
16016
16049
|
await this._setRemoteDescription({
|
|
16017
16050
|
type: "answer",
|
|
16018
16051
|
sdp
|
|
16019
16052
|
});
|
|
16020
16053
|
}
|
|
16021
16054
|
} catch (error) {
|
|
16022
|
-
logger$
|
|
16055
|
+
logger$17.error("[RTCPeerConnectionController] Error updating answer status:", error);
|
|
16023
16056
|
this._errors$.next(toError(error));
|
|
16024
16057
|
readyToConnect = false;
|
|
16025
16058
|
} finally {
|
|
@@ -16038,7 +16071,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16038
16071
|
await this.handleOfferReceived();
|
|
16039
16072
|
break;
|
|
16040
16073
|
case "failed":
|
|
16041
|
-
logger$
|
|
16074
|
+
logger$17.error("[RTCPeerConnectionController] Offer failed to be processed by remote.");
|
|
16042
16075
|
break;
|
|
16043
16076
|
case "sent":
|
|
16044
16077
|
default:
|
|
@@ -16070,7 +16103,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16070
16103
|
}
|
|
16071
16104
|
await this.setupLocalTracks();
|
|
16072
16105
|
const { answerOptions } = this;
|
|
16073
|
-
logger$
|
|
16106
|
+
logger$17.debug("[RTCPeerConnectionController] Creating inbound answer with options:", answerOptions);
|
|
16074
16107
|
await this.createAnswer(answerOptions);
|
|
16075
16108
|
}
|
|
16076
16109
|
async handleOfferReceived() {
|
|
@@ -16078,7 +16111,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16078
16111
|
this._isNegotiating$.next(true);
|
|
16079
16112
|
await this._setRemoteDescription(this.sdpInit);
|
|
16080
16113
|
const { answerOptions } = this;
|
|
16081
|
-
logger$
|
|
16114
|
+
logger$17.debug("[RTCPeerConnectionController] Creating answer with options:", answerOptions);
|
|
16082
16115
|
await this.createAnswer(answerOptions);
|
|
16083
16116
|
}
|
|
16084
16117
|
readyToConnect() {
|
|
@@ -16086,7 +16119,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16086
16119
|
this.connectionTimer = setTimeout(() => {
|
|
16087
16120
|
this.removeConnectionTimer();
|
|
16088
16121
|
if (this.peerConnection?.connectionState !== "connected") {
|
|
16089
|
-
logger$
|
|
16122
|
+
logger$17.debug("[RTCPeerConnectionController] Connection timeout, restarting ICE gathering with relay only.");
|
|
16090
16123
|
this.iceGatheringController.restartICEGatheringWithRelayOnly();
|
|
16091
16124
|
}
|
|
16092
16125
|
}, this.connectionTimeout);
|
|
@@ -16108,14 +16141,14 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16108
16141
|
const stereo = this.options.stereo ?? PreferencesContainer.instance.stereoAudio;
|
|
16109
16142
|
if (preferredAudioCodecs.length > 0 || preferredVideoCodecs.length > 0) {
|
|
16110
16143
|
result = setCodecPreferences(result, preferredAudioCodecs, preferredVideoCodecs);
|
|
16111
|
-
logger$
|
|
16144
|
+
logger$17.debug("[RTCPeerConnectionController] Applied codec preferences to SDP", {
|
|
16112
16145
|
preferredAudioCodecs,
|
|
16113
16146
|
preferredVideoCodecs
|
|
16114
16147
|
});
|
|
16115
16148
|
}
|
|
16116
16149
|
if (stereo) {
|
|
16117
16150
|
result = enableStereoOpus(result);
|
|
16118
|
-
logger$
|
|
16151
|
+
logger$17.debug("[RTCPeerConnectionController] Applied stereo Opus to SDP");
|
|
16119
16152
|
}
|
|
16120
16153
|
return Promise.resolve(result);
|
|
16121
16154
|
}
|
|
@@ -16171,25 +16204,25 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16171
16204
|
...this.peerConnection.getConfiguration(),
|
|
16172
16205
|
iceTransportPolicy: "relay"
|
|
16173
16206
|
});
|
|
16174
|
-
logger$
|
|
16207
|
+
logger$17.debug("[RTCPeerConnectionController] ICE transport policy set to relay-only");
|
|
16175
16208
|
} catch (error) {
|
|
16176
|
-
logger$
|
|
16209
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to set relay-only policy:", error);
|
|
16177
16210
|
}
|
|
16178
16211
|
this.setupEventListeners();
|
|
16179
16212
|
this._isNegotiating$.next(true);
|
|
16180
|
-
logger$
|
|
16213
|
+
logger$17.debug(`[RTCPeerConnectionController] Triggering ICE restart${relayOnly ? " (relay-only)" : ""}.`);
|
|
16181
16214
|
try {
|
|
16182
16215
|
const offer = await this.peerConnection.createOffer({ iceRestart: true });
|
|
16183
16216
|
await this.setLocalDescription(offer);
|
|
16184
16217
|
} catch (error) {
|
|
16185
|
-
logger$
|
|
16218
|
+
logger$17.error("[RTCPeerConnectionController] ICE restart offer failed:", error);
|
|
16186
16219
|
this._errors$.next(toError(error));
|
|
16187
16220
|
this.negotiationEnded();
|
|
16188
16221
|
if (policyChanged) this.restoreIceTransportPolicy();
|
|
16189
16222
|
throw error;
|
|
16190
16223
|
}
|
|
16191
16224
|
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) => {
|
|
16192
|
-
logger$
|
|
16225
|
+
logger$17.warn("[RTCPeerConnectionController] Error waiting for ICE gathering to complete:", error);
|
|
16193
16226
|
this.restoreIceTransportPolicy();
|
|
16194
16227
|
});
|
|
16195
16228
|
}
|
|
@@ -16199,9 +16232,9 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16199
16232
|
...this.peerConnection.getConfiguration(),
|
|
16200
16233
|
iceTransportPolicy: this.options.relayOnly ? "relay" : "all"
|
|
16201
16234
|
});
|
|
16202
|
-
logger$
|
|
16235
|
+
logger$17.debug("[RTCPeerConnectionController] ICE transport policy restored");
|
|
16203
16236
|
} catch (error) {
|
|
16204
|
-
logger$
|
|
16237
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to restore ICE transport policy:", error);
|
|
16205
16238
|
}
|
|
16206
16239
|
}
|
|
16207
16240
|
/**
|
|
@@ -16213,13 +16246,13 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16213
16246
|
await this.setupRemoteTracks();
|
|
16214
16247
|
}
|
|
16215
16248
|
async setupLocalTracks() {
|
|
16216
|
-
logger$
|
|
16249
|
+
logger$17.debug("[RTCPeerConnectionController] Setting up local tracks/transceivers.");
|
|
16217
16250
|
const localStream = this.localStream ?? await this.localStreamController.buildLocalStream();
|
|
16218
16251
|
if (this.transceiverController?.useAddStream ?? false) {
|
|
16219
|
-
logger$
|
|
16252
|
+
logger$17.warn("[RTCPeerConnectionController] Using deprecated addStream API to add local stream.");
|
|
16220
16253
|
this.peerConnection?.addStream(localStream);
|
|
16221
16254
|
if (!this.isNegotiating) {
|
|
16222
|
-
logger$
|
|
16255
|
+
logger$17.debug("[RTCPeerConnectionController] Forcing negotiationneeded after local tracks setup.");
|
|
16223
16256
|
this.negotiationNeeded$.next();
|
|
16224
16257
|
}
|
|
16225
16258
|
return;
|
|
@@ -16235,7 +16268,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16235
16268
|
const transceivers = (kind === "audio" ? this.transceiverController?.audioTransceivers : this.transceiverController?.videoTransceivers) ?? [];
|
|
16236
16269
|
await this.transceiverController?.setupTransceiverSender(track, localStream, transceivers[index]);
|
|
16237
16270
|
} else {
|
|
16238
|
-
logger$
|
|
16271
|
+
logger$17.debug(`[RTCPeerConnectionController] Using addTrack for local ${kind} track:`, track.id);
|
|
16239
16272
|
this.peerConnection?.addTrack(track, localStream);
|
|
16240
16273
|
}
|
|
16241
16274
|
}
|
|
@@ -16252,7 +16285,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16252
16285
|
async setupRemoteTracks() {
|
|
16253
16286
|
if (!this.peerConnection) throw new DependencyError("RTCPeerConnection is not initialized");
|
|
16254
16287
|
this.peerConnection.ontrack = (event) => {
|
|
16255
|
-
logger$
|
|
16288
|
+
logger$17.debug("[RTCPeerConnectionController] Remote track received:", event.track.kind);
|
|
16256
16289
|
if (event.streams[0]) this._remoteStream$.next(event.streams[0]);
|
|
16257
16290
|
else {
|
|
16258
16291
|
const existingTracks = this._remoteStream$.value?.getTracks() ?? [];
|
|
@@ -16276,7 +16309,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16276
16309
|
try {
|
|
16277
16310
|
stream = await this.getUserMedia({ audio: constraints });
|
|
16278
16311
|
} catch (error) {
|
|
16279
|
-
logger$
|
|
16312
|
+
logger$17.error("[RTCPeerConnectionController] Failed to re-acquire mic for pipeline restore:", error);
|
|
16280
16313
|
this._errors$.next(toError(error));
|
|
16281
16314
|
return;
|
|
16282
16315
|
}
|
|
@@ -16298,7 +16331,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16298
16331
|
try {
|
|
16299
16332
|
this._localAudioPipeline = new LocalAudioPipeline();
|
|
16300
16333
|
} catch (error) {
|
|
16301
|
-
logger$
|
|
16334
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to create LocalAudioPipeline:", error);
|
|
16302
16335
|
return null;
|
|
16303
16336
|
}
|
|
16304
16337
|
this.subscribeTo(this.localStreamController.localAudioTracks$, () => {
|
|
@@ -16320,7 +16353,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16320
16353
|
try {
|
|
16321
16354
|
await sender.replaceTrack(this._localAudioPipeline.outputTrack);
|
|
16322
16355
|
} catch (error) {
|
|
16323
|
-
logger$
|
|
16356
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to route audio sender through pipeline:", error);
|
|
16324
16357
|
}
|
|
16325
16358
|
}
|
|
16326
16359
|
/**
|
|
@@ -16336,9 +16369,9 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16336
16369
|
try {
|
|
16337
16370
|
const localStream = this.localStreamController.addTrack(track);
|
|
16338
16371
|
this.peerConnection.addTrack(track, localStream);
|
|
16339
|
-
logger$
|
|
16372
|
+
logger$17.debug(`[RTCPeerConnectionController] ${track.kind} track added:`, track.id);
|
|
16340
16373
|
} catch (error) {
|
|
16341
|
-
logger$
|
|
16374
|
+
logger$17.error(`[RTCPeerConnectionController] Failed to add ${track.kind} track:`, error);
|
|
16342
16375
|
this._errors$.next(toError(error));
|
|
16343
16376
|
throw error;
|
|
16344
16377
|
}
|
|
@@ -16355,15 +16388,15 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16355
16388
|
}
|
|
16356
16389
|
const sender = this.peerConnection.getSenders().find((sender$1) => sender$1.track?.id === trackId);
|
|
16357
16390
|
if (!sender) {
|
|
16358
|
-
logger$
|
|
16391
|
+
logger$17.debug(`[RTCPeerConnectionController] track not found: ${trackId}`);
|
|
16359
16392
|
return;
|
|
16360
16393
|
}
|
|
16361
16394
|
try {
|
|
16362
16395
|
this.peerConnection.removeTrack(sender);
|
|
16363
16396
|
this.localStreamController.removeTrack(trackId);
|
|
16364
|
-
logger$
|
|
16397
|
+
logger$17.debug(`[RTCPeerConnectionController] ${sender.track?.kind} track removed:`, trackId);
|
|
16365
16398
|
} catch (error) {
|
|
16366
|
-
logger$
|
|
16399
|
+
logger$17.error(`[RTCPeerConnectionController] Failed to remove ${sender.track?.kind} track:`, error);
|
|
16367
16400
|
this._errors$.next(toError(error));
|
|
16368
16401
|
throw error;
|
|
16369
16402
|
}
|
|
@@ -16390,7 +16423,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16390
16423
|
async replaceAudioTrackWithConstraints(constraints) {
|
|
16391
16424
|
const senders = this.peerConnection?.getSenders().filter((s) => s.track?.kind === "audio" && s.track.readyState === "live");
|
|
16392
16425
|
if (!senders || senders.length === 0) {
|
|
16393
|
-
logger$
|
|
16426
|
+
logger$17.warn("[RTCPeerConnectionController] No live audio sender to replace");
|
|
16394
16427
|
return;
|
|
16395
16428
|
}
|
|
16396
16429
|
for (const sender of senders) {
|
|
@@ -16408,7 +16441,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16408
16441
|
const newTrack = (await this.getUserMedia({ audio: mergedConstraints })).getAudioTracks()[0];
|
|
16409
16442
|
await sender.replaceTrack(newTrack);
|
|
16410
16443
|
this.localStreamController.addTrack(newTrack);
|
|
16411
|
-
logger$
|
|
16444
|
+
logger$17.debug(`[RTCPeerConnectionController] Audio track replaced for server-pushed params. New track: ${newTrack.id}`);
|
|
16412
16445
|
}
|
|
16413
16446
|
}
|
|
16414
16447
|
/**
|
|
@@ -16416,7 +16449,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16416
16449
|
* Completes all observables to prevent memory leaks.
|
|
16417
16450
|
*/
|
|
16418
16451
|
destroy() {
|
|
16419
|
-
logger$
|
|
16452
|
+
logger$17.debug(`[RTCPeerConnectionController] Destroying RTCPeerConnectionController. ${this.propose}`);
|
|
16420
16453
|
this.removeConnectionTimer();
|
|
16421
16454
|
this._iceGatheringController?.destroy();
|
|
16422
16455
|
this._localAudioPipeline?.destroy();
|
|
@@ -16442,7 +16475,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16442
16475
|
}
|
|
16443
16476
|
stopRemoteTracks() {
|
|
16444
16477
|
this._remoteStream$.value?.getTracks().forEach((track) => {
|
|
16445
|
-
logger$
|
|
16478
|
+
logger$17.debug(`[RTCPeerConnectionController] Stopping remote track: ${track.kind}`);
|
|
16446
16479
|
track.stop();
|
|
16447
16480
|
});
|
|
16448
16481
|
}
|
|
@@ -16459,7 +16492,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16459
16492
|
...params,
|
|
16460
16493
|
sdp: finalRemote
|
|
16461
16494
|
};
|
|
16462
|
-
logger$
|
|
16495
|
+
logger$17.debug("[RTCPeerConnectionController] Setting remote description:", answer);
|
|
16463
16496
|
return this.peerConnection.setRemoteDescription(answer);
|
|
16464
16497
|
}
|
|
16465
16498
|
};
|
|
@@ -16474,7 +16507,11 @@ function isVertoInviteMessage(value) {
|
|
|
16474
16507
|
const msg = value;
|
|
16475
16508
|
return msg.method === "verto.invite" && isObject(msg.params) && hasProperty(msg.params, "sdp") && hasProperty(msg.params, "callID");
|
|
16476
16509
|
}
|
|
16477
|
-
|
|
16510
|
+
/**
|
|
16511
|
+
* Guards inbound `verto.bye` frames (server → client). Outbound bye messages are
|
|
16512
|
+
* built locally and never type-guarded, so only the inbound shape is asserted.
|
|
16513
|
+
*/
|
|
16514
|
+
function isVertoByeInboundMessage(value) {
|
|
16478
16515
|
if (!isVertoMethodMessage(value)) return false;
|
|
16479
16516
|
return value.method === "verto.bye";
|
|
16480
16517
|
}
|
|
@@ -16494,11 +16531,19 @@ function isVertoMediaParamsInnerParams(value) {
|
|
|
16494
16531
|
function isVertoPingInnerParams(value) {
|
|
16495
16532
|
return isObject(value) && hasProperty(value, "jsonrpc") && value.jsonrpc === "2.0" && hasProperty(value, "method") && value.method === "verto.ping";
|
|
16496
16533
|
}
|
|
16534
|
+
/**
|
|
16535
|
+
* Guards the params of an inbound `verto.bye` frame (top-level `callID`). Use
|
|
16536
|
+
* this to recognise the bye payload extracted by `vertoBye$`, e.g. when racing
|
|
16537
|
+
* it against a boolean answer/reject signal.
|
|
16538
|
+
*/
|
|
16539
|
+
function isVertoByeInboundParamsGuard(value) {
|
|
16540
|
+
return isObject(value) && hasProperty(value, "callID") && typeof value.callID === "string";
|
|
16541
|
+
}
|
|
16497
16542
|
|
|
16498
16543
|
//#endregion
|
|
16499
16544
|
//#region src/managers/VertoManager.ts
|
|
16500
16545
|
var import_cjs$15 = require_cjs();
|
|
16501
|
-
const logger$
|
|
16546
|
+
const logger$16 = getLogger();
|
|
16502
16547
|
/**
|
|
16503
16548
|
* Decide what value goes on the `node_id` field of a `webrtc.verto` envelope.
|
|
16504
16549
|
*
|
|
@@ -16554,7 +16599,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16554
16599
|
try {
|
|
16555
16600
|
await this.executeVerto(vertoModifyMessage);
|
|
16556
16601
|
} catch (error) {
|
|
16557
|
-
logger$
|
|
16602
|
+
logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto hold:", error);
|
|
16558
16603
|
throw error;
|
|
16559
16604
|
}
|
|
16560
16605
|
}
|
|
@@ -16567,7 +16612,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16567
16612
|
try {
|
|
16568
16613
|
await this.executeVerto(vertoModifyMessage);
|
|
16569
16614
|
} catch (error) {
|
|
16570
|
-
logger$
|
|
16615
|
+
logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto unhold:", error);
|
|
16571
16616
|
throw error;
|
|
16572
16617
|
}
|
|
16573
16618
|
}
|
|
@@ -16620,7 +16665,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16620
16665
|
if (event.member_id) this.setSelfIdIfNull(event.member_id);
|
|
16621
16666
|
});
|
|
16622
16667
|
this.subscribeTo(this.vertoMedia$, (event) => {
|
|
16623
|
-
logger$
|
|
16668
|
+
logger$16.debug("[WebRTCManager] Received Verto media event (early media SDP):", event);
|
|
16624
16669
|
const { sdp, callID } = event;
|
|
16625
16670
|
this.emitMainSignalingStatus(callID, "ringing");
|
|
16626
16671
|
this._rtcPeerConnectionsMap.get(callID)?.updateAnswerStatus({
|
|
@@ -16629,7 +16674,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16629
16674
|
});
|
|
16630
16675
|
});
|
|
16631
16676
|
this.subscribeTo(this.vertoAnswer$, (event) => {
|
|
16632
|
-
logger$
|
|
16677
|
+
logger$16.debug("[WebRTCManager] Received Verto answer event:", event);
|
|
16633
16678
|
const { sdp, callID } = event;
|
|
16634
16679
|
this.emitMainSignalingStatus(callID, "connecting");
|
|
16635
16680
|
this._rtcPeerConnectionsMap.get(callID)?.updateAnswerStatus({
|
|
@@ -16638,7 +16683,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16638
16683
|
});
|
|
16639
16684
|
});
|
|
16640
16685
|
this.subscribeTo(this.vertoMediaParams$, (event) => {
|
|
16641
|
-
logger$
|
|
16686
|
+
logger$16.debug("[WebRTCManager] Received Verto mediaParams event:", event);
|
|
16642
16687
|
const { mediaParams, callID } = event;
|
|
16643
16688
|
const rtcPeerConnController = this._rtcPeerConnectionsMap.get(callID);
|
|
16644
16689
|
const { audio, video } = mediaParams;
|
|
@@ -16652,7 +16697,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16652
16697
|
timestamp: Date.now()
|
|
16653
16698
|
});
|
|
16654
16699
|
} catch (error) {
|
|
16655
|
-
logger$
|
|
16700
|
+
logger$16.warn("[WebRTCManager] Error applying server-pushed media params:", error);
|
|
16656
16701
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
16657
16702
|
}
|
|
16658
16703
|
})();
|
|
@@ -16674,13 +16719,13 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16674
16719
|
*/
|
|
16675
16720
|
setNodeIdIfNull(nodeId) {
|
|
16676
16721
|
if (!this._nodeId$.value && nodeId) {
|
|
16677
|
-
logger$
|
|
16722
|
+
logger$16.debug(`[WebRTCManager] Early node_id set: ${nodeId}`);
|
|
16678
16723
|
this._nodeId$.next(nodeId);
|
|
16679
16724
|
}
|
|
16680
16725
|
}
|
|
16681
16726
|
setSelfIdIfNull(selfId) {
|
|
16682
16727
|
if (!this._selfId$.value && selfId) {
|
|
16683
|
-
logger$
|
|
16728
|
+
logger$16.debug(`[WebRTCManager] Early selfId set: ${selfId}`);
|
|
16684
16729
|
this._selfId$.next(selfId);
|
|
16685
16730
|
}
|
|
16686
16731
|
}
|
|
@@ -16689,7 +16734,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16689
16734
|
const vertoPongMessage = VertoPong({ ...vertoPing });
|
|
16690
16735
|
await this.executeVerto(vertoPongMessage);
|
|
16691
16736
|
} catch (error) {
|
|
16692
|
-
logger$
|
|
16737
|
+
logger$16.warn("[WebRTCManager] Call might disconnect, error sending Verto pong:", error);
|
|
16693
16738
|
this.onError?.(new VertoPongError(error));
|
|
16694
16739
|
}
|
|
16695
16740
|
}
|
|
@@ -16699,7 +16744,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16699
16744
|
if (audio) await this.mainPeerConnection.updateSendersConstraints("audio", audio);
|
|
16700
16745
|
if (video) await this.mainPeerConnection.updateSendersConstraints("video", video);
|
|
16701
16746
|
} catch (error) {
|
|
16702
|
-
logger$
|
|
16747
|
+
logger$16.warn("[WebRTCManager] Error updating media constraints:", error);
|
|
16703
16748
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
16704
16749
|
throw error;
|
|
16705
16750
|
}
|
|
@@ -16729,20 +16774,20 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16729
16774
|
try {
|
|
16730
16775
|
const pc = this.mainPeerConnection.peerConnection;
|
|
16731
16776
|
if (!pc) {
|
|
16732
|
-
logger$
|
|
16777
|
+
logger$16.warn("[WebRTCManager] No peer connection for keyframe request");
|
|
16733
16778
|
return;
|
|
16734
16779
|
}
|
|
16735
16780
|
const videoReceiver = pc.getReceivers().find((r) => r.track.kind === "video");
|
|
16736
16781
|
if (!videoReceiver) {
|
|
16737
|
-
logger$
|
|
16782
|
+
logger$16.warn("[WebRTCManager] No video receiver for keyframe request");
|
|
16738
16783
|
return;
|
|
16739
16784
|
}
|
|
16740
16785
|
if (typeof videoReceiver.requestKeyFrame === "function") {
|
|
16741
16786
|
videoReceiver.requestKeyFrame();
|
|
16742
|
-
logger$
|
|
16743
|
-
} else logger$
|
|
16787
|
+
logger$16.debug("[WebRTCManager] Keyframe requested via RTCRtpReceiver.requestKeyFrame()");
|
|
16788
|
+
} else logger$16.debug("[WebRTCManager] requestKeyFrame() not supported, skipping");
|
|
16744
16789
|
} catch (error) {
|
|
16745
|
-
logger$
|
|
16790
|
+
logger$16.warn("[WebRTCManager] Keyframe request failed (non-fatal):", error);
|
|
16746
16791
|
}
|
|
16747
16792
|
}
|
|
16748
16793
|
/**
|
|
@@ -16760,13 +16805,13 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16760
16805
|
try {
|
|
16761
16806
|
const controller = this.mainPeerConnection;
|
|
16762
16807
|
if (!controller.peerConnection) {
|
|
16763
|
-
logger$
|
|
16808
|
+
logger$16.warn("[WebRTCManager] No peer connection for ICE restart");
|
|
16764
16809
|
return;
|
|
16765
16810
|
}
|
|
16766
16811
|
await controller.triggerIceRestart(relayOnly);
|
|
16767
|
-
logger$
|
|
16812
|
+
logger$16.info(`[WebRTCManager] ICE restart initiated${relayOnly ? " (relay-only)" : ""}`);
|
|
16768
16813
|
} catch (error) {
|
|
16769
|
-
logger$
|
|
16814
|
+
logger$16.error("[WebRTCManager] ICE restart failed:", error);
|
|
16770
16815
|
throw error;
|
|
16771
16816
|
}
|
|
16772
16817
|
}
|
|
@@ -16784,13 +16829,13 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16784
16829
|
const entries = Array.from(this._rtcPeerConnectionsMap.entries());
|
|
16785
16830
|
for (const [id, controller] of entries) try {
|
|
16786
16831
|
if (!controller.peerConnection) {
|
|
16787
|
-
logger$
|
|
16832
|
+
logger$16.debug(`[WebRTCManager] No peer connection for leg ${id}, skipping ICE restart`);
|
|
16788
16833
|
continue;
|
|
16789
16834
|
}
|
|
16790
16835
|
await controller.triggerIceRestart(relayOnly);
|
|
16791
|
-
logger$
|
|
16836
|
+
logger$16.info(`[WebRTCManager] ICE restart initiated for leg ${id}${relayOnly ? " (relay-only)" : ""}`);
|
|
16792
16837
|
} catch (error) {
|
|
16793
|
-
logger$
|
|
16838
|
+
logger$16.warn(`[WebRTCManager] ICE restart failed for leg ${id}:`, error);
|
|
16794
16839
|
}
|
|
16795
16840
|
}
|
|
16796
16841
|
/**
|
|
@@ -16802,7 +16847,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16802
16847
|
requestKeyframeAll() {
|
|
16803
16848
|
for (const [id, controller] of this._rtcPeerConnectionsMap) {
|
|
16804
16849
|
if (controller.isScreenShare) {
|
|
16805
|
-
logger$
|
|
16850
|
+
logger$16.debug(`[WebRTCManager] Skipping keyframe for send-only screen share leg ${id}`);
|
|
16806
16851
|
continue;
|
|
16807
16852
|
}
|
|
16808
16853
|
try {
|
|
@@ -16812,10 +16857,10 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16812
16857
|
if (!videoReceiver) continue;
|
|
16813
16858
|
if (typeof videoReceiver.requestKeyFrame === "function") {
|
|
16814
16859
|
videoReceiver.requestKeyFrame();
|
|
16815
|
-
logger$
|
|
16860
|
+
logger$16.debug(`[WebRTCManager] Keyframe requested for leg ${id}`);
|
|
16816
16861
|
}
|
|
16817
16862
|
} catch (error) {
|
|
16818
|
-
logger$
|
|
16863
|
+
logger$16.warn(`[WebRTCManager] Keyframe request failed for leg ${id} (non-fatal):`, error);
|
|
16819
16864
|
}
|
|
16820
16865
|
}
|
|
16821
16866
|
}
|
|
@@ -16832,7 +16877,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16832
16877
|
return this.cachedObservable("vertoMediaParams$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoMediaParamsInnerParams, "params"), (0, import_cjs$15.takeUntil)(this.destroyed$)));
|
|
16833
16878
|
}
|
|
16834
16879
|
get vertoBye$() {
|
|
16835
|
-
return this.cachedObservable("vertoBye$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(
|
|
16880
|
+
return this.cachedObservable("vertoBye$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoByeInboundMessage, "params"), (0, import_cjs$15.takeUntil)(this.destroyed$)));
|
|
16836
16881
|
}
|
|
16837
16882
|
get vertoAttach$() {
|
|
16838
16883
|
return this.cachedObservable("vertoAttach$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoAttachMessage, "params"), (0, import_cjs$15.takeUntil)(this.destroyed$)));
|
|
@@ -16876,7 +16921,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16876
16921
|
default:
|
|
16877
16922
|
}
|
|
16878
16923
|
} catch (error) {
|
|
16879
|
-
logger$
|
|
16924
|
+
logger$16.error(`[WebRTCManager] Error sending Verto ${vertoMethod}:`, error);
|
|
16880
16925
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
16881
16926
|
if (vertoMethod === "verto.modify") this.onModifyFailed?.();
|
|
16882
16927
|
}
|
|
@@ -16891,7 +16936,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16891
16936
|
sdp
|
|
16892
16937
|
});
|
|
16893
16938
|
} catch (error) {
|
|
16894
|
-
logger$
|
|
16939
|
+
logger$16.warn("[WebRTCManager] Error processing modify response:", error);
|
|
16895
16940
|
const modifyError = error instanceof Error ? error : new Error(String(error), { cause: error });
|
|
16896
16941
|
this.onError?.(modifyError);
|
|
16897
16942
|
}
|
|
@@ -16901,7 +16946,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16901
16946
|
const rtcPeerConnController = this._rtcPeerConnectionsMap.get(callId);
|
|
16902
16947
|
if (!rtcPeerConnController) {
|
|
16903
16948
|
const signalingError = new DependencyError(`Cannot emit signaling status, RTCPeerConnectionController not found for callID: ${callId}`);
|
|
16904
|
-
logger$
|
|
16949
|
+
logger$16.error("[WebRTCManager] Failed to emit signaling status:", {
|
|
16905
16950
|
callId,
|
|
16906
16951
|
status,
|
|
16907
16952
|
signalingError
|
|
@@ -16917,7 +16962,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16917
16962
|
this._nodeId$.next(getValueFrom(response, "result.node_id") ?? null);
|
|
16918
16963
|
const memberId = getValueFrom(response, "result.result.result.memberID") ?? null;
|
|
16919
16964
|
const callId = getValueFrom(response, "result.result.result.callID") ?? null;
|
|
16920
|
-
logger$
|
|
16965
|
+
logger$16.debug("[WebRTCManager] Verto invite response:", {
|
|
16921
16966
|
callId,
|
|
16922
16967
|
memberId,
|
|
16923
16968
|
response
|
|
@@ -16927,14 +16972,14 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16927
16972
|
if (callId) {
|
|
16928
16973
|
this.webRtcCallSession.addCallId(callId);
|
|
16929
16974
|
this.attachManager.attach(this.buildAttachableCall(callId));
|
|
16930
|
-
} else logger$
|
|
16975
|
+
} else logger$16.warn("[WebRTCManager] Cannot attach call, missing callId:", {
|
|
16931
16976
|
nodeId: this.nodeId,
|
|
16932
16977
|
callId
|
|
16933
16978
|
});
|
|
16934
|
-
logger$
|
|
16935
|
-
logger$
|
|
16979
|
+
logger$16.info("[WebRTCManager] Verto invite successful");
|
|
16980
|
+
logger$16.debug(`[WebRTCManager] nodeid: ${this._nodeId$.value}, selfId: ${this._selfId$.value}`);
|
|
16936
16981
|
} else {
|
|
16937
|
-
logger$
|
|
16982
|
+
logger$16.error("[WebRTCManager] Verto invite failed:", response);
|
|
16938
16983
|
const inviteError = response.error ? new JSONRPCError(response.error.code, response.error.message, response.error.data) : /* @__PURE__ */ new Error("Verto invite failed: unexpected response");
|
|
16939
16984
|
this.onError?.(inviteError);
|
|
16940
16985
|
}
|
|
@@ -16979,17 +17024,17 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16979
17024
|
if (options.initOffer) this.handleInboundAnswer(rtcPeerConnController);
|
|
16980
17025
|
}
|
|
16981
17026
|
async handleInboundAnswer(rtcPeerConnController) {
|
|
16982
|
-
logger$
|
|
17027
|
+
logger$16.debug("[WebRTCManager] Waiting for inbound call to be accepted or rejected");
|
|
16983
17028
|
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);
|
|
16984
17029
|
if (vertoByeOrAccepted === null) {
|
|
16985
|
-
logger$
|
|
17030
|
+
logger$16.debug("[WebRTCManager] Inbound answer handler aborted (destroyed).");
|
|
16986
17031
|
return;
|
|
16987
17032
|
}
|
|
16988
|
-
if (
|
|
16989
|
-
logger$
|
|
17033
|
+
if (isVertoByeInboundParamsGuard(vertoByeOrAccepted)) {
|
|
17034
|
+
logger$16.info("[WebRTCManager] Inbound call ended by remote before answer.");
|
|
16990
17035
|
this.callSession?.destroy();
|
|
16991
17036
|
} else if (!vertoByeOrAccepted) {
|
|
16992
|
-
logger$
|
|
17037
|
+
logger$16.info("[WebRTCManager] Inbound call rejected by user.");
|
|
16993
17038
|
try {
|
|
16994
17039
|
await this.bye("USER_BUSY");
|
|
16995
17040
|
} finally {
|
|
@@ -16997,19 +17042,19 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16997
17042
|
this.callSession?.destroy();
|
|
16998
17043
|
}
|
|
16999
17044
|
} else {
|
|
17000
|
-
logger$
|
|
17045
|
+
logger$16.debug("[WebRTCManager] Inbound call accepted, creating SDP answer");
|
|
17001
17046
|
const answerOptions = this.webRtcCallSession.answerMediaOptions;
|
|
17002
17047
|
try {
|
|
17003
17048
|
await rtcPeerConnController.acceptInbound(answerOptions);
|
|
17004
17049
|
} catch (error) {
|
|
17005
|
-
logger$
|
|
17050
|
+
logger$16.error("[WebRTCManager] Error creating inbound answer:", error);
|
|
17006
17051
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
17007
17052
|
}
|
|
17008
17053
|
}
|
|
17009
17054
|
}
|
|
17010
17055
|
setupVertoAttachHandler() {
|
|
17011
17056
|
this.subscribeTo(this.vertoAttach$, async (vertoAttach) => {
|
|
17012
|
-
logger$
|
|
17057
|
+
logger$16.debug("[WebRTCManager] Received Verto attach event for existing call:", vertoAttach);
|
|
17013
17058
|
const { callID } = vertoAttach;
|
|
17014
17059
|
await this.attachManager.attach({
|
|
17015
17060
|
nodeId: this.nodeId ?? void 0,
|
|
@@ -17081,17 +17126,17 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17081
17126
|
};
|
|
17082
17127
|
}
|
|
17083
17128
|
async sendLocalDescriptionOnceAccepted(vertoMessageRequest, rtcPeerConnectionController) {
|
|
17084
|
-
logger$
|
|
17129
|
+
logger$16.debug("[WebRTCManager] Waiting for call to be accepted or ended before sending answer");
|
|
17085
17130
|
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);
|
|
17086
17131
|
if (vertoByeOrAccepted === null) {
|
|
17087
|
-
logger$
|
|
17132
|
+
logger$16.debug("[WebRTCManager] Destroyed while waiting for call acceptance");
|
|
17088
17133
|
return;
|
|
17089
17134
|
}
|
|
17090
|
-
if (
|
|
17091
|
-
logger$
|
|
17135
|
+
if (isVertoByeInboundParamsGuard(vertoByeOrAccepted)) {
|
|
17136
|
+
logger$16.info("[WebRTCManager] Call ended before answer was sent.");
|
|
17092
17137
|
this.callSession?.destroy();
|
|
17093
17138
|
} else if (!vertoByeOrAccepted) {
|
|
17094
|
-
logger$
|
|
17139
|
+
logger$16.info("[WebRTCManager] Call was not accepted, sending verto.bye.");
|
|
17095
17140
|
try {
|
|
17096
17141
|
await this.bye("USER_BUSY");
|
|
17097
17142
|
} finally {
|
|
@@ -17099,14 +17144,14 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17099
17144
|
this.callSession?.destroy();
|
|
17100
17145
|
}
|
|
17101
17146
|
} else {
|
|
17102
|
-
logger$
|
|
17147
|
+
logger$16.debug("[WebRTCManager] Call accepted, sending answer");
|
|
17103
17148
|
try {
|
|
17104
17149
|
this.emitMainSignalingStatus(rtcPeerConnectionController.id, "connecting");
|
|
17105
17150
|
await this.sendLocalDescription(vertoMessageRequest, rtcPeerConnectionController);
|
|
17106
17151
|
await rtcPeerConnectionController.updateAnswerStatus({ status: "sent" });
|
|
17107
17152
|
await this.attachManager.attach(this.buildAttachableCall());
|
|
17108
17153
|
} catch (error) {
|
|
17109
|
-
logger$
|
|
17154
|
+
logger$16.error("[WebRTCManager] Error sending Verto answer:", error);
|
|
17110
17155
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
17111
17156
|
await rtcPeerConnectionController.updateAnswerStatus({ status: "failed" });
|
|
17112
17157
|
}
|
|
@@ -17205,10 +17250,10 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17205
17250
|
});
|
|
17206
17251
|
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$)));
|
|
17207
17252
|
this._screenShareStatus$.next("started");
|
|
17208
|
-
logger$
|
|
17253
|
+
logger$16.info("[WebRTCManager] Screen share started successfully.");
|
|
17209
17254
|
return rtcPeerConnController.id;
|
|
17210
17255
|
} catch (error) {
|
|
17211
|
-
logger$
|
|
17256
|
+
logger$16.warn("[WebRTCManager] Error initializing additional peer connection:", error);
|
|
17212
17257
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
17213
17258
|
if (rtcPeerConnController) rtcPeerConnController.destroy();
|
|
17214
17259
|
this._screenShareStatus$.next("none");
|
|
@@ -17227,9 +17272,9 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17227
17272
|
if (removeTrack) return this.mainPeerConnection.stopTrackSender(removeTrack, { updateTransceiverDirection: true });
|
|
17228
17273
|
}
|
|
17229
17274
|
async removeScreenMedia() {
|
|
17230
|
-
if (!["starting", "started"].includes(this._screenShareStatus$.value)) logger$
|
|
17275
|
+
if (!["starting", "started"].includes(this._screenShareStatus$.value)) logger$16.warn("[WebRTCManager] No active screen share to stop.");
|
|
17231
17276
|
if (!this._screenShareId) {
|
|
17232
|
-
logger$
|
|
17277
|
+
logger$16.debug("[WebRTCManager] No screen share peer connection found.");
|
|
17233
17278
|
return;
|
|
17234
17279
|
}
|
|
17235
17280
|
this._screenShareStatus$.next("stopping");
|
|
@@ -17258,7 +17303,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17258
17303
|
dialogParams: this.dialogParams(rtcPeerConnController)
|
|
17259
17304
|
}));
|
|
17260
17305
|
} catch (error) {
|
|
17261
|
-
logger$
|
|
17306
|
+
logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto bye:", error);
|
|
17262
17307
|
throw error;
|
|
17263
17308
|
}
|
|
17264
17309
|
}
|
|
@@ -17276,7 +17321,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17276
17321
|
try {
|
|
17277
17322
|
await this.executeVerto(vertoInfoMessage);
|
|
17278
17323
|
} catch (error) {
|
|
17279
|
-
logger$
|
|
17324
|
+
logger$16.warn("[WebRTCManager] Error sending DTMF digits:", error);
|
|
17280
17325
|
throw error;
|
|
17281
17326
|
}
|
|
17282
17327
|
}
|
|
@@ -17287,10 +17332,10 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17287
17332
|
action: "transfer"
|
|
17288
17333
|
});
|
|
17289
17334
|
try {
|
|
17290
|
-
logger$
|
|
17335
|
+
logger$16.debug("[WebRTCManager] Transferring call with options:", options);
|
|
17291
17336
|
await this.executeVerto(message);
|
|
17292
17337
|
} catch (error) {
|
|
17293
|
-
logger$
|
|
17338
|
+
logger$16.error("[WebRTCManager] Error transferring call:", error);
|
|
17294
17339
|
throw error;
|
|
17295
17340
|
}
|
|
17296
17341
|
}
|
|
@@ -17307,7 +17352,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17307
17352
|
//#endregion
|
|
17308
17353
|
//#region src/controllers/RemoteAudioMeter.ts
|
|
17309
17354
|
var import_cjs$14 = require_cjs();
|
|
17310
|
-
const logger$
|
|
17355
|
+
const logger$15 = getLogger();
|
|
17311
17356
|
/**
|
|
17312
17357
|
* Read-only audio level meter for a remote MediaStream. Attaches an
|
|
17313
17358
|
* AnalyserNode to a MediaStreamAudioSourceNode so it observes the stream
|
|
@@ -17342,7 +17387,7 @@ var RemoteAudioMeter = class extends Destroyable {
|
|
|
17342
17387
|
try {
|
|
17343
17388
|
this._source.disconnect();
|
|
17344
17389
|
} catch (error) {
|
|
17345
|
-
logger$
|
|
17390
|
+
logger$15.debug("[RemoteAudioMeter] source disconnect warning:", error);
|
|
17346
17391
|
}
|
|
17347
17392
|
this._source = null;
|
|
17348
17393
|
this._stream = null;
|
|
@@ -17359,7 +17404,7 @@ var RemoteAudioMeter = class extends Destroyable {
|
|
|
17359
17404
|
this._source = null;
|
|
17360
17405
|
}
|
|
17361
17406
|
this._audioContext.close().catch((error) => {
|
|
17362
|
-
logger$
|
|
17407
|
+
logger$15.debug("[RemoteAudioMeter] audio context close warning:", error);
|
|
17363
17408
|
});
|
|
17364
17409
|
super.destroy();
|
|
17365
17410
|
}
|
|
@@ -17378,7 +17423,7 @@ var RemoteAudioMeter = class extends Destroyable {
|
|
|
17378
17423
|
//#endregion
|
|
17379
17424
|
//#region src/controllers/RTCStatsMonitor.ts
|
|
17380
17425
|
var import_cjs$13 = require_cjs();
|
|
17381
|
-
const logger$
|
|
17426
|
+
const logger$14 = getLogger();
|
|
17382
17427
|
const DEFAULT_POLLING_INTERVAL_MS = 1e3;
|
|
17383
17428
|
const DEFAULT_BASELINE_SAMPLES = 10;
|
|
17384
17429
|
const DEFAULT_NO_AUDIO_PACKET_THRESHOLD_MS = 2e3;
|
|
@@ -17468,9 +17513,9 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
17468
17513
|
const now = Date.now();
|
|
17469
17514
|
this.lastAudioPacketChangeTime = now;
|
|
17470
17515
|
this.lastVideoPacketChangeTime = now;
|
|
17471
|
-
logger$
|
|
17516
|
+
logger$14.debug("[RTCStatsMonitor] Starting stats monitoring");
|
|
17472
17517
|
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) => {
|
|
17473
|
-
logger$
|
|
17518
|
+
logger$14.warn("[RTCStatsMonitor] Failed to get stats:", err);
|
|
17474
17519
|
return import_cjs$13.EMPTY;
|
|
17475
17520
|
}))), (0, import_cjs$13.filter)(() => this.running), (0, import_cjs$13.map)((report) => this.extractSample(report))), (sample$1) => this._sample$.next(sample$1));
|
|
17476
17521
|
this.subscribeTo(this._sample$.pipe((0, import_cjs$13.take)(this.baselineSampleCount), (0, import_cjs$13.toArray)(), (0, import_cjs$13.map)((samples) => ({
|
|
@@ -17478,7 +17523,7 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
17478
17523
|
jitter: samples.reduce((s, b) => s + b.audioJitter, 0) / samples.length,
|
|
17479
17524
|
ready: true
|
|
17480
17525
|
}))), (baseline) => {
|
|
17481
|
-
logger$
|
|
17526
|
+
logger$14.debug(`[RTCStatsMonitor] Baseline established: rtt=${baseline.rtt.toFixed(1)}ms, jitter=${baseline.jitter.toFixed(1)}ms`);
|
|
17482
17527
|
this._baseline$.next(baseline);
|
|
17483
17528
|
});
|
|
17484
17529
|
this.subscribeTo(this._sample$.pipe((0, import_cjs$13.scan)((acc, sample$1) => ({
|
|
@@ -17515,10 +17560,10 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
17515
17560
|
stop() {
|
|
17516
17561
|
if (!this.running) return;
|
|
17517
17562
|
this.running = false;
|
|
17518
|
-
logger$
|
|
17563
|
+
logger$14.debug("[RTCStatsMonitor] Stopping stats monitoring");
|
|
17519
17564
|
}
|
|
17520
17565
|
destroy() {
|
|
17521
|
-
logger$
|
|
17566
|
+
logger$14.debug("[RTCStatsMonitor] Destroying RTCStatsMonitor");
|
|
17522
17567
|
this.stop();
|
|
17523
17568
|
super.destroy();
|
|
17524
17569
|
}
|
|
@@ -17647,7 +17692,7 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
17647
17692
|
//#endregion
|
|
17648
17693
|
//#region src/managers/CallRecoveryManager.ts
|
|
17649
17694
|
var import_cjs$12 = require_cjs();
|
|
17650
|
-
const logger$
|
|
17695
|
+
const logger$13 = getLogger();
|
|
17651
17696
|
const DEFAULT_DEBOUNCE_TIME_MS = 2e3;
|
|
17652
17697
|
const DEFAULT_COOLDOWN_MS = 1e4;
|
|
17653
17698
|
const DEFAULT_ICE_GRACE_PERIOD_MS = 3e3;
|
|
@@ -17744,10 +17789,10 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17744
17789
|
*/
|
|
17745
17790
|
async requestIceRestart() {
|
|
17746
17791
|
if (this._recoveryState$.value === "recovering") {
|
|
17747
|
-
logger$
|
|
17792
|
+
logger$13.info("CallRecoveryManager: manual ICE restart skipped — recovery already in progress");
|
|
17748
17793
|
return;
|
|
17749
17794
|
}
|
|
17750
|
-
logger$
|
|
17795
|
+
logger$13.info("CallRecoveryManager: manual ICE restart requested");
|
|
17751
17796
|
this.transitionTo("recovering");
|
|
17752
17797
|
await this.executeIceRestart(false);
|
|
17753
17798
|
this.startCooldown();
|
|
@@ -17763,7 +17808,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17763
17808
|
* WebSocket reconnect or call state recovers to 'connected'.
|
|
17764
17809
|
*/
|
|
17765
17810
|
reset() {
|
|
17766
|
-
logger$
|
|
17811
|
+
logger$13.info("CallRecoveryManager: resetting counters");
|
|
17767
17812
|
this._attemptCount = 0;
|
|
17768
17813
|
this._keyframeBurstCount = 0;
|
|
17769
17814
|
this._keyframeBurstStart = 0;
|
|
@@ -17778,7 +17823,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17778
17823
|
*/
|
|
17779
17824
|
notifyModifyFailed() {
|
|
17780
17825
|
if (this._recoveryState$.value === "cooldown" || this._recoveryState$.value === "idle") {
|
|
17781
|
-
logger$
|
|
17826
|
+
logger$13.info("CallRecoveryManager: verto.modify failed — re-entering recovery");
|
|
17782
17827
|
this._cooldownUntil = 0;
|
|
17783
17828
|
this.transitionTo("idle");
|
|
17784
17829
|
this.pushTrigger({
|
|
@@ -17802,7 +17847,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17802
17847
|
reason: `bandwidth ${bitrateKbps}kbps below threshold ${this._config.degradationBitrateThreshold}kbps`,
|
|
17803
17848
|
timestamp: Date.now()
|
|
17804
17849
|
});
|
|
17805
|
-
logger$
|
|
17850
|
+
logger$13.warn(`CallRecoveryManager: disabling video — bandwidth ${bitrateKbps}kbps < ${this._config.degradationBitrateThreshold}kbps`);
|
|
17806
17851
|
} else if (wasConstrained && bitrateKbps >= this._config.degradationRecoveryThreshold) {
|
|
17807
17852
|
this._bandwidthConstrained$.next(false);
|
|
17808
17853
|
this._callbacks.enableVideo();
|
|
@@ -17811,7 +17856,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17811
17856
|
reason: `bandwidth ${bitrateKbps}kbps recovered above ${this._config.degradationRecoveryThreshold}kbps`,
|
|
17812
17857
|
timestamp: Date.now()
|
|
17813
17858
|
});
|
|
17814
|
-
logger$
|
|
17859
|
+
logger$13.info(`CallRecoveryManager: restoring video — bandwidth ${bitrateKbps}kbps >= ${this._config.degradationRecoveryThreshold}kbps`);
|
|
17815
17860
|
}
|
|
17816
17861
|
}
|
|
17817
17862
|
/**
|
|
@@ -17830,14 +17875,14 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17830
17875
|
handleWebSocketReconnect() {
|
|
17831
17876
|
const pcState = this._callbacks.getPeerConnectionState();
|
|
17832
17877
|
if (pcState === "connected" || pcState === "completed") {
|
|
17833
|
-
logger$
|
|
17878
|
+
logger$13.info("CallRecoveryManager: signal-only reconnect — peer connection still alive");
|
|
17834
17879
|
this.emitEvent({
|
|
17835
17880
|
action: "signal_reconnect",
|
|
17836
17881
|
reason: "WebSocket reconnected, peer connection still connected",
|
|
17837
17882
|
timestamp: Date.now()
|
|
17838
17883
|
});
|
|
17839
17884
|
} else {
|
|
17840
|
-
logger$
|
|
17885
|
+
logger$13.info("CallRecoveryManager: full reconnect — peer connection also down");
|
|
17841
17886
|
this.emitEvent({
|
|
17842
17887
|
action: "full_reconnect",
|
|
17843
17888
|
reason: "WebSocket reconnected, peer connection not connected — ICE restart needed",
|
|
@@ -17861,7 +17906,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17861
17906
|
}), (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$))), {
|
|
17862
17907
|
next: () => {},
|
|
17863
17908
|
error: (err) => {
|
|
17864
|
-
logger$
|
|
17909
|
+
logger$13.error("CallRecoveryManager: pipeline error", err);
|
|
17865
17910
|
this.transitionTo("idle");
|
|
17866
17911
|
}
|
|
17867
17912
|
});
|
|
@@ -17888,27 +17933,27 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17888
17933
|
reason: `no packet loss for ${this._config.packetLossRecoveryDelaySec}s — restoring video`,
|
|
17889
17934
|
timestamp: Date.now()
|
|
17890
17935
|
});
|
|
17891
|
-
logger$
|
|
17936
|
+
logger$13.info(`CallRecoveryManager: restoring video — no packet loss for ${this._config.packetLossRecoveryDelaySec}s`);
|
|
17892
17937
|
});
|
|
17893
17938
|
}
|
|
17894
17939
|
passGateChecks(signalingReady) {
|
|
17895
17940
|
if (this._callbacks.isNegotiating()) {
|
|
17896
|
-
logger$
|
|
17941
|
+
logger$13.debug("CallRecoveryManager: gate blocked — negotiation in progress");
|
|
17897
17942
|
this.transitionTo("idle");
|
|
17898
17943
|
return false;
|
|
17899
17944
|
}
|
|
17900
17945
|
if (!signalingReady) {
|
|
17901
|
-
logger$
|
|
17946
|
+
logger$13.debug("CallRecoveryManager: gate blocked — signaling not ready");
|
|
17902
17947
|
this.transitionTo("idle");
|
|
17903
17948
|
return false;
|
|
17904
17949
|
}
|
|
17905
17950
|
if (!this._callbacks.isCallConnected()) {
|
|
17906
|
-
logger$
|
|
17951
|
+
logger$13.debug("CallRecoveryManager: gate blocked — call not connected");
|
|
17907
17952
|
this.transitionTo("idle");
|
|
17908
17953
|
return false;
|
|
17909
17954
|
}
|
|
17910
17955
|
if (this.isCooldownActive()) {
|
|
17911
|
-
logger$
|
|
17956
|
+
logger$13.debug("CallRecoveryManager: gate blocked — cooldown active");
|
|
17912
17957
|
this.transitionTo("cooldown");
|
|
17913
17958
|
return false;
|
|
17914
17959
|
}
|
|
@@ -17919,9 +17964,9 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17919
17964
|
}
|
|
17920
17965
|
executeTieredRecovery(trigger) {
|
|
17921
17966
|
this.transitionTo("recovering");
|
|
17922
|
-
logger$
|
|
17967
|
+
logger$13.info(`CallRecoveryManager: starting tiered recovery — source=${trigger.source} detail=${trigger.detail}`);
|
|
17923
17968
|
return (0, import_cjs$12.from)(this.runTiers(trigger)).pipe((0, import_cjs$12.tap)(() => this.startCooldown()), (0, import_cjs$12.catchError)((err) => {
|
|
17924
|
-
logger$
|
|
17969
|
+
logger$13.error("CallRecoveryManager: tiered recovery failed", err);
|
|
17925
17970
|
this.startCooldown();
|
|
17926
17971
|
return import_cjs$12.EMPTY;
|
|
17927
17972
|
}));
|
|
@@ -17929,7 +17974,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17929
17974
|
async runTiers(trigger) {
|
|
17930
17975
|
this.executeKeyframe(trigger.detail);
|
|
17931
17976
|
if (trigger.issueType && DEGRADATION_ONLY_ISSUES.has(trigger.issueType)) {
|
|
17932
|
-
logger$
|
|
17977
|
+
logger$13.debug(`CallRecoveryManager: degradation-only issue (${trigger.issueType}) — Tier 1 only, skipping ICE restart`);
|
|
17933
17978
|
return;
|
|
17934
17979
|
}
|
|
17935
17980
|
if (this._attemptCount < this._config.maxAttempts) {
|
|
@@ -17946,13 +17991,13 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17946
17991
|
maxAttempts: this._config.maxAttempts,
|
|
17947
17992
|
timestamp: Date.now()
|
|
17948
17993
|
});
|
|
17949
|
-
logger$
|
|
17994
|
+
logger$13.warn("CallRecoveryManager: max recovery attempts reached");
|
|
17950
17995
|
}
|
|
17951
17996
|
}
|
|
17952
17997
|
executeKeyframe(reason) {
|
|
17953
17998
|
const now = Date.now();
|
|
17954
17999
|
if (now < this._keyframeCooldownUntil) {
|
|
17955
|
-
logger$
|
|
18000
|
+
logger$13.debug("CallRecoveryManager: keyframe request skipped — cooldown active");
|
|
17956
18001
|
return;
|
|
17957
18002
|
}
|
|
17958
18003
|
if (now - this._keyframeBurstStart > this._config.keyframeBurstWindowMs) {
|
|
@@ -17961,7 +18006,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17961
18006
|
}
|
|
17962
18007
|
if (this._keyframeBurstCount >= this._config.keyframeMaxBurst) {
|
|
17963
18008
|
this._keyframeCooldownUntil = now + this._config.keyframeCooldownMs;
|
|
17964
|
-
logger$
|
|
18009
|
+
logger$13.debug(`CallRecoveryManager: keyframe burst limit reached (${this._config.keyframeMaxBurst}), cooldown until ${this._keyframeCooldownUntil}`);
|
|
17965
18010
|
return;
|
|
17966
18011
|
}
|
|
17967
18012
|
this._keyframeBurstCount += 1;
|
|
@@ -17971,12 +18016,12 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17971
18016
|
reason,
|
|
17972
18017
|
timestamp: now
|
|
17973
18018
|
});
|
|
17974
|
-
logger$
|
|
18019
|
+
logger$13.debug(`CallRecoveryManager: keyframe requested (burst ${this._keyframeBurstCount}/${this._config.keyframeMaxBurst})`);
|
|
17975
18020
|
}
|
|
17976
18021
|
async executeIceRestart(relayOnly) {
|
|
17977
18022
|
this._attemptCount += 1;
|
|
17978
18023
|
const tier = relayOnly ? "Tier 3 (relay-only)" : "Tier 2 (standard)";
|
|
17979
|
-
logger$
|
|
18024
|
+
logger$13.info(`CallRecoveryManager: ${tier} ICE restart — attempt ${this._attemptCount}/${this._config.maxAttempts}`);
|
|
17980
18025
|
this.emitEvent({
|
|
17981
18026
|
action: "reinvite_started",
|
|
17982
18027
|
reason: `${tier} ICE restart`,
|
|
@@ -17993,7 +18038,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17993
18038
|
maxAttempts: this._config.maxAttempts,
|
|
17994
18039
|
timestamp: Date.now()
|
|
17995
18040
|
});
|
|
17996
|
-
logger$
|
|
18041
|
+
logger$13.info(`CallRecoveryManager: ${tier} ICE restart succeeded`);
|
|
17997
18042
|
this._attemptCount = 0;
|
|
17998
18043
|
return true;
|
|
17999
18044
|
}
|
|
@@ -18004,7 +18049,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
18004
18049
|
maxAttempts: this._config.maxAttempts,
|
|
18005
18050
|
timestamp: Date.now()
|
|
18006
18051
|
});
|
|
18007
|
-
logger$
|
|
18052
|
+
logger$13.warn(`CallRecoveryManager: ${tier} ICE restart failed`);
|
|
18008
18053
|
return false;
|
|
18009
18054
|
} catch {
|
|
18010
18055
|
this.emitEvent({
|
|
@@ -18014,7 +18059,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
18014
18059
|
maxAttempts: this._config.maxAttempts,
|
|
18015
18060
|
timestamp: Date.now()
|
|
18016
18061
|
});
|
|
18017
|
-
logger$
|
|
18062
|
+
logger$13.warn(`CallRecoveryManager: ${tier} ICE restart timed out`);
|
|
18018
18063
|
return false;
|
|
18019
18064
|
}
|
|
18020
18065
|
}
|
|
@@ -18035,7 +18080,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
18035
18080
|
transitionTo(state) {
|
|
18036
18081
|
const prev = this._recoveryState$.value;
|
|
18037
18082
|
if (prev !== state) {
|
|
18038
|
-
logger$
|
|
18083
|
+
logger$13.debug(`CallRecoveryManager: state ${prev} -> ${state}`);
|
|
18039
18084
|
this._recoveryState$.next(state);
|
|
18040
18085
|
}
|
|
18041
18086
|
}
|
|
@@ -18138,7 +18183,13 @@ function mosToQualityLevel(mos) {
|
|
|
18138
18183
|
//#endregion
|
|
18139
18184
|
//#region src/core/entities/Call.ts
|
|
18140
18185
|
var import_cjs$11 = require_cjs();
|
|
18141
|
-
const logger$
|
|
18186
|
+
const logger$12 = getLogger();
|
|
18187
|
+
/**
|
|
18188
|
+
* Verto method for setting member layout positions. Its gateway DTO requires a
|
|
18189
|
+
* `targets` array whose entries are `{ target, position }` (NOT bare targets),
|
|
18190
|
+
* so {@link WebRTCCall.buildMethodParams} special-cases it. See issue #19400.
|
|
18191
|
+
*/
|
|
18192
|
+
const POSITION_SET_METHOD = "call.member.position.set";
|
|
18142
18193
|
/**
|
|
18143
18194
|
* Ratio between the critical and warning RTT spike multipliers.
|
|
18144
18195
|
* Warning threshold = baseline * warningMultiplier (default 3x)
|
|
@@ -18156,7 +18207,7 @@ const fromDestinationParams = (destination) => {
|
|
|
18156
18207
|
});
|
|
18157
18208
|
return params;
|
|
18158
18209
|
} catch (error) {
|
|
18159
|
-
logger$
|
|
18210
|
+
logger$12.warn(`Failed to parse destination URI: ${destination}`, error);
|
|
18160
18211
|
return {};
|
|
18161
18212
|
}
|
|
18162
18213
|
};
|
|
@@ -18346,7 +18397,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18346
18397
|
* @throws {JSONRPCError} If the RPC call returns an error.
|
|
18347
18398
|
*/
|
|
18348
18399
|
async executeMethod(target, method, args) {
|
|
18349
|
-
const params = this.buildMethodParams(target, args);
|
|
18400
|
+
const params = this.buildMethodParams(target, args, method);
|
|
18350
18401
|
const request = buildRPCRequest({
|
|
18351
18402
|
method,
|
|
18352
18403
|
params
|
|
@@ -18356,16 +18407,20 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18356
18407
|
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);
|
|
18357
18408
|
return response;
|
|
18358
18409
|
} catch (error) {
|
|
18359
|
-
logger$
|
|
18410
|
+
logger$12.error(`[Call] Error executing method ${method} with params`, params, error);
|
|
18360
18411
|
throw error;
|
|
18361
18412
|
}
|
|
18362
18413
|
}
|
|
18363
|
-
buildMethodParams(target, args) {
|
|
18414
|
+
buildMethodParams(target, args, method) {
|
|
18364
18415
|
const self = {
|
|
18365
18416
|
node_id: this.nodeId ?? "",
|
|
18366
18417
|
call_id: this.id,
|
|
18367
18418
|
member_id: this.vertoManager.selfId ?? ""
|
|
18368
18419
|
};
|
|
18420
|
+
if (method === POSITION_SET_METHOD) return {
|
|
18421
|
+
...args,
|
|
18422
|
+
self
|
|
18423
|
+
};
|
|
18369
18424
|
if (typeof target === "object") return {
|
|
18370
18425
|
...args,
|
|
18371
18426
|
self,
|
|
@@ -18559,9 +18614,9 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18559
18614
|
*/
|
|
18560
18615
|
initResilienceSubsystems() {
|
|
18561
18616
|
const pc = this.rtcPeerConnection;
|
|
18562
|
-
logger$
|
|
18617
|
+
logger$12.debug(`[Call] initResilienceSubsystems: pc=${pc ? "exists" : "undefined"}, connectionState=${pc?.connectionState}`);
|
|
18563
18618
|
if (!pc) {
|
|
18564
|
-
logger$
|
|
18619
|
+
logger$12.warn("[Call] No peer connection available, skipping resilience init");
|
|
18565
18620
|
return;
|
|
18566
18621
|
}
|
|
18567
18622
|
try {
|
|
@@ -18596,14 +18651,14 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18596
18651
|
disableVideo: () => {
|
|
18597
18652
|
try {
|
|
18598
18653
|
this.vertoManager.muteMainVideoInputDevice();
|
|
18599
|
-
logger$
|
|
18654
|
+
logger$12.debug("[Call] Recovery manager disabled video");
|
|
18600
18655
|
} catch {
|
|
18601
|
-
logger$
|
|
18656
|
+
logger$12.debug("[Call] Recovery manager failed to disable video");
|
|
18602
18657
|
}
|
|
18603
18658
|
},
|
|
18604
18659
|
enableVideo: () => {
|
|
18605
18660
|
this.vertoManager.unmuteMainVideoInputDevice().catch(() => {
|
|
18606
|
-
logger$
|
|
18661
|
+
logger$12.debug("[Call] Recovery manager failed to enable video");
|
|
18607
18662
|
});
|
|
18608
18663
|
},
|
|
18609
18664
|
isNegotiating: () => this.vertoManager.mainPeerConnection.isNegotiating,
|
|
@@ -18653,7 +18708,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18653
18708
|
this.subscribeTo(this._recoveryManager.recoveryEvent$, (event) => {
|
|
18654
18709
|
this._recoveryEvent$.next(event);
|
|
18655
18710
|
if (event.action === "max_attempts_reached") {
|
|
18656
|
-
logger$
|
|
18711
|
+
logger$12.warn("[Call] All recovery attempts exhausted, terminating call");
|
|
18657
18712
|
this.emitError({
|
|
18658
18713
|
kind: "network",
|
|
18659
18714
|
fatal: true,
|
|
@@ -18673,13 +18728,13 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18673
18728
|
else if (event.type === "online") this._recoveryManager?.handleWebSocketReconnect();
|
|
18674
18729
|
});
|
|
18675
18730
|
this.subscribeTo(this.clientSession.authenticated$.pipe((0, import_cjs$11.skip)(1), (0, import_cjs$11.filter)(Boolean)), () => {
|
|
18676
|
-
logger$
|
|
18731
|
+
logger$12.debug("[Call] WebSocket reconnected — notifying recovery manager");
|
|
18677
18732
|
this._recoveryManager?.handleWebSocketReconnect();
|
|
18678
18733
|
});
|
|
18679
18734
|
this._statsMonitor.start();
|
|
18680
|
-
logger$
|
|
18735
|
+
logger$12.debug("[Call] Resilience subsystems initialized for call", this.id);
|
|
18681
18736
|
} catch (error) {
|
|
18682
|
-
logger$
|
|
18737
|
+
logger$12.warn("[Call] Failed to initialize resilience subsystems:", error);
|
|
18683
18738
|
}
|
|
18684
18739
|
}
|
|
18685
18740
|
/**
|
|
@@ -18762,19 +18817,19 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18762
18817
|
}
|
|
18763
18818
|
isCallSessionEvent(event) {
|
|
18764
18819
|
try {
|
|
18765
|
-
logger$
|
|
18820
|
+
logger$12.debug("[Call] Checking if event is for this call session:", event);
|
|
18766
18821
|
const callId = getValueFrom(event, "params.params.callID") ?? getValueFrom(event, "params.call_id");
|
|
18767
18822
|
const roomSessionId = getValueFrom(event, "params.room_session_id");
|
|
18768
|
-
logger$
|
|
18823
|
+
logger$12.debug(`[Call] Extracted session identifiers callID: ${callId} and roomSessionID: ${roomSessionId} from event:`);
|
|
18769
18824
|
return callId === this.id || !!callId && this.callEventsManager.isCallIdValid(callId) || !!roomSessionId && this.callEventsManager.isRoomSessionIdValid(roomSessionId);
|
|
18770
18825
|
} catch (error) {
|
|
18771
|
-
logger$
|
|
18826
|
+
logger$12.error("[Call] Error checking if event is for this call session:", error);
|
|
18772
18827
|
return false;
|
|
18773
18828
|
}
|
|
18774
18829
|
}
|
|
18775
18830
|
get callSessionEvents$() {
|
|
18776
18831
|
return this.cachedObservable("callSessionEvents$", () => this.clientSession.signalingEvent$.pipe((0, import_cjs$11.filter)((event) => this.isCallSessionEvent(event)), (0, import_cjs$11.tap)((event) => {
|
|
18777
|
-
logger$
|
|
18832
|
+
logger$12.debug("[Call] Received call session event:", event);
|
|
18778
18833
|
}), (0, import_cjs$11.takeUntil)(this.destroyed$), (0, import_cjs$11.share)()));
|
|
18779
18834
|
}
|
|
18780
18835
|
/** Observable of call-updated events. */
|
|
@@ -18844,16 +18899,16 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18844
18899
|
this._customSubscriptions.set(eventType, filtered$);
|
|
18845
18900
|
}, (error) => {
|
|
18846
18901
|
this._customSubscriptions.delete(eventType);
|
|
18847
|
-
logger$
|
|
18902
|
+
logger$12.warn(`[Call] verto.subscribe for '${eventType}' failed, not caching:`, error);
|
|
18848
18903
|
});
|
|
18849
18904
|
this._customSubscriptions.set(eventType, filtered$);
|
|
18850
18905
|
return filtered$;
|
|
18851
18906
|
}
|
|
18852
18907
|
get webrtcMessages$() {
|
|
18853
|
-
return this.cachedObservable("webrtcMessages$", () => this.callSessionEvents$.pipe(filterAs(isWebrtcMessageMetadata, "params"), (0, import_cjs$11.tap)((event) => logger$
|
|
18908
|
+
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)()));
|
|
18854
18909
|
}
|
|
18855
18910
|
get callEvent$() {
|
|
18856
|
-
return this.cachedObservable("callEvent$", () => this.callSessionEvents$.pipe(filterAs(isSignalwireCallMetadata, "params"), (0, import_cjs$11.tap)((event) => logger$
|
|
18911
|
+
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)()));
|
|
18857
18912
|
}
|
|
18858
18913
|
get layoutEvent$() {
|
|
18859
18914
|
return this.cachedObservable("layoutEvent$", () => this.callEvent$.pipe(filterAs(isLayoutChangedMetadata, "params")));
|
|
@@ -18931,10 +18986,21 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18931
18986
|
return this.deferEmission(this._answered$.asObservable());
|
|
18932
18987
|
}
|
|
18933
18988
|
/**
|
|
18934
|
-
* Sets the call layout and participant positions.
|
|
18989
|
+
* Sets the call layout and, optionally, individual participant positions.
|
|
18990
|
+
*
|
|
18991
|
+
* The gateway `call.layout.set` DTO has **no** `positions` member, so when
|
|
18992
|
+
* `positions` is provided this method issues a `call.member.position.set`
|
|
18993
|
+
* request per member (via {@link Participant.setPosition}, which keys each
|
|
18994
|
+
* position by that member's own call context) alongside `call.layout.set`
|
|
18995
|
+
* (issue #19400, Flag #6).
|
|
18996
|
+
*
|
|
18997
|
+
* **These operations are NOT atomic.** The layout is applied first, then each
|
|
18998
|
+
* member position sequentially, so members may briefly flash into their
|
|
18999
|
+
* default slots before being moved to the requested positions.
|
|
18935
19000
|
*
|
|
18936
19001
|
* @param layout - Layout name (must be one of {@link layouts}).
|
|
18937
|
-
* @param positions -
|
|
19002
|
+
* @param positions - Optional map of member IDs to {@link VideoPosition} values.
|
|
19003
|
+
* When omitted or empty, only the layout is changed.
|
|
18938
19004
|
* @throws {InvalidParams} If the layout is not in the available {@link layouts}.
|
|
18939
19005
|
*
|
|
18940
19006
|
* @example
|
|
@@ -18947,10 +19013,17 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18947
19013
|
async setLayout(layout, positions) {
|
|
18948
19014
|
if (!this.layouts.includes(layout)) throw new InvalidParams(`Layout ${layout} is not available in the current call layouts: ${this.layouts.join(", ")}`);
|
|
18949
19015
|
const selfId = await (0, import_cjs$11.firstValueFrom)(this.selfId$.pipe((0, import_cjs$11.filter)((id) => id !== null)));
|
|
18950
|
-
await this.executeMethod(selfId, "call.layout.set", {
|
|
18951
|
-
|
|
18952
|
-
|
|
18953
|
-
|
|
19016
|
+
await this.executeMethod(selfId, "call.layout.set", { layout });
|
|
19017
|
+
const positionEntries = Object.entries(positions ?? {});
|
|
19018
|
+
if (positionEntries.length === 0) return;
|
|
19019
|
+
for (const [memberId, position] of positionEntries) {
|
|
19020
|
+
const participant = this.participants.find((p) => p.id === memberId);
|
|
19021
|
+
if (!participant) {
|
|
19022
|
+
logger$12.warn(`[Call] setLayout: member ${memberId} not found in participants; skipping position ${position}`);
|
|
19023
|
+
continue;
|
|
19024
|
+
}
|
|
19025
|
+
await participant.setPosition(position);
|
|
19026
|
+
}
|
|
18954
19027
|
}
|
|
18955
19028
|
/**
|
|
18956
19029
|
* Transfers the call to another destination.
|
|
@@ -18982,7 +19055,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18982
19055
|
setLocalMicrophoneGain(value) {
|
|
18983
19056
|
const pipeline = this.vertoManager.ensureLocalAudioPipeline();
|
|
18984
19057
|
if (!pipeline) {
|
|
18985
|
-
logger$
|
|
19058
|
+
logger$12.warn("[Call] setLocalMicrophoneGain: audio pipeline unavailable");
|
|
18986
19059
|
return;
|
|
18987
19060
|
}
|
|
18988
19061
|
const percent = Math.max(0, Math.min(200, value));
|
|
@@ -19027,7 +19100,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
19027
19100
|
enablePushToTalk() {
|
|
19028
19101
|
const pipeline = this.vertoManager.ensureLocalAudioPipeline();
|
|
19029
19102
|
if (!pipeline) {
|
|
19030
|
-
logger$
|
|
19103
|
+
logger$12.warn("[Call] enablePushToTalk: audio pipeline unavailable");
|
|
19031
19104
|
return;
|
|
19032
19105
|
}
|
|
19033
19106
|
pipeline.setPTTActive(false);
|
|
@@ -19190,7 +19263,7 @@ var CallFactory = class {
|
|
|
19190
19263
|
//#endregion
|
|
19191
19264
|
//#region src/behaviors/Collection.ts
|
|
19192
19265
|
var import_cjs$10 = require_cjs();
|
|
19193
|
-
const logger$
|
|
19266
|
+
const logger$11 = getLogger();
|
|
19194
19267
|
var Fetcher = class {
|
|
19195
19268
|
constructor(endpoint, params, http) {
|
|
19196
19269
|
this.endpoint = endpoint;
|
|
@@ -19214,7 +19287,7 @@ var Fetcher = class {
|
|
|
19214
19287
|
this.hasMore = !!this.nextUrl;
|
|
19215
19288
|
return result.data.filter(this.filter).map(this.mapper);
|
|
19216
19289
|
}
|
|
19217
|
-
logger$
|
|
19290
|
+
logger$11.error("Failed to fetch entity");
|
|
19218
19291
|
return [];
|
|
19219
19292
|
}
|
|
19220
19293
|
async id(v) {
|
|
@@ -19290,7 +19363,7 @@ var EntityCollection = class extends Destroyable {
|
|
|
19290
19363
|
this._hasMore$.next(this.fetchController.hasMore ?? false);
|
|
19291
19364
|
this._loading$.next(false);
|
|
19292
19365
|
} catch (error) {
|
|
19293
|
-
logger$
|
|
19366
|
+
logger$11.error(`Failed to fetch initial collection data`, error);
|
|
19294
19367
|
this._hasMore$.next(this.fetchController.hasMore ?? false);
|
|
19295
19368
|
this._loading$.next(false);
|
|
19296
19369
|
this.onError?.(new CollectionFetchError("fetchMore", error));
|
|
@@ -19304,7 +19377,7 @@ var EntityCollection = class extends Destroyable {
|
|
|
19304
19377
|
if (data) this.upsertData(data);
|
|
19305
19378
|
return data;
|
|
19306
19379
|
} catch (error) {
|
|
19307
|
-
logger$
|
|
19380
|
+
logger$11.error(`Failed to fetch data for (${String(key)}:${String(value)}) :`, error);
|
|
19308
19381
|
this._loading$.next(false);
|
|
19309
19382
|
this.onError?.(new CollectionFetchError(`tryFetch(${String(key)})`, error));
|
|
19310
19383
|
}
|
|
@@ -19553,13 +19626,13 @@ var Address = class extends Destroyable {
|
|
|
19553
19626
|
//#endregion
|
|
19554
19627
|
//#region src/core/utils.ts
|
|
19555
19628
|
var import_cjs$8 = require_cjs();
|
|
19556
|
-
const logger$
|
|
19629
|
+
const logger$10 = getLogger();
|
|
19557
19630
|
const isRPCConnectResult = (e) => {
|
|
19558
|
-
logger$
|
|
19631
|
+
logger$10.debug("isRPCConnectResult check:", e);
|
|
19559
19632
|
if (!e || typeof e !== "object") return false;
|
|
19560
19633
|
const result = e;
|
|
19561
19634
|
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";
|
|
19562
|
-
logger$
|
|
19635
|
+
logger$10.debug("isRPCConnectResult check result:", is);
|
|
19563
19636
|
return is;
|
|
19564
19637
|
};
|
|
19565
19638
|
var PendingRPC = class PendingRPC {
|
|
@@ -19568,7 +19641,7 @@ var PendingRPC = class PendingRPC {
|
|
|
19568
19641
|
}
|
|
19569
19642
|
constructor(request, responses$, options) {
|
|
19570
19643
|
this.id = v4_default();
|
|
19571
|
-
logger$
|
|
19644
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}: method:${request.method}] Creating PendingRPC`);
|
|
19572
19645
|
this.request = request;
|
|
19573
19646
|
const timeoutMs = options?.timeoutMs ?? PendingRPC.defaultTimeoutMs;
|
|
19574
19647
|
const signal = options?.signal;
|
|
@@ -19594,22 +19667,22 @@ var PendingRPC = class PendingRPC {
|
|
|
19594
19667
|
isSettled = true;
|
|
19595
19668
|
if (response.error) {
|
|
19596
19669
|
const rpcError = new JSONRPCError(response.error.code, response.error.message, response.error.data, void 0, request.id);
|
|
19597
|
-
logger$
|
|
19670
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with RPC error:`, rpcError);
|
|
19598
19671
|
reject(rpcError);
|
|
19599
19672
|
} else {
|
|
19600
|
-
logger$
|
|
19673
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Resolving promise with response:`, response);
|
|
19601
19674
|
resolve(response);
|
|
19602
19675
|
}
|
|
19603
19676
|
subscription.unsubscribe();
|
|
19604
19677
|
},
|
|
19605
19678
|
error: (error) => {
|
|
19606
|
-
logger$
|
|
19679
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with error:`, error);
|
|
19607
19680
|
isSettled = true;
|
|
19608
19681
|
reject(error);
|
|
19609
19682
|
subscription.unsubscribe();
|
|
19610
19683
|
},
|
|
19611
19684
|
complete: () => {
|
|
19612
|
-
logger$
|
|
19685
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Observable completed`);
|
|
19613
19686
|
if (!isSettled) reject(new RPCTimeoutError(request.id, timeoutMs));
|
|
19614
19687
|
subscription.unsubscribe();
|
|
19615
19688
|
}
|
|
@@ -19630,7 +19703,7 @@ var PendingRPC = class PendingRPC {
|
|
|
19630
19703
|
//#endregion
|
|
19631
19704
|
//#region src/managers/ClientSessionManager.ts
|
|
19632
19705
|
var import_cjs$7 = require_cjs();
|
|
19633
|
-
const logger$
|
|
19706
|
+
const logger$9 = getLogger();
|
|
19634
19707
|
const getAddressSearchURI = (options) => {
|
|
19635
19708
|
const to = options.to?.split("?")[0];
|
|
19636
19709
|
const from$9 = options.from?.startsWith("subscriber://") ? options.from.replace("subscriber://", "") : options.from;
|
|
@@ -19728,7 +19801,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19728
19801
|
try {
|
|
19729
19802
|
return await this.transport.execute(request, options);
|
|
19730
19803
|
} catch (error) {
|
|
19731
|
-
logger$
|
|
19804
|
+
logger$9.debug("[Session] Execute Error", error);
|
|
19732
19805
|
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
19733
19806
|
throw error;
|
|
19734
19807
|
}
|
|
@@ -19742,13 +19815,13 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19742
19815
|
return true;
|
|
19743
19816
|
}
|
|
19744
19817
|
setupMessageHandlers() {
|
|
19745
|
-
logger$
|
|
19818
|
+
logger$9.debug("[Session] Setting up message handlers");
|
|
19746
19819
|
this.subscribeTo(this.authStateEvent$, async (authStateEvent) => {
|
|
19747
|
-
logger$
|
|
19820
|
+
logger$9.debug("[Session] Authorization state event received:", authStateEvent);
|
|
19748
19821
|
try {
|
|
19749
19822
|
await this.updateAuthorizationStateInStorage(authStateEvent.authorization_state);
|
|
19750
19823
|
} catch (error) {
|
|
19751
|
-
logger$
|
|
19824
|
+
logger$9.error("[Session] Failed to handle authorization state update:", error);
|
|
19752
19825
|
this._errors$.next(new AuthStateHandlerError(error));
|
|
19753
19826
|
}
|
|
19754
19827
|
});
|
|
@@ -19756,29 +19829,29 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19756
19829
|
if (this._authState$.value.kind === "authenticated") this._authState$.next({ kind: "unauthenticated" });
|
|
19757
19830
|
});
|
|
19758
19831
|
this.subscribeTo(this.transport.connectionStatus$.pipe((0, import_cjs$7.filter)((status) => status === "connected"), (0, import_cjs$7.exhaustMap)(() => {
|
|
19759
|
-
logger$
|
|
19832
|
+
logger$9.debug("[Session] Connection established, initiating authentication");
|
|
19760
19833
|
return (0, import_cjs$7.from)(this.authenticate()).pipe((0, import_cjs$7.catchError)((error) => {
|
|
19761
19834
|
this.handleAuthenticationError(error).catch((err) => {
|
|
19762
|
-
logger$
|
|
19835
|
+
logger$9.error("[Session] Error handling authentication failure:", err);
|
|
19763
19836
|
});
|
|
19764
19837
|
return import_cjs$7.EMPTY;
|
|
19765
19838
|
}));
|
|
19766
19839
|
})), void 0);
|
|
19767
19840
|
this.subscribeTo(this.vertoInvite$, async (invite) => {
|
|
19768
|
-
logger$
|
|
19841
|
+
logger$9.debug("[Session] Verto invite received:", invite);
|
|
19769
19842
|
try {
|
|
19770
19843
|
await this.createInboundCall(invite);
|
|
19771
19844
|
} catch (error) {
|
|
19772
|
-
logger$
|
|
19845
|
+
logger$9.error("[Session] Error handling Verto invite:", error);
|
|
19773
19846
|
this._errors$.next(new VertoInviteHandlerError(error));
|
|
19774
19847
|
}
|
|
19775
19848
|
});
|
|
19776
19849
|
this.subscribeTo(this.vertoAttach$, async (attach) => {
|
|
19777
|
-
logger$
|
|
19850
|
+
logger$9.debug("[Session] Verto attach received:", attach);
|
|
19778
19851
|
try {
|
|
19779
19852
|
await this.handleVertoAttach(attach);
|
|
19780
19853
|
} catch (error) {
|
|
19781
|
-
logger$
|
|
19854
|
+
logger$9.error("[Session] Error handling Verto attach:", error);
|
|
19782
19855
|
this._errors$.next(new VertoAttachHandlerError(error));
|
|
19783
19856
|
}
|
|
19784
19857
|
});
|
|
@@ -19788,36 +19861,36 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19788
19861
|
const storedState = await this.storage.getItem(this.authorizationStateKey);
|
|
19789
19862
|
this.authorizationState$.next(storedState ?? void 0);
|
|
19790
19863
|
} catch (error) {
|
|
19791
|
-
logger$
|
|
19864
|
+
logger$9.error("Failed to retrieve authorization state from storage:", error);
|
|
19792
19865
|
this.authorizationState$.next(void 0);
|
|
19793
19866
|
}
|
|
19794
19867
|
}
|
|
19795
19868
|
async updateAuthorizationStateInStorage(authorizationState) {
|
|
19796
19869
|
if (!authorizationState) {
|
|
19797
|
-
logger$
|
|
19870
|
+
logger$9.debug("[Session] Removing authorization state from storage");
|
|
19798
19871
|
try {
|
|
19799
19872
|
await this.storage.removeItem(this.authorizationStateKey);
|
|
19800
19873
|
this.authorizationState$.next(void 0);
|
|
19801
19874
|
} catch (error) {
|
|
19802
|
-
logger$
|
|
19875
|
+
logger$9.error("Failed to remove authorization state from storage:", error);
|
|
19803
19876
|
throw error;
|
|
19804
19877
|
}
|
|
19805
19878
|
return;
|
|
19806
19879
|
}
|
|
19807
19880
|
try {
|
|
19808
|
-
logger$
|
|
19881
|
+
logger$9.debug("[Session] Updating authorization state in storage");
|
|
19809
19882
|
await this.storage.setItem(this.authorizationStateKey, authorizationState);
|
|
19810
19883
|
this.authorizationState$.next(authorizationState);
|
|
19811
19884
|
} catch (error) {
|
|
19812
|
-
logger$
|
|
19885
|
+
logger$9.error("Failed to retrieve authorization state from storage:", error);
|
|
19813
19886
|
throw error;
|
|
19814
19887
|
}
|
|
19815
19888
|
}
|
|
19816
19889
|
get authStateEvent$() {
|
|
19817
19890
|
return this.cachedObservable("authStateEvent$", () => this.signalingEvent$.pipe((0, import_cjs$7.tap)((msg) => {
|
|
19818
|
-
logger$
|
|
19891
|
+
logger$9.debug("[Session] Received incoming message:", msg);
|
|
19819
19892
|
}), filterAs(isSignalwireAuthorizationStateMetadata, "params"), (0, import_cjs$7.tap)((event) => {
|
|
19820
|
-
logger$
|
|
19893
|
+
logger$9.debug("[Session] Authorization state event received:", event.authorization_state);
|
|
19821
19894
|
})));
|
|
19822
19895
|
}
|
|
19823
19896
|
get signalingEvent$() {
|
|
@@ -19855,42 +19928,72 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19855
19928
|
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 })));
|
|
19856
19929
|
}
|
|
19857
19930
|
async handleAuthenticationError(error) {
|
|
19858
|
-
logger$
|
|
19931
|
+
logger$9.error("Authentication error:", error);
|
|
19859
19932
|
const isRecoverableAuthError = error instanceof JSONRPCError && (error.code === RPC_ERROR_REQUESTER_VALIDATION_FAILED || error.code === RPC_ERROR_INVALID_PARAMS || error.code === RPC_ERROR_AUTHENTICATION_FAILED);
|
|
19860
19933
|
const hasStoredState = await (0, import_cjs$7.firstValueFrom)(this.authorizationState$.pipe((0, import_cjs$7.take)(1))) !== void 0;
|
|
19861
19934
|
if (isRecoverableAuthError && hasStoredState) {
|
|
19862
|
-
logger$
|
|
19935
|
+
logger$9.debug("[Session] Recoverable auth error — cleaning up stored state and reconnecting fresh");
|
|
19863
19936
|
try {
|
|
19864
19937
|
await this.cleanupStoredConnectionParams();
|
|
19865
19938
|
} catch (cleanupError) {
|
|
19866
|
-
logger$
|
|
19939
|
+
logger$9.error("Failed to cleanup stored connection params:", cleanupError);
|
|
19867
19940
|
} finally {
|
|
19868
19941
|
this.transport.reconnect();
|
|
19869
19942
|
}
|
|
19870
19943
|
} else this._errors$.next(error);
|
|
19871
19944
|
}
|
|
19945
|
+
/**
|
|
19946
|
+
* Clear the resume state (authorization_state + protocol) only.
|
|
19947
|
+
*
|
|
19948
|
+
* This is the stale-auth-state recovery helper used by handleAuthError:
|
|
19949
|
+
* the server rejected a reconnect, so the resume state is discarded and a
|
|
19950
|
+
* fresh connect follows. Attach records are deliberately preserved — the
|
|
19951
|
+
* session lives on through the reconnect and reattachCalls() needs the
|
|
19952
|
+
* stored call references afterwards. Do NOT add detachAll() here.
|
|
19953
|
+
*
|
|
19954
|
+
* For public teardown (disconnect/destroy), use {@link teardownSessionState}
|
|
19955
|
+
* instead, which clears the attach records as well.
|
|
19956
|
+
*/
|
|
19872
19957
|
async cleanupStoredConnectionParams() {
|
|
19873
19958
|
await this.transport.setProtocol(void 0);
|
|
19874
19959
|
await this.updateAuthorizationStateInStorage(void 0);
|
|
19875
19960
|
this._authorization$.next(void 0);
|
|
19876
19961
|
}
|
|
19962
|
+
/**
|
|
19963
|
+
* Public-teardown helper for disconnect()/destroy(). Clears the resume
|
|
19964
|
+
* state (authorization_state + protocol) AND the attach records as one
|
|
19965
|
+
* atomic unit.
|
|
19966
|
+
*
|
|
19967
|
+
* The two stores are coupled: the backend only honors attach records
|
|
19968
|
+
* within the session identified by the resume state, so ending the
|
|
19969
|
+
* session must clear both. Clearing one without the other strands records
|
|
19970
|
+
* no future session can honor (disconnect) or revives a session the
|
|
19971
|
+
* developer explicitly ended (destroy).
|
|
19972
|
+
*
|
|
19973
|
+
* Distinct from {@link cleanupStoredConnectionParams}, which keeps the
|
|
19974
|
+
* attach records for the stale-auth-state recovery path.
|
|
19975
|
+
*/
|
|
19976
|
+
async teardownSessionState() {
|
|
19977
|
+
await this.cleanupStoredConnectionParams();
|
|
19978
|
+
await this.attachManager.detachAll();
|
|
19979
|
+
}
|
|
19877
19980
|
async updateAuthState(authorization_state) {
|
|
19878
19981
|
try {
|
|
19879
19982
|
await this.storage.setItem(this.authorizationStateKey, authorization_state);
|
|
19880
19983
|
} catch (error) {
|
|
19881
|
-
logger$
|
|
19984
|
+
logger$9.error("Failed to update authorization state in storage:", error);
|
|
19882
19985
|
this._errors$.next(new AuthStateHandlerError(error));
|
|
19883
19986
|
}
|
|
19884
19987
|
}
|
|
19885
19988
|
async reauthenticate(token, dpopToken, options) {
|
|
19886
|
-
logger$
|
|
19989
|
+
logger$9.debug("[Session] Re-authenticating session");
|
|
19887
19990
|
try {
|
|
19888
19991
|
let resolvedDpopToken = dpopToken;
|
|
19889
19992
|
if (!resolvedDpopToken && this.dpopManager?.initialized) try {
|
|
19890
19993
|
resolvedDpopToken = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
|
|
19891
19994
|
} catch (error) {
|
|
19892
19995
|
if (this.clientBound) throw error;
|
|
19893
|
-
logger$
|
|
19996
|
+
logger$9.warn("[Session] Failed to create DPoP proof for reauthenticate:", error);
|
|
19894
19997
|
}
|
|
19895
19998
|
const request = RPCReauthenticate({
|
|
19896
19999
|
project: this._authorization$.value?.project_id ?? "",
|
|
@@ -19898,24 +20001,24 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19898
20001
|
...resolvedDpopToken ? { dpop_token: resolvedDpopToken } : {}
|
|
19899
20002
|
});
|
|
19900
20003
|
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) => {
|
|
19901
|
-
logger$
|
|
20004
|
+
logger$9.error("[Session] Re-authentication RPC failed:", err);
|
|
19902
20005
|
throw err;
|
|
19903
20006
|
})));
|
|
19904
20007
|
if (options?.clientBound) this._wasClientBound = true;
|
|
19905
|
-
logger$
|
|
20008
|
+
logger$9.debug("[Session] Re-authentication successful, updating stored auth state");
|
|
19906
20009
|
} catch (error) {
|
|
19907
|
-
logger$
|
|
20010
|
+
logger$9.error("[Session] Re-authentication failed:", error);
|
|
19908
20011
|
this._errors$.next(new AuthStateHandlerError(error));
|
|
19909
20012
|
throw error;
|
|
19910
20013
|
}
|
|
19911
20014
|
}
|
|
19912
20015
|
async authenticate() {
|
|
19913
|
-
logger$
|
|
20016
|
+
logger$9.debug("[Session] Starting authentication process");
|
|
19914
20017
|
const persistedParams = await (0, import_cjs$7.firstValueFrom)((0, import_cjs$7.combineLatest)({
|
|
19915
20018
|
protocol: this.transport.protocol$,
|
|
19916
20019
|
authorization_state: this.authorizationState$
|
|
19917
20020
|
}).pipe((0, import_cjs$7.take)(1)));
|
|
19918
|
-
logger$
|
|
20021
|
+
logger$9.debug("[Session] Persisted params:\n", {
|
|
19919
20022
|
protocol: persistedParams.protocol,
|
|
19920
20023
|
authStateLength: persistedParams.authorization_state?.length
|
|
19921
20024
|
});
|
|
@@ -19923,16 +20026,16 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19923
20026
|
const storedToken = this.getCredential().token;
|
|
19924
20027
|
const isReconnect = hasReconnectState && storedToken;
|
|
19925
20028
|
let dpopToken;
|
|
19926
|
-
if (isReconnect) logger$
|
|
20029
|
+
if (isReconnect) logger$9.debug("[Session] Reconnecting with stored jwt_token + authorization_state");
|
|
19927
20030
|
else if (this.onBeforeReconnect && this.clientBound) {
|
|
19928
|
-
logger$
|
|
20031
|
+
logger$9.debug("[Session] Refreshing credentials before fresh connect");
|
|
19929
20032
|
await this.onBeforeReconnect();
|
|
19930
20033
|
}
|
|
19931
20034
|
if ((!isReconnect || this.clientBound) && this.dpopManager?.initialized) try {
|
|
19932
20035
|
dpopToken = await this.dpopManager.createRpcProof({ method: "signalwire.connect" });
|
|
19933
20036
|
} catch (error) {
|
|
19934
20037
|
if (this.clientBound) throw error;
|
|
19935
|
-
logger$
|
|
20038
|
+
logger$9.warn("[Session] Failed to create DPoP proof for connect, proceeding without:", error);
|
|
19936
20039
|
}
|
|
19937
20040
|
const rpcConnectRequest = RPCConnect({
|
|
19938
20041
|
authentication: isReconnect ? { jwt_token: storedToken } : this.authentication,
|
|
@@ -19949,12 +20052,12 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19949
20052
|
} : {}
|
|
19950
20053
|
});
|
|
19951
20054
|
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)(() => {
|
|
19952
|
-
logger$
|
|
20055
|
+
logger$9.debug("[Session] Response passed filter, processing authentication result");
|
|
19953
20056
|
}), (0, import_cjs$7.take)(1), (0, import_cjs$7.catchError)((err) => {
|
|
19954
|
-
logger$
|
|
20057
|
+
logger$9.error("[Session] Authentication RPC failed:", err);
|
|
19955
20058
|
throw err;
|
|
19956
20059
|
})));
|
|
19957
|
-
logger$
|
|
20060
|
+
logger$9.debug("[Session] Processing authentication result:", {
|
|
19958
20061
|
hasProtocol: !!response.protocol,
|
|
19959
20062
|
hasAuthorization: !!response.authorization,
|
|
19960
20063
|
hasIceServers: !!response.ice_servers
|
|
@@ -19963,12 +20066,12 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19963
20066
|
this._authorization$.next(response.authorization);
|
|
19964
20067
|
this._iceServers$.next(response.ice_servers ?? []);
|
|
19965
20068
|
this._authState$.next({ kind: "authenticated" });
|
|
19966
|
-
logger$
|
|
20069
|
+
logger$9.debug("[Session] Authentication completed successfully");
|
|
19967
20070
|
}
|
|
19968
20071
|
async disconnect() {
|
|
19969
20072
|
this.transport.disconnect();
|
|
19970
20073
|
this._authState$.next({ kind: "unauthenticated" });
|
|
19971
|
-
await this.
|
|
20074
|
+
await this.teardownSessionState();
|
|
19972
20075
|
}
|
|
19973
20076
|
async createInboundCall(invite) {
|
|
19974
20077
|
const callSession = await this.createCall({
|
|
@@ -19999,11 +20102,11 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19999
20102
|
async handleVertoAttach(attach) {
|
|
20000
20103
|
const { callID } = attach;
|
|
20001
20104
|
if (callID in this._calls$.value) {
|
|
20002
|
-
logger$
|
|
20105
|
+
logger$9.debug(`[Session] Verto attach for existing call ${callID}, deferring to per-call handler`);
|
|
20003
20106
|
return;
|
|
20004
20107
|
}
|
|
20005
20108
|
const storedOptions = await this.attachManager.consumePendingAttachment(callID);
|
|
20006
|
-
logger$
|
|
20109
|
+
logger$9.debug(`[Session] Creating reattached call for callID: ${callID}`);
|
|
20007
20110
|
const callSession = await this.createCall({
|
|
20008
20111
|
nodeId: attach.node_id,
|
|
20009
20112
|
callId: callID,
|
|
@@ -20034,7 +20137,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
20034
20137
|
});
|
|
20035
20138
|
return callSession;
|
|
20036
20139
|
} catch (error) {
|
|
20037
|
-
logger$
|
|
20140
|
+
logger$9.error("[Session] Error creating outbound call:", error);
|
|
20038
20141
|
callSession?.destroy();
|
|
20039
20142
|
const callError = new CallCreateError(error instanceof import_cjs$7.TimeoutError ? "Call create timeout" : "Call creation failed", error, "outbound");
|
|
20040
20143
|
this._errors$.next(callError);
|
|
@@ -20052,7 +20155,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
20052
20155
|
address = this._directory.get(addressId);
|
|
20053
20156
|
if (!address) throw new DependencyError(`Address ID: ${addressId} not found`);
|
|
20054
20157
|
} catch {
|
|
20055
|
-
logger$
|
|
20158
|
+
logger$9.warn(`[Session] Directory lookup failed for ${addressURI}, proceeding with raw URI`);
|
|
20056
20159
|
}
|
|
20057
20160
|
const callSession = this.callFactory.createCall(address, { ...options });
|
|
20058
20161
|
this.subscribeTo(callSession.status$.pipe((0, import_cjs$7.filter)((status) => status === "destroyed"), (0, import_cjs$7.take)(1)), () => {
|
|
@@ -20061,7 +20164,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
20061
20164
|
});
|
|
20062
20165
|
return callSession;
|
|
20063
20166
|
} catch (error) {
|
|
20064
|
-
logger$
|
|
20167
|
+
logger$9.error("[Session] Error creating call session:", error);
|
|
20065
20168
|
throw new CallCreateError("Call create error", error, options.initOffer ? "inbound" : "outbound");
|
|
20066
20169
|
}
|
|
20067
20170
|
}
|
|
@@ -20110,7 +20213,7 @@ const isString = (obj) => typeof obj === "string";
|
|
|
20110
20213
|
//#endregion
|
|
20111
20214
|
//#region src/managers/ConversationsManager.ts
|
|
20112
20215
|
var import_cjs$6 = require_cjs();
|
|
20113
|
-
const logger$
|
|
20216
|
+
const logger$8 = getLogger();
|
|
20114
20217
|
var ConversationMessagesFetcher = class extends Fetcher {
|
|
20115
20218
|
constructor(groupId, http) {
|
|
20116
20219
|
super(`/api/fabric/conversations/${groupId}/messages`, "page_size=100", http);
|
|
@@ -20150,13 +20253,13 @@ var ConversationsManager = class {
|
|
|
20150
20253
|
}
|
|
20151
20254
|
throw new ConversationError("Join Failed - Unexpected response");
|
|
20152
20255
|
} catch (error) {
|
|
20153
|
-
logger$
|
|
20256
|
+
logger$8.error("[ConversationsManager] Failed to join conversation:", error);
|
|
20154
20257
|
throw error;
|
|
20155
20258
|
}
|
|
20156
20259
|
}
|
|
20157
20260
|
async getConversationMessageCollection(addressId) {
|
|
20158
20261
|
const groupId = this.groupIds.get(addressId) ?? await this.join(addressId);
|
|
20159
|
-
return Promise.resolve(new ConversationMessageCollection(groupId, this.clientSession.signalingEvent$.pipe(filterAs(isConversationMessageMetadata, "params"), (0, import_cjs$6.tap)((event) => logger$
|
|
20262
|
+
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));
|
|
20160
20263
|
}
|
|
20161
20264
|
async sendText(text, destinationAddressId) {
|
|
20162
20265
|
const groupId = this.groupIds.get(destinationAddressId) ?? await this.join(destinationAddressId);
|
|
@@ -20173,7 +20276,7 @@ var ConversationsManager = class {
|
|
|
20173
20276
|
})).ok) return;
|
|
20174
20277
|
throw new ConversationError("Send Text Failed - Unexpected response");
|
|
20175
20278
|
} catch (error) {
|
|
20176
|
-
logger$
|
|
20279
|
+
logger$8.error("[ConversationsManager] Failed to send text message:", error);
|
|
20177
20280
|
throw error;
|
|
20178
20281
|
}
|
|
20179
20282
|
}
|
|
@@ -20182,7 +20285,7 @@ var ConversationsManager = class {
|
|
|
20182
20285
|
//#endregion
|
|
20183
20286
|
//#region src/managers/DeviceTokenManager.ts
|
|
20184
20287
|
var import_cjs$5 = require_cjs();
|
|
20185
|
-
const logger$
|
|
20288
|
+
const logger$7 = getLogger();
|
|
20186
20289
|
/**
|
|
20187
20290
|
* Resolves the token expiry timestamp (epoch seconds) using a 3-tier priority chain:
|
|
20188
20291
|
* 1. `data.expires_at` — server-provided absolute timestamp
|
|
@@ -20192,7 +20295,7 @@ const logger$6 = getLogger();
|
|
|
20192
20295
|
function resolveExpiresAt(data) {
|
|
20193
20296
|
if (data.expires_at) return data.expires_at;
|
|
20194
20297
|
if (data.expires_in) return Math.floor(Date.now() / 1e3) + data.expires_in;
|
|
20195
|
-
logger$
|
|
20298
|
+
logger$7.warn("[DeviceToken] Could not determine token expiry, using default");
|
|
20196
20299
|
return Math.floor(Date.now() / 1e3) + DEVICE_TOKEN_DEFAULT_EXPIRE_IN;
|
|
20197
20300
|
}
|
|
20198
20301
|
/**
|
|
@@ -20225,11 +20328,12 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20225
20328
|
this.getCredential = getCredential;
|
|
20226
20329
|
this._currentToken$ = this.createBehaviorSubject(null);
|
|
20227
20330
|
this._refreshInProgress = false;
|
|
20331
|
+
this._paused = false;
|
|
20228
20332
|
this._effectiveExpireIn = DEVICE_TOKEN_DEFAULT_EXPIRE_IN;
|
|
20229
20333
|
this.subscribeTo(this._currentToken$.pipe((0, import_cjs$5.filter)(Boolean), (0, import_cjs$5.switchMap)((tokenData) => {
|
|
20230
20334
|
const expiresAt = resolveExpiresAt(tokenData);
|
|
20231
20335
|
const refreshIn = Math.max(expiresAt * 1e3 - Date.now() - DEVICE_TOKEN_REFRESH_BUFFER_MS, 1e3);
|
|
20232
|
-
logger$
|
|
20336
|
+
logger$7.debug(`[DeviceToken] Scheduling Client Bound SAT refresh in ${refreshIn}ms`);
|
|
20233
20337
|
return (0, import_cjs$5.timer)(refreshIn);
|
|
20234
20338
|
})), () => {
|
|
20235
20339
|
this.executeRefresh();
|
|
@@ -20243,7 +20347,12 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20243
20347
|
* Activates the Client Bound SAT flow when the user's token has
|
|
20244
20348
|
* `sat:refresh` scope.
|
|
20245
20349
|
*
|
|
20246
|
-
*
|
|
20350
|
+
* Returns an {@link ActivationResult} indicating whether the manager
|
|
20351
|
+
* took ownership of refresh duties. The caller must use the `activated`
|
|
20352
|
+
* boolean to decide whether to keep its own refresh path armed — when
|
|
20353
|
+
* `activated` is `false`, the caller is responsible for refresh.
|
|
20354
|
+
*
|
|
20355
|
+
* Steps on success:
|
|
20247
20356
|
* 1. Check user's `sat_claims` for `sat:refresh` scope
|
|
20248
20357
|
* 2. Call `/api/fabric/subscriber/devices/token` with a DPoP proof
|
|
20249
20358
|
* 3. Reauthenticate the session with the Client Bound SAT + DPoP proof
|
|
@@ -20252,32 +20361,51 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20252
20361
|
async activate(user, session, updateCredential) {
|
|
20253
20362
|
const { satClaims } = user;
|
|
20254
20363
|
if (!satClaims?.scope?.includes(SAT_REFRESH_SCOPE)) {
|
|
20255
|
-
logger$
|
|
20256
|
-
return
|
|
20364
|
+
logger$7.debug("[DeviceToken] No sat:refresh scope, skipping Client Bound SAT activation");
|
|
20365
|
+
return {
|
|
20366
|
+
activated: false,
|
|
20367
|
+
reason: "no-scope"
|
|
20368
|
+
};
|
|
20257
20369
|
}
|
|
20258
20370
|
this._session = session;
|
|
20259
20371
|
this._updateCredential = updateCredential;
|
|
20260
20372
|
try {
|
|
20373
|
+
const cached = this._currentToken$.value;
|
|
20374
|
+
if (cached && this.isTokenFresh(cached)) {
|
|
20375
|
+
logger$7.debug("[DeviceToken] Reusing cached Client Bound SAT — skipping /devices/token");
|
|
20376
|
+
const rpcProof$1 = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
|
|
20377
|
+
await session.reauthenticate(cached.token, rpcProof$1, { clientBound: true });
|
|
20378
|
+
updateCredential({ token: cached.token });
|
|
20379
|
+
return { activated: true };
|
|
20380
|
+
}
|
|
20261
20381
|
const tokenData = await this.obtainToken();
|
|
20262
20382
|
if (!tokenData.expires_at && !tokenData.expires_in && satClaims.expires_at) tokenData.expires_at = satClaims.expires_at;
|
|
20263
20383
|
this._effectiveExpireIn = resolveExpireIn(tokenData);
|
|
20264
20384
|
const rpcProof = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
|
|
20265
20385
|
await session.reauthenticate(tokenData.token, rpcProof, { clientBound: true });
|
|
20266
20386
|
updateCredential({ token: tokenData.token });
|
|
20267
|
-
logger$
|
|
20387
|
+
logger$7.info("[DeviceToken] Client Bound SAT activated successfully");
|
|
20268
20388
|
this._currentToken$.next(tokenData);
|
|
20389
|
+
return { activated: true };
|
|
20269
20390
|
} catch (error) {
|
|
20270
|
-
logger$
|
|
20391
|
+
logger$7.error("[DeviceToken] Failed to activate Client Bound SAT:", error);
|
|
20271
20392
|
this.errorHandler(new DPoPInitError(error, "Failed to activate Client Bound SAT"));
|
|
20272
|
-
|
|
20273
|
-
|
|
20274
|
-
|
|
20275
|
-
|
|
20276
|
-
expires_at: expiresAt
|
|
20277
|
-
});
|
|
20393
|
+
return {
|
|
20394
|
+
activated: false,
|
|
20395
|
+
reason: "endpoint-failed"
|
|
20396
|
+
};
|
|
20278
20397
|
}
|
|
20279
20398
|
}
|
|
20280
20399
|
/**
|
|
20400
|
+
* Returns true when the cached token has enough headroom before expiry to
|
|
20401
|
+
* be safely reused on reactivation. The headroom matches the refresh
|
|
20402
|
+
* buffer, so a token within the refresh window is treated as stale (the
|
|
20403
|
+
* reactive pipeline is about to refresh it anyway).
|
|
20404
|
+
*/
|
|
20405
|
+
isTokenFresh(token) {
|
|
20406
|
+
return resolveExpiresAt(token) * 1e3 - Date.now() > DEVICE_TOKEN_REFRESH_BUFFER_MS;
|
|
20407
|
+
}
|
|
20408
|
+
/**
|
|
20281
20409
|
* Obtains a Client Bound SAT from `/api/fabric/subscriber/devices/token`.
|
|
20282
20410
|
* Returns the full {@link DeviceTokenResponse} including expiry metadata.
|
|
20283
20411
|
*/
|
|
@@ -20307,7 +20435,7 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20307
20435
|
* handled by the reactive pipeline).
|
|
20308
20436
|
*/
|
|
20309
20437
|
async refreshToken(session, currentToken, updateCredential) {
|
|
20310
|
-
logger$
|
|
20438
|
+
logger$7.debug("[DeviceToken] Refreshing Client Bound SAT");
|
|
20311
20439
|
const dpopProof = await this.dpopManager.createHttpProof({
|
|
20312
20440
|
method: "POST",
|
|
20313
20441
|
uri: DEVICE_REFRESH_ENDPOINT,
|
|
@@ -20329,7 +20457,7 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20329
20457
|
const rpcProof = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
|
|
20330
20458
|
await session.reauthenticate(data.token, rpcProof);
|
|
20331
20459
|
updateCredential({ token: data.token });
|
|
20332
|
-
logger$
|
|
20460
|
+
logger$7.info("[DeviceToken] Client Bound SAT refreshed successfully");
|
|
20333
20461
|
return data;
|
|
20334
20462
|
}
|
|
20335
20463
|
/**
|
|
@@ -20338,18 +20466,22 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20338
20466
|
* On all retries exhausted, emits to `errorHandler`.
|
|
20339
20467
|
*/
|
|
20340
20468
|
async executeRefresh() {
|
|
20469
|
+
if (this._paused) {
|
|
20470
|
+
logger$7.debug("[DeviceToken] Manager paused, skipping refresh");
|
|
20471
|
+
return;
|
|
20472
|
+
}
|
|
20341
20473
|
if (this._refreshInProgress) {
|
|
20342
|
-
logger$
|
|
20474
|
+
logger$7.debug("[DeviceToken] Refresh already in progress, skipping");
|
|
20343
20475
|
return;
|
|
20344
20476
|
}
|
|
20345
20477
|
const session = this._session;
|
|
20346
20478
|
const updateCredential = this._updateCredential;
|
|
20347
20479
|
if (!session || !updateCredential) {
|
|
20348
|
-
logger$
|
|
20480
|
+
logger$7.warn("[DeviceToken] Cannot refresh: session or updateCredential not set");
|
|
20349
20481
|
return;
|
|
20350
20482
|
}
|
|
20351
20483
|
if (!session.authenticated) {
|
|
20352
|
-
logger$
|
|
20484
|
+
logger$7.debug("[DeviceToken] Session not authenticated, deferring refresh");
|
|
20353
20485
|
return;
|
|
20354
20486
|
}
|
|
20355
20487
|
this._refreshInProgress = true;
|
|
@@ -20359,7 +20491,7 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20359
20491
|
const newTokenData = await this.retryRefresh(session, currentToken, updateCredential);
|
|
20360
20492
|
this._currentToken$.next(newTokenData);
|
|
20361
20493
|
} catch (error) {
|
|
20362
|
-
logger$
|
|
20494
|
+
logger$7.error("[DeviceToken] Automatic Client Bound SAT refresh failed:", error);
|
|
20363
20495
|
this.errorHandler(error instanceof TokenRefreshError ? error : new TokenRefreshError("Automatic token refresh failed", error));
|
|
20364
20496
|
} finally {
|
|
20365
20497
|
this._refreshInProgress = false;
|
|
@@ -20377,18 +20509,215 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20377
20509
|
lastError = error;
|
|
20378
20510
|
if (attempt < DEVICE_TOKEN_REFRESH_MAX_RETRIES - 1) {
|
|
20379
20511
|
const delay$1 = DEVICE_TOKEN_REFRESH_RETRY_BASE_MS * Math.pow(2, attempt);
|
|
20380
|
-
logger$
|
|
20512
|
+
logger$7.warn(`[DeviceToken] Refresh attempt ${attempt + 1} failed, retrying in ${delay$1}ms`);
|
|
20381
20513
|
await new Promise((resolve) => setTimeout(resolve, delay$1));
|
|
20382
20514
|
}
|
|
20383
20515
|
}
|
|
20384
20516
|
throw lastError instanceof Error ? lastError : new TokenRefreshError("All refresh retries exhausted", lastError);
|
|
20385
20517
|
}
|
|
20518
|
+
/**
|
|
20519
|
+
* Stops the reactive refresh pipeline from firing. Use when the underlying
|
|
20520
|
+
* session is being torn down (e.g., during {@link SignalWire.disconnect})
|
|
20521
|
+
* so a scheduled refresh cannot fire against a destroyed session.
|
|
20522
|
+
*
|
|
20523
|
+
* The manager's state (cached token, effective TTL, subscriptions) is
|
|
20524
|
+
* preserved — call {@link resume} to re-enable firing after reconnect.
|
|
20525
|
+
*/
|
|
20526
|
+
pause() {
|
|
20527
|
+
this._paused = true;
|
|
20528
|
+
}
|
|
20529
|
+
/** Re-enables the reactive refresh pipeline after a previous {@link pause}. */
|
|
20530
|
+
resume() {
|
|
20531
|
+
this._paused = false;
|
|
20532
|
+
}
|
|
20386
20533
|
/** Cleans up the manager, cancelling the reactive pipeline and all subscriptions. */
|
|
20387
20534
|
destroy() {
|
|
20388
20535
|
super.destroy();
|
|
20389
20536
|
}
|
|
20390
20537
|
};
|
|
20391
20538
|
|
|
20539
|
+
//#endregion
|
|
20540
|
+
//#region src/managers/CredentialRefreshCoordinator.ts
|
|
20541
|
+
const logger$6 = getLogger();
|
|
20542
|
+
const defaultDeviceTokenManagerFactory = (dpopManager, http, errorHandler, getCredential) => new DeviceTokenManager(dpopManager, http, errorHandler, getCredential);
|
|
20543
|
+
/**
|
|
20544
|
+
* Centralizes credential-refresh ownership across the two competing
|
|
20545
|
+
* mechanisms — developer-provided `CredentialProvider.refresh()` and the
|
|
20546
|
+
* Client Bound SAT path via {@link DeviceTokenManager}.
|
|
20547
|
+
*
|
|
20548
|
+
* Maintains the invariant: **at most one refresh mechanism is armed at a
|
|
20549
|
+
* time, and at least one is armed whenever the current credential has an
|
|
20550
|
+
* `expiry_at`**.
|
|
20551
|
+
*
|
|
20552
|
+
* Replaces the previous design where refresh state was distributed across
|
|
20553
|
+
* `SignalWire` (timer field, scheduler method, activation helper) and
|
|
20554
|
+
* `DeviceTokenManager` (reactive pipeline). Centralizing the invariant in
|
|
20555
|
+
* one component eliminates the bug class that produced issue #19074.
|
|
20556
|
+
*
|
|
20557
|
+
* Race-safety:
|
|
20558
|
+
* - `_activating` flag prevents overlapping `activate()` calls from racing.
|
|
20559
|
+
* - `_activationGeneration` lets late resolutions detect they've been
|
|
20560
|
+
* preempted by a newer activation (e.g., reconnect during in-flight
|
|
20561
|
+
* `obtainToken`).
|
|
20562
|
+
*/
|
|
20563
|
+
var CredentialRefreshCoordinator = class extends Destroyable {
|
|
20564
|
+
constructor(dpopManager, deps) {
|
|
20565
|
+
super();
|
|
20566
|
+
this.deps = deps;
|
|
20567
|
+
this._activating = false;
|
|
20568
|
+
this._activationGeneration = 0;
|
|
20569
|
+
if (dpopManager?.initialized) this._deviceTokenManager = (deps.deviceTokenManagerFactory ?? defaultDeviceTokenManagerFactory)(dpopManager, deps.http, (error) => deps.notifier.onError(error), () => deps.store.read());
|
|
20570
|
+
}
|
|
20571
|
+
/** True when the Client Bound SAT path is available (DPoP initialized). */
|
|
20572
|
+
get clientBoundSATAvailable() {
|
|
20573
|
+
return this._deviceTokenManager !== void 0;
|
|
20574
|
+
}
|
|
20575
|
+
/** True when the developer-provided refresh timer is currently armed. */
|
|
20576
|
+
get developerRefreshArmed() {
|
|
20577
|
+
return this._developerTimerId !== void 0;
|
|
20578
|
+
}
|
|
20579
|
+
/**
|
|
20580
|
+
* Arms the developer-provided refresh timer to fire shortly before
|
|
20581
|
+
* `expiresAt`. Replaces any previously scheduled developer refresh.
|
|
20582
|
+
*
|
|
20583
|
+
* Idempotent — multiple calls just reschedule. On retry exhaustion,
|
|
20584
|
+
* invokes `deps.onRefreshExhausted` so the orchestrator can disconnect.
|
|
20585
|
+
*/
|
|
20586
|
+
scheduleDeveloperRefresh(provider, expiresAt, attempt = 0) {
|
|
20587
|
+
if (this._developerTimerId !== void 0) clearTimeout(this._developerTimerId);
|
|
20588
|
+
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);
|
|
20589
|
+
this._developerTimerId = setTimeout(async () => {
|
|
20590
|
+
try {
|
|
20591
|
+
if (!provider.refresh) throw new InvalidCredentialsError("Credential provider does not support refresh");
|
|
20592
|
+
const newCredentials = await provider.refresh();
|
|
20593
|
+
this.deps.store.write(newCredentials);
|
|
20594
|
+
this.deps.store.persist(newCredentials);
|
|
20595
|
+
logger$6.info("[Coordinator] Credentials refreshed successfully.");
|
|
20596
|
+
if (newCredentials.expiry_at) this.scheduleDeveloperRefresh(provider, newCredentials.expiry_at, 0);
|
|
20597
|
+
} catch (error) {
|
|
20598
|
+
const nextAttempt = attempt + 1;
|
|
20599
|
+
logger$6.error(`[Coordinator] Credential refresh failed (attempt ${nextAttempt}/${CREDENTIAL_REFRESH_MAX_RETRIES}):`, error);
|
|
20600
|
+
this.deps.notifier.onError(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
20601
|
+
if (nextAttempt < CREDENTIAL_REFRESH_MAX_RETRIES) this.scheduleDeveloperRefresh(provider, expiresAt, nextAttempt);
|
|
20602
|
+
else {
|
|
20603
|
+
logger$6.error("[Coordinator] Credential refresh exhausted all retries. Disconnecting.");
|
|
20604
|
+
this.deps.notifier.onError(new TokenRefreshError("Credential refresh failed after max retries"));
|
|
20605
|
+
this.deps.notifier.onRefreshExhausted();
|
|
20606
|
+
}
|
|
20607
|
+
}
|
|
20608
|
+
}, refreshInterval);
|
|
20609
|
+
}
|
|
20610
|
+
/**
|
|
20611
|
+
* Cancels any scheduled developer-provided refresh. Idempotent.
|
|
20612
|
+
*
|
|
20613
|
+
* @internal Used by the coordinator's own activation flow. External
|
|
20614
|
+
* callers should use {@link suspend} for disconnect-time quiescence —
|
|
20615
|
+
* `suspend()` also pauses the internal Client Bound SAT pipeline.
|
|
20616
|
+
*/
|
|
20617
|
+
cancelDeveloperRefresh() {
|
|
20618
|
+
if (this._developerTimerId !== void 0) {
|
|
20619
|
+
clearTimeout(this._developerTimerId);
|
|
20620
|
+
this._developerTimerId = void 0;
|
|
20621
|
+
}
|
|
20622
|
+
}
|
|
20623
|
+
/**
|
|
20624
|
+
* Suspends both refresh paths — cancels the developer timer and pauses
|
|
20625
|
+
* the internal reactive pipeline. Use when the underlying session is
|
|
20626
|
+
* being torn down (e.g., {@link SignalWire.disconnect}). The next
|
|
20627
|
+
* {@link activate} call re-enables the internal pipeline.
|
|
20628
|
+
*
|
|
20629
|
+
* The internal manager's cached token survives — see
|
|
20630
|
+
* {@link DeviceTokenManager.pause} — so a subsequent reconnect can
|
|
20631
|
+
* skip the `/devices/token` exchange entirely.
|
|
20632
|
+
*/
|
|
20633
|
+
suspend() {
|
|
20634
|
+
this.cancelDeveloperRefresh();
|
|
20635
|
+
this._deviceTokenManager?.pause();
|
|
20636
|
+
}
|
|
20637
|
+
/**
|
|
20638
|
+
* Asks the Client Bound SAT path to take over refresh. If it accepts,
|
|
20639
|
+
* the developer-provided timer (if any) is cancelled. If it declines, the
|
|
20640
|
+
* developer timer remains armed and a `credential_refresh_fallback`
|
|
20641
|
+
* warning is emitted.
|
|
20642
|
+
*
|
|
20643
|
+
* **Idempotent** — re-entrant calls during an in-flight activation are
|
|
20644
|
+
* dropped. Use `_activationGeneration` to detect stale resolutions
|
|
20645
|
+
* (e.g., a reconnect-triggered activate() that races with an earlier one).
|
|
20646
|
+
*/
|
|
20647
|
+
async activate(user, session) {
|
|
20648
|
+
if (this._activating) {
|
|
20649
|
+
logger$6.debug("[Coordinator] activate() in flight; ignoring re-entrant call");
|
|
20650
|
+
return;
|
|
20651
|
+
}
|
|
20652
|
+
if (!this._deviceTokenManager) return;
|
|
20653
|
+
this._deviceTokenManager.resume();
|
|
20654
|
+
const generation = ++this._activationGeneration;
|
|
20655
|
+
this._activating = true;
|
|
20656
|
+
try {
|
|
20657
|
+
const result = await this.withActivationTimeout(this._deviceTokenManager.activate(user, session, (cred) => this.deps.store.merge(cred)));
|
|
20658
|
+
if (generation !== this._activationGeneration) {
|
|
20659
|
+
logger$6.debug("[Coordinator] activate() result discarded (preempted by newer activation)");
|
|
20660
|
+
return;
|
|
20661
|
+
}
|
|
20662
|
+
if (result.activated) {
|
|
20663
|
+
this.cancelDeveloperRefresh();
|
|
20664
|
+
logger$6.debug("[Coordinator] Developer refresh disabled — Client Bound SAT owns refresh");
|
|
20665
|
+
return;
|
|
20666
|
+
}
|
|
20667
|
+
logger$6.warn(`[SignalWire] [SW-REFRESH-FALLBACK] Client Bound SAT declined (reason=${result.reason}); using developer-provided refresh handler.`);
|
|
20668
|
+
this.deps.notifier.onWarning({
|
|
20669
|
+
code: "credential_refresh_fallback",
|
|
20670
|
+
source: "CredentialProvider",
|
|
20671
|
+
reason: result.reason,
|
|
20672
|
+
message: `Client Bound SAT activation declined (${result.reason}); using developer-provided refresh.`
|
|
20673
|
+
});
|
|
20674
|
+
} finally {
|
|
20675
|
+
this._activating = false;
|
|
20676
|
+
}
|
|
20677
|
+
}
|
|
20678
|
+
destroy() {
|
|
20679
|
+
this.cancelDeveloperRefresh();
|
|
20680
|
+
this._deviceTokenManager?.destroy();
|
|
20681
|
+
this._deviceTokenManager = void 0;
|
|
20682
|
+
super.destroy();
|
|
20683
|
+
}
|
|
20684
|
+
/**
|
|
20685
|
+
* Races the manager's `activate()` against a hard timeout. A wedged HTTP
|
|
20686
|
+
* layer (e.g., proxy issues) could otherwise hang the activation
|
|
20687
|
+
* indefinitely, leaving the session with no refresh mechanism while the
|
|
20688
|
+
* `_activating` guard blocks subsequent retries.
|
|
20689
|
+
*
|
|
20690
|
+
* On timeout, the manager is paused immediately. The inner `activate()`
|
|
20691
|
+
* may still complete in the background and emit to `_currentToken$`,
|
|
20692
|
+
* which would normally arm the reactive refresh pipeline; pausing
|
|
20693
|
+
* prevents that pipeline from firing while the developer refresh path
|
|
20694
|
+
* is the active mechanism — preserving the "at most one mechanism
|
|
20695
|
+
* armed" invariant. The next `activate()` call resumes the manager.
|
|
20696
|
+
*/
|
|
20697
|
+
async withActivationTimeout(inner) {
|
|
20698
|
+
return new Promise((resolve) => {
|
|
20699
|
+
const timer$4 = setTimeout(() => {
|
|
20700
|
+
this._deviceTokenManager?.pause();
|
|
20701
|
+
resolve({
|
|
20702
|
+
activated: false,
|
|
20703
|
+
reason: "activation-timeout"
|
|
20704
|
+
});
|
|
20705
|
+
}, CREDENTIAL_ACTIVATE_TIMEOUT_MS);
|
|
20706
|
+
inner.then((result) => {
|
|
20707
|
+
clearTimeout(timer$4);
|
|
20708
|
+
resolve(result);
|
|
20709
|
+
}, (error) => {
|
|
20710
|
+
clearTimeout(timer$4);
|
|
20711
|
+
this.deps.notifier.onError(error instanceof Error ? error : new Error(String(error)));
|
|
20712
|
+
resolve({
|
|
20713
|
+
activated: false,
|
|
20714
|
+
reason: "endpoint-failed"
|
|
20715
|
+
});
|
|
20716
|
+
});
|
|
20717
|
+
});
|
|
20718
|
+
}
|
|
20719
|
+
};
|
|
20720
|
+
|
|
20392
20721
|
//#endregion
|
|
20393
20722
|
//#region src/managers/DiagnosticsCollector.ts
|
|
20394
20723
|
const logger$5 = getLogger();
|
|
@@ -20909,6 +21238,10 @@ var TransportManager = class extends Destroyable {
|
|
|
20909
21238
|
if (!isSignalwireRequest(message)) return true;
|
|
20910
21239
|
const eventChannel = message.params.event_channel;
|
|
20911
21240
|
if (!eventChannel) return true;
|
|
21241
|
+
if (message.params.event_type.startsWith("conversation.")) {
|
|
21242
|
+
logger$2.debug(`[Transport] Received conversation event: ${message.params.event_type} (event_channel: ${eventChannel})`);
|
|
21243
|
+
return true;
|
|
21244
|
+
}
|
|
20912
21245
|
const currentProtocol = this._currentProtocol;
|
|
20913
21246
|
if (!currentProtocol) return true;
|
|
20914
21247
|
if (!eventChannel.includes(currentProtocol)) {
|
|
@@ -21102,6 +21435,7 @@ var SignalWire = class extends Destroyable {
|
|
|
21102
21435
|
this._isConnected$ = this.createBehaviorSubject(false);
|
|
21103
21436
|
this._isRegistered$ = this.createBehaviorSubject(false);
|
|
21104
21437
|
this._errors$ = this.createReplaySubject(1);
|
|
21438
|
+
this._warnings$ = this.createReplaySubject(10);
|
|
21105
21439
|
this._options = {};
|
|
21106
21440
|
this._deps = new DependencyContainer();
|
|
21107
21441
|
this._credentialProvider = credentialProvider;
|
|
@@ -21161,6 +21495,27 @@ var SignalWire = class extends Destroyable {
|
|
|
21161
21495
|
*/
|
|
21162
21496
|
async resolveCredentials() {
|
|
21163
21497
|
const fingerprint = await this.initDPoP();
|
|
21498
|
+
this._refreshCoordinator = new CredentialRefreshCoordinator(this._dpopManager, {
|
|
21499
|
+
http: this._deps.http,
|
|
21500
|
+
notifier: {
|
|
21501
|
+
onError: (error) => this._errors$.next(error),
|
|
21502
|
+
onWarning: (warning) => this._warnings$.next(warning),
|
|
21503
|
+
onRefreshExhausted: () => void this.disconnect()
|
|
21504
|
+
},
|
|
21505
|
+
store: {
|
|
21506
|
+
read: () => this._deps.credential,
|
|
21507
|
+
write: (credential) => {
|
|
21508
|
+
this._deps.credential = credential;
|
|
21509
|
+
},
|
|
21510
|
+
merge: (partial) => {
|
|
21511
|
+
this._deps.credential = {
|
|
21512
|
+
...this._deps.credential,
|
|
21513
|
+
...partial
|
|
21514
|
+
};
|
|
21515
|
+
},
|
|
21516
|
+
persist: (credential) => this.persistCredential(credential)
|
|
21517
|
+
}
|
|
21518
|
+
});
|
|
21164
21519
|
if (this._credentialProvider) return this.validateCredentials(this._credentialProvider, void 0, fingerprint);
|
|
21165
21520
|
for (const scope of this._deps.persistSession ? ["local", "session"] : ["session"]) try {
|
|
21166
21521
|
const cached = await this._deps.storage.getItem("sw:cached_credential", scope);
|
|
@@ -21190,7 +21545,16 @@ var SignalWire = class extends Destroyable {
|
|
|
21190
21545
|
logger$1.error("[SignalWire] Provided credentials have expired.");
|
|
21191
21546
|
throw new InvalidCredentialsError("Provided credentials have expired.");
|
|
21192
21547
|
}
|
|
21193
|
-
if (_credentials.expiry_at && credentialProvider?.refresh) this.
|
|
21548
|
+
if (_credentials.expiry_at && credentialProvider?.refresh) this._refreshCoordinator?.scheduleDeveloperRefresh(credentialProvider, _credentials.expiry_at);
|
|
21549
|
+
else if (_credentials.expiry_at && !credentialProvider?.refresh) {
|
|
21550
|
+
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.`);
|
|
21551
|
+
this._warnings$.next({
|
|
21552
|
+
code: "credential_no_refresh_handler",
|
|
21553
|
+
source: "CredentialProvider",
|
|
21554
|
+
message: "Credential has expiry_at but no refresh handler. Session will terminate at expiry unless the SAT carries 'sat:refresh' scope.",
|
|
21555
|
+
expiresAt: _credentials.expiry_at
|
|
21556
|
+
});
|
|
21557
|
+
}
|
|
21194
21558
|
this._deps.credential = _credentials;
|
|
21195
21559
|
this.persistCredential(_credentials);
|
|
21196
21560
|
if (this.isConnected && this._clientSession.authenticated && _credentials.token) try {
|
|
@@ -21201,35 +21565,6 @@ var SignalWire = class extends Destroyable {
|
|
|
21201
21565
|
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
21202
21566
|
}
|
|
21203
21567
|
}
|
|
21204
|
-
/**
|
|
21205
|
-
* Schedules credential refresh with exponential backoff retry on failure.
|
|
21206
|
-
* On success, resets attempt counter and schedules the next refresh.
|
|
21207
|
-
* After exhausting retries, emits TokenRefreshError and disconnects.
|
|
21208
|
-
*/
|
|
21209
|
-
scheduleCredentialRefresh(credentialProvider, expiresAt, attempt = 0) {
|
|
21210
|
-
if (this._refreshTimerId !== void 0) clearTimeout(this._refreshTimerId);
|
|
21211
|
-
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);
|
|
21212
|
-
this._refreshTimerId = setTimeout(async () => {
|
|
21213
|
-
try {
|
|
21214
|
-
if (!credentialProvider.refresh) throw new InvalidCredentialsError("Credential provider does not support refresh");
|
|
21215
|
-
const newCredentials = await credentialProvider.refresh();
|
|
21216
|
-
this._deps.credential = newCredentials;
|
|
21217
|
-
this.persistCredential(newCredentials);
|
|
21218
|
-
logger$1.info("[SignalWire] Credentials refreshed successfully.");
|
|
21219
|
-
if (newCredentials.expiry_at) this.scheduleCredentialRefresh(credentialProvider, newCredentials.expiry_at, 0);
|
|
21220
|
-
} catch (error) {
|
|
21221
|
-
const nextAttempt = attempt + 1;
|
|
21222
|
-
logger$1.error(`[SignalWire] Credential refresh failed (attempt ${nextAttempt}/${CREDENTIAL_REFRESH_MAX_RETRIES}):`, error);
|
|
21223
|
-
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
21224
|
-
if (nextAttempt < CREDENTIAL_REFRESH_MAX_RETRIES) this.scheduleCredentialRefresh(credentialProvider, expiresAt, nextAttempt);
|
|
21225
|
-
else {
|
|
21226
|
-
logger$1.error("[SignalWire] Credential refresh exhausted all retries. Disconnecting.");
|
|
21227
|
-
this._errors$.next(new TokenRefreshError("Credential refresh failed after max retries"));
|
|
21228
|
-
this.disconnect();
|
|
21229
|
-
}
|
|
21230
|
-
}
|
|
21231
|
-
}, refreshInterval);
|
|
21232
|
-
}
|
|
21233
21568
|
/** Persist credential to localStorage when persistSession is enabled. */
|
|
21234
21569
|
persistCredential(credential) {
|
|
21235
21570
|
if (!credential.token) return;
|
|
@@ -21323,6 +21658,7 @@ var SignalWire = class extends Destroyable {
|
|
|
21323
21658
|
logger$1.debug("[SignalWire] Credential expired, refreshing before reconnect");
|
|
21324
21659
|
const newCredentials = await this._credentialProvider.authenticate(fingerprint ? { fingerprint } : void 0);
|
|
21325
21660
|
this._deps.credential = newCredentials;
|
|
21661
|
+
if (newCredentials.expiry_at && this._credentialProvider.refresh) this._refreshCoordinator?.scheduleDeveloperRefresh(this._credentialProvider, newCredentials.expiry_at);
|
|
21326
21662
|
logger$1.debug("[SignalWire] Credential refreshed successfully for reconnect");
|
|
21327
21663
|
} catch (error) {
|
|
21328
21664
|
logger$1.error("[SignalWire] Failed to refresh credentials for reconnect:", error);
|
|
@@ -21334,33 +21670,12 @@ var SignalWire = class extends Destroyable {
|
|
|
21334
21670
|
this._errors$.next(error);
|
|
21335
21671
|
});
|
|
21336
21672
|
await this._clientSession.connect();
|
|
21337
|
-
|
|
21338
|
-
if (this._refreshTimerId) {
|
|
21339
|
-
clearTimeout(this._refreshTimerId);
|
|
21340
|
-
this._refreshTimerId = void 0;
|
|
21341
|
-
logger$1.debug("[SignalWire] Developer refresh disabled — Client Bound SAT activation starting");
|
|
21342
|
-
}
|
|
21343
|
-
this._deviceTokenManager = new DeviceTokenManager(this._dpopManager, this._deps.http, (error) => this._errors$.next(error), () => this._deps.credential);
|
|
21344
|
-
await this._deviceTokenManager.activate(this._deps.user, this._clientSession, (cred) => {
|
|
21345
|
-
this._deps.credential = {
|
|
21346
|
-
...this._deps.credential,
|
|
21347
|
-
...cred
|
|
21348
|
-
};
|
|
21349
|
-
});
|
|
21350
|
-
}
|
|
21673
|
+
await this._refreshCoordinator?.activate(this._deps.user, this._clientSession);
|
|
21351
21674
|
this.subscribeTo(this._clientSession.authenticated$.pipe((0, import_cjs$1.skip)(1), (0, import_cjs$1.filter)(Boolean)), async () => {
|
|
21352
21675
|
try {
|
|
21353
|
-
|
|
21354
|
-
await this._deviceTokenManager.activate(this._deps.user, this._clientSession, (cred) => {
|
|
21355
|
-
this._deps.credential = {
|
|
21356
|
-
...this._deps.credential,
|
|
21357
|
-
...cred
|
|
21358
|
-
};
|
|
21359
|
-
});
|
|
21360
|
-
logger$1.debug("[SignalWire] Client Bound SAT re-activated after reconnect");
|
|
21361
|
-
}
|
|
21676
|
+
await this._refreshCoordinator?.activate(this._deps.user, this._clientSession);
|
|
21362
21677
|
} catch (error) {
|
|
21363
|
-
logger$1.error("[SignalWire]
|
|
21678
|
+
logger$1.error("[SignalWire] Refresh re-arm after reconnect failed (non-fatal):", error);
|
|
21364
21679
|
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
21365
21680
|
}
|
|
21366
21681
|
try {
|
|
@@ -21446,6 +21761,19 @@ var SignalWire = class extends Destroyable {
|
|
|
21446
21761
|
get errors$() {
|
|
21447
21762
|
return this.deferEmission(this._errors$.asObservable());
|
|
21448
21763
|
}
|
|
21764
|
+
/**
|
|
21765
|
+
* Observable stream of non-fatal SDK warnings.
|
|
21766
|
+
*
|
|
21767
|
+
* Subscribe to detect SDK behaviors that affect session liveness or developer-facing
|
|
21768
|
+
* contracts but do not warrant disconnection — e.g., a fallback from Client Bound SAT
|
|
21769
|
+
* refresh to the developer-provided `refresh()` because the SAT lacks `sat:refresh`
|
|
21770
|
+
* scope. Discriminated by `code`.
|
|
21771
|
+
*
|
|
21772
|
+
* Independent from {@link errors$}: existing error consumers are not notified.
|
|
21773
|
+
*/
|
|
21774
|
+
get warnings$() {
|
|
21775
|
+
return this.deferEmission(this._warnings$.asObservable());
|
|
21776
|
+
}
|
|
21449
21777
|
/** Platform WebRTC capabilities detected at construction time. */
|
|
21450
21778
|
get platformCapabilities() {
|
|
21451
21779
|
this._platformCapabilities ??= detectPlatformCapabilities(this._options.webRTCApiProvider);
|
|
@@ -21514,7 +21842,7 @@ var SignalWire = class extends Destroyable {
|
|
|
21514
21842
|
logger$1.warn("[SignalWire] Failed to initialize VisibilityController:", error);
|
|
21515
21843
|
}
|
|
21516
21844
|
try {
|
|
21517
|
-
this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "
|
|
21845
|
+
this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "4.0.0-rc.1" });
|
|
21518
21846
|
} catch (error) {
|
|
21519
21847
|
logger$1.warn("[SignalWire] Failed to initialize DiagnosticsCollector:", error);
|
|
21520
21848
|
}
|
|
@@ -21522,14 +21850,19 @@ var SignalWire = class extends Destroyable {
|
|
|
21522
21850
|
/**
|
|
21523
21851
|
* Disconnects the WebSocket and tears down the current session.
|
|
21524
21852
|
*
|
|
21853
|
+
* Ends the session identified by the protocol and clears its persisted
|
|
21854
|
+
* resume state (`authorization_state` + protocol) and attach records
|
|
21855
|
+
* together — a later {@link connect} with the same credentials starts a
|
|
21856
|
+
* fresh session and cannot reattach to the ended session's calls.
|
|
21857
|
+
* Credentials and device preferences are preserved. To temporarily stop
|
|
21858
|
+
* receiving inbound calls while keeping the session alive, use
|
|
21859
|
+
* `unregister()` instead.
|
|
21860
|
+
*
|
|
21525
21861
|
* The client can be reconnected by calling {@link connect} again,
|
|
21526
21862
|
* which creates a fresh transport and session.
|
|
21527
21863
|
*/
|
|
21528
21864
|
async disconnect() {
|
|
21529
|
-
|
|
21530
|
-
clearTimeout(this._refreshTimerId);
|
|
21531
|
-
this._refreshTimerId = void 0;
|
|
21532
|
-
}
|
|
21865
|
+
this._refreshCoordinator?.suspend();
|
|
21533
21866
|
this._diagnosticsCollector?.record("connection", "disconnected");
|
|
21534
21867
|
await this.teardownTransportAndSession();
|
|
21535
21868
|
this._isConnected$.next(false);
|
|
@@ -21916,15 +22249,20 @@ var SignalWire = class extends Destroyable {
|
|
|
21916
22249
|
prefs.preferredVideoInput = null;
|
|
21917
22250
|
await this._deviceController.clearDeviceState();
|
|
21918
22251
|
}
|
|
21919
|
-
/**
|
|
22252
|
+
/**
|
|
22253
|
+
* Destroys the client, clearing timers and releasing all resources.
|
|
22254
|
+
*
|
|
22255
|
+
* Intentionally destroying the client ends its session: the resume state
|
|
22256
|
+
* (`authorization_state` + protocol) and the attach records are both
|
|
22257
|
+
* cleared. Credentials and device preferences are preserved — use
|
|
22258
|
+
* {@link resetToDefaults} for a full wipe. To temporarily stop receiving
|
|
22259
|
+
* inbound calls while keeping the session alive, use `unregister()`.
|
|
22260
|
+
*/
|
|
21920
22261
|
destroy() {
|
|
21921
|
-
|
|
21922
|
-
|
|
21923
|
-
this._refreshTimerId = void 0;
|
|
21924
|
-
}
|
|
21925
|
-
this._deviceTokenManager?.destroy();
|
|
22262
|
+
this._refreshCoordinator?.destroy();
|
|
22263
|
+
this._refreshCoordinator = void 0;
|
|
21926
22264
|
this._dpopManager?.destroy();
|
|
21927
|
-
|
|
22265
|
+
this._clientSession.teardownSessionState();
|
|
21928
22266
|
this._transport.destroy();
|
|
21929
22267
|
this._clientSession.destroy();
|
|
21930
22268
|
try {
|
|
@@ -22039,7 +22377,7 @@ var StaticCredentialProvider = class {
|
|
|
22039
22377
|
/**
|
|
22040
22378
|
* Library version from package.json, injected at build time.
|
|
22041
22379
|
*/
|
|
22042
|
-
const version = "
|
|
22380
|
+
const version = "4.0.0-rc.1";
|
|
22043
22381
|
/**
|
|
22044
22382
|
* Flag indicating the library has been loaded and is ready to use.
|
|
22045
22383
|
* For UMD builds: `window.SignalWire.ready`
|
|
@@ -22061,7 +22399,7 @@ const ready = true;
|
|
|
22061
22399
|
*/
|
|
22062
22400
|
const emitReadyEvent = () => {
|
|
22063
22401
|
if (typeof window !== "undefined") {
|
|
22064
|
-
const event = new CustomEvent("signalwire:js:ready", { detail: { version: "
|
|
22402
|
+
const event = new CustomEvent("signalwire:js:ready", { detail: { version: "4.0.0-rc.1" } });
|
|
22065
22403
|
window.dispatchEvent(event);
|
|
22066
22404
|
}
|
|
22067
22405
|
};
|