@signalwire/js 4.0.0-dev-20260910191529 → 4.0.0-dev-20260911135809
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 +546 -192
- package/dist/browser.mjs.map +1 -1
- package/dist/browser.umd.js +546 -191
- package/dist/browser.umd.js.map +1 -1
- package/dist/index.cjs +496 -172
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2092 -2025
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +2092 -2025
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +496 -173
- package/dist/index.mjs.map +1 -1
- package/dist/operators/index.cjs +1 -1
- package/dist/operators/index.mjs +1 -1
- package/dist/{operators-Bn4Ij3VB.cjs → operators-B3G5z2B1.cjs} +50 -1
- package/dist/operators-B3G5z2B1.cjs.map +1 -0
- package/dist/{operators-Zxmwpb0j.mjs → operators-GSyCbUIU.mjs} +33 -2
- package/dist/operators-GSyCbUIU.mjs.map +1 -0
- package/package.json +1 -1
- package/dist/operators-Bn4Ij3VB.cjs.map +0 -1
- package/dist/operators-Zxmwpb0j.mjs.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_operators = require('./operators-
|
|
1
|
+
const require_operators = require('./operators-B3G5z2B1.cjs');
|
|
2
2
|
let jwt_decode = require("jwt-decode");
|
|
3
3
|
let rxjs = require("rxjs");
|
|
4
4
|
let uuid = require("uuid");
|
|
@@ -131,7 +131,7 @@ const asyncRetry = async ({ asyncCallable, maxRetries: retries = DEFAULT_MAX_RET
|
|
|
131
131
|
|
|
132
132
|
//#endregion
|
|
133
133
|
//#region src/controllers/HTTPRequestController.ts
|
|
134
|
-
const logger$
|
|
134
|
+
const logger$32 = require_operators.getLogger();
|
|
135
135
|
const GET_PARAMS = {
|
|
136
136
|
method: "GET",
|
|
137
137
|
headers: { Accept: "application/json" }
|
|
@@ -195,7 +195,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
195
195
|
this._responses$.next(response);
|
|
196
196
|
return response;
|
|
197
197
|
} catch (error) {
|
|
198
|
-
logger$
|
|
198
|
+
logger$32.error("[HTTPRequestController] Request error:", error);
|
|
199
199
|
this._status$.next("error");
|
|
200
200
|
const err = error instanceof Error ? error : new Error("HTTP request failed", { cause: error });
|
|
201
201
|
this._errors$.next(err);
|
|
@@ -222,7 +222,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
222
222
|
const url = this.buildURL(request.url);
|
|
223
223
|
const headers = this.buildHeaders(request.headers);
|
|
224
224
|
const timeout$4 = request.timeout ?? this.requestTimeout;
|
|
225
|
-
logger$
|
|
225
|
+
logger$32.debug("[HTTPRequestController] Executing request:", {
|
|
226
226
|
method: request.method,
|
|
227
227
|
url,
|
|
228
228
|
headers: Object.keys(headers).reduce((acc, key) => {
|
|
@@ -242,7 +242,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
242
242
|
});
|
|
243
243
|
clearTimeout(timeoutId);
|
|
244
244
|
const httpResponse = await this.convertResponse(response);
|
|
245
|
-
logger$
|
|
245
|
+
logger$32.debug("[HTTPRequestController] Response received:", {
|
|
246
246
|
status: response.status,
|
|
247
247
|
statusText: response.statusText,
|
|
248
248
|
headers: [...response.headers.entries()],
|
|
@@ -252,7 +252,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
252
252
|
} catch (error) {
|
|
253
253
|
clearTimeout(timeoutId);
|
|
254
254
|
if (error instanceof Error && error.name === "AbortError") throw new require_operators.RequestTimeoutError(`Request timeout after ${timeout$4}ms`, { cause: error });
|
|
255
|
-
logger$
|
|
255
|
+
logger$32.error("[HTTPRequestController] Request failed:", error);
|
|
256
256
|
throw error;
|
|
257
257
|
}
|
|
258
258
|
}
|
|
@@ -266,8 +266,8 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
266
266
|
const credential = this.getCredential();
|
|
267
267
|
if (credential.token) {
|
|
268
268
|
headers.Authorization = `Bearer ${credential.token}`;
|
|
269
|
-
logger$
|
|
270
|
-
} else logger$
|
|
269
|
+
logger$32.debug("[HTTPRequestController] Using Bearer token auth, token length:", credential.token.length);
|
|
270
|
+
} else logger$32.warn("[HTTPRequestController] No credentials available for authentication");
|
|
271
271
|
return headers;
|
|
272
272
|
}
|
|
273
273
|
/**
|
|
@@ -415,7 +415,7 @@ function fromMsToSec(milliseconds) {
|
|
|
415
415
|
|
|
416
416
|
//#endregion
|
|
417
417
|
//#region src/containers/PreferencesContainer.ts
|
|
418
|
-
const logger$
|
|
418
|
+
const logger$31 = require_operators.getLogger();
|
|
419
419
|
var PreferencesContainer = class PreferencesContainer {
|
|
420
420
|
static get instance() {
|
|
421
421
|
this._instance ??= new PreferencesContainer();
|
|
@@ -1077,7 +1077,7 @@ var ClientPreferences = class {
|
|
|
1077
1077
|
if (!this._storage) return;
|
|
1078
1078
|
const data = collectStoredPreferences();
|
|
1079
1079
|
this._storage.setItem(require_operators.PREFERENCES_STORAGE_KEY, data, "local").catch((error) => {
|
|
1080
|
-
logger$
|
|
1080
|
+
logger$31.error(`[ClientPreferences] Failed to save preferences: ${String(error)}`);
|
|
1081
1081
|
});
|
|
1082
1082
|
}
|
|
1083
1083
|
/** Loads preferences from storage and applies them to the container. */
|
|
@@ -1086,7 +1086,7 @@ var ClientPreferences = class {
|
|
|
1086
1086
|
this._storage.getItem(require_operators.PREFERENCES_STORAGE_KEY, "local").then((stored) => {
|
|
1087
1087
|
if (stored) applyStoredPreferences(stored);
|
|
1088
1088
|
}).catch((error) => {
|
|
1089
|
-
logger$
|
|
1089
|
+
logger$31.error(`[ClientPreferences] Failed to load preferences: ${String(error)}`);
|
|
1090
1090
|
});
|
|
1091
1091
|
}
|
|
1092
1092
|
};
|
|
@@ -1107,7 +1107,7 @@ function toError(value) {
|
|
|
1107
1107
|
|
|
1108
1108
|
//#endregion
|
|
1109
1109
|
//#region src/controllers/NavigatorDeviceController.ts
|
|
1110
|
-
const logger$
|
|
1110
|
+
const logger$30 = require_operators.getLogger();
|
|
1111
1111
|
/** Maps a device kind to its storage key. */
|
|
1112
1112
|
const DEVICE_STORAGE_KEYS = {
|
|
1113
1113
|
audioinput: require_operators.DEVICE_STORAGE_KEY_AUDIO_INPUT,
|
|
@@ -1129,7 +1129,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1129
1129
|
super();
|
|
1130
1130
|
this.webRTCApiProvider = webRTCApiProvider;
|
|
1131
1131
|
this.deviceChangeHandler = () => {
|
|
1132
|
-
logger$
|
|
1132
|
+
logger$30.debug("[DeviceController] Device change detected");
|
|
1133
1133
|
this.enumerateDevices();
|
|
1134
1134
|
};
|
|
1135
1135
|
this._devicesState$ = this.createBehaviorSubject(initialDevicesState);
|
|
@@ -1194,13 +1194,13 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1194
1194
|
return this.cachedObservable("videoInputDevices$", () => this._devicesState$.pipe((0, rxjs.map)((state) => state.videoinput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$)));
|
|
1195
1195
|
}
|
|
1196
1196
|
get selectedAudioInputDevice$() {
|
|
1197
|
-
return this.cachedObservable("selectedAudioInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, rxjs.map)((state) => state.audioinput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.tap)((info) => logger$
|
|
1197
|
+
return this.cachedObservable("selectedAudioInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, rxjs.map)((state) => state.audioinput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.tap)((info) => logger$30.debug("[DeviceController] Selected audio input device changed:", info))));
|
|
1198
1198
|
}
|
|
1199
1199
|
get selectedAudioOutputDevice$() {
|
|
1200
|
-
return this.cachedObservable("selectedAudioOutputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, rxjs.map)((state) => state.audiooutput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.tap)((info) => logger$
|
|
1200
|
+
return this.cachedObservable("selectedAudioOutputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, rxjs.map)((state) => state.audiooutput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.tap)((info) => logger$30.debug("[DeviceController] Selected audio output device changed:", info))));
|
|
1201
1201
|
}
|
|
1202
1202
|
get selectedVideoInputDevice$() {
|
|
1203
|
-
return this.cachedObservable("selectedVideoInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, rxjs.map)((state) => state.videoinput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.tap)((info) => logger$
|
|
1203
|
+
return this.cachedObservable("selectedVideoInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, rxjs.map)((state) => state.videoinput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.tap)((info) => logger$30.debug("[DeviceController] Selected video input device changed:", info))));
|
|
1204
1204
|
}
|
|
1205
1205
|
get selectedAudioInputDevice() {
|
|
1206
1206
|
if (this._audioInputDisabled$.value) return null;
|
|
@@ -1275,7 +1275,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1275
1275
|
if (device) this.persistDeviceSelection("audioinput", device);
|
|
1276
1276
|
}
|
|
1277
1277
|
selectVideoInputDevice(device) {
|
|
1278
|
-
logger$
|
|
1278
|
+
logger$30.debug("[DeviceController] Setting selected video input device:", device);
|
|
1279
1279
|
if (this._videoInputDisabled$.value && device) this._videoInputDisabled$.next(false);
|
|
1280
1280
|
const previous = this._selectedDevicesState$.value.videoinput;
|
|
1281
1281
|
if (previous && previous.deviceId !== device?.deviceId) this._deviceHistory.push("videoinput", previous);
|
|
@@ -1332,7 +1332,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1332
1332
|
}
|
|
1333
1333
|
const fromHistory = this._deviceHistory.findInHistory(kind, devices);
|
|
1334
1334
|
if (fromHistory) {
|
|
1335
|
-
logger$
|
|
1335
|
+
logger$30.debug(`[DeviceController] Device disappeared, falling back to history: ${fromHistory.label}`);
|
|
1336
1336
|
this.emitDeviceRecovered(kind, selected, fromHistory, "device_disconnected");
|
|
1337
1337
|
return fromHistory;
|
|
1338
1338
|
}
|
|
@@ -1385,7 +1385,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1385
1385
|
try {
|
|
1386
1386
|
await this._storageManager.setItem(DEVICE_STORAGE_KEYS[kind], stored, "local");
|
|
1387
1387
|
} catch (error) {
|
|
1388
|
-
logger$
|
|
1388
|
+
logger$30.error(`[DeviceController] Failed to persist device selection for ${kind}:`, error);
|
|
1389
1389
|
}
|
|
1390
1390
|
}
|
|
1391
1391
|
async loadPersistedDevices() {
|
|
@@ -1401,7 +1401,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1401
1401
|
[kind]: stored
|
|
1402
1402
|
};
|
|
1403
1403
|
} catch (error) {
|
|
1404
|
-
logger$
|
|
1404
|
+
logger$30.error(`[DeviceController] Failed to load persisted device for ${kind}:`, error);
|
|
1405
1405
|
}
|
|
1406
1406
|
}
|
|
1407
1407
|
/** Clears device history, persisted selections, and re-enumerates devices. */
|
|
@@ -1419,7 +1419,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1419
1419
|
this.disableDeviceMonitoring();
|
|
1420
1420
|
this.webRTCApiProvider.mediaDevices.addEventListener("devicechange", this.deviceChangeHandler);
|
|
1421
1421
|
if (PreferencesContainer.instance.devicePollingInterval > 0) this._devicesPoolingSubscription = (0, rxjs.interval)(PreferencesContainer.instance.devicePollingInterval).subscribe(() => {
|
|
1422
|
-
logger$
|
|
1422
|
+
logger$30.debug("[DeviceController] Polling devices due to interval");
|
|
1423
1423
|
this.enumerateDevices();
|
|
1424
1424
|
});
|
|
1425
1425
|
this.enumerateDevices();
|
|
@@ -1445,13 +1445,13 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1445
1445
|
videoinput: []
|
|
1446
1446
|
});
|
|
1447
1447
|
this._devicesState$.next(devicesByKind);
|
|
1448
|
-
logger$
|
|
1448
|
+
logger$30.debug("[DeviceController] Devices enumerated:", {
|
|
1449
1449
|
audioInputs: devicesByKind.audioinput.length,
|
|
1450
1450
|
audioOutputs: devicesByKind.audiooutput.length,
|
|
1451
1451
|
videoInputs: devicesByKind.videoinput.length
|
|
1452
1452
|
});
|
|
1453
1453
|
} catch (error) {
|
|
1454
|
-
logger$
|
|
1454
|
+
logger$30.error("[DeviceController] Failed to enumerate devices:", error);
|
|
1455
1455
|
this._errors$.next(toError(error));
|
|
1456
1456
|
}
|
|
1457
1457
|
}
|
|
@@ -1467,7 +1467,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1467
1467
|
stream.getTracks().forEach((t) => t.stop());
|
|
1468
1468
|
return capabilities;
|
|
1469
1469
|
} catch (error) {
|
|
1470
|
-
logger$
|
|
1470
|
+
logger$30.error("[DeviceController] Failed to get device capabilities:", error);
|
|
1471
1471
|
this._errors$.next(toError(error));
|
|
1472
1472
|
throw error;
|
|
1473
1473
|
}
|
|
@@ -1718,7 +1718,7 @@ var DependencyContainer = class {
|
|
|
1718
1718
|
|
|
1719
1719
|
//#endregion
|
|
1720
1720
|
//#region src/controllers/CryptoController.ts
|
|
1721
|
-
const logger$
|
|
1721
|
+
const logger$29 = require_operators.getLogger();
|
|
1722
1722
|
const DPOP_DB_NAME = "sw-dpop";
|
|
1723
1723
|
const DPOP_DB_VERSION = 1;
|
|
1724
1724
|
const DPOP_STORE_NAME = "keys";
|
|
@@ -1777,7 +1777,7 @@ async function loadKeyPairFromDB() {
|
|
|
1777
1777
|
tx.oncomplete = () => db.close();
|
|
1778
1778
|
});
|
|
1779
1779
|
} catch (error) {
|
|
1780
|
-
logger$
|
|
1780
|
+
logger$29.warn("[DPoP] Failed to load key pair from IndexedDB:", error);
|
|
1781
1781
|
return null;
|
|
1782
1782
|
}
|
|
1783
1783
|
}
|
|
@@ -1797,7 +1797,7 @@ async function saveKeyPairToDB(keyPair) {
|
|
|
1797
1797
|
};
|
|
1798
1798
|
});
|
|
1799
1799
|
} catch (error) {
|
|
1800
|
-
logger$
|
|
1800
|
+
logger$29.warn("[DPoP] Failed to save key pair to IndexedDB:", error);
|
|
1801
1801
|
}
|
|
1802
1802
|
}
|
|
1803
1803
|
async function deleteKeyPairFromDB() {
|
|
@@ -1816,7 +1816,7 @@ async function deleteKeyPairFromDB() {
|
|
|
1816
1816
|
};
|
|
1817
1817
|
});
|
|
1818
1818
|
} catch (error) {
|
|
1819
|
-
logger$
|
|
1819
|
+
logger$29.warn("[DPoP] Failed to delete key pair from IndexedDB:", error);
|
|
1820
1820
|
}
|
|
1821
1821
|
}
|
|
1822
1822
|
/**
|
|
@@ -1876,13 +1876,13 @@ var CryptoController = class {
|
|
|
1876
1876
|
this._publicJwk = await crypto.subtle.exportKey("jwk", stored.publicKey);
|
|
1877
1877
|
this._fingerprint = await computeJwkThumbprint(this._publicJwk);
|
|
1878
1878
|
this._initialized = true;
|
|
1879
|
-
logger$
|
|
1879
|
+
logger$29.debug("[DPoP] Key pair restored from IndexedDB, fingerprint:", this._fingerprint);
|
|
1880
1880
|
return this._fingerprint;
|
|
1881
1881
|
} catch (error) {
|
|
1882
|
-
logger$
|
|
1882
|
+
logger$29.warn("[DPoP] Stored key pair unusable, generating new one:", error);
|
|
1883
1883
|
await deleteKeyPairFromDB();
|
|
1884
1884
|
}
|
|
1885
|
-
logger$
|
|
1885
|
+
logger$29.debug("[DPoP] Generating RSA key pair");
|
|
1886
1886
|
this._keyPair = await crypto.subtle.generateKey({
|
|
1887
1887
|
name: "RSASSA-PKCS1-v1_5",
|
|
1888
1888
|
modulusLength: 2048,
|
|
@@ -1897,7 +1897,7 @@ var CryptoController = class {
|
|
|
1897
1897
|
this._fingerprint = await computeJwkThumbprint(this._publicJwk);
|
|
1898
1898
|
this._initialized = true;
|
|
1899
1899
|
await saveKeyPairToDB(this._keyPair);
|
|
1900
|
-
logger$
|
|
1900
|
+
logger$29.debug("[DPoP] Key pair generated and persisted, fingerprint:", this._fingerprint);
|
|
1901
1901
|
return this._fingerprint;
|
|
1902
1902
|
}
|
|
1903
1903
|
/**
|
|
@@ -1963,7 +1963,7 @@ var CryptoController = class {
|
|
|
1963
1963
|
this._fingerprint = null;
|
|
1964
1964
|
this._initialized = false;
|
|
1965
1965
|
deleteKeyPairFromDB();
|
|
1966
|
-
logger$
|
|
1966
|
+
logger$29.debug("[DPoP] Controller destroyed");
|
|
1967
1967
|
}
|
|
1968
1968
|
get publicJwk() {
|
|
1969
1969
|
if (!this._publicJwk) throw new require_operators.DPoPInitError("CryptoController not initialized. Call init() first.");
|
|
@@ -1986,7 +1986,7 @@ var CryptoController = class {
|
|
|
1986
1986
|
|
|
1987
1987
|
//#endregion
|
|
1988
1988
|
//#region src/controllers/NetworkMonitor.ts
|
|
1989
|
-
const logger$
|
|
1989
|
+
const logger$28 = require_operators.getLogger();
|
|
1990
1990
|
/**
|
|
1991
1991
|
* Safely check whether we are running in a browser environment
|
|
1992
1992
|
* with `window` and the relevant event targets.
|
|
@@ -2043,7 +2043,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
2043
2043
|
}
|
|
2044
2044
|
attachListeners() {
|
|
2045
2045
|
if (!hasBrowserNetworkEvents()) {
|
|
2046
|
-
logger$
|
|
2046
|
+
logger$28.debug("NetworkMonitor: no browser environment detected, skipping event listeners");
|
|
2047
2047
|
return;
|
|
2048
2048
|
}
|
|
2049
2049
|
window.addEventListener("online", this._onOnline);
|
|
@@ -2051,7 +2051,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
2051
2051
|
const connection = getNetworkConnection();
|
|
2052
2052
|
if (connection) connection.addEventListener("change", this._onConnectionChange);
|
|
2053
2053
|
this._listenersAttached = true;
|
|
2054
|
-
logger$
|
|
2054
|
+
logger$28.debug("NetworkMonitor: event listeners attached");
|
|
2055
2055
|
}
|
|
2056
2056
|
removeListeners() {
|
|
2057
2057
|
if (!this._listenersAttached) return;
|
|
@@ -2062,10 +2062,10 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
2062
2062
|
if (connection) connection.removeEventListener("change", this._onConnectionChange);
|
|
2063
2063
|
}
|
|
2064
2064
|
this._listenersAttached = false;
|
|
2065
|
-
logger$
|
|
2065
|
+
logger$28.debug("NetworkMonitor: event listeners removed");
|
|
2066
2066
|
}
|
|
2067
2067
|
handleOnline() {
|
|
2068
|
-
logger$
|
|
2068
|
+
logger$28.info("NetworkMonitor: browser went online");
|
|
2069
2069
|
this._isOnline$.next(true);
|
|
2070
2070
|
this._networkChange$.next({
|
|
2071
2071
|
type: "online",
|
|
@@ -2074,7 +2074,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
2074
2074
|
});
|
|
2075
2075
|
}
|
|
2076
2076
|
handleOffline() {
|
|
2077
|
-
logger$
|
|
2077
|
+
logger$28.info("NetworkMonitor: browser went offline");
|
|
2078
2078
|
this._isOnline$.next(false);
|
|
2079
2079
|
this._networkChange$.next({
|
|
2080
2080
|
type: "offline",
|
|
@@ -2083,7 +2083,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
2083
2083
|
}
|
|
2084
2084
|
handleConnectionChange() {
|
|
2085
2085
|
const networkType = getNetworkType();
|
|
2086
|
-
logger$
|
|
2086
|
+
logger$28.info(`NetworkMonitor: connection changed — effectiveType=${networkType ?? "unknown"}`);
|
|
2087
2087
|
this._networkChange$.next({
|
|
2088
2088
|
type: "connection_change",
|
|
2089
2089
|
timestamp: Date.now(),
|
|
@@ -2198,7 +2198,7 @@ function getNavigatorMediaDevices() {
|
|
|
2198
2198
|
|
|
2199
2199
|
//#endregion
|
|
2200
2200
|
//#region src/controllers/PreflightRunner.ts
|
|
2201
|
-
const logger$
|
|
2201
|
+
const logger$27 = require_operators.getLogger();
|
|
2202
2202
|
const DEFAULT_MEDIA_TEST_DURATION_S = 10;
|
|
2203
2203
|
const ICE_GATHERING_TIMEOUT_MS = 1e4;
|
|
2204
2204
|
const SIGNALING_RTT_TIMEOUT_MS = 5e3;
|
|
@@ -2247,7 +2247,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
2247
2247
|
if (!this._options.skipMediaTest) try {
|
|
2248
2248
|
bandwidth = await this.testMediaBandwidth(destination);
|
|
2249
2249
|
} catch (error) {
|
|
2250
|
-
logger$
|
|
2250
|
+
logger$27.warn("[PreflightRunner] Media bandwidth test failed:", error);
|
|
2251
2251
|
warnings.push("Media bandwidth test failed");
|
|
2252
2252
|
}
|
|
2253
2253
|
return {
|
|
@@ -2259,7 +2259,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
2259
2259
|
warnings
|
|
2260
2260
|
};
|
|
2261
2261
|
} catch (error) {
|
|
2262
|
-
logger$
|
|
2262
|
+
logger$27.error("[PreflightRunner] Preflight test failed:", error);
|
|
2263
2263
|
throw new require_operators.PreflightError("preflight", error instanceof Error ? error : new Error(String(error)));
|
|
2264
2264
|
} finally {
|
|
2265
2265
|
this.destroy();
|
|
@@ -2290,7 +2290,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
2290
2290
|
if (track.kind === "video" && track.readyState === "live") videoWorking = true;
|
|
2291
2291
|
}
|
|
2292
2292
|
} catch (error) {
|
|
2293
|
-
logger$
|
|
2293
|
+
logger$27.warn("[PreflightRunner] Device test failed:", error);
|
|
2294
2294
|
} finally {
|
|
2295
2295
|
if (audioStream) audioStream.getTracks().forEach((t) => t.stop());
|
|
2296
2296
|
}
|
|
@@ -2348,7 +2348,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
2348
2348
|
rttMs
|
|
2349
2349
|
};
|
|
2350
2350
|
} catch (error) {
|
|
2351
|
-
logger$
|
|
2351
|
+
logger$27.warn("[PreflightRunner] ICE connectivity test failed:", error);
|
|
2352
2352
|
return {
|
|
2353
2353
|
type: "failed",
|
|
2354
2354
|
turnReachable: false,
|
|
@@ -2395,7 +2395,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
2395
2395
|
|
|
2396
2396
|
//#endregion
|
|
2397
2397
|
//#region src/controllers/VisibilityController.ts
|
|
2398
|
-
const logger$
|
|
2398
|
+
const logger$26 = require_operators.getLogger();
|
|
2399
2399
|
/**
|
|
2400
2400
|
* Checks whether the document visibility API is available.
|
|
2401
2401
|
*/
|
|
@@ -2432,8 +2432,8 @@ var VisibilityController = class extends Destroyable {
|
|
|
2432
2432
|
this._boundHandler = this._handleVisibilityChange.bind(this);
|
|
2433
2433
|
if (this._hasVisibilityApi) {
|
|
2434
2434
|
document.addEventListener("visibilitychange", this._boundHandler);
|
|
2435
|
-
logger$
|
|
2436
|
-
} else logger$
|
|
2435
|
+
logger$26.debug("VisibilityController: listening for visibilitychange events");
|
|
2436
|
+
} else logger$26.debug("VisibilityController: document visibility API not available, defaulting to visible");
|
|
2437
2437
|
}
|
|
2438
2438
|
/**
|
|
2439
2439
|
* Observable of the current visibility state.
|
|
@@ -2458,7 +2458,7 @@ var VisibilityController = class extends Destroyable {
|
|
|
2458
2458
|
destroy() {
|
|
2459
2459
|
if (this._hasVisibilityApi) {
|
|
2460
2460
|
document.removeEventListener("visibilitychange", this._boundHandler);
|
|
2461
|
-
logger$
|
|
2461
|
+
logger$26.debug("VisibilityController: removed visibilitychange listener");
|
|
2462
2462
|
}
|
|
2463
2463
|
super.destroy();
|
|
2464
2464
|
}
|
|
@@ -2476,7 +2476,7 @@ var VisibilityController = class extends Destroyable {
|
|
|
2476
2476
|
timestamp: Date.now()
|
|
2477
2477
|
};
|
|
2478
2478
|
this._visibilityChange$.next(changeEvent);
|
|
2479
|
-
logger$
|
|
2479
|
+
logger$26.debug("VisibilityController: visibility changed", {
|
|
2480
2480
|
from: previousState,
|
|
2481
2481
|
to: newState
|
|
2482
2482
|
});
|
|
@@ -2720,7 +2720,7 @@ function isRequesterValidationError(error) {
|
|
|
2720
2720
|
|
|
2721
2721
|
//#endregion
|
|
2722
2722
|
//#region src/managers/AttachManager.ts
|
|
2723
|
-
const logger$
|
|
2723
|
+
const logger$25 = require_operators.getLogger();
|
|
2724
2724
|
var AttachManager = class {
|
|
2725
2725
|
constructor(storage, deviceController, reconnectCallsTimeout, attachKey, credentialRecovered) {
|
|
2726
2726
|
this.storage = storage;
|
|
@@ -2742,7 +2742,7 @@ var AttachManager = class {
|
|
|
2742
2742
|
try {
|
|
2743
2743
|
return await this.storage.getItem(this.attachKey) ?? {};
|
|
2744
2744
|
} catch (error) {
|
|
2745
|
-
logger$
|
|
2745
|
+
logger$25.warn("[AttachManager] Failed to retrieve attached calls from storage", error);
|
|
2746
2746
|
return {};
|
|
2747
2747
|
}
|
|
2748
2748
|
}
|
|
@@ -2750,7 +2750,7 @@ var AttachManager = class {
|
|
|
2750
2750
|
try {
|
|
2751
2751
|
await this.storage.setItem(this.attachKey, attached);
|
|
2752
2752
|
} catch (error) {
|
|
2753
|
-
logger$
|
|
2753
|
+
logger$25.warn("[AttachManager] Failed to write attached calls to storage", error);
|
|
2754
2754
|
}
|
|
2755
2755
|
}
|
|
2756
2756
|
/**
|
|
@@ -2769,7 +2769,7 @@ var AttachManager = class {
|
|
|
2769
2769
|
}
|
|
2770
2770
|
async attach(call) {
|
|
2771
2771
|
if (!call.to) {
|
|
2772
|
-
logger$
|
|
2772
|
+
logger$25.warn("[AttachManager] Skip attach for calls with no destination");
|
|
2773
2773
|
return;
|
|
2774
2774
|
}
|
|
2775
2775
|
const destination = call.to;
|
|
@@ -2829,25 +2829,25 @@ var AttachManager = class {
|
|
|
2829
2829
|
callId,
|
|
2830
2830
|
...options
|
|
2831
2831
|
});
|
|
2832
|
-
logger$
|
|
2832
|
+
logger$25.info(`[AttachManager] Reattached call ${callId} (attempt ${attempt})`);
|
|
2833
2833
|
succeeded = true;
|
|
2834
2834
|
break;
|
|
2835
2835
|
} catch (error) {
|
|
2836
|
-
logger$
|
|
2836
|
+
logger$25.warn(`[AttachManager] Reattach attempt ${attempt}/3 failed for call ${callId}:`, error);
|
|
2837
2837
|
if (isRequesterValidationError(error)) {
|
|
2838
2838
|
refusedOnCredentials = true;
|
|
2839
|
-
logger$
|
|
2839
|
+
logger$25.warn(`[AttachManager] Reattach of ${callId} was refused on credentials; not retrying.`);
|
|
2840
2840
|
break;
|
|
2841
2841
|
}
|
|
2842
2842
|
if (attempt < 3) await new Promise((r) => setTimeout(r, (attempt + 1) * 1e3));
|
|
2843
2843
|
}
|
|
2844
2844
|
if (!succeeded) if (this.credentialRecovered() && !refusedOnCredentials) {
|
|
2845
|
-
logger$
|
|
2845
|
+
logger$25.warn(`[AttachManager] Reattach of ${callId} was denied after a verified reauthentication, removing reference`);
|
|
2846
2846
|
await this.detach({
|
|
2847
2847
|
id: callId,
|
|
2848
2848
|
mediaDirections: attachment.mediaDirections
|
|
2849
2849
|
});
|
|
2850
|
-
} else logger$
|
|
2850
|
+
} else logger$25.warn(`[AttachManager] Reattach failed for call ${callId}; keeping the reference (credential refused or never proven good)`);
|
|
2851
2851
|
}
|
|
2852
2852
|
}
|
|
2853
2853
|
/**
|
|
@@ -3160,7 +3160,7 @@ function toggleHandraiseMethod(is) {
|
|
|
3160
3160
|
|
|
3161
3161
|
//#endregion
|
|
3162
3162
|
//#region src/core/entities/Participant.ts
|
|
3163
|
-
const logger$
|
|
3163
|
+
const logger$24 = require_operators.getLogger();
|
|
3164
3164
|
const initialState = {};
|
|
3165
3165
|
/**
|
|
3166
3166
|
* Represents a participant in a call.
|
|
@@ -3634,6 +3634,9 @@ var SelfParticipant = class extends Participant {
|
|
|
3634
3634
|
*
|
|
3635
3635
|
* The call is unaffected when acquisition fails.
|
|
3636
3636
|
*
|
|
3637
|
+
* Known issue: stopping the share before its leg connects can leave this
|
|
3638
|
+
* promise pending until the call ends, so do not gate UI on it.
|
|
3639
|
+
*
|
|
3637
3640
|
* @throws The raw `getDisplayMedia` error. A dismissed picker or a
|
|
3638
3641
|
* permission denial rejects with a `NotAllowedError` `DOMException` —
|
|
3639
3642
|
* inspect `error.name` to tell benign cancels apart from real failures.
|
|
@@ -3642,7 +3645,7 @@ var SelfParticipant = class extends Participant {
|
|
|
3642
3645
|
try {
|
|
3643
3646
|
await this.vertoManager.addScreenMedia();
|
|
3644
3647
|
} catch (error) {
|
|
3645
|
-
logger$
|
|
3648
|
+
logger$24.error("[Participant.startScreenShare] Screen share error:", error);
|
|
3646
3649
|
throw error;
|
|
3647
3650
|
}
|
|
3648
3651
|
}
|
|
@@ -3663,14 +3666,18 @@ var SelfParticipant = class extends Participant {
|
|
|
3663
3666
|
*
|
|
3664
3667
|
* The call is unaffected when acquisition fails.
|
|
3665
3668
|
*
|
|
3669
|
+
* Known issue: removing the device before its leg connects can leave this
|
|
3670
|
+
* promise pending until the call ends, so do not gate UI on it.
|
|
3671
|
+
*
|
|
3666
3672
|
* @throws The raw `getUserMedia` error (e.g. `NotAllowedError` on
|
|
3667
|
-
* permission denial) — inspect `error.name` to decide how to react
|
|
3673
|
+
* permission denial) — inspect `error.name` to decide how to react — or
|
|
3674
|
+
* `AuxiliaryLegTimeoutError` if the leg does not connect in time.
|
|
3668
3675
|
*/
|
|
3669
3676
|
async addAdditionalDevice(options) {
|
|
3670
3677
|
try {
|
|
3671
3678
|
await this.vertoManager.addInputDevice(options);
|
|
3672
3679
|
} catch (error) {
|
|
3673
|
-
logger$
|
|
3680
|
+
logger$24.error("[Participant.addAdditionalDevice] Additional device error:", error);
|
|
3674
3681
|
throw error;
|
|
3675
3682
|
}
|
|
3676
3683
|
}
|
|
@@ -3733,7 +3740,7 @@ var SelfParticipant = class extends Participant {
|
|
|
3733
3740
|
*/
|
|
3734
3741
|
exitStudioModeIfActive() {
|
|
3735
3742
|
if (this._studioAudio$.value) {
|
|
3736
|
-
logger$
|
|
3743
|
+
logger$24.debug("[SelfParticipant] Exiting studio audio mode due to individual flag toggle");
|
|
3737
3744
|
this._studioAudio$.next(false);
|
|
3738
3745
|
}
|
|
3739
3746
|
}
|
|
@@ -3757,7 +3764,7 @@ var SelfParticipant = class extends Participant {
|
|
|
3757
3764
|
try {
|
|
3758
3765
|
await super.mute();
|
|
3759
3766
|
} catch (error) {
|
|
3760
|
-
logger$
|
|
3767
|
+
logger$24.warn("[Participant.toggleAudioInput] Server Error while muting audio input, proceeding with local toggle anyway", error);
|
|
3761
3768
|
} finally {
|
|
3762
3769
|
this.vertoManager.muteMainAudioInputDevice();
|
|
3763
3770
|
}
|
|
@@ -3767,7 +3774,7 @@ var SelfParticipant = class extends Participant {
|
|
|
3767
3774
|
try {
|
|
3768
3775
|
await super.unmute();
|
|
3769
3776
|
} catch (error) {
|
|
3770
|
-
logger$
|
|
3777
|
+
logger$24.warn("[Participant.toggleAudioInput] Server Error while unmuting audio input, proceeding with local toggle anyway", error);
|
|
3771
3778
|
} finally {
|
|
3772
3779
|
await this.vertoManager.unmuteMainAudioInputDevice();
|
|
3773
3780
|
}
|
|
@@ -3777,7 +3784,7 @@ var SelfParticipant = class extends Participant {
|
|
|
3777
3784
|
try {
|
|
3778
3785
|
await super.muteVideo();
|
|
3779
3786
|
} catch (error) {
|
|
3780
|
-
logger$
|
|
3787
|
+
logger$24.warn("[Participant.toggleVideoInput] Server Error while muting video input, proceeding with local toggle anyway", error);
|
|
3781
3788
|
} finally {
|
|
3782
3789
|
this.vertoManager.muteMainVideoInputDevice();
|
|
3783
3790
|
}
|
|
@@ -3787,7 +3794,7 @@ var SelfParticipant = class extends Participant {
|
|
|
3787
3794
|
try {
|
|
3788
3795
|
await super.unmuteVideo();
|
|
3789
3796
|
} catch (error) {
|
|
3790
|
-
logger$
|
|
3797
|
+
logger$24.warn("[Participant.toggleVideoInput] Server Error while unmuting video input, proceeding with local toggle anyway", error);
|
|
3791
3798
|
} finally {
|
|
3792
3799
|
await this.vertoManager.unmuteMainVideoInputDevice();
|
|
3793
3800
|
}
|
|
@@ -3881,7 +3888,7 @@ function isLayoutChangedPayload(value) {
|
|
|
3881
3888
|
|
|
3882
3889
|
//#endregion
|
|
3883
3890
|
//#region src/managers/CallEventsManager.ts
|
|
3884
|
-
const logger$
|
|
3891
|
+
const logger$23 = require_operators.getLogger();
|
|
3885
3892
|
const initialSessionState = {};
|
|
3886
3893
|
/** @internal */
|
|
3887
3894
|
var CallEventsManager = class extends Destroyable {
|
|
@@ -3985,7 +3992,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
3985
3992
|
}
|
|
3986
3993
|
initSubscriptions() {
|
|
3987
3994
|
this.subscribeTo(this.callJoinedEvent$, (callJoinedEvent) => {
|
|
3988
|
-
logger$
|
|
3995
|
+
logger$23.debug("[CallEventsManager] Handling call.joined event for call/session IDs:", {
|
|
3989
3996
|
callId: callJoinedEvent.call_id,
|
|
3990
3997
|
roomSessionId: callJoinedEvent.room_session_id
|
|
3991
3998
|
});
|
|
@@ -4012,19 +4019,19 @@ var CallEventsManager = class extends Destroyable {
|
|
|
4012
4019
|
if (this._self$.value?.capabilities.setLayout) this.updateLayouts();
|
|
4013
4020
|
});
|
|
4014
4021
|
this.subscribeTo(this.memberUpdates$, (member) => {
|
|
4015
|
-
logger$
|
|
4022
|
+
logger$23.debug("[CallEventsManager] Handling member update event for member ID:", member);
|
|
4016
4023
|
this.upsertParticipant(member);
|
|
4017
4024
|
});
|
|
4018
4025
|
this.subscribeTo(this.webRtcCallSession.memberLeft$, (memberLeftEvent) => {
|
|
4019
|
-
logger$
|
|
4026
|
+
logger$23.debug("[CallEventsManager] Handling member.left event for member ID:", memberLeftEvent.member.member_id);
|
|
4020
4027
|
const participants = { ...this._participants$.value };
|
|
4021
4028
|
if (memberLeftEvent.member.member_id in participants) {
|
|
4022
4029
|
delete participants[memberLeftEvent.member.member_id];
|
|
4023
4030
|
this._participants$.next(participants);
|
|
4024
|
-
} else logger$
|
|
4031
|
+
} else logger$23.warn(`[CallEventsManager] Received member.left event for unknown member ID: ${memberLeftEvent.member.member_id}`);
|
|
4025
4032
|
});
|
|
4026
4033
|
this.subscribeTo(this.webRtcCallSession.callUpdated$, (callUpdatedEvent) => {
|
|
4027
|
-
logger$
|
|
4034
|
+
logger$23.debug("[CallEventsManager] Handling call.updated event:", callUpdatedEvent);
|
|
4028
4035
|
const roomSession = callUpdatedEvent.room_session;
|
|
4029
4036
|
this._sessionState$.next({
|
|
4030
4037
|
...this._sessionState$.value,
|
|
@@ -4039,7 +4046,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
4039
4046
|
});
|
|
4040
4047
|
});
|
|
4041
4048
|
this.subscribeTo(this.layoutChangedEvent$, (layoutChangedEvent) => {
|
|
4042
|
-
logger$
|
|
4049
|
+
logger$23.debug("[CallEventsManager] Handling layout.changed event:", layoutChangedEvent);
|
|
4043
4050
|
this._sessionState$.next({
|
|
4044
4051
|
...this._sessionState$.value,
|
|
4045
4052
|
layout_name: layoutChangedEvent.id,
|
|
@@ -4049,10 +4056,10 @@ var CallEventsManager = class extends Destroyable {
|
|
|
4049
4056
|
});
|
|
4050
4057
|
}
|
|
4051
4058
|
updateParticipantPositions(layoutChangedEvent) {
|
|
4052
|
-
if (Object.keys(this._participants$.value).length > 0 && !layoutChangedEvent.layers.some((layer) => !!layer.member_id)) logger$
|
|
4059
|
+
if (Object.keys(this._participants$.value).length > 0 && !layoutChangedEvent.layers.some((layer) => !!layer.member_id)) logger$23.warn("[CallEventsManager] No layers with member_id found in layout.changed event. Nothing to update.");
|
|
4053
4060
|
layoutChangedEvent.layers.filter((layer) => !!layer.member_id).filter((layer) => {
|
|
4054
4061
|
if (!(layer.member_id in this._participants$.value)) {
|
|
4055
|
-
logger$
|
|
4062
|
+
logger$23.warn(`[CallEventsManager] Skipping layout layer for unknown member_id: ${layer.member_id}`);
|
|
4056
4063
|
return false;
|
|
4057
4064
|
}
|
|
4058
4065
|
return true;
|
|
@@ -4072,7 +4079,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
4072
4079
|
this.webRtcCallSession.executeMethod(this.selfId, "call.layout.list", {}).then((response) => {
|
|
4073
4080
|
const layouts = response.result?.layouts;
|
|
4074
4081
|
if (!layouts) {
|
|
4075
|
-
logger$
|
|
4082
|
+
logger$23.warn("[CallEventsManager] Layout list response carried no layouts; keeping current layouts");
|
|
4076
4083
|
return;
|
|
4077
4084
|
}
|
|
4078
4085
|
this._sessionState$.next({
|
|
@@ -4080,7 +4087,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
4080
4087
|
layouts
|
|
4081
4088
|
});
|
|
4082
4089
|
}).catch((error) => {
|
|
4083
|
-
logger$
|
|
4090
|
+
logger$23.error("[CallEventsManager] Error fetching layouts:", error);
|
|
4084
4091
|
});
|
|
4085
4092
|
}
|
|
4086
4093
|
updateParticipants(members) {
|
|
@@ -4096,7 +4103,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
4096
4103
|
}
|
|
4097
4104
|
const participant = this._participants$.value[member.member_id];
|
|
4098
4105
|
const oldValue = participant.value;
|
|
4099
|
-
logger$
|
|
4106
|
+
logger$23.debug("[CallEventsManager] Updating participant:", member.member_id, {
|
|
4100
4107
|
oldValue,
|
|
4101
4108
|
newValue: member
|
|
4102
4109
|
});
|
|
@@ -4109,17 +4116,17 @@ var CallEventsManager = class extends Destroyable {
|
|
|
4109
4116
|
}
|
|
4110
4117
|
get callJoinedEvent$() {
|
|
4111
4118
|
return this.cachedObservable("callJoinedEvent$", () => this.webRtcCallSession.callEvent$.pipe((0, rxjs.filter)(isCallJoinedPayload), (0, rxjs.tap)((event) => {
|
|
4112
|
-
logger$
|
|
4119
|
+
logger$23.debug("[CallEventsManager] Call joined event:", event);
|
|
4113
4120
|
})));
|
|
4114
4121
|
}
|
|
4115
4122
|
get layoutChangedEvent$() {
|
|
4116
4123
|
return this.cachedObservable("layoutChangedEvent$", () => this.webRtcCallSession.callEvent$.pipe(require_operators.filterAs(isLayoutChangedPayload, "layout"), (0, rxjs.tap)((event) => {
|
|
4117
|
-
logger$
|
|
4124
|
+
logger$23.debug("[CallEventsManager] Layout changed event:", event);
|
|
4118
4125
|
})));
|
|
4119
4126
|
}
|
|
4120
4127
|
get memberUpdates$() {
|
|
4121
4128
|
return this.cachedObservable("memberUpdates$", () => (0, rxjs.merge)(this.webRtcCallSession.memberJoined$, this.webRtcCallSession.memberUpdated$, this.webRtcCallSession.memberTalking$).pipe((0, rxjs.map)((event) => event.member), (0, rxjs.tap)((event) => {
|
|
4122
|
-
logger$
|
|
4129
|
+
logger$23.debug("[CallEventsManager] Member update event:", event);
|
|
4123
4130
|
})));
|
|
4124
4131
|
}
|
|
4125
4132
|
destroy() {
|
|
@@ -4138,6 +4145,111 @@ var CallEventsManager = class extends Destroyable {
|
|
|
4138
4145
|
}
|
|
4139
4146
|
};
|
|
4140
4147
|
|
|
4148
|
+
//#endregion
|
|
4149
|
+
//#region src/controllers/ConstraintFallbackHelper.ts
|
|
4150
|
+
/**
|
|
4151
|
+
* ConstraintFallbackHelper - Provides getUserMedia with automatic constraint
|
|
4152
|
+
* fallback on OverconstrainedError.
|
|
4153
|
+
*
|
|
4154
|
+
* When a specific device ID is requested, the helper tries progressively
|
|
4155
|
+
* looser constraints:
|
|
4156
|
+
* 1. `{ deviceId: { exact: deviceId } }` -- exact match
|
|
4157
|
+
* 2. `{ deviceId: deviceId }` -- preferred (browser may pick another)
|
|
4158
|
+
* 3. `{}` -- no constraint, browser default
|
|
4159
|
+
*
|
|
4160
|
+
* This prevents stale device IDs from blocking call setup.
|
|
4161
|
+
*
|
|
4162
|
+
* @see Section 5.8 and Section 11 of the Implementation Guide
|
|
4163
|
+
*/
|
|
4164
|
+
const logger$22 = require_operators.getLogger();
|
|
4165
|
+
/**
|
|
4166
|
+
* Attempts getUserMedia with progressively looser constraints.
|
|
4167
|
+
*
|
|
4168
|
+
* The function tries three levels of constraint specificity for the given
|
|
4169
|
+
* device kind. Each level is only attempted if the previous one fails with
|
|
4170
|
+
* an OverconstrainedError. Non-OverconstrainedError failures (e.g.,
|
|
4171
|
+
* NotAllowedError) are thrown immediately without fallback.
|
|
4172
|
+
*
|
|
4173
|
+
* @param mediaDevices - Anything exposing `getUserMedia` (a full
|
|
4174
|
+
* `WebRTCMediaDevices`, or a shim wrapping one)
|
|
4175
|
+
* @param constraints - The full MediaStreamConstraints to use as a base
|
|
4176
|
+
* @param kind - Which track kind to apply fallback to ('audio' | 'video')
|
|
4177
|
+
* @param deviceId - The device ID to try (if undefined, calls getUserMedia as-is)
|
|
4178
|
+
* @returns The stream and the fallback level that succeeded
|
|
4179
|
+
* @throws When all fallback levels fail, or when a non-OverconstrainedError occurs
|
|
4180
|
+
*/
|
|
4181
|
+
async function getUserMediaWithFallback(mediaDevices, constraints, kind, deviceId) {
|
|
4182
|
+
if (!deviceId) return {
|
|
4183
|
+
stream: await mediaDevices.getUserMedia(constraints),
|
|
4184
|
+
fallbackLevel: "default"
|
|
4185
|
+
};
|
|
4186
|
+
const baseConstraints = typeof constraints[kind] === "object" ? constraints[kind] : {};
|
|
4187
|
+
try {
|
|
4188
|
+
const exactConstraints = {
|
|
4189
|
+
...constraints,
|
|
4190
|
+
[kind]: {
|
|
4191
|
+
...baseConstraints,
|
|
4192
|
+
deviceId: { exact: deviceId }
|
|
4193
|
+
}
|
|
4194
|
+
};
|
|
4195
|
+
return {
|
|
4196
|
+
stream: await mediaDevices.getUserMedia(exactConstraints),
|
|
4197
|
+
fallbackLevel: "exact"
|
|
4198
|
+
};
|
|
4199
|
+
} catch (error) {
|
|
4200
|
+
if (!isOverconstrainedError(error)) throw error;
|
|
4201
|
+
logger$22.debug(`[ConstraintFallbackHelper] Exact constraint failed for ${kind}, trying preferred`, { deviceId });
|
|
4202
|
+
}
|
|
4203
|
+
try {
|
|
4204
|
+
const preferredConstraints = {
|
|
4205
|
+
...constraints,
|
|
4206
|
+
[kind]: {
|
|
4207
|
+
...baseConstraints,
|
|
4208
|
+
deviceId
|
|
4209
|
+
}
|
|
4210
|
+
};
|
|
4211
|
+
return {
|
|
4212
|
+
stream: await mediaDevices.getUserMedia(preferredConstraints),
|
|
4213
|
+
fallbackLevel: "preferred"
|
|
4214
|
+
};
|
|
4215
|
+
} catch (error) {
|
|
4216
|
+
if (!isOverconstrainedError(error)) throw error;
|
|
4217
|
+
logger$22.debug(`[ConstraintFallbackHelper] Preferred constraint failed for ${kind}, trying default`, { deviceId });
|
|
4218
|
+
}
|
|
4219
|
+
try {
|
|
4220
|
+
const defaultConstraints = {
|
|
4221
|
+
...constraints,
|
|
4222
|
+
[kind]: { ...baseConstraints }
|
|
4223
|
+
};
|
|
4224
|
+
if (typeof defaultConstraints[kind] === "object") {
|
|
4225
|
+
const { deviceId: _removed, ...rest } = defaultConstraints[kind];
|
|
4226
|
+
defaultConstraints[kind] = rest;
|
|
4227
|
+
}
|
|
4228
|
+
const stream = await mediaDevices.getUserMedia(defaultConstraints);
|
|
4229
|
+
logger$22.warn(`[ConstraintFallbackHelper] Fell back to browser default for ${kind}`, { requestedDeviceId: deviceId });
|
|
4230
|
+
return {
|
|
4231
|
+
stream,
|
|
4232
|
+
fallbackLevel: "default"
|
|
4233
|
+
};
|
|
4234
|
+
} catch (error) {
|
|
4235
|
+
logger$22.error(`[ConstraintFallbackHelper] All fallback levels exhausted for ${kind}`, {
|
|
4236
|
+
deviceId,
|
|
4237
|
+
error
|
|
4238
|
+
});
|
|
4239
|
+
throw error;
|
|
4240
|
+
}
|
|
4241
|
+
}
|
|
4242
|
+
/**
|
|
4243
|
+
* Checks whether an error is an OverconstrainedError.
|
|
4244
|
+
*
|
|
4245
|
+
* Browsers may throw either a native OverconstrainedError or a DOMException
|
|
4246
|
+
* with a specific name.
|
|
4247
|
+
*/
|
|
4248
|
+
function isOverconstrainedError(error) {
|
|
4249
|
+
if (error instanceof Error) return error.name === "OverconstrainedError" || error.name === "ConstraintNotSatisfiedError";
|
|
4250
|
+
return false;
|
|
4251
|
+
}
|
|
4252
|
+
|
|
4141
4253
|
//#endregion
|
|
4142
4254
|
//#region src/helpers/SDPHelper.ts
|
|
4143
4255
|
/**
|
|
@@ -4693,6 +4805,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
4693
4805
|
this._localAudioTracks$ = this.createBehaviorSubject([]);
|
|
4694
4806
|
this._localVideoTracks$ = this.createBehaviorSubject([]);
|
|
4695
4807
|
this._mediaTrackEnded$ = this.createSubject();
|
|
4808
|
+
this._trackOrigins = /* @__PURE__ */ new WeakMap();
|
|
4696
4809
|
}
|
|
4697
4810
|
get localStream$() {
|
|
4698
4811
|
return this._localStream$.asObservable().pipe((0, rxjs.takeUntil)(this.destroyed$));
|
|
@@ -4715,6 +4828,22 @@ var LocalStreamController = class extends Destroyable {
|
|
|
4715
4828
|
get localVideoTracks() {
|
|
4716
4829
|
return this._localVideoTracks$.value;
|
|
4717
4830
|
}
|
|
4831
|
+
tagTracks(tracks, origin) {
|
|
4832
|
+
for (const track of tracks) this._trackOrigins.set(track, origin);
|
|
4833
|
+
}
|
|
4834
|
+
setTrackOrigin(track, origin) {
|
|
4835
|
+
this._trackOrigins.set(track, origin);
|
|
4836
|
+
}
|
|
4837
|
+
getTrackOrigin(track) {
|
|
4838
|
+
return this._trackOrigins.get(track);
|
|
4839
|
+
}
|
|
4840
|
+
/**
|
|
4841
|
+
* Fail-safe: an unrecorded track reads as not-a-device-capture, so a missed
|
|
4842
|
+
* tagging site leaves media alone rather than destroying it.
|
|
4843
|
+
*/
|
|
4844
|
+
isDeviceCapture(track) {
|
|
4845
|
+
return this._trackOrigins.get(track) === "device";
|
|
4846
|
+
}
|
|
4718
4847
|
/**
|
|
4719
4848
|
* Build the local media stream based on the provided options.
|
|
4720
4849
|
*/
|
|
@@ -4724,6 +4853,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
4724
4853
|
if (this.options.inputAudioStream ?? this.options.inputVideoStream) {
|
|
4725
4854
|
const tracks = [...this.options.inputAudioStream?.getTracks() ?? [], ...this.options.inputVideoStream?.getTracks() ?? []];
|
|
4726
4855
|
stream = new MediaStream(tracks);
|
|
4856
|
+
this.tagTracks(tracks, "application");
|
|
4727
4857
|
} else if (this.options.propose === "screenshare") {
|
|
4728
4858
|
logger$19.debug("[LocalStreamController] Requesting display media for screen sharing with audio:", Boolean(this.options.inputAudioDeviceConstraints));
|
|
4729
4859
|
stream = await this.options.getDisplayMedia({
|
|
@@ -4731,6 +4861,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
4731
4861
|
audio: Boolean(this.options.inputAudioDeviceConstraints)
|
|
4732
4862
|
});
|
|
4733
4863
|
logger$19.debug("[LocalStreamController] Screen share media obtained:", stream);
|
|
4864
|
+
this.tagTracks(stream.getTracks(), "display");
|
|
4734
4865
|
} else {
|
|
4735
4866
|
const constraints = {
|
|
4736
4867
|
audio: this.options.inputAudioDeviceConstraints,
|
|
@@ -4739,6 +4870,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
4739
4870
|
logger$19.debug("[LocalStreamController] Requesting user media with constraints:", constraints);
|
|
4740
4871
|
stream = await this.options.getUserMedia(constraints);
|
|
4741
4872
|
logger$19.debug("[LocalStreamController] User media obtained:", stream);
|
|
4873
|
+
this.tagTracks(stream.getTracks(), "device");
|
|
4742
4874
|
}
|
|
4743
4875
|
this._localStream$.next(stream);
|
|
4744
4876
|
this._localAudioTracks$.next(stream.getAudioTracks());
|
|
@@ -4748,10 +4880,13 @@ var LocalStreamController = class extends Destroyable {
|
|
|
4748
4880
|
/**
|
|
4749
4881
|
* Add a local media track to the local stream.
|
|
4750
4882
|
* @param track - The MediaStreamTrack to add
|
|
4883
|
+
* @param origin - Defaults to `'device'`; every internal caller passes a
|
|
4884
|
+
* fresh `getUserMedia` capture.
|
|
4751
4885
|
* @returns The MediaStream (either existing or newly created)
|
|
4752
4886
|
*/
|
|
4753
|
-
addTrack(track) {
|
|
4887
|
+
addTrack(track, origin = "device") {
|
|
4754
4888
|
const localStream = this._localStream$.value ?? new MediaStream();
|
|
4889
|
+
this._trackOrigins.set(track, origin);
|
|
4755
4890
|
track.addEventListener("ended", this.mediaTrackEndedHandler);
|
|
4756
4891
|
localStream.addTrack(track);
|
|
4757
4892
|
this._localStream$.next(localStream);
|
|
@@ -5021,15 +5156,27 @@ var TransceiverController = class extends Destroyable {
|
|
|
5021
5156
|
for (let i = 0; i < Number(msStreamsNumber); i++) this.peerConnection.addTransceiver("video", { direction: "recvonly" });
|
|
5022
5157
|
}
|
|
5023
5158
|
}
|
|
5159
|
+
/**
|
|
5160
|
+
* @returns whether every live sender of the kind took the constraints. A
|
|
5161
|
+
* skipped non-device sender, an exhausted fallback, and having no live sender
|
|
5162
|
+
* at all all report `false` — `mediaParamsUpdated.applied` is built from this,
|
|
5163
|
+
* and an application told `true` cannot tell a working push from a no-op.
|
|
5164
|
+
*/
|
|
5024
5165
|
async updateSendersConstraints(kind, constraints) {
|
|
5025
5166
|
if (!constraints) {
|
|
5026
5167
|
this.stopTrackSender(kind);
|
|
5027
|
-
return
|
|
5168
|
+
return false;
|
|
5028
5169
|
}
|
|
5029
5170
|
const senders = this.peerConnection.getSenders().filter((sender) => sender.track?.kind === kind && sender.track.readyState === "live");
|
|
5171
|
+
let applied = senders.length > 0;
|
|
5030
5172
|
for (const sender of senders) {
|
|
5031
5173
|
const { track } = sender;
|
|
5032
5174
|
if (track) {
|
|
5175
|
+
if (!this.options.localStreamController.isDeviceCapture(track)) {
|
|
5176
|
+
logger$18.debug(`[TransceiverController] Skipping ${kind} constraints for a non-device track (origin: ${this.options.localStreamController.getTrackOrigin(track) ?? "unrecorded"}), track ${track.id}`);
|
|
5177
|
+
applied = false;
|
|
5178
|
+
continue;
|
|
5179
|
+
}
|
|
5033
5180
|
const constraintsToApply = {
|
|
5034
5181
|
...track.getConstraints(),
|
|
5035
5182
|
...constraints
|
|
@@ -5045,33 +5192,39 @@ var TransceiverController = class extends Destroyable {
|
|
|
5045
5192
|
} catch (fallbackError) {
|
|
5046
5193
|
logger$18.warn(`[TransceiverController] Track replacement fallback also failed for ${kind} track:`, fallbackError);
|
|
5047
5194
|
this.options.onError?.(new require_operators.MediaTrackError("updateSendersConstraints", kind, fallbackError));
|
|
5195
|
+
applied = false;
|
|
5048
5196
|
}
|
|
5049
5197
|
}
|
|
5050
5198
|
}
|
|
5051
5199
|
}
|
|
5200
|
+
return applied;
|
|
5052
5201
|
}
|
|
5053
5202
|
/**
|
|
5054
|
-
* Fallback when applyConstraints fails
|
|
5055
|
-
* one via getUserMedia with the merged constraints (preserving the current
|
|
5056
|
-
* deviceId), replace the sender track, and update the localStream.
|
|
5203
|
+
* Fallback when applyConstraints fails, which on iOS Safari it silently does.
|
|
5057
5204
|
*
|
|
5058
|
-
*
|
|
5059
|
-
*
|
|
5205
|
+
* Order matters: acquiring before stopping means a failed acquisition leaves
|
|
5206
|
+
* the existing media playing. The deviceId goes through the fallback ladder
|
|
5207
|
+
* rather than pinned `{ exact }`, so a stale id degrades instead of failing.
|
|
5060
5208
|
*/
|
|
5061
5209
|
async replaceTrackFallback(sender, oldTrack, kind, mergedConstraints) {
|
|
5062
5210
|
const { deviceId } = oldTrack.getSettings();
|
|
5063
|
-
const
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5211
|
+
const { stream, fallbackLevel } = await getUserMediaWithFallback({ getUserMedia: this.options.getUserMedia }, { [kind]: mergedConstraints }, kind, deviceId);
|
|
5212
|
+
const newTrack = stream.getTracks().find((t) => t.kind === kind);
|
|
5213
|
+
if (!newTrack) {
|
|
5214
|
+
stream.getTracks().forEach((t) => t.stop());
|
|
5215
|
+
throw new require_operators.MediaTrackError("replaceTrackFallback", kind, /* @__PURE__ */ new Error("getUserMedia returned no track of the requested kind"));
|
|
5216
|
+
}
|
|
5217
|
+
try {
|
|
5218
|
+
await sender.replaceTrack(newTrack);
|
|
5219
|
+
} catch (error) {
|
|
5220
|
+
stream.getTracks().forEach((t) => t.stop());
|
|
5221
|
+
throw error;
|
|
5222
|
+
}
|
|
5223
|
+
const oldTrackId = oldTrack.id;
|
|
5224
|
+
this.options.localStreamController.removeTrack(oldTrackId);
|
|
5068
5225
|
oldTrack.stop();
|
|
5069
|
-
this.options.localStreamController.removeTrack(trackId);
|
|
5070
|
-
const newTrack = (await this.options.getUserMedia({ [kind]: constraintsWithDevice })).getTracks().find((t) => t.kind === kind);
|
|
5071
|
-
if (!newTrack) throw new require_operators.MediaTrackError("replaceTrackFallback", kind, /* @__PURE__ */ new Error("getUserMedia returned no track of the requested kind"));
|
|
5072
|
-
await sender.replaceTrack(newTrack);
|
|
5073
5226
|
this.options.localStreamController.addTrack(newTrack);
|
|
5074
|
-
logger$18.debug(`[TransceiverController] Track replacement fallback succeeded for ${kind}. New track: ${newTrack.id}`);
|
|
5227
|
+
logger$18.debug(`[TransceiverController] Track replacement fallback succeeded for ${kind} (deviceId fallback level: ${fallbackLevel}). New track: ${newTrack.id}`);
|
|
5075
5228
|
}
|
|
5076
5229
|
getMediaDirections() {
|
|
5077
5230
|
if (this.peerConnection.connectionState === "connected") return this.peerConnection.getTransceivers().reduce((acc, transceiver) => {
|
|
@@ -5182,6 +5335,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5182
5335
|
this._iceGatheringState$ = this.createReplaySubject(1);
|
|
5183
5336
|
this._errors$ = this.createReplaySubject(1);
|
|
5184
5337
|
this._iceCandidates$ = this.createReplaySubject(1);
|
|
5338
|
+
this._localMediaSettled$ = this.createReplaySubject(1);
|
|
5185
5339
|
this._initialized$ = this.createReplaySubject(1);
|
|
5186
5340
|
this._remoteDescription$ = this.createReplaySubject(1);
|
|
5187
5341
|
this._remoteStream$ = this.createBehaviorSubject(null);
|
|
@@ -5285,6 +5439,10 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5285
5439
|
get remoteDescription$() {
|
|
5286
5440
|
return this.cachedObservable("remoteDescription$", () => this._remoteDescription$.asObservable().pipe((0, rxjs.takeUntil)(this.destroyed$)));
|
|
5287
5441
|
}
|
|
5442
|
+
/** Emits once local media is settled — acquired, or knowingly receive-only. */
|
|
5443
|
+
get localMediaSettled$() {
|
|
5444
|
+
return this.cachedObservable("localMediaSettled$", () => this._localMediaSettled$.asObservable().pipe((0, rxjs.takeUntil)(this.destroyed$)));
|
|
5445
|
+
}
|
|
5288
5446
|
get localStream$() {
|
|
5289
5447
|
return this.cachedObservable("localStream$", () => this.localStreamController.localStream$.pipe((0, rxjs.takeUntil)(this.destroyed$)));
|
|
5290
5448
|
}
|
|
@@ -5440,7 +5598,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5440
5598
|
this._isNegotiating$.next(true);
|
|
5441
5599
|
await this._setRemoteDescription(this.sdpInit);
|
|
5442
5600
|
} else {
|
|
5443
|
-
await this.setupTrackHandling();
|
|
5601
|
+
if (!await this.setupTrackHandling()) return;
|
|
5444
5602
|
this._initialized$.next(true);
|
|
5445
5603
|
}
|
|
5446
5604
|
} catch (error) {
|
|
@@ -5564,7 +5722,10 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5564
5722
|
inputVideoDeviceConstraints: this.inputVideoDeviceConstraints
|
|
5565
5723
|
});
|
|
5566
5724
|
}
|
|
5567
|
-
await this.setupLocalTracks()
|
|
5725
|
+
if (!await this.setupLocalTracks()) {
|
|
5726
|
+
logger$17.debug("[RTCPeerConnectionController] Inbound answer abandoned; the connection went away.");
|
|
5727
|
+
return;
|
|
5728
|
+
}
|
|
5568
5729
|
const { answerOptions } = this;
|
|
5569
5730
|
logger$17.debug("[RTCPeerConnectionController] Creating inbound answer with options:", answerOptions);
|
|
5570
5731
|
await this.createAnswer(answerOptions);
|
|
@@ -5702,35 +5863,52 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5702
5863
|
}
|
|
5703
5864
|
/**
|
|
5704
5865
|
* Setup track handling for remote tracks.
|
|
5866
|
+
*
|
|
5867
|
+
* @returns `false` when the connection went away while local media was being
|
|
5868
|
+
* acquired — see {@link setupLocalTracks}.
|
|
5705
5869
|
*/
|
|
5706
5870
|
async setupTrackHandling() {
|
|
5707
5871
|
if (!this.peerConnection) throw new require_operators.DependencyError("RTCPeerConnection is not initialized");
|
|
5708
|
-
await this.setupLocalTracks();
|
|
5872
|
+
if (!await this.setupLocalTracks()) return false;
|
|
5709
5873
|
await this.setupRemoteTracks();
|
|
5874
|
+
return true;
|
|
5710
5875
|
}
|
|
5876
|
+
/**
|
|
5877
|
+
* @returns `false` when the connection was torn down while getUserMedia was
|
|
5878
|
+
* in flight. The acquisition is not cancellable, so the caller must stop
|
|
5879
|
+
* rather than go on to touch a peer connection that is closed or gone.
|
|
5880
|
+
*/
|
|
5711
5881
|
async setupLocalTracks() {
|
|
5712
5882
|
logger$17.debug("[RTCPeerConnectionController] Setting up local tracks/transceivers.");
|
|
5713
5883
|
if (this.hasNoLocalMediaToSend()) {
|
|
5714
5884
|
if (!this.receiveAudio && !this.receiveVideo) throw new require_operators.InvalidParams("Call requests no media: enable audio/video or receiveAudio/receiveVideo");
|
|
5715
5885
|
logger$17.debug("[RTCPeerConnectionController] No local media requested; negotiating receive-only.");
|
|
5716
5886
|
this.setupReceiveOnlyTransceivers();
|
|
5717
|
-
|
|
5887
|
+
this._localMediaSettled$.next();
|
|
5888
|
+
return true;
|
|
5718
5889
|
}
|
|
5719
5890
|
let localStream;
|
|
5720
5891
|
try {
|
|
5721
5892
|
localStream = this.localStream ?? await this.localStreamController.buildLocalStream();
|
|
5722
5893
|
} catch (error) {
|
|
5723
5894
|
this.handleLocalMediaFailure(error);
|
|
5724
|
-
|
|
5895
|
+
this._localMediaSettled$.next();
|
|
5896
|
+
return true;
|
|
5725
5897
|
}
|
|
5898
|
+
if (!this.peerConnection || this.peerConnection.signalingState === "closed") {
|
|
5899
|
+
logger$17.debug("[RTCPeerConnectionController] Local media arrived after teardown; releasing it.");
|
|
5900
|
+
localStream.getTracks().forEach((track) => track.stop());
|
|
5901
|
+
return false;
|
|
5902
|
+
}
|
|
5903
|
+
this._localMediaSettled$.next();
|
|
5726
5904
|
if (this.transceiverController?.useAddStream ?? false) {
|
|
5727
5905
|
logger$17.warn("[RTCPeerConnectionController] Using deprecated addStream API to add local stream.");
|
|
5728
|
-
this.peerConnection
|
|
5906
|
+
this.peerConnection.addStream(localStream);
|
|
5729
5907
|
if (!this.isNegotiating) {
|
|
5730
5908
|
logger$17.debug("[RTCPeerConnectionController] Forcing negotiationneeded after local tracks setup.");
|
|
5731
5909
|
this.negotiationNeeded$.next();
|
|
5732
5910
|
}
|
|
5733
|
-
return;
|
|
5911
|
+
return true;
|
|
5734
5912
|
}
|
|
5735
5913
|
for (const kind of ["audio", "video"]) {
|
|
5736
5914
|
const tracks = (kind === "audio" ? localStream.getAudioTracks() : localStream.getVideoTracks()).map((track, index) => ({
|
|
@@ -5744,10 +5922,11 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5744
5922
|
await this.transceiverController?.setupTransceiverSender(track, localStream, transceivers[index]);
|
|
5745
5923
|
} else {
|
|
5746
5924
|
logger$17.debug(`[RTCPeerConnectionController] Using addTrack for local ${kind} track:`, track.id);
|
|
5747
|
-
this.peerConnection
|
|
5925
|
+
this.peerConnection.addTrack(track, localStream);
|
|
5748
5926
|
}
|
|
5749
5927
|
}
|
|
5750
5928
|
}
|
|
5929
|
+
return true;
|
|
5751
5930
|
}
|
|
5752
5931
|
/** True for a main connection with no local media to send. */
|
|
5753
5932
|
hasNoLocalMediaToSend() {
|
|
@@ -5868,6 +6047,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5868
6047
|
const sender = (this.transceiverController?.audioTransceivers.at(0))?.sender ?? this.peerConnection.getSenders().find((s) => s.track?.kind === "audio");
|
|
5869
6048
|
if (!sender || !raw) return;
|
|
5870
6049
|
try {
|
|
6050
|
+
this.localStreamController.setTrackOrigin(this._localAudioPipeline.outputTrack, "processed");
|
|
5871
6051
|
await sender.replaceTrack(this._localAudioPipeline.outputTrack);
|
|
5872
6052
|
} catch (error) {
|
|
5873
6053
|
logger$17.warn("[RTCPeerConnectionController] Failed to route audio sender through pipeline:", error);
|
|
@@ -5928,37 +6108,62 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5928
6108
|
for (const existingTrack of existingTracks) this.removeLocalTrack(existingTrack.id);
|
|
5929
6109
|
this.addLocalTrack(track);
|
|
5930
6110
|
}
|
|
6111
|
+
/**
|
|
6112
|
+
* @returns whether the constraints reached the media the leg is sending.
|
|
6113
|
+
*
|
|
6114
|
+
* With the pipeline engaged the audio sender carries the processed
|
|
6115
|
+
* destination track, so the sender scan would find nothing it may touch and
|
|
6116
|
+
* every audio constraint API would silently no-op. The constraints belong to
|
|
6117
|
+
* the pipeline's device source, which is the capture that sender ultimately
|
|
6118
|
+
* carries.
|
|
6119
|
+
*/
|
|
5931
6120
|
async updateSendersConstraints(kind, constraints) {
|
|
5932
|
-
|
|
6121
|
+
if (kind === "audio" && constraints && this._localAudioPipeline) return this.applyPipelineSourceConstraints(constraints);
|
|
6122
|
+
return await this.transceiverController?.updateSendersConstraints(kind, constraints) ?? false;
|
|
5933
6123
|
}
|
|
5934
6124
|
/**
|
|
5935
|
-
*
|
|
5936
|
-
*
|
|
5937
|
-
*
|
|
5938
|
-
*
|
|
6125
|
+
* Mirror of the sender path for a piped audio leg: same merge, same fallback
|
|
6126
|
+
* ladder, same device-capture invariant — but the swap target is the pipeline
|
|
6127
|
+
* input, so the sender keeps emitting the pipeline's output track and its
|
|
6128
|
+
* identity survives the change.
|
|
5939
6129
|
*/
|
|
5940
|
-
async
|
|
5941
|
-
const
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
6130
|
+
async applyPipelineSourceConstraints(constraints) {
|
|
6131
|
+
const pipeline = this._localAudioPipeline;
|
|
6132
|
+
const source = this.localStreamController.localAudioTracks.at(0);
|
|
6133
|
+
if (!pipeline || !source) {
|
|
6134
|
+
logger$17.debug("[RTCPeerConnectionController] No pipeline input to constrain.");
|
|
6135
|
+
return false;
|
|
5945
6136
|
}
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
6137
|
+
if (!this.localStreamController.isDeviceCapture(source)) {
|
|
6138
|
+
logger$17.debug(`[RTCPeerConnectionController] Skipping audio constraints for a non-device pipeline input (origin: ${this.localStreamController.getTrackOrigin(source) ?? "unrecorded"}).`);
|
|
6139
|
+
return false;
|
|
6140
|
+
}
|
|
6141
|
+
const merged = {
|
|
6142
|
+
...source.getConstraints(),
|
|
6143
|
+
...constraints
|
|
6144
|
+
};
|
|
6145
|
+
try {
|
|
6146
|
+
await source.applyConstraints(merged);
|
|
6147
|
+
logger$17.debug("[RTCPeerConnectionController] Pipeline input constraints updated:", merged);
|
|
6148
|
+
return true;
|
|
6149
|
+
} catch (error) {
|
|
6150
|
+
logger$17.warn("[RTCPeerConnectionController] applyConstraints failed on the pipeline input, re-acquiring:", error);
|
|
6151
|
+
}
|
|
6152
|
+
try {
|
|
6153
|
+
const { stream } = await getUserMediaWithFallback({ getUserMedia: async (c) => this.getUserMedia(c) }, { audio: merged }, "audio", source.getSettings().deviceId);
|
|
6154
|
+
const newTrack = stream.getAudioTracks().at(0);
|
|
6155
|
+
if (!newTrack) {
|
|
6156
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
6157
|
+
throw new Error("getUserMedia returned no audio track");
|
|
6158
|
+
}
|
|
6159
|
+
this.localStreamController.removeTrack(source.id);
|
|
5960
6160
|
this.localStreamController.addTrack(newTrack);
|
|
5961
|
-
|
|
6161
|
+
pipeline.setInputTrack(newTrack);
|
|
6162
|
+
return true;
|
|
6163
|
+
} catch (error) {
|
|
6164
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to re-acquire the pipeline input for constraints:", error);
|
|
6165
|
+
this._errors$.next(new require_operators.MediaTrackError("updateSendersConstraints", "audio", error));
|
|
6166
|
+
return false;
|
|
5962
6167
|
}
|
|
5963
6168
|
}
|
|
5964
6169
|
/**
|
|
@@ -6141,7 +6346,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6141
6346
|
this._signalingStatus$ = this.createReplaySubject(1);
|
|
6142
6347
|
this._screenShareStatus$ = this.createBehaviorSubject("none");
|
|
6143
6348
|
this._rtcPeerConnectionsMap = /* @__PURE__ */ new Map();
|
|
6144
|
-
this.
|
|
6349
|
+
this._legErrors$ = this.createSubject();
|
|
6145
6350
|
this._nodeId$ = this.createBehaviorSubject(options.nodeId ?? null);
|
|
6146
6351
|
this.onError = options.onError;
|
|
6147
6352
|
this.onModifyFailed = options.onModifyFailed;
|
|
@@ -6189,6 +6394,10 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6189
6394
|
get selfId$() {
|
|
6190
6395
|
return this._selfId$.asObservable();
|
|
6191
6396
|
}
|
|
6397
|
+
/** Separates the media phase of call creation from the signalling phase. */
|
|
6398
|
+
get localMediaSettled$() {
|
|
6399
|
+
return this.mainPeerConnection.localMediaSettled$;
|
|
6400
|
+
}
|
|
6192
6401
|
get localStream() {
|
|
6193
6402
|
return this._rtcPeerConnectionsMap.get(this.webRtcCallSession.id)?.localStream ?? null;
|
|
6194
6403
|
}
|
|
@@ -6245,20 +6454,11 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6245
6454
|
const { mediaParams, callID } = event;
|
|
6246
6455
|
const rtcPeerConnController = this._rtcPeerConnectionsMap.get(callID);
|
|
6247
6456
|
const { audio, video } = mediaParams;
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
6253
|
-
audio,
|
|
6254
|
-
video,
|
|
6255
|
-
timestamp: Date.now()
|
|
6256
|
-
});
|
|
6257
|
-
} catch (error) {
|
|
6258
|
-
logger$16.warn("[WebRTCManager] Error applying server-pushed media params:", error);
|
|
6259
|
-
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
6260
|
-
}
|
|
6261
|
-
})();
|
|
6457
|
+
if (!rtcPeerConnController) {
|
|
6458
|
+
logger$16.warn(`[WebRTCManager] Ignoring server-pushed media params for unknown leg ${callID}`);
|
|
6459
|
+
return;
|
|
6460
|
+
}
|
|
6461
|
+
this.applyServerMediaParams(rtcPeerConnController, audio, video);
|
|
6262
6462
|
});
|
|
6263
6463
|
this.subscribeTo(this.vertoPing$, (vertoPing) => {
|
|
6264
6464
|
this.attachManager.attach(this.buildAttachableCall());
|
|
@@ -6266,6 +6466,75 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6266
6466
|
});
|
|
6267
6467
|
}
|
|
6268
6468
|
/**
|
|
6469
|
+
* An auxiliary-leg failure must never destroy the call; main-leg and
|
|
6470
|
+
* call-level errors keep `CallFactory.isFatalError`'s classification.
|
|
6471
|
+
*
|
|
6472
|
+
* Every site holding a peer connection reports through here, so the invariant
|
|
6473
|
+
* is structural rather than per-call-site — which is how cloud-product#20523
|
|
6474
|
+
* happened, with only one of fourteen sites passing `{ fatal: false }`.
|
|
6475
|
+
*
|
|
6476
|
+
* `override` composes rather than replaces: a caller may force non-fatal for a
|
|
6477
|
+
* reason of its own (a `verto.info` frame is best-effort whichever leg carries
|
|
6478
|
+
* it), and an auxiliary leg stays non-fatal regardless.
|
|
6479
|
+
*/
|
|
6480
|
+
reportLegError(error, rtcPeerConnController, override) {
|
|
6481
|
+
const leg = rtcPeerConnController?.propose;
|
|
6482
|
+
const legId = rtcPeerConnController?.id;
|
|
6483
|
+
const auxiliary = Boolean(rtcPeerConnController) && !rtcPeerConnController?.isMainDevice;
|
|
6484
|
+
this.onError?.(error, {
|
|
6485
|
+
...override?.fatal === false || auxiliary ? { fatal: false } : {},
|
|
6486
|
+
...leg ? { leg } : {},
|
|
6487
|
+
...legId ? { legId } : {}
|
|
6488
|
+
});
|
|
6489
|
+
if (legId) this._legErrors$.next({
|
|
6490
|
+
legId,
|
|
6491
|
+
error
|
|
6492
|
+
});
|
|
6493
|
+
}
|
|
6494
|
+
/**
|
|
6495
|
+
* Errors reported for one leg, as a stream that fails with them.
|
|
6496
|
+
*
|
|
6497
|
+
* Signaling failures are reported, never thrown — so nothing that waits on a
|
|
6498
|
+
* leg's progress would otherwise learn of a rejected invite. Merging this in
|
|
6499
|
+
* lets the wait end with the reason the server gave.
|
|
6500
|
+
*/
|
|
6501
|
+
legError$(legId) {
|
|
6502
|
+
return this._legErrors$.pipe((0, rxjs.filter)((report) => report.legId === legId), (0, rxjs.map)((report) => {
|
|
6503
|
+
throw report.error;
|
|
6504
|
+
}));
|
|
6505
|
+
}
|
|
6506
|
+
/**
|
|
6507
|
+
* Audio and video are applied independently so a failure in one cannot
|
|
6508
|
+
* suppress the other, and `mediaParamsUpdated` is emitted whatever happens —
|
|
6509
|
+
* an application should not be starved of the params by a constraint failure.
|
|
6510
|
+
*/
|
|
6511
|
+
async applyServerMediaParams(rtcPeerConnController, audio, video) {
|
|
6512
|
+
const failures = [];
|
|
6513
|
+
let applied = true;
|
|
6514
|
+
if (audio) try {
|
|
6515
|
+
applied = await rtcPeerConnController.updateSendersConstraints("audio", audio) && applied;
|
|
6516
|
+
} catch (error) {
|
|
6517
|
+
applied = false;
|
|
6518
|
+
failures.push(toError(error));
|
|
6519
|
+
}
|
|
6520
|
+
if (video) try {
|
|
6521
|
+
applied = await rtcPeerConnController.updateSendersConstraints("video", video) && applied;
|
|
6522
|
+
} catch (error) {
|
|
6523
|
+
applied = false;
|
|
6524
|
+
failures.push(toError(error));
|
|
6525
|
+
}
|
|
6526
|
+
this.webRtcCallSession.emitMediaParamsUpdated({
|
|
6527
|
+
audio,
|
|
6528
|
+
video,
|
|
6529
|
+
timestamp: Date.now(),
|
|
6530
|
+
applied
|
|
6531
|
+
});
|
|
6532
|
+
for (const failure of failures) {
|
|
6533
|
+
logger$16.warn("[WebRTCManager] Error applying server-pushed media params:", failure);
|
|
6534
|
+
this.reportLegError(failure, rtcPeerConnController, { fatal: false });
|
|
6535
|
+
}
|
|
6536
|
+
}
|
|
6537
|
+
/**
|
|
6269
6538
|
* Set node_id/selfId only when the current value is null.
|
|
6270
6539
|
*
|
|
6271
6540
|
* During reattach, `call.joined` and `verto.answer` events can deliver
|
|
@@ -6303,7 +6572,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6303
6572
|
if (video) await this.mainPeerConnection.updateSendersConstraints("video", video);
|
|
6304
6573
|
} catch (error) {
|
|
6305
6574
|
logger$16.warn("[WebRTCManager] Error updating media constraints:", error);
|
|
6306
|
-
this.
|
|
6575
|
+
this.reportLegError(toError(error), this.mainPeerConnection);
|
|
6307
6576
|
throw error;
|
|
6308
6577
|
}
|
|
6309
6578
|
}
|
|
@@ -6471,23 +6740,24 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6471
6740
|
return response;
|
|
6472
6741
|
}
|
|
6473
6742
|
async executeVerto(message, optionals = {}) {
|
|
6474
|
-
const
|
|
6743
|
+
const params = {
|
|
6475
6744
|
callID: optionals.callID ?? this.webRtcCallSession.id,
|
|
6476
6745
|
node_id: optionals.node_id ?? this._nodeId$.value ?? "",
|
|
6477
6746
|
message,
|
|
6478
6747
|
subscribe: optionals.subscribe
|
|
6479
|
-
}
|
|
6748
|
+
};
|
|
6749
|
+
const webrtcVertoMessage = WebrtcVerto(params);
|
|
6480
6750
|
const response = await this.webRtcCallSession.execute(webrtcVertoMessage);
|
|
6481
6751
|
const nonFatal = message.method === "verto.info" ? { fatal: false } : void 0;
|
|
6482
6752
|
if (response.error) {
|
|
6483
6753
|
const error = new require_operators.JSONRPCError(response.error.code, response.error.message, response.error.data);
|
|
6484
|
-
this.
|
|
6754
|
+
this.reportLegError(error, this._rtcPeerConnectionsMap.get(params.callID), nonFatal);
|
|
6485
6755
|
return response;
|
|
6486
6756
|
}
|
|
6487
6757
|
const innerResult = require_operators.getValueFrom(response, "result.result");
|
|
6488
6758
|
if (innerResult?.error) {
|
|
6489
6759
|
const error = new require_operators.JSONRPCError(innerResult.error.code, innerResult.error.message, innerResult.error.data);
|
|
6490
|
-
this.
|
|
6760
|
+
this.reportLegError(error, this._rtcPeerConnectionsMap.get(params.callID), nonFatal);
|
|
6491
6761
|
return response;
|
|
6492
6762
|
}
|
|
6493
6763
|
return response;
|
|
@@ -6508,7 +6778,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6508
6778
|
}
|
|
6509
6779
|
} catch (error) {
|
|
6510
6780
|
logger$16.error(`[WebRTCManager] Error sending Verto ${vertoMethod}:`, error);
|
|
6511
|
-
this.
|
|
6781
|
+
this.reportLegError(toError(error), rtcPeerConnController);
|
|
6512
6782
|
if (vertoMethod === "verto.modify") this.onModifyFailed?.();
|
|
6513
6783
|
}
|
|
6514
6784
|
}
|
|
@@ -6524,7 +6794,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6524
6794
|
} catch (error) {
|
|
6525
6795
|
logger$16.warn("[WebRTCManager] Error processing modify response:", error);
|
|
6526
6796
|
const modifyError = error instanceof Error ? error : new Error(String(error), { cause: error });
|
|
6527
|
-
this.
|
|
6797
|
+
this.reportLegError(modifyError, rtcPeerConnController);
|
|
6528
6798
|
}
|
|
6529
6799
|
}
|
|
6530
6800
|
}
|
|
@@ -6537,7 +6807,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6537
6807
|
status,
|
|
6538
6808
|
signalingError
|
|
6539
6809
|
});
|
|
6540
|
-
this.
|
|
6810
|
+
this.reportLegError(signalingError, null, { fatal: false });
|
|
6541
6811
|
return;
|
|
6542
6812
|
}
|
|
6543
6813
|
if (rtcPeerConnController.isMainDevice) this._signalingStatus$.next(status);
|
|
@@ -6567,7 +6837,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6567
6837
|
} else {
|
|
6568
6838
|
logger$16.error("[WebRTCManager] Verto invite failed:", response);
|
|
6569
6839
|
const inviteError = response.error ? new require_operators.JSONRPCError(response.error.code, response.error.message, response.error.data) : /* @__PURE__ */ new Error("Verto invite failed: unexpected response");
|
|
6570
|
-
this.
|
|
6840
|
+
this.reportLegError(inviteError, rtcPeerConnController);
|
|
6571
6841
|
}
|
|
6572
6842
|
}
|
|
6573
6843
|
get RTCPeerConnectionConfig() {
|
|
@@ -6606,7 +6876,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6606
6876
|
this._rtcPeerConnectionsMap.set(rtcPeerConnController.id, rtcPeerConnController);
|
|
6607
6877
|
this._rtcPeerConnections$.next(Array.from(this._rtcPeerConnectionsMap.values()));
|
|
6608
6878
|
this.subscribeTo(rtcPeerConnController.errors$, (error) => {
|
|
6609
|
-
this.
|
|
6879
|
+
this.reportLegError(error, rtcPeerConnController);
|
|
6610
6880
|
});
|
|
6611
6881
|
if (options.initOffer) this.handleInboundAnswer(rtcPeerConnController);
|
|
6612
6882
|
}
|
|
@@ -6635,7 +6905,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6635
6905
|
await rtcPeerConnController.acceptInbound(answerOptions);
|
|
6636
6906
|
} catch (error) {
|
|
6637
6907
|
logger$16.error("[WebRTCManager] Error creating inbound answer:", error);
|
|
6638
|
-
this.
|
|
6908
|
+
this.reportLegError(toError(error), rtcPeerConnController);
|
|
6639
6909
|
}
|
|
6640
6910
|
}
|
|
6641
6911
|
}
|
|
@@ -6739,7 +7009,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6739
7009
|
await this.attachManager.attach(this.buildAttachableCall());
|
|
6740
7010
|
} catch (error) {
|
|
6741
7011
|
logger$16.error("[WebRTCManager] Error sending Verto answer:", error);
|
|
6742
|
-
this.
|
|
7012
|
+
this.reportLegError(toError(error), rtcPeerConnectionController);
|
|
6743
7013
|
await rtcPeerConnectionController.updateAnswerStatus({ status: "failed" });
|
|
6744
7014
|
}
|
|
6745
7015
|
}
|
|
@@ -6811,7 +7081,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6811
7081
|
await this.mainPeerConnection.restoreTrackSender(deviceKind);
|
|
6812
7082
|
} else {
|
|
6813
7083
|
const error = new require_operators.InvalidParams("No valid device to be added");
|
|
6814
|
-
this.
|
|
7084
|
+
this.reportLegError(error, this.mainPeerConnection);
|
|
6815
7085
|
throw error;
|
|
6816
7086
|
}
|
|
6817
7087
|
}
|
|
@@ -6836,21 +7106,30 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6836
7106
|
this._rtcPeerConnections$.next(Array.from(this._rtcPeerConnectionsMap.values()));
|
|
6837
7107
|
this.subscribeTo(rtcPeerConnController.errors$, (error) => {
|
|
6838
7108
|
firstPeerConnectionError ??= error;
|
|
6839
|
-
this.
|
|
7109
|
+
this.reportLegError(error, rtcPeerConnController);
|
|
6840
7110
|
});
|
|
6841
|
-
|
|
7111
|
+
const pc = rtcPeerConnController;
|
|
7112
|
+
await (0, rxjs.firstValueFrom)((0, rxjs.merge)(pc.localMediaSettled$.pipe((0, rxjs.take)(1), (0, rxjs.switchMap)(() => pc.connectionState$.pipe((0, rxjs.filter)((state) => state === "connected"), (0, rxjs.take)(1), (0, rxjs.timeout)(require_operators.DEFAULT_AUX_LEG_CONNECT_TIMEOUT_MS)))), this.legError$(pc.id)).pipe((0, rxjs.takeUntil)(this.destroyed$)));
|
|
6842
7113
|
if (isScreenShare) this._screenShareStatus$.next("started");
|
|
6843
7114
|
logger$16.info(`[WebRTCManager] Additional peer connection connected (${propose}).`);
|
|
6844
7115
|
return rtcPeerConnController.id;
|
|
6845
7116
|
} catch (error) {
|
|
6846
7117
|
logger$16.warn("[WebRTCManager] Error initializing additional peer connection:", error);
|
|
6847
|
-
if (rtcPeerConnController)
|
|
6848
|
-
|
|
7118
|
+
if (rtcPeerConnController) {
|
|
7119
|
+
rtcPeerConnController.destroy();
|
|
7120
|
+
this._rtcPeerConnectionsMap.delete(rtcPeerConnController.id);
|
|
7121
|
+
this._rtcPeerConnections$.next(Array.from(this._rtcPeerConnectionsMap.values()));
|
|
7122
|
+
}
|
|
7123
|
+
if (isScreenShare) {
|
|
7124
|
+
this._screenShareStatus$.next("none");
|
|
7125
|
+
this._screenShareId = void 0;
|
|
7126
|
+
}
|
|
6849
7127
|
if (firstPeerConnectionError) throw firstPeerConnectionError instanceof require_operators.MediaAccessError && firstPeerConnectionError.originalError instanceof Error ? firstPeerConnectionError.originalError : firstPeerConnectionError;
|
|
6850
7128
|
if (error instanceof rxjs.EmptyError) {
|
|
6851
7129
|
logger$16.debug("[WebRTCManager] Additional peer connection aborted before connecting.");
|
|
6852
7130
|
return;
|
|
6853
7131
|
}
|
|
7132
|
+
if (error instanceof rxjs.TimeoutError) throw new require_operators.AuxiliaryLegTimeoutError(propose, error);
|
|
6854
7133
|
throw error instanceof Error ? error : new Error(String(error), { cause: error });
|
|
6855
7134
|
}
|
|
6856
7135
|
}
|
|
@@ -6896,7 +7175,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6896
7175
|
await this.executeVerto(VertoBye({
|
|
6897
7176
|
...causeParams,
|
|
6898
7177
|
dialogParams: this.dialogParams(rtcPeerConnController)
|
|
6899
|
-
}));
|
|
7178
|
+
}), { callID: rtcPeerConnController.id });
|
|
6900
7179
|
} catch (error) {
|
|
6901
7180
|
logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto bye:", error);
|
|
6902
7181
|
throw error;
|
|
@@ -8290,13 +8569,13 @@ var WebRTCCall = class extends Destroyable {
|
|
|
8290
8569
|
get bandwidthConstrained$() {
|
|
8291
8570
|
return this.deferEmission(this._bandwidthConstrained$.asObservable());
|
|
8292
8571
|
}
|
|
8293
|
-
/** Observable that emits when server
|
|
8572
|
+
/** Observable that emits when the server pushes media params. */
|
|
8294
8573
|
get mediaParamsUpdated$() {
|
|
8295
8574
|
return this.deferEmission(this._mediaParamsUpdated$.asObservable());
|
|
8296
8575
|
}
|
|
8297
8576
|
/**
|
|
8298
8577
|
* @internal Emit a media params update event.
|
|
8299
|
-
* Called by the VertoManager when server
|
|
8578
|
+
* Called by the VertoManager when the server pushes media params.
|
|
8300
8579
|
*/
|
|
8301
8580
|
emitMediaParamsUpdated(event) {
|
|
8302
8581
|
this._mediaParamsUpdated$.next(event);
|
|
@@ -8504,6 +8783,10 @@ var WebRTCCall = class extends Destroyable {
|
|
|
8504
8783
|
get selfId$() {
|
|
8505
8784
|
return this.vertoManager.selfId$;
|
|
8506
8785
|
}
|
|
8786
|
+
/** @internal Lets call creation bound the media and signalling phases apart. */
|
|
8787
|
+
get localMediaSettled$() {
|
|
8788
|
+
return this.vertoManager.localMediaSettled$;
|
|
8789
|
+
}
|
|
8507
8790
|
/** Local participant's member ID, or `null` if not joined. */
|
|
8508
8791
|
get selfId() {
|
|
8509
8792
|
return this.vertoManager.selfId;
|
|
@@ -8899,6 +9182,9 @@ var WebRTCCall = class extends Destroyable {
|
|
|
8899
9182
|
/**
|
|
8900
9183
|
* Infers the semantic error category from a raw Error thrown by VertoManager
|
|
8901
9184
|
* or an RTCPeerConnection layer.
|
|
9185
|
+
*
|
|
9186
|
+
* Pure function — exported for unit testing.
|
|
9187
|
+
* @internal
|
|
8902
9188
|
*/
|
|
8903
9189
|
function inferCallErrorKind(error) {
|
|
8904
9190
|
if (error instanceof require_operators.RPCTimeoutError) return "timeout";
|
|
@@ -8917,7 +9203,14 @@ const RECOVERABLE_RPC_CODES = new Set([
|
|
|
8917
9203
|
require_operators.RPC_ERROR_AUTHENTICATION_FAILED,
|
|
8918
9204
|
require_operators.RPC_ERROR_INVALID_PARAMS
|
|
8919
9205
|
]);
|
|
8920
|
-
/**
|
|
9206
|
+
/**
|
|
9207
|
+
* A *fallback*: callers knowing which leg failed pass an explicit `fatal` and
|
|
9208
|
+
* never reach here, so the default-fatal branch only sees call- and main-leg
|
|
9209
|
+
* errors. Auxiliary legs go through `WebRTCVertoManager.reportLegError`.
|
|
9210
|
+
*
|
|
9211
|
+
* Pure function — exported for unit testing.
|
|
9212
|
+
* @internal
|
|
9213
|
+
*/
|
|
8921
9214
|
function isFatalError(error) {
|
|
8922
9215
|
if (error instanceof require_operators.VertoPongError) return false;
|
|
8923
9216
|
if (error instanceof require_operators.MediaTrackError) return false;
|
|
@@ -8952,7 +9245,9 @@ var CallFactory = class {
|
|
|
8952
9245
|
kind: inferCallErrorKind(error),
|
|
8953
9246
|
fatal: options$1?.fatal ?? isFatalError(error),
|
|
8954
9247
|
error,
|
|
8955
|
-
callId: callInstance.id
|
|
9248
|
+
callId: callInstance.id,
|
|
9249
|
+
...options$1?.leg ? { leg: options$1.leg } : {},
|
|
9250
|
+
...options$1?.legId ? { legId: options$1.legId } : {}
|
|
8956
9251
|
};
|
|
8957
9252
|
callInstance.emitError(callError);
|
|
8958
9253
|
},
|
|
@@ -9420,6 +9715,30 @@ const logger$9 = require_operators.getLogger();
|
|
|
9420
9715
|
function shouldAbortDial(callError) {
|
|
9421
9716
|
return callError.fatal || !(callError.error instanceof require_operators.MediaAccessError);
|
|
9422
9717
|
}
|
|
9718
|
+
/**
|
|
9719
|
+
* Wait for a dialed call to be ready, or for the failure that stops it.
|
|
9720
|
+
*
|
|
9721
|
+
* Local media acquisition is deliberately unbounded: a permission prompt or a
|
|
9722
|
+
* device picker is human time, and `getUserMedia` cannot be cancelled anyway.
|
|
9723
|
+
* The clock starts only once acquisition settles, so a slow human never spends
|
|
9724
|
+
* the server's budget.
|
|
9725
|
+
*
|
|
9726
|
+
* `merge` rather than `race`, because the two legs settle asymmetrically. A
|
|
9727
|
+
* fatal acquisition failure reports the error and then destroys the call in the
|
|
9728
|
+
* same synchronous step; `errors$` defers delivery by a microtask while
|
|
9729
|
+
* `localMediaSettled$` completes immediately. Under `race` that bare completion
|
|
9730
|
+
* ended the wait first and `dial()` rejected with an RxJS `EmptyError`, burying
|
|
9731
|
+
* the `NotAllowedError` applications are told to inspect. Under `merge` the
|
|
9732
|
+
* completed leg is simply spent, and the queued error — enqueued before the
|
|
9733
|
+
* completion, so delivered before it — arrives to reject the wait. A dial
|
|
9734
|
+
* abandoned with no error at all still ends both legs, and the resulting
|
|
9735
|
+
* `EmptyError` remains the benign-cancel signal.
|
|
9736
|
+
*
|
|
9737
|
+
* Exported for unit testing.
|
|
9738
|
+
*/
|
|
9739
|
+
async function awaitDialReady(session, signalingTimeoutMs) {
|
|
9740
|
+
return (0, rxjs.firstValueFrom)((0, rxjs.merge)(session.localMediaSettled$.pipe((0, rxjs.take)(1), (0, rxjs.switchMap)(() => session.selfId$.pipe((0, rxjs.filter)((id) => Boolean(id)), (0, rxjs.take)(1), (0, rxjs.timeout)(signalingTimeoutMs)))), session.errors$.pipe((0, rxjs.filter)(shouldAbortDial), (0, rxjs.take)(1), (0, rxjs.switchMap)((callError) => (0, rxjs.throwError)(() => callError.error)))));
|
|
9741
|
+
}
|
|
9423
9742
|
const getAddressSearchURI = (options) => {
|
|
9424
9743
|
const to = options.to?.split("?")[0];
|
|
9425
9744
|
const from$8 = options.from?.startsWith("subscriber://") ? options.from.replace("subscriber://", "") : options.from;
|
|
@@ -9436,7 +9755,6 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
9436
9755
|
this.authorizationStateKey = authorizationStateKey;
|
|
9437
9756
|
this.attachManager = attachManager;
|
|
9438
9757
|
this.dpopManager = dpopManager;
|
|
9439
|
-
this.callCreateTimeout = 6e3;
|
|
9440
9758
|
this.agent = `signalwire-js/4.0.0`;
|
|
9441
9759
|
this.eventAcks = true;
|
|
9442
9760
|
this.authorizationState$ = this.createReplaySubject(1);
|
|
@@ -9864,7 +10182,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
9864
10182
|
to: destinationURI,
|
|
9865
10183
|
...options
|
|
9866
10184
|
});
|
|
9867
|
-
await (
|
|
10185
|
+
await awaitDialReady(callSession, require_operators.DEFAULT_CALL_SIGNALING_TIMEOUT_MS);
|
|
9868
10186
|
this._calls$.next({
|
|
9869
10187
|
[`${callSession.id}`]: callSession,
|
|
9870
10188
|
...this._calls$.value
|
|
@@ -12036,6 +12354,11 @@ var SignalWire = class extends Destroyable {
|
|
|
12036
12354
|
* Returns a {@link Call} in `'ringing'` state. Subscribe to {@link Call.status$}
|
|
12037
12355
|
* to track progression through `'connected'` → `'disconnected'`.
|
|
12038
12356
|
*
|
|
12357
|
+
* Local media acquisition is deliberately unbounded: an unanswered permission
|
|
12358
|
+
* prompt leaves this promise pending indefinitely, so apply your own bound if
|
|
12359
|
+
* your UI needs one. The 12 s signaling budget starts only once acquisition
|
|
12360
|
+
* settles.
|
|
12361
|
+
*
|
|
12039
12362
|
* @param destination - Address URI string (e.g. `'/public/my-room'`) or {@link Address} instance.
|
|
12040
12363
|
* @param options - Media and dial options (audio/video, device constraints). Overrides defaults.
|
|
12041
12364
|
* @returns The created {@link Call} instance.
|
|
@@ -12498,6 +12821,7 @@ emitReadyEvent();
|
|
|
12498
12821
|
|
|
12499
12822
|
//#endregion
|
|
12500
12823
|
exports.Address = Address;
|
|
12824
|
+
exports.AuxiliaryLegTimeoutError = require_operators.AuxiliaryLegTimeoutError;
|
|
12501
12825
|
exports.CallCreateError = require_operators.CallCreateError;
|
|
12502
12826
|
exports.CallNotReadyError = require_operators.CallNotReadyError;
|
|
12503
12827
|
exports.ClientPreferences = ClientPreferences;
|