@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/index.cjs
CHANGED
|
@@ -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$31 = 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$31.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$31.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$31.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$31.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$31.debug("[HTTPRequestController] Using Bearer token auth, token length:", credential.token.length);
|
|
270
|
+
} else logger$31.warn("[HTTPRequestController] No credentials available for authentication");
|
|
271
271
|
return headers;
|
|
272
272
|
}
|
|
273
273
|
/**
|
|
@@ -437,6 +437,13 @@ const CREDENTIAL_REFRESH_RETRY_BASE_MS = 1e3;
|
|
|
437
437
|
const CREDENTIAL_REFRESH_MAX_DELAY_MS = 3e4;
|
|
438
438
|
/** Buffer in milliseconds before token expiry to trigger refresh. */
|
|
439
439
|
const CREDENTIAL_REFRESH_BUFFER_MS = 5e3;
|
|
440
|
+
/**
|
|
441
|
+
* Maximum time the coordinator will wait for `DeviceTokenManager.activate()`
|
|
442
|
+
* to resolve before treating the activation as failed and falling back to
|
|
443
|
+
* the developer-provided refresh path. Prevents a wedged HTTP layer from
|
|
444
|
+
* leaving the session with no active refresh mechanism.
|
|
445
|
+
*/
|
|
446
|
+
const CREDENTIAL_ACTIVATE_TIMEOUT_MS = 3e4;
|
|
440
447
|
/** JSON-RPC error code for requester validation failure (corrupted auth state). */
|
|
441
448
|
const RPC_ERROR_REQUESTER_VALIDATION_FAILED = -32003;
|
|
442
449
|
/** JSON-RPC error code for invalid params (e.g., missing authentication block). */
|
|
@@ -539,7 +546,7 @@ function fromMsToSec(milliseconds) {
|
|
|
539
546
|
|
|
540
547
|
//#endregion
|
|
541
548
|
//#region src/containers/PreferencesContainer.ts
|
|
542
|
-
const logger$
|
|
549
|
+
const logger$30 = require_operators.getLogger();
|
|
543
550
|
var PreferencesContainer = class PreferencesContainer {
|
|
544
551
|
static get instance() {
|
|
545
552
|
this._instance ??= new PreferencesContainer();
|
|
@@ -1201,7 +1208,7 @@ var ClientPreferences = class {
|
|
|
1201
1208
|
if (!this._storage) return;
|
|
1202
1209
|
const data = collectStoredPreferences();
|
|
1203
1210
|
this._storage.setItem(PREFERENCES_STORAGE_KEY, data, "local").catch((error) => {
|
|
1204
|
-
logger$
|
|
1211
|
+
logger$30.error(`[ClientPreferences] Failed to save preferences: ${String(error)}`);
|
|
1205
1212
|
});
|
|
1206
1213
|
}
|
|
1207
1214
|
/** Loads preferences from storage and applies them to the container. */
|
|
@@ -1210,7 +1217,7 @@ var ClientPreferences = class {
|
|
|
1210
1217
|
this._storage.getItem(PREFERENCES_STORAGE_KEY, "local").then((stored) => {
|
|
1211
1218
|
if (stored) applyStoredPreferences(stored);
|
|
1212
1219
|
}).catch((error) => {
|
|
1213
|
-
logger$
|
|
1220
|
+
logger$30.error(`[ClientPreferences] Failed to load preferences: ${String(error)}`);
|
|
1214
1221
|
});
|
|
1215
1222
|
}
|
|
1216
1223
|
};
|
|
@@ -1231,7 +1238,7 @@ function toError(value) {
|
|
|
1231
1238
|
|
|
1232
1239
|
//#endregion
|
|
1233
1240
|
//#region src/controllers/NavigatorDeviceController.ts
|
|
1234
|
-
const logger$
|
|
1241
|
+
const logger$29 = require_operators.getLogger();
|
|
1235
1242
|
/** Maps a device kind to its storage key. */
|
|
1236
1243
|
const DEVICE_STORAGE_KEYS = {
|
|
1237
1244
|
audioinput: DEVICE_STORAGE_KEY_AUDIO_INPUT,
|
|
@@ -1253,7 +1260,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1253
1260
|
super();
|
|
1254
1261
|
this.webRTCApiProvider = webRTCApiProvider;
|
|
1255
1262
|
this.deviceChangeHandler = () => {
|
|
1256
|
-
logger$
|
|
1263
|
+
logger$29.debug("[DeviceController] Device change detected");
|
|
1257
1264
|
this.enumerateDevices();
|
|
1258
1265
|
};
|
|
1259
1266
|
this._devicesState$ = this.createBehaviorSubject(initialDevicesState);
|
|
@@ -1318,13 +1325,13 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1318
1325
|
return this.cachedObservable("videoInputDevices$", () => this._devicesState$.pipe((0, rxjs.map)((state) => state.videoinput), (0, rxjs.distinctUntilChanged)(), (0, rxjs.takeUntil)(this.destroyed$)));
|
|
1319
1326
|
}
|
|
1320
1327
|
get selectedAudioInputDevice$() {
|
|
1321
|
-
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$
|
|
1328
|
+
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$29.debug("[DeviceController] Selected audio input device changed:", info))));
|
|
1322
1329
|
}
|
|
1323
1330
|
get selectedAudioOutputDevice$() {
|
|
1324
|
-
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$
|
|
1331
|
+
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$29.debug("[DeviceController] Selected audio output device changed:", info))));
|
|
1325
1332
|
}
|
|
1326
1333
|
get selectedVideoInputDevice$() {
|
|
1327
|
-
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$
|
|
1334
|
+
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$29.debug("[DeviceController] Selected video input device changed:", info))));
|
|
1328
1335
|
}
|
|
1329
1336
|
get selectedAudioInputDevice() {
|
|
1330
1337
|
if (this._audioInputDisabled$.value) return null;
|
|
@@ -1399,7 +1406,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1399
1406
|
if (device) this.persistDeviceSelection("audioinput", device);
|
|
1400
1407
|
}
|
|
1401
1408
|
selectVideoInputDevice(device) {
|
|
1402
|
-
logger$
|
|
1409
|
+
logger$29.debug("[DeviceController] Setting selected video input device:", device);
|
|
1403
1410
|
if (this._videoInputDisabled$.value && device) this._videoInputDisabled$.next(false);
|
|
1404
1411
|
const previous = this._selectedDevicesState$.value.videoinput;
|
|
1405
1412
|
if (previous && previous.deviceId !== device?.deviceId) this._deviceHistory.push("videoinput", previous);
|
|
@@ -1456,7 +1463,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1456
1463
|
}
|
|
1457
1464
|
const fromHistory = this._deviceHistory.findInHistory(kind, devices);
|
|
1458
1465
|
if (fromHistory) {
|
|
1459
|
-
logger$
|
|
1466
|
+
logger$29.debug(`[DeviceController] Device disappeared, falling back to history: ${fromHistory.label}`);
|
|
1460
1467
|
this.emitDeviceRecovered(kind, selected, fromHistory, "device_disconnected");
|
|
1461
1468
|
return fromHistory;
|
|
1462
1469
|
}
|
|
@@ -1509,7 +1516,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1509
1516
|
try {
|
|
1510
1517
|
await this._storageManager.setItem(DEVICE_STORAGE_KEYS[kind], stored, "local");
|
|
1511
1518
|
} catch (error) {
|
|
1512
|
-
logger$
|
|
1519
|
+
logger$29.error(`[DeviceController] Failed to persist device selection for ${kind}:`, error);
|
|
1513
1520
|
}
|
|
1514
1521
|
}
|
|
1515
1522
|
async loadPersistedDevices() {
|
|
@@ -1525,7 +1532,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1525
1532
|
[kind]: stored
|
|
1526
1533
|
};
|
|
1527
1534
|
} catch (error) {
|
|
1528
|
-
logger$
|
|
1535
|
+
logger$29.error(`[DeviceController] Failed to load persisted device for ${kind}:`, error);
|
|
1529
1536
|
}
|
|
1530
1537
|
}
|
|
1531
1538
|
/** Clears device history, persisted selections, and re-enumerates devices. */
|
|
@@ -1543,7 +1550,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1543
1550
|
this.disableDeviceMonitoring();
|
|
1544
1551
|
this.webRTCApiProvider.mediaDevices.addEventListener("devicechange", this.deviceChangeHandler);
|
|
1545
1552
|
if (PreferencesContainer.instance.devicePollingInterval > 0) this._devicesPoolingSubscription = (0, rxjs.interval)(PreferencesContainer.instance.devicePollingInterval).subscribe(() => {
|
|
1546
|
-
logger$
|
|
1553
|
+
logger$29.debug("[DeviceController] Polling devices due to interval");
|
|
1547
1554
|
this.enumerateDevices();
|
|
1548
1555
|
});
|
|
1549
1556
|
this.enumerateDevices();
|
|
@@ -1569,13 +1576,13 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1569
1576
|
videoinput: []
|
|
1570
1577
|
});
|
|
1571
1578
|
this._devicesState$.next(devicesByKind);
|
|
1572
|
-
logger$
|
|
1579
|
+
logger$29.debug("[DeviceController] Devices enumerated:", {
|
|
1573
1580
|
audioInputs: devicesByKind.audioinput.length,
|
|
1574
1581
|
audioOutputs: devicesByKind.audiooutput.length,
|
|
1575
1582
|
videoInputs: devicesByKind.videoinput.length
|
|
1576
1583
|
});
|
|
1577
1584
|
} catch (error) {
|
|
1578
|
-
logger$
|
|
1585
|
+
logger$29.error("[DeviceController] Failed to enumerate devices:", error);
|
|
1579
1586
|
this._errors$.next(toError(error));
|
|
1580
1587
|
}
|
|
1581
1588
|
}
|
|
@@ -1591,7 +1598,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
1591
1598
|
stream.getTracks().forEach((t) => t.stop());
|
|
1592
1599
|
return capabilities;
|
|
1593
1600
|
} catch (error) {
|
|
1594
|
-
logger$
|
|
1601
|
+
logger$29.error("[DeviceController] Failed to get device capabilities:", error);
|
|
1595
1602
|
this._errors$.next(toError(error));
|
|
1596
1603
|
throw error;
|
|
1597
1604
|
}
|
|
@@ -1842,7 +1849,7 @@ var DependencyContainer = class {
|
|
|
1842
1849
|
|
|
1843
1850
|
//#endregion
|
|
1844
1851
|
//#region src/controllers/CryptoController.ts
|
|
1845
|
-
const logger$
|
|
1852
|
+
const logger$28 = require_operators.getLogger();
|
|
1846
1853
|
const DPOP_DB_NAME = "sw-dpop";
|
|
1847
1854
|
const DPOP_DB_VERSION = 1;
|
|
1848
1855
|
const DPOP_STORE_NAME = "keys";
|
|
@@ -1901,7 +1908,7 @@ async function loadKeyPairFromDB() {
|
|
|
1901
1908
|
tx.oncomplete = () => db.close();
|
|
1902
1909
|
});
|
|
1903
1910
|
} catch (error) {
|
|
1904
|
-
logger$
|
|
1911
|
+
logger$28.warn("[DPoP] Failed to load key pair from IndexedDB:", error);
|
|
1905
1912
|
return null;
|
|
1906
1913
|
}
|
|
1907
1914
|
}
|
|
@@ -1921,7 +1928,7 @@ async function saveKeyPairToDB(keyPair) {
|
|
|
1921
1928
|
};
|
|
1922
1929
|
});
|
|
1923
1930
|
} catch (error) {
|
|
1924
|
-
logger$
|
|
1931
|
+
logger$28.warn("[DPoP] Failed to save key pair to IndexedDB:", error);
|
|
1925
1932
|
}
|
|
1926
1933
|
}
|
|
1927
1934
|
async function deleteKeyPairFromDB() {
|
|
@@ -1940,7 +1947,7 @@ async function deleteKeyPairFromDB() {
|
|
|
1940
1947
|
};
|
|
1941
1948
|
});
|
|
1942
1949
|
} catch (error) {
|
|
1943
|
-
logger$
|
|
1950
|
+
logger$28.warn("[DPoP] Failed to delete key pair from IndexedDB:", error);
|
|
1944
1951
|
}
|
|
1945
1952
|
}
|
|
1946
1953
|
/**
|
|
@@ -2000,13 +2007,13 @@ var CryptoController = class {
|
|
|
2000
2007
|
this._publicJwk = await crypto.subtle.exportKey("jwk", stored.publicKey);
|
|
2001
2008
|
this._fingerprint = await computeJwkThumbprint(this._publicJwk);
|
|
2002
2009
|
this._initialized = true;
|
|
2003
|
-
logger$
|
|
2010
|
+
logger$28.debug("[DPoP] Key pair restored from IndexedDB, fingerprint:", this._fingerprint);
|
|
2004
2011
|
return this._fingerprint;
|
|
2005
2012
|
} catch (error) {
|
|
2006
|
-
logger$
|
|
2013
|
+
logger$28.warn("[DPoP] Stored key pair unusable, generating new one:", error);
|
|
2007
2014
|
await deleteKeyPairFromDB();
|
|
2008
2015
|
}
|
|
2009
|
-
logger$
|
|
2016
|
+
logger$28.debug("[DPoP] Generating RSA key pair");
|
|
2010
2017
|
this._keyPair = await crypto.subtle.generateKey({
|
|
2011
2018
|
name: "RSASSA-PKCS1-v1_5",
|
|
2012
2019
|
modulusLength: 2048,
|
|
@@ -2021,7 +2028,7 @@ var CryptoController = class {
|
|
|
2021
2028
|
this._fingerprint = await computeJwkThumbprint(this._publicJwk);
|
|
2022
2029
|
this._initialized = true;
|
|
2023
2030
|
await saveKeyPairToDB(this._keyPair);
|
|
2024
|
-
logger$
|
|
2031
|
+
logger$28.debug("[DPoP] Key pair generated and persisted, fingerprint:", this._fingerprint);
|
|
2025
2032
|
return this._fingerprint;
|
|
2026
2033
|
}
|
|
2027
2034
|
/**
|
|
@@ -2087,7 +2094,7 @@ var CryptoController = class {
|
|
|
2087
2094
|
this._fingerprint = null;
|
|
2088
2095
|
this._initialized = false;
|
|
2089
2096
|
deleteKeyPairFromDB();
|
|
2090
|
-
logger$
|
|
2097
|
+
logger$28.debug("[DPoP] Controller destroyed");
|
|
2091
2098
|
}
|
|
2092
2099
|
get publicJwk() {
|
|
2093
2100
|
if (!this._publicJwk) throw new require_operators.DPoPInitError("CryptoController not initialized. Call init() first.");
|
|
@@ -2110,7 +2117,7 @@ var CryptoController = class {
|
|
|
2110
2117
|
|
|
2111
2118
|
//#endregion
|
|
2112
2119
|
//#region src/controllers/NetworkMonitor.ts
|
|
2113
|
-
const logger$
|
|
2120
|
+
const logger$27 = require_operators.getLogger();
|
|
2114
2121
|
/**
|
|
2115
2122
|
* Safely check whether we are running in a browser environment
|
|
2116
2123
|
* with `window` and the relevant event targets.
|
|
@@ -2167,7 +2174,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
2167
2174
|
}
|
|
2168
2175
|
attachListeners() {
|
|
2169
2176
|
if (!hasBrowserNetworkEvents()) {
|
|
2170
|
-
logger$
|
|
2177
|
+
logger$27.debug("NetworkMonitor: no browser environment detected, skipping event listeners");
|
|
2171
2178
|
return;
|
|
2172
2179
|
}
|
|
2173
2180
|
window.addEventListener("online", this._onOnline);
|
|
@@ -2175,7 +2182,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
2175
2182
|
const connection = getNetworkConnection();
|
|
2176
2183
|
if (connection) connection.addEventListener("change", this._onConnectionChange);
|
|
2177
2184
|
this._listenersAttached = true;
|
|
2178
|
-
logger$
|
|
2185
|
+
logger$27.debug("NetworkMonitor: event listeners attached");
|
|
2179
2186
|
}
|
|
2180
2187
|
removeListeners() {
|
|
2181
2188
|
if (!this._listenersAttached) return;
|
|
@@ -2186,10 +2193,10 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
2186
2193
|
if (connection) connection.removeEventListener("change", this._onConnectionChange);
|
|
2187
2194
|
}
|
|
2188
2195
|
this._listenersAttached = false;
|
|
2189
|
-
logger$
|
|
2196
|
+
logger$27.debug("NetworkMonitor: event listeners removed");
|
|
2190
2197
|
}
|
|
2191
2198
|
handleOnline() {
|
|
2192
|
-
logger$
|
|
2199
|
+
logger$27.info("NetworkMonitor: browser went online");
|
|
2193
2200
|
this._isOnline$.next(true);
|
|
2194
2201
|
this._networkChange$.next({
|
|
2195
2202
|
type: "online",
|
|
@@ -2198,7 +2205,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
2198
2205
|
});
|
|
2199
2206
|
}
|
|
2200
2207
|
handleOffline() {
|
|
2201
|
-
logger$
|
|
2208
|
+
logger$27.info("NetworkMonitor: browser went offline");
|
|
2202
2209
|
this._isOnline$.next(false);
|
|
2203
2210
|
this._networkChange$.next({
|
|
2204
2211
|
type: "offline",
|
|
@@ -2207,7 +2214,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
2207
2214
|
}
|
|
2208
2215
|
handleConnectionChange() {
|
|
2209
2216
|
const networkType = getNetworkType();
|
|
2210
|
-
logger$
|
|
2217
|
+
logger$27.info(`NetworkMonitor: connection changed — effectiveType=${networkType ?? "unknown"}`);
|
|
2211
2218
|
this._networkChange$.next({
|
|
2212
2219
|
type: "connection_change",
|
|
2213
2220
|
timestamp: Date.now(),
|
|
@@ -2322,7 +2329,7 @@ function getNavigatorMediaDevices() {
|
|
|
2322
2329
|
|
|
2323
2330
|
//#endregion
|
|
2324
2331
|
//#region src/controllers/PreflightRunner.ts
|
|
2325
|
-
const logger$
|
|
2332
|
+
const logger$26 = require_operators.getLogger();
|
|
2326
2333
|
const DEFAULT_MEDIA_TEST_DURATION_S = 10;
|
|
2327
2334
|
const ICE_GATHERING_TIMEOUT_MS = 1e4;
|
|
2328
2335
|
const SIGNALING_RTT_TIMEOUT_MS = 5e3;
|
|
@@ -2371,7 +2378,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
2371
2378
|
if (!this._options.skipMediaTest) try {
|
|
2372
2379
|
bandwidth = await this.testMediaBandwidth(destination);
|
|
2373
2380
|
} catch (error) {
|
|
2374
|
-
logger$
|
|
2381
|
+
logger$26.warn("[PreflightRunner] Media bandwidth test failed:", error);
|
|
2375
2382
|
warnings.push("Media bandwidth test failed");
|
|
2376
2383
|
}
|
|
2377
2384
|
return {
|
|
@@ -2383,7 +2390,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
2383
2390
|
warnings
|
|
2384
2391
|
};
|
|
2385
2392
|
} catch (error) {
|
|
2386
|
-
logger$
|
|
2393
|
+
logger$26.error("[PreflightRunner] Preflight test failed:", error);
|
|
2387
2394
|
throw new require_operators.PreflightError("preflight", error instanceof Error ? error : new Error(String(error)));
|
|
2388
2395
|
} finally {
|
|
2389
2396
|
this.destroy();
|
|
@@ -2414,7 +2421,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
2414
2421
|
if (track.kind === "video" && track.readyState === "live") videoWorking = true;
|
|
2415
2422
|
}
|
|
2416
2423
|
} catch (error) {
|
|
2417
|
-
logger$
|
|
2424
|
+
logger$26.warn("[PreflightRunner] Device test failed:", error);
|
|
2418
2425
|
} finally {
|
|
2419
2426
|
if (audioStream) audioStream.getTracks().forEach((t) => t.stop());
|
|
2420
2427
|
}
|
|
@@ -2472,7 +2479,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
2472
2479
|
rttMs
|
|
2473
2480
|
};
|
|
2474
2481
|
} catch (error) {
|
|
2475
|
-
logger$
|
|
2482
|
+
logger$26.warn("[PreflightRunner] ICE connectivity test failed:", error);
|
|
2476
2483
|
return {
|
|
2477
2484
|
type: "failed",
|
|
2478
2485
|
turnReachable: false,
|
|
@@ -2519,7 +2526,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
2519
2526
|
|
|
2520
2527
|
//#endregion
|
|
2521
2528
|
//#region src/controllers/VisibilityController.ts
|
|
2522
|
-
const logger$
|
|
2529
|
+
const logger$25 = require_operators.getLogger();
|
|
2523
2530
|
/**
|
|
2524
2531
|
* Checks whether the document visibility API is available.
|
|
2525
2532
|
*/
|
|
@@ -2556,8 +2563,8 @@ var VisibilityController = class extends Destroyable {
|
|
|
2556
2563
|
this._boundHandler = this._handleVisibilityChange.bind(this);
|
|
2557
2564
|
if (this._hasVisibilityApi) {
|
|
2558
2565
|
document.addEventListener("visibilitychange", this._boundHandler);
|
|
2559
|
-
logger$
|
|
2560
|
-
} else logger$
|
|
2566
|
+
logger$25.debug("VisibilityController: listening for visibilitychange events");
|
|
2567
|
+
} else logger$25.debug("VisibilityController: document visibility API not available, defaulting to visible");
|
|
2561
2568
|
}
|
|
2562
2569
|
/**
|
|
2563
2570
|
* Observable of the current visibility state.
|
|
@@ -2582,7 +2589,7 @@ var VisibilityController = class extends Destroyable {
|
|
|
2582
2589
|
destroy() {
|
|
2583
2590
|
if (this._hasVisibilityApi) {
|
|
2584
2591
|
document.removeEventListener("visibilitychange", this._boundHandler);
|
|
2585
|
-
logger$
|
|
2592
|
+
logger$25.debug("VisibilityController: removed visibilitychange listener");
|
|
2586
2593
|
}
|
|
2587
2594
|
super.destroy();
|
|
2588
2595
|
}
|
|
@@ -2600,7 +2607,7 @@ var VisibilityController = class extends Destroyable {
|
|
|
2600
2607
|
timestamp: Date.now()
|
|
2601
2608
|
};
|
|
2602
2609
|
this._visibilityChange$.next(changeEvent);
|
|
2603
|
-
logger$
|
|
2610
|
+
logger$25.debug("VisibilityController: visibility changed", {
|
|
2604
2611
|
from: previousState,
|
|
2605
2612
|
to: newState
|
|
2606
2613
|
});
|
|
@@ -2803,7 +2810,7 @@ const RPCEventAckResponse = (id) => makeRPCResponse({
|
|
|
2803
2810
|
|
|
2804
2811
|
//#endregion
|
|
2805
2812
|
//#region src/managers/AttachManager.ts
|
|
2806
|
-
const logger$
|
|
2813
|
+
const logger$24 = require_operators.getLogger();
|
|
2807
2814
|
var AttachManager = class {
|
|
2808
2815
|
constructor(storage, deviceController, reconnectCallsTimeout, attachKey) {
|
|
2809
2816
|
this.storage = storage;
|
|
@@ -2824,7 +2831,7 @@ var AttachManager = class {
|
|
|
2824
2831
|
try {
|
|
2825
2832
|
return await this.storage.getItem(this.attachKey) ?? {};
|
|
2826
2833
|
} catch (error) {
|
|
2827
|
-
logger$
|
|
2834
|
+
logger$24.warn("[AttachManager] Failed to retrieve attached calls from storage", error);
|
|
2828
2835
|
return {};
|
|
2829
2836
|
}
|
|
2830
2837
|
}
|
|
@@ -2832,7 +2839,7 @@ var AttachManager = class {
|
|
|
2832
2839
|
try {
|
|
2833
2840
|
await this.storage.setItem(this.attachKey, attached);
|
|
2834
2841
|
} catch (error) {
|
|
2835
|
-
logger$
|
|
2842
|
+
logger$24.warn("[AttachManager] Failed to write attached calls to storage", error);
|
|
2836
2843
|
}
|
|
2837
2844
|
}
|
|
2838
2845
|
/**
|
|
@@ -2851,7 +2858,7 @@ var AttachManager = class {
|
|
|
2851
2858
|
}
|
|
2852
2859
|
async attach(call) {
|
|
2853
2860
|
if (!call.to) {
|
|
2854
|
-
logger$
|
|
2861
|
+
logger$24.warn("[AttachManager] Skip attach for calls with no destination");
|
|
2855
2862
|
return;
|
|
2856
2863
|
}
|
|
2857
2864
|
const destination = call.to;
|
|
@@ -2904,15 +2911,15 @@ var AttachManager = class {
|
|
|
2904
2911
|
callId,
|
|
2905
2912
|
...options
|
|
2906
2913
|
});
|
|
2907
|
-
logger$
|
|
2914
|
+
logger$24.info(`[AttachManager] Reattached call ${callId} (attempt ${attempt})`);
|
|
2908
2915
|
succeeded = true;
|
|
2909
2916
|
break;
|
|
2910
2917
|
} catch (error) {
|
|
2911
|
-
logger$
|
|
2918
|
+
logger$24.warn(`[AttachManager] Reattach attempt ${attempt}/3 failed for call ${callId}:`, error);
|
|
2912
2919
|
if (attempt < 3) await new Promise((r) => setTimeout(r, (attempt + 1) * 1e3));
|
|
2913
2920
|
}
|
|
2914
2921
|
if (!succeeded) {
|
|
2915
|
-
logger$
|
|
2922
|
+
logger$24.warn(`[AttachManager] Reattach failed after 3 attempts for call ${callId}, removing reference`);
|
|
2916
2923
|
await this.detach({
|
|
2917
2924
|
id: callId,
|
|
2918
2925
|
mediaDirections: attachment.mediaDirections
|
|
@@ -2998,7 +3005,9 @@ const DEFAULT_MEMBER_CAPABILITIES = {
|
|
|
2998
3005
|
position: false,
|
|
2999
3006
|
meta: false,
|
|
3000
3007
|
remove: false,
|
|
3001
|
-
audioFlags: false
|
|
3008
|
+
audioFlags: false,
|
|
3009
|
+
denoise: false,
|
|
3010
|
+
lowbitrate: false
|
|
3002
3011
|
};
|
|
3003
3012
|
/**
|
|
3004
3013
|
* Default call capabilities with no permissions
|
|
@@ -3071,7 +3080,9 @@ function computeMemberCapabilities(flags, memberType) {
|
|
|
3071
3080
|
position: hasBooleanCapability(flags, memberType, null, "position"),
|
|
3072
3081
|
meta: hasBooleanCapability(flags, memberType, null, "meta"),
|
|
3073
3082
|
remove: hasBooleanCapability(flags, memberType, null, "remove"),
|
|
3074
|
-
audioFlags: hasBooleanCapability(flags, memberType, null, "audioflags")
|
|
3083
|
+
audioFlags: hasBooleanCapability(flags, memberType, null, "audioflags"),
|
|
3084
|
+
denoise: hasBooleanCapability(flags, memberType, null, "denoise"),
|
|
3085
|
+
lowbitrate: hasBooleanCapability(flags, memberType, null, "lowbitrate")
|
|
3075
3086
|
};
|
|
3076
3087
|
}
|
|
3077
3088
|
/**
|
|
@@ -3226,7 +3237,7 @@ function toggleHandraiseMethod(is) {
|
|
|
3226
3237
|
|
|
3227
3238
|
//#endregion
|
|
3228
3239
|
//#region src/core/entities/Participant.ts
|
|
3229
|
-
const logger$
|
|
3240
|
+
const logger$23 = require_operators.getLogger();
|
|
3230
3241
|
const initialState = {};
|
|
3231
3242
|
/**
|
|
3232
3243
|
* Represents a participant in a call.
|
|
@@ -3452,6 +3463,10 @@ var Participant = class extends Destroyable {
|
|
|
3452
3463
|
get nodeId() {
|
|
3453
3464
|
return this._state$.value.node_id;
|
|
3454
3465
|
}
|
|
3466
|
+
/** Call ID for this participant's leg, or `undefined` if not available. */
|
|
3467
|
+
get callId() {
|
|
3468
|
+
return this._state$.value.call_id;
|
|
3469
|
+
}
|
|
3455
3470
|
/** @internal */
|
|
3456
3471
|
get value() {
|
|
3457
3472
|
return this._state$.value;
|
|
@@ -3513,8 +3528,9 @@ var Participant = class extends Destroyable {
|
|
|
3513
3528
|
noise_suppression: !this.noiseSuppression
|
|
3514
3529
|
});
|
|
3515
3530
|
}
|
|
3531
|
+
/** Toggles low-bitrate mode for this participant's media. */
|
|
3516
3532
|
async toggleLowbitrate() {
|
|
3517
|
-
|
|
3533
|
+
await this.executeMethod(this.id, "call.lowbitrate.set", { lowbitrate: !this.lowbitrate });
|
|
3518
3534
|
}
|
|
3519
3535
|
/**
|
|
3520
3536
|
* Adjusts the **conference-only** microphone energy gate / sensitivity level
|
|
@@ -3561,10 +3577,27 @@ var Participant = class extends Destroyable {
|
|
|
3561
3577
|
}
|
|
3562
3578
|
/**
|
|
3563
3579
|
* Sets the participant's position in the video layout.
|
|
3580
|
+
*
|
|
3581
|
+
* Requires the `member.position` capability. The gateway keys positions by the
|
|
3582
|
+
* **target member's own** `call_id`/`node_id` (see issue #19400 and the legacy
|
|
3583
|
+
* `setPositions` implementation), so this sends the participant's own call
|
|
3584
|
+
* context — matching {@link Participant.remove}. A resolved promise does not
|
|
3585
|
+
* guarantee a visible change: the backend silently returns `200` (no-op) for
|
|
3586
|
+
* non-conference targets.
|
|
3587
|
+
*
|
|
3564
3588
|
* @param value - The {@link VideoPosition} to assign (e.g. `'auto'`, `'reserved-0'`).
|
|
3565
3589
|
*/
|
|
3566
3590
|
async setPosition(value) {
|
|
3567
|
-
|
|
3591
|
+
const state = this._state$.value;
|
|
3592
|
+
const target = {
|
|
3593
|
+
member_id: this.id,
|
|
3594
|
+
call_id: state.call_id ?? "",
|
|
3595
|
+
node_id: state.node_id ?? ""
|
|
3596
|
+
};
|
|
3597
|
+
await this.executeMethod(target, "call.member.position.set", { targets: [{
|
|
3598
|
+
target,
|
|
3599
|
+
position: value
|
|
3600
|
+
}] });
|
|
3568
3601
|
}
|
|
3569
3602
|
/** Removes this participant from the call. */
|
|
3570
3603
|
async remove() {
|
|
@@ -3659,7 +3692,7 @@ var SelfParticipant = class extends Participant {
|
|
|
3659
3692
|
try {
|
|
3660
3693
|
await this.vertoManager.addScreenMedia();
|
|
3661
3694
|
} catch (error) {
|
|
3662
|
-
logger$
|
|
3695
|
+
logger$23.error("[Participant.startScreenShare] Screen share error:", error);
|
|
3663
3696
|
}
|
|
3664
3697
|
}
|
|
3665
3698
|
/** Observable of the current screen share status. */
|
|
@@ -3679,7 +3712,7 @@ var SelfParticipant = class extends Participant {
|
|
|
3679
3712
|
try {
|
|
3680
3713
|
await this.vertoManager.addInputDevice(options);
|
|
3681
3714
|
} catch (error) {
|
|
3682
|
-
logger$
|
|
3715
|
+
logger$23.error("[Participant.startScreenShare] Screen share error:", error);
|
|
3683
3716
|
}
|
|
3684
3717
|
}
|
|
3685
3718
|
/** Removes an additional media input device by ID. */
|
|
@@ -3741,7 +3774,7 @@ var SelfParticipant = class extends Participant {
|
|
|
3741
3774
|
*/
|
|
3742
3775
|
exitStudioModeIfActive() {
|
|
3743
3776
|
if (this._studioAudio$.value) {
|
|
3744
|
-
logger$
|
|
3777
|
+
logger$23.debug("[SelfParticipant] Exiting studio audio mode due to individual flag toggle");
|
|
3745
3778
|
this._studioAudio$.next(false);
|
|
3746
3779
|
}
|
|
3747
3780
|
}
|
|
@@ -3765,7 +3798,7 @@ var SelfParticipant = class extends Participant {
|
|
|
3765
3798
|
try {
|
|
3766
3799
|
await super.mute();
|
|
3767
3800
|
} catch (error) {
|
|
3768
|
-
logger$
|
|
3801
|
+
logger$23.warn("[Participant.toggleAudioInput] Server Error while muting audio input, proceeding with local toggle anyway", error);
|
|
3769
3802
|
} finally {
|
|
3770
3803
|
this.vertoManager.muteMainAudioInputDevice();
|
|
3771
3804
|
}
|
|
@@ -3775,7 +3808,7 @@ var SelfParticipant = class extends Participant {
|
|
|
3775
3808
|
try {
|
|
3776
3809
|
await super.unmute();
|
|
3777
3810
|
} catch (error) {
|
|
3778
|
-
logger$
|
|
3811
|
+
logger$23.warn("[Participant.toggleAudioInput] Server Error while unmuting audio input, proceeding with local toggle anyway", error);
|
|
3779
3812
|
} finally {
|
|
3780
3813
|
await this.vertoManager.unmuteMainAudioInputDevice();
|
|
3781
3814
|
}
|
|
@@ -3785,7 +3818,7 @@ var SelfParticipant = class extends Participant {
|
|
|
3785
3818
|
try {
|
|
3786
3819
|
await super.muteVideo();
|
|
3787
3820
|
} catch (error) {
|
|
3788
|
-
logger$
|
|
3821
|
+
logger$23.warn("[Participant.toggleVideoInput] Server Error while muting video input, proceeding with local toggle anyway", error);
|
|
3789
3822
|
} finally {
|
|
3790
3823
|
this.vertoManager.muteMainVideoInputDevice();
|
|
3791
3824
|
}
|
|
@@ -3795,7 +3828,7 @@ var SelfParticipant = class extends Participant {
|
|
|
3795
3828
|
try {
|
|
3796
3829
|
await super.unmuteVideo();
|
|
3797
3830
|
} catch (error) {
|
|
3798
|
-
logger$
|
|
3831
|
+
logger$23.warn("[Participant.toggleVideoInput] Server Error while unmuting video input, proceeding with local toggle anyway", error);
|
|
3799
3832
|
} finally {
|
|
3800
3833
|
await this.vertoManager.unmuteMainVideoInputDevice();
|
|
3801
3834
|
}
|
|
@@ -3889,7 +3922,7 @@ function isLayoutChangedPayload(value) {
|
|
|
3889
3922
|
|
|
3890
3923
|
//#endregion
|
|
3891
3924
|
//#region src/managers/CallEventsManager.ts
|
|
3892
|
-
const logger$
|
|
3925
|
+
const logger$22 = require_operators.getLogger();
|
|
3893
3926
|
const initialSessionState = {};
|
|
3894
3927
|
/** @internal */
|
|
3895
3928
|
var CallEventsManager = class extends Destroyable {
|
|
@@ -3993,7 +4026,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
3993
4026
|
}
|
|
3994
4027
|
initSubscriptions() {
|
|
3995
4028
|
this.subscribeTo(this.callJoinedEvent$, (callJoinedEvent) => {
|
|
3996
|
-
logger$
|
|
4029
|
+
logger$22.debug("[CallEventsManager] Handling call.joined event for call/session IDs:", {
|
|
3997
4030
|
callId: callJoinedEvent.call_id,
|
|
3998
4031
|
roomSessionId: callJoinedEvent.room_session_id
|
|
3999
4032
|
});
|
|
@@ -4020,19 +4053,19 @@ var CallEventsManager = class extends Destroyable {
|
|
|
4020
4053
|
if (this._self$.value?.capabilities.setLayout) this.updateLayouts();
|
|
4021
4054
|
});
|
|
4022
4055
|
this.subscribeTo(this.memberUpdates$, (member) => {
|
|
4023
|
-
logger$
|
|
4056
|
+
logger$22.debug("[CallEventsManager] Handling member update event for member ID:", member);
|
|
4024
4057
|
this.upsertParticipant(member);
|
|
4025
4058
|
});
|
|
4026
4059
|
this.subscribeTo(this.webRtcCallSession.memberLeft$, (memberLeftEvent) => {
|
|
4027
|
-
logger$
|
|
4060
|
+
logger$22.debug("[CallEventsManager] Handling member.left event for member ID:", memberLeftEvent.member.member_id);
|
|
4028
4061
|
const participants = { ...this._participants$.value };
|
|
4029
4062
|
if (memberLeftEvent.member.member_id in participants) {
|
|
4030
4063
|
delete participants[memberLeftEvent.member.member_id];
|
|
4031
4064
|
this._participants$.next(participants);
|
|
4032
|
-
} else logger$
|
|
4065
|
+
} else logger$22.warn(`[CallEventsManager] Received member.left event for unknown member ID: ${memberLeftEvent.member.member_id}`);
|
|
4033
4066
|
});
|
|
4034
4067
|
this.subscribeTo(this.webRtcCallSession.callUpdated$, (callUpdatedEvent) => {
|
|
4035
|
-
logger$
|
|
4068
|
+
logger$22.debug("[CallEventsManager] Handling call.updated event:", callUpdatedEvent);
|
|
4036
4069
|
const roomSession = callUpdatedEvent.room_session;
|
|
4037
4070
|
this._sessionState$.next({
|
|
4038
4071
|
...this._sessionState$.value,
|
|
@@ -4047,7 +4080,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
4047
4080
|
});
|
|
4048
4081
|
});
|
|
4049
4082
|
this.subscribeTo(this.layoutChangedEvent$, (layoutChangedEvent) => {
|
|
4050
|
-
logger$
|
|
4083
|
+
logger$22.debug("[CallEventsManager] Handling layout.changed event:", layoutChangedEvent);
|
|
4051
4084
|
this._sessionState$.next({
|
|
4052
4085
|
...this._sessionState$.value,
|
|
4053
4086
|
layout_name: layoutChangedEvent.id,
|
|
@@ -4057,10 +4090,10 @@ var CallEventsManager = class extends Destroyable {
|
|
|
4057
4090
|
});
|
|
4058
4091
|
}
|
|
4059
4092
|
updateParticipantPositions(layoutChangedEvent) {
|
|
4060
|
-
if (Object.keys(this._participants$.value).length > 0 && !layoutChangedEvent.layers.some((layer) => !!layer.member_id)) logger$
|
|
4093
|
+
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.");
|
|
4061
4094
|
layoutChangedEvent.layers.filter((layer) => !!layer.member_id).filter((layer) => {
|
|
4062
4095
|
if (!(layer.member_id in this._participants$.value)) {
|
|
4063
|
-
logger$
|
|
4096
|
+
logger$22.warn(`[CallEventsManager] Skipping layout layer for unknown member_id: ${layer.member_id}`);
|
|
4064
4097
|
return false;
|
|
4065
4098
|
}
|
|
4066
4099
|
return true;
|
|
@@ -4083,7 +4116,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
4083
4116
|
layouts: response.result.layouts
|
|
4084
4117
|
});
|
|
4085
4118
|
}).catch((error) => {
|
|
4086
|
-
logger$
|
|
4119
|
+
logger$22.error("[CallEventsManager] Error fetching layouts:", error);
|
|
4087
4120
|
});
|
|
4088
4121
|
}
|
|
4089
4122
|
updateParticipants(members) {
|
|
@@ -4099,7 +4132,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
4099
4132
|
}
|
|
4100
4133
|
const participant = this._participants$.value[member.member_id];
|
|
4101
4134
|
const oldValue = participant.value;
|
|
4102
|
-
logger$
|
|
4135
|
+
logger$22.debug("[CallEventsManager] Updating participant:", member.member_id, {
|
|
4103
4136
|
oldValue,
|
|
4104
4137
|
newValue: member
|
|
4105
4138
|
});
|
|
@@ -4112,17 +4145,17 @@ var CallEventsManager = class extends Destroyable {
|
|
|
4112
4145
|
}
|
|
4113
4146
|
get callJoinedEvent$() {
|
|
4114
4147
|
return this.cachedObservable("callJoinedEvent$", () => this.webRtcCallSession.callEvent$.pipe((0, rxjs.filter)(isCallJoinedPayload), (0, rxjs.tap)((event) => {
|
|
4115
|
-
logger$
|
|
4148
|
+
logger$22.debug("[CallEventsManager] Call joined event:", event);
|
|
4116
4149
|
})));
|
|
4117
4150
|
}
|
|
4118
4151
|
get layoutChangedEvent$() {
|
|
4119
4152
|
return this.cachedObservable("layoutChangedEvent$", () => this.webRtcCallSession.callEvent$.pipe(require_operators.filterAs(isLayoutChangedPayload, "layout"), (0, rxjs.tap)((event) => {
|
|
4120
|
-
logger$
|
|
4153
|
+
logger$22.debug("[CallEventsManager] Layout changed event:", event);
|
|
4121
4154
|
})));
|
|
4122
4155
|
}
|
|
4123
4156
|
get memberUpdates$() {
|
|
4124
4157
|
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) => {
|
|
4125
|
-
logger$
|
|
4158
|
+
logger$22.debug("[CallEventsManager] Member update event:", event);
|
|
4126
4159
|
})));
|
|
4127
4160
|
}
|
|
4128
4161
|
destroy() {
|
|
@@ -4378,7 +4411,7 @@ function appendStereoParams(fmtpLine, maxBitrate) {
|
|
|
4378
4411
|
|
|
4379
4412
|
//#endregion
|
|
4380
4413
|
//#region src/controllers/ICEGatheringController.ts
|
|
4381
|
-
const logger$
|
|
4414
|
+
const logger$21 = require_operators.getLogger();
|
|
4382
4415
|
var ICEGatheringController = class extends Destroyable {
|
|
4383
4416
|
constructor(peerConnection, peerConnectionControllerNegotiating$, options = {}) {
|
|
4384
4417
|
super();
|
|
@@ -4386,23 +4419,23 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
4386
4419
|
this.peerConnectionControllerNegotiating$ = peerConnectionControllerNegotiating$;
|
|
4387
4420
|
this.onicegatheringstatechangeHandler = () => {
|
|
4388
4421
|
const { iceGatheringState } = this.peerConnection;
|
|
4389
|
-
logger$
|
|
4422
|
+
logger$21.debug(`[ICEGatheringController] ICE gathering state changed to: ${iceGatheringState}`);
|
|
4390
4423
|
if (iceGatheringState === "gathering") this._iceCandidatesState.next({
|
|
4391
4424
|
state: "gathering",
|
|
4392
4425
|
validSDP: false
|
|
4393
4426
|
});
|
|
4394
4427
|
};
|
|
4395
4428
|
this.onicecandidateHandler = (event) => {
|
|
4396
|
-
logger$
|
|
4429
|
+
logger$21.debug("[ICEGatheringController] ICE candidate event received:", event.candidate);
|
|
4397
4430
|
this.removeTimer("iceCandidateTimer");
|
|
4398
4431
|
if (event.candidate) this.iceCandidateTimer = setTimeout(() => {
|
|
4399
4432
|
if (this.peerConnection.iceGatheringState !== "complete") {
|
|
4400
|
-
logger$
|
|
4433
|
+
logger$21.warn("[ICEGatheringController] ICE candidate timeout, using current SDP");
|
|
4401
4434
|
this.handleICECandidateTimeout();
|
|
4402
4435
|
}
|
|
4403
4436
|
}, this.iceCandidateTimeout);
|
|
4404
4437
|
else {
|
|
4405
|
-
logger$
|
|
4438
|
+
logger$21.debug("[ICEGatheringController] ICE gathering completed: null candidate received");
|
|
4406
4439
|
this.removeTimer("iceGatheringTimer");
|
|
4407
4440
|
this.handleICEGatheringComplete();
|
|
4408
4441
|
}
|
|
@@ -4420,7 +4453,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
4420
4453
|
this.setupEventListeners();
|
|
4421
4454
|
this.iceGatheringTimer = setTimeout(() => {
|
|
4422
4455
|
if (this.peerConnection.iceGatheringState !== "complete") {
|
|
4423
|
-
logger$
|
|
4456
|
+
logger$21.warn("[ICEGatheringController] ICE gathering timeout, using current SDP");
|
|
4424
4457
|
this.handleICEGatheringTimeout();
|
|
4425
4458
|
}
|
|
4426
4459
|
}, this.iceGatheringTimeout);
|
|
@@ -4447,9 +4480,9 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
4447
4480
|
this.relayOnly = value;
|
|
4448
4481
|
}
|
|
4449
4482
|
handleICEGatheringComplete() {
|
|
4450
|
-
logger$
|
|
4451
|
-
logger$
|
|
4452
|
-
logger$
|
|
4483
|
+
logger$21.debug("[ICEGatheringController] Handling ICE gathering complete");
|
|
4484
|
+
logger$21.debug(`[ICEGatheringController] Checking ICE gathering state: ${this.peerConnection.iceGatheringState}`);
|
|
4485
|
+
logger$21.debug("[ICEGatheringController] ICE gathering complete");
|
|
4453
4486
|
this._iceCandidatesState.next({
|
|
4454
4487
|
state: "complete",
|
|
4455
4488
|
validSDP: this.hasValidLocalDescriptionSDP
|
|
@@ -4465,21 +4498,21 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
4465
4498
|
this.removeTimer("iceGatheringTimer");
|
|
4466
4499
|
const validSDP = this.hasValidLocalDescriptionSDP;
|
|
4467
4500
|
if (validSDP) {
|
|
4468
|
-
logger$
|
|
4501
|
+
logger$21.debug("[ICEGatheringController] Local SDP is valid");
|
|
4469
4502
|
this._iceCandidatesState.next({
|
|
4470
4503
|
state: "timeout",
|
|
4471
4504
|
validSDP
|
|
4472
4505
|
});
|
|
4473
4506
|
this.stopGathering();
|
|
4474
|
-
} else logger$
|
|
4507
|
+
} else logger$21.debug("### ICE gathering timeout\n", this.peerConnection.localDescription?.sdp);
|
|
4475
4508
|
}
|
|
4476
4509
|
handleICECandidateTimeout() {
|
|
4477
4510
|
if (this.iceCandidateTimer) this.removeTimer("iceCandidateTimer");
|
|
4478
|
-
logger$
|
|
4511
|
+
logger$21.warn("[ICEGatheringController] ICE candidate timeout");
|
|
4479
4512
|
const validSDP = this.hasValidLocalDescriptionSDP;
|
|
4480
4513
|
if (!validSDP && !this.relayOnly) this.restartICEGatheringWithRelayOnly();
|
|
4481
4514
|
else {
|
|
4482
|
-
logger$
|
|
4515
|
+
logger$21.debug("[ICEGatheringController] Using current SDP due to ICE candidate timeout");
|
|
4483
4516
|
this._iceCandidatesState.next({
|
|
4484
4517
|
state: "timeout",
|
|
4485
4518
|
validSDP
|
|
@@ -4488,7 +4521,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
4488
4521
|
}
|
|
4489
4522
|
}
|
|
4490
4523
|
restartICEGatheringWithRelayOnly() {
|
|
4491
|
-
logger$
|
|
4524
|
+
logger$21.debug("[ICEGatheringController] Restarting ICE gathering with relay-only candidates");
|
|
4492
4525
|
this.relayOnly = true;
|
|
4493
4526
|
this.peerConnection.setConfiguration({
|
|
4494
4527
|
...this.peerConnection.getConfiguration(),
|
|
@@ -4503,7 +4536,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
4503
4536
|
}
|
|
4504
4537
|
}
|
|
4505
4538
|
clearAllTimers() {
|
|
4506
|
-
logger$
|
|
4539
|
+
logger$21.debug("[ICEGatheringController] Clearing all timers");
|
|
4507
4540
|
this.removeTimer("iceGatheringTimer");
|
|
4508
4541
|
this.removeTimer("iceCandidateTimer");
|
|
4509
4542
|
}
|
|
@@ -4512,7 +4545,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
4512
4545
|
this.peerConnection.removeEventListener("icecandidate", this.onicecandidateHandler);
|
|
4513
4546
|
}
|
|
4514
4547
|
destroy() {
|
|
4515
|
-
logger$
|
|
4548
|
+
logger$21.debug("[ICEGatheringController] Destroying ICEGatheringController");
|
|
4516
4549
|
this.clearAllTimers();
|
|
4517
4550
|
this.removeEventListeners();
|
|
4518
4551
|
super.destroy();
|
|
@@ -4521,7 +4554,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
4521
4554
|
|
|
4522
4555
|
//#endregion
|
|
4523
4556
|
//#region src/controllers/LocalAudioPipeline.ts
|
|
4524
|
-
const logger$
|
|
4557
|
+
const logger$20 = require_operators.getLogger();
|
|
4525
4558
|
/**
|
|
4526
4559
|
* Web Audio pipeline for the local microphone stream.
|
|
4527
4560
|
*
|
|
@@ -4633,7 +4666,7 @@ var LocalAudioPipeline = class extends Destroyable {
|
|
|
4633
4666
|
try {
|
|
4634
4667
|
this._inputSource.disconnect();
|
|
4635
4668
|
} catch (error) {
|
|
4636
|
-
logger$
|
|
4669
|
+
logger$20.debug("[LocalAudioPipeline] input disconnect warning:", error);
|
|
4637
4670
|
}
|
|
4638
4671
|
this._inputSource = null;
|
|
4639
4672
|
}
|
|
@@ -4643,7 +4676,7 @@ var LocalAudioPipeline = class extends Destroyable {
|
|
|
4643
4676
|
this._inputSource = this._audioContext.createMediaStreamSource(this._inputStream);
|
|
4644
4677
|
this._inputSource.connect(this._gainNode);
|
|
4645
4678
|
if (this._audioContext.state === "suspended") this._audioContext.resume().catch((error) => {
|
|
4646
|
-
logger$
|
|
4679
|
+
logger$20.warn("[LocalAudioPipeline] AudioContext resume failed:", error);
|
|
4647
4680
|
});
|
|
4648
4681
|
}
|
|
4649
4682
|
destroy() {
|
|
@@ -4658,7 +4691,7 @@ var LocalAudioPipeline = class extends Destroyable {
|
|
|
4658
4691
|
this._analyser.disconnect();
|
|
4659
4692
|
} catch {}
|
|
4660
4693
|
this._audioContext.close().catch((error) => {
|
|
4661
|
-
logger$
|
|
4694
|
+
logger$20.debug("[LocalAudioPipeline] audio context close warning:", error);
|
|
4662
4695
|
});
|
|
4663
4696
|
super.destroy();
|
|
4664
4697
|
}
|
|
@@ -4684,7 +4717,7 @@ var LocalAudioPipeline = class extends Destroyable {
|
|
|
4684
4717
|
|
|
4685
4718
|
//#endregion
|
|
4686
4719
|
//#region src/controllers/LocalStreamController.ts
|
|
4687
|
-
const logger$
|
|
4720
|
+
const logger$19 = require_operators.getLogger();
|
|
4688
4721
|
var LocalStreamController = class extends Destroyable {
|
|
4689
4722
|
constructor(options) {
|
|
4690
4723
|
super();
|
|
@@ -4722,26 +4755,26 @@ var LocalStreamController = class extends Destroyable {
|
|
|
4722
4755
|
* Build the local media stream based on the provided options.
|
|
4723
4756
|
*/
|
|
4724
4757
|
async buildLocalStream() {
|
|
4725
|
-
logger$
|
|
4758
|
+
logger$19.debug("[LocalStreamController] Building local media stream.");
|
|
4726
4759
|
let stream;
|
|
4727
4760
|
if (this.options.inputAudioStream ?? this.options.inputVideoStream) {
|
|
4728
4761
|
const tracks = [...this.options.inputAudioStream?.getTracks() ?? [], ...this.options.inputVideoStream?.getTracks() ?? []];
|
|
4729
4762
|
stream = new MediaStream(tracks);
|
|
4730
4763
|
} else if (this.options.propose === "screenshare") {
|
|
4731
|
-
logger$
|
|
4764
|
+
logger$19.debug("[LocalStreamController] Requesting display media for screen sharing with audio:", Boolean(this.options.inputAudioDeviceConstraints));
|
|
4732
4765
|
stream = await this.options.getDisplayMedia({
|
|
4733
4766
|
video: true,
|
|
4734
4767
|
audio: Boolean(this.options.inputAudioDeviceConstraints)
|
|
4735
4768
|
});
|
|
4736
|
-
logger$
|
|
4769
|
+
logger$19.debug("[LocalStreamController] Screen share media obtained:", stream);
|
|
4737
4770
|
} else {
|
|
4738
4771
|
const constraints = {
|
|
4739
4772
|
audio: this.options.inputAudioDeviceConstraints,
|
|
4740
4773
|
video: this.options.inputVideoDeviceConstraints
|
|
4741
4774
|
};
|
|
4742
|
-
logger$
|
|
4775
|
+
logger$19.debug("[LocalStreamController] Requesting user media with constraints:", constraints);
|
|
4743
4776
|
stream = await this.options.getUserMedia(constraints);
|
|
4744
|
-
logger$
|
|
4777
|
+
logger$19.debug("[LocalStreamController] User media obtained:", stream);
|
|
4745
4778
|
}
|
|
4746
4779
|
this._localStream$.next(stream);
|
|
4747
4780
|
this._localAudioTracks$.next(stream.getAudioTracks());
|
|
@@ -4760,7 +4793,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
4760
4793
|
this._localStream$.next(localStream);
|
|
4761
4794
|
if (track.kind === "video") this._localVideoTracks$.next(localStream.getVideoTracks());
|
|
4762
4795
|
else this._localAudioTracks$.next(localStream.getAudioTracks());
|
|
4763
|
-
logger$
|
|
4796
|
+
logger$19.debug(`[LocalStreamController] ${track.kind} track added:`, track.id);
|
|
4764
4797
|
return localStream;
|
|
4765
4798
|
}
|
|
4766
4799
|
/**
|
|
@@ -4772,7 +4805,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
4772
4805
|
const stream = this._localStream$.value;
|
|
4773
4806
|
const track = stream?.getTracks().find((t) => t.id === trackId);
|
|
4774
4807
|
if (!track) {
|
|
4775
|
-
logger$
|
|
4808
|
+
logger$19.debug(`[LocalStreamController] track not found: ${trackId}`);
|
|
4776
4809
|
return;
|
|
4777
4810
|
}
|
|
4778
4811
|
track.removeEventListener("ended", this.mediaTrackEndedHandler);
|
|
@@ -4781,7 +4814,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
4781
4814
|
this._localStream$.next(stream);
|
|
4782
4815
|
if (track.kind === "video") this._localVideoTracks$.next(stream?.getVideoTracks() ?? []);
|
|
4783
4816
|
else this._localAudioTracks$.next(stream?.getAudioTracks() ?? []);
|
|
4784
|
-
logger$
|
|
4817
|
+
logger$19.debug(`[LocalStreamController] ${track.kind} track removed:`, trackId);
|
|
4785
4818
|
return track;
|
|
4786
4819
|
}
|
|
4787
4820
|
/**
|
|
@@ -4816,7 +4849,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
4816
4849
|
*/
|
|
4817
4850
|
stopAllTracks() {
|
|
4818
4851
|
this._localStream$.value?.getTracks().forEach((track) => {
|
|
4819
|
-
logger$
|
|
4852
|
+
logger$19.debug(`[LocalStreamController] Stopping local track: ${track.kind}`);
|
|
4820
4853
|
track.removeEventListener("ended", this.mediaTrackEndedHandler);
|
|
4821
4854
|
track.stop();
|
|
4822
4855
|
});
|
|
@@ -4832,7 +4865,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
4832
4865
|
|
|
4833
4866
|
//#endregion
|
|
4834
4867
|
//#region src/controllers/TransceiverController.ts
|
|
4835
|
-
const logger$
|
|
4868
|
+
const logger$18 = require_operators.getLogger();
|
|
4836
4869
|
const getDirection = (send, recv) => {
|
|
4837
4870
|
if (send && recv) return "sendrecv";
|
|
4838
4871
|
else if (send && !recv) return "sendonly";
|
|
@@ -4940,7 +4973,7 @@ var TransceiverController = class extends Destroyable {
|
|
|
4940
4973
|
sendEncodings: isAudio ? void 0 : this.sendEncodings,
|
|
4941
4974
|
streams: direction === "recvonly" ? void 0 : [localStream]
|
|
4942
4975
|
};
|
|
4943
|
-
logger$
|
|
4976
|
+
logger$18.debug(`[TransceiverController] Setting up transceiver sender for local ${track.kind} track:`, {
|
|
4944
4977
|
transceiver,
|
|
4945
4978
|
transceiverParams
|
|
4946
4979
|
});
|
|
@@ -4948,11 +4981,11 @@ var TransceiverController = class extends Destroyable {
|
|
|
4948
4981
|
await transceiver.sender.replaceTrack(track);
|
|
4949
4982
|
transceiver.direction = transceiverParams.direction;
|
|
4950
4983
|
if (transceiverParams.streams?.some((stream) => Boolean(stream))) {
|
|
4951
|
-
logger$
|
|
4984
|
+
logger$18.debug(`[TransceiverController] Setting streams for transceiver sender for local ${track.kind} track:`, transceiverParams.streams);
|
|
4952
4985
|
transceiver.sender.setStreams(...transceiverParams.streams);
|
|
4953
4986
|
}
|
|
4954
4987
|
} else {
|
|
4955
|
-
logger$
|
|
4988
|
+
logger$18.debug(`[TransceiverController] Adding new transceiver for local ${track.kind} track:`, track.id);
|
|
4956
4989
|
this.peerConnection.addTransceiver(track, transceiverParams);
|
|
4957
4990
|
}
|
|
4958
4991
|
}
|
|
@@ -4966,13 +4999,13 @@ var TransceiverController = class extends Destroyable {
|
|
|
4966
4999
|
if (options.updateTransceiverDirection) transceiver.direction = "inactive";
|
|
4967
5000
|
}
|
|
4968
5001
|
} catch (error) {
|
|
4969
|
-
logger$
|
|
5002
|
+
logger$18.error("[TransceiverController] stopTrackSender error", kind, error);
|
|
4970
5003
|
this.options.onError?.(new require_operators.MediaTrackError("stopTrackSender", kind, error));
|
|
4971
5004
|
}
|
|
4972
5005
|
}
|
|
4973
5006
|
async restoreTrackSender(kind) {
|
|
4974
5007
|
try {
|
|
4975
|
-
logger$
|
|
5008
|
+
logger$18.debug("[TransceiverController] restoreTrackSender called", kind);
|
|
4976
5009
|
const constraints = {};
|
|
4977
5010
|
const transceivers = this.transceiverByKind(kind);
|
|
4978
5011
|
for (const transceiver of transceivers) {
|
|
@@ -4982,23 +5015,23 @@ var TransceiverController = class extends Destroyable {
|
|
|
4982
5015
|
if (trackKind === "audio" || trackKind === "video") constraints[trackKind] = this.getConstraintsFor(trackKind);
|
|
4983
5016
|
}
|
|
4984
5017
|
}
|
|
4985
|
-
logger$
|
|
5018
|
+
logger$18.debug("[TransceiverController] restoreTrackSender constraints:", constraints);
|
|
4986
5019
|
if (Object.keys(constraints).length === 0) {
|
|
4987
|
-
logger$
|
|
5020
|
+
logger$18.warn("[TransceiverController] restoreTrackSender: no tracks need restoration", kind);
|
|
4988
5021
|
return;
|
|
4989
5022
|
}
|
|
4990
5023
|
const newTracks = (await this.options.getUserMedia(constraints)).getTracks();
|
|
4991
|
-
logger$
|
|
5024
|
+
logger$18.debug("[TransceiverController] restoreTrackSender new tracks:", newTracks);
|
|
4992
5025
|
for (const newTrack of newTracks) {
|
|
4993
5026
|
this.options.localStreamController.addTrack(newTrack);
|
|
4994
5027
|
const trackKind = newTrack.kind;
|
|
4995
5028
|
const transceiverOfKind = this.transceiverByKind(trackKind)[0];
|
|
4996
5029
|
transceiverOfKind.direction = trackKind === "audio" ? this.audioDirection : this.videoDirection;
|
|
4997
|
-
logger$
|
|
5030
|
+
logger$18.debug("[TransceiverController] restoreTrackSender setting direction for", trackKind, transceiverOfKind.direction);
|
|
4998
5031
|
await transceiverOfKind.sender.replaceTrack(newTrack);
|
|
4999
5032
|
}
|
|
5000
5033
|
} catch (error) {
|
|
5001
|
-
logger$
|
|
5034
|
+
logger$18.error("[TransceiverController] restoreTrackSender error", kind, error);
|
|
5002
5035
|
this.options.onError?.(new require_operators.MediaTrackError("restoreTrackSender", kind, error));
|
|
5003
5036
|
}
|
|
5004
5037
|
}
|
|
@@ -5039,14 +5072,14 @@ var TransceiverController = class extends Destroyable {
|
|
|
5039
5072
|
};
|
|
5040
5073
|
try {
|
|
5041
5074
|
await track.applyConstraints(constraintsToApply);
|
|
5042
|
-
logger$
|
|
5043
|
-
logger$
|
|
5075
|
+
logger$18.debug(`[TransceiverController] Updated ${kind} sender constraints:`, constraintsToApply);
|
|
5076
|
+
logger$18.debug(`[TransceiverController] Updated ${kind} sender constraints:`, track.getConstraints());
|
|
5044
5077
|
} catch (error) {
|
|
5045
|
-
logger$
|
|
5078
|
+
logger$18.warn(`[TransceiverController] applyConstraints failed for ${kind} track ${track.id}, attempting track replacement fallback:`, error);
|
|
5046
5079
|
try {
|
|
5047
5080
|
await this.replaceTrackFallback(sender, track, kind, constraintsToApply);
|
|
5048
5081
|
} catch (fallbackError) {
|
|
5049
|
-
logger$
|
|
5082
|
+
logger$18.warn(`[TransceiverController] Track replacement fallback also failed for ${kind} track:`, fallbackError);
|
|
5050
5083
|
this.options.onError?.(new require_operators.MediaTrackError("updateSendersConstraints", kind, fallbackError));
|
|
5051
5084
|
}
|
|
5052
5085
|
}
|
|
@@ -5074,7 +5107,7 @@ var TransceiverController = class extends Destroyable {
|
|
|
5074
5107
|
if (!newTrack) throw new require_operators.MediaTrackError("replaceTrackFallback", kind, /* @__PURE__ */ new Error("getUserMedia returned no track of the requested kind"));
|
|
5075
5108
|
await sender.replaceTrack(newTrack);
|
|
5076
5109
|
this.options.localStreamController.addTrack(newTrack);
|
|
5077
|
-
logger$
|
|
5110
|
+
logger$18.debug(`[TransceiverController] Track replacement fallback succeeded for ${kind}. New track: ${newTrack.id}`);
|
|
5078
5111
|
}
|
|
5079
5112
|
getMediaDirections() {
|
|
5080
5113
|
if (this.peerConnection.connectionState === "connected") return this.peerConnection.getTransceivers().reduce((acc, transceiver) => {
|
|
@@ -5104,7 +5137,7 @@ var TransceiverController = class extends Destroyable {
|
|
|
5104
5137
|
|
|
5105
5138
|
//#endregion
|
|
5106
5139
|
//#region src/controllers/RTCPeerConnectionController.ts
|
|
5107
|
-
const logger$
|
|
5140
|
+
const logger$17 = require_operators.getLogger();
|
|
5108
5141
|
var RTCPeerConnectionController = class extends Destroyable {
|
|
5109
5142
|
constructor(options = {}, remoteSessionDescription, deviceController) {
|
|
5110
5143
|
super();
|
|
@@ -5120,43 +5153,43 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5120
5153
|
this.oniceconnectionstatechangeHandler = () => {
|
|
5121
5154
|
if (this.peerConnection) {
|
|
5122
5155
|
const { iceConnectionState } = this.peerConnection;
|
|
5123
|
-
logger$
|
|
5156
|
+
logger$17.debug(`[RTCPeerConnectionController] ICE connection state changed to: ${iceConnectionState}`);
|
|
5124
5157
|
this._iceConnectionState$.next(this.peerConnection.iceConnectionState);
|
|
5125
5158
|
}
|
|
5126
5159
|
};
|
|
5127
5160
|
this.onconnectionstatechangeHandler = () => {
|
|
5128
5161
|
if (this.peerConnection) {
|
|
5129
5162
|
const { connectionState } = this.peerConnection;
|
|
5130
|
-
logger$
|
|
5163
|
+
logger$17.debug(`[RTCPeerConnectionController] Connection state changed to: ${connectionState}`);
|
|
5131
5164
|
if (connectionState === "connected") this.removeConnectionTimer();
|
|
5132
5165
|
this._connectionState$.next(this.peerConnection.connectionState);
|
|
5133
5166
|
}
|
|
5134
5167
|
};
|
|
5135
5168
|
this.onsignalingstatechangeHandler = () => {
|
|
5136
|
-
logger$
|
|
5169
|
+
logger$17.debug(`[RTCPeerConnectionController] Signaling state changed to: ${this.peerConnection?.signalingState}`);
|
|
5137
5170
|
};
|
|
5138
5171
|
this.onicegatheringstatechangeHandler = () => {
|
|
5139
5172
|
if (this.peerConnection) this._iceGatheringState$.next(this.peerConnection.iceGatheringState);
|
|
5140
5173
|
};
|
|
5141
5174
|
this.onnegotiationneededHandler = (event) => {
|
|
5142
|
-
logger$
|
|
5175
|
+
logger$17.debug("[RTCPeerConnectionController] Negotiation needed event received.", event);
|
|
5143
5176
|
this.negotiationNeeded$.next();
|
|
5144
5177
|
};
|
|
5145
5178
|
this.updateSelectedInputDevice = async (kind, deviceInfo) => {
|
|
5146
5179
|
try {
|
|
5147
5180
|
const { localStream } = this;
|
|
5148
5181
|
if (!localStream) {
|
|
5149
|
-
logger$
|
|
5182
|
+
logger$17.warn("[RTCPeerConnectionController] No local stream available to update input device.");
|
|
5150
5183
|
return;
|
|
5151
5184
|
}
|
|
5152
|
-
logger$
|
|
5185
|
+
logger$17.debug(`[RTCPeerConnectionController] Updating selected ${kind} input device:`, localStream.getTracks());
|
|
5153
5186
|
const track = localStream.getTracks().find((track$1) => track$1.kind === kind);
|
|
5154
5187
|
if (track) {
|
|
5155
5188
|
this.transceiverController?.stopTrackSender(kind);
|
|
5156
5189
|
this.localStreamController.removeTrack(track.id);
|
|
5157
|
-
logger$
|
|
5190
|
+
logger$17.debug(`[RTCPeerConnectionController] Stopped existing ${kind} track: ${track.id}`, localStream.getTracks());
|
|
5158
5191
|
if (!deviceInfo) {
|
|
5159
|
-
logger$
|
|
5192
|
+
logger$17.debug(`[RTCPeerConnectionController] ${kind} input device selected: none`);
|
|
5160
5193
|
return;
|
|
5161
5194
|
}
|
|
5162
5195
|
const streamTrack = (await this.getUserMedia({ [kind]: {
|
|
@@ -5164,15 +5197,15 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5164
5197
|
...this.deviceController.deviceInfoToConstraints(deviceInfo)
|
|
5165
5198
|
} })).getTracks().find((t) => t.kind === kind);
|
|
5166
5199
|
if (streamTrack) {
|
|
5167
|
-
logger$
|
|
5200
|
+
logger$17.debug(`[RTCPeerConnectionController] Adding new ${kind} track: ${streamTrack.id}`);
|
|
5168
5201
|
this.localStreamController.addTrack(streamTrack);
|
|
5169
5202
|
await this.transceiverController?.replaceSenderTrack(kind, streamTrack);
|
|
5170
|
-
logger$
|
|
5203
|
+
logger$17.debug(`[RTCPeerConnectionController] Added new ${kind} track: ${streamTrack.id}`, this.localStream?.getTracks());
|
|
5171
5204
|
}
|
|
5172
5205
|
}
|
|
5173
|
-
logger$
|
|
5206
|
+
logger$17.debug(`[RTCPeerConnectionController] ${kind} input device selected:`, deviceInfo?.label);
|
|
5174
5207
|
} catch (error) {
|
|
5175
|
-
logger$
|
|
5208
|
+
logger$17.error(`[RTCPeerConnectionController] Failed to select ${kind} input device:`, error);
|
|
5176
5209
|
this._errors$.next(toError(error));
|
|
5177
5210
|
throw error;
|
|
5178
5211
|
}
|
|
@@ -5422,15 +5455,15 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5422
5455
|
this.setupPeerConnection();
|
|
5423
5456
|
this.subscribeTo(this.negotiationNeeded$.pipe((0, rxjs.auditTime)(0), (0, rxjs.exhaustMap)(async () => this.startNegotiation())), {
|
|
5424
5457
|
next: () => {
|
|
5425
|
-
logger$
|
|
5458
|
+
logger$17.debug("[RTCPeerConnectionController] Start Negotiation completed successfully");
|
|
5426
5459
|
},
|
|
5427
5460
|
error: (error) => {
|
|
5428
|
-
logger$
|
|
5461
|
+
logger$17.error("[RTCPeerConnectionController] Start Negotiation error:", error);
|
|
5429
5462
|
this._errors$.next(toError(error));
|
|
5430
5463
|
}
|
|
5431
5464
|
});
|
|
5432
5465
|
this.subscribeTo((0, rxjs.merge)(this.deviceController.selectedAudioInputDevice$.pipe((0, rxjs.map)((deviceInfo) => ["audio", deviceInfo])), this.deviceController.selectedVideoInputDevice$.pipe((0, rxjs.map)((deviceInfo) => ["video", deviceInfo]))).pipe((0, rxjs.skipWhile)(() => !this.localStreamController.localStream)), async ([kind, deviceInfo]) => {
|
|
5433
|
-
logger$
|
|
5466
|
+
logger$17.debug(`[RTCPeerConnectionController] Selected input device changed for:`, {
|
|
5434
5467
|
kind,
|
|
5435
5468
|
deviceInfo
|
|
5436
5469
|
});
|
|
@@ -5447,7 +5480,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5447
5480
|
this._initialized$.next(true);
|
|
5448
5481
|
}
|
|
5449
5482
|
} catch (error) {
|
|
5450
|
-
logger$
|
|
5483
|
+
logger$17.error("[RTCPeerConnectionController] Initialization error:", error);
|
|
5451
5484
|
this._errors$.next(toError(error));
|
|
5452
5485
|
this.destroy();
|
|
5453
5486
|
}
|
|
@@ -5479,22 +5512,22 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5479
5512
|
}
|
|
5480
5513
|
async startNegotiation() {
|
|
5481
5514
|
if (this.isNegotiating) {
|
|
5482
|
-
logger$
|
|
5515
|
+
logger$17.debug("[RTCPeerConnectionController] Negotiation already in progress, skipping.");
|
|
5483
5516
|
return;
|
|
5484
5517
|
}
|
|
5485
5518
|
this.setupEventListeners();
|
|
5486
5519
|
if (this.type === "answer") {
|
|
5487
|
-
logger$
|
|
5520
|
+
logger$17.debug("[RTCPeerConnectionController] This is an answer type still, skipping offer creation.");
|
|
5488
5521
|
return;
|
|
5489
5522
|
}
|
|
5490
5523
|
this._isNegotiating$.next(true);
|
|
5491
|
-
logger$
|
|
5524
|
+
logger$17.debug("[RTCPeerConnectionController] Starting negotiation.");
|
|
5492
5525
|
try {
|
|
5493
5526
|
const { offerOptions } = this;
|
|
5494
|
-
logger$
|
|
5527
|
+
logger$17.debug("[RTCPeerConnectionController] Creating offer with options:", offerOptions);
|
|
5495
5528
|
await this.createOffer(offerOptions);
|
|
5496
5529
|
} catch (error) {
|
|
5497
|
-
logger$
|
|
5530
|
+
logger$17.error("[RTCPeerConnectionController] Error during negotiation:", error);
|
|
5498
5531
|
this._errors$.next(toError(error));
|
|
5499
5532
|
}
|
|
5500
5533
|
}
|
|
@@ -5510,14 +5543,14 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5510
5543
|
let readyToConnect = status !== "failed";
|
|
5511
5544
|
try {
|
|
5512
5545
|
if (status === "received" && sdp) {
|
|
5513
|
-
logger$
|
|
5546
|
+
logger$17.debug("[RTCPeerConnectionController] Received answer SDP:", sdp);
|
|
5514
5547
|
await this._setRemoteDescription({
|
|
5515
5548
|
type: "answer",
|
|
5516
5549
|
sdp
|
|
5517
5550
|
});
|
|
5518
5551
|
}
|
|
5519
5552
|
} catch (error) {
|
|
5520
|
-
logger$
|
|
5553
|
+
logger$17.error("[RTCPeerConnectionController] Error updating answer status:", error);
|
|
5521
5554
|
this._errors$.next(toError(error));
|
|
5522
5555
|
readyToConnect = false;
|
|
5523
5556
|
} finally {
|
|
@@ -5536,7 +5569,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5536
5569
|
await this.handleOfferReceived();
|
|
5537
5570
|
break;
|
|
5538
5571
|
case "failed":
|
|
5539
|
-
logger$
|
|
5572
|
+
logger$17.error("[RTCPeerConnectionController] Offer failed to be processed by remote.");
|
|
5540
5573
|
break;
|
|
5541
5574
|
case "sent":
|
|
5542
5575
|
default:
|
|
@@ -5568,7 +5601,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5568
5601
|
}
|
|
5569
5602
|
await this.setupLocalTracks();
|
|
5570
5603
|
const { answerOptions } = this;
|
|
5571
|
-
logger$
|
|
5604
|
+
logger$17.debug("[RTCPeerConnectionController] Creating inbound answer with options:", answerOptions);
|
|
5572
5605
|
await this.createAnswer(answerOptions);
|
|
5573
5606
|
}
|
|
5574
5607
|
async handleOfferReceived() {
|
|
@@ -5576,7 +5609,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5576
5609
|
this._isNegotiating$.next(true);
|
|
5577
5610
|
await this._setRemoteDescription(this.sdpInit);
|
|
5578
5611
|
const { answerOptions } = this;
|
|
5579
|
-
logger$
|
|
5612
|
+
logger$17.debug("[RTCPeerConnectionController] Creating answer with options:", answerOptions);
|
|
5580
5613
|
await this.createAnswer(answerOptions);
|
|
5581
5614
|
}
|
|
5582
5615
|
readyToConnect() {
|
|
@@ -5584,7 +5617,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5584
5617
|
this.connectionTimer = setTimeout(() => {
|
|
5585
5618
|
this.removeConnectionTimer();
|
|
5586
5619
|
if (this.peerConnection?.connectionState !== "connected") {
|
|
5587
|
-
logger$
|
|
5620
|
+
logger$17.debug("[RTCPeerConnectionController] Connection timeout, restarting ICE gathering with relay only.");
|
|
5588
5621
|
this.iceGatheringController.restartICEGatheringWithRelayOnly();
|
|
5589
5622
|
}
|
|
5590
5623
|
}, this.connectionTimeout);
|
|
@@ -5606,14 +5639,14 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5606
5639
|
const stereo = this.options.stereo ?? PreferencesContainer.instance.stereoAudio;
|
|
5607
5640
|
if (preferredAudioCodecs.length > 0 || preferredVideoCodecs.length > 0) {
|
|
5608
5641
|
result = setCodecPreferences(result, preferredAudioCodecs, preferredVideoCodecs);
|
|
5609
|
-
logger$
|
|
5642
|
+
logger$17.debug("[RTCPeerConnectionController] Applied codec preferences to SDP", {
|
|
5610
5643
|
preferredAudioCodecs,
|
|
5611
5644
|
preferredVideoCodecs
|
|
5612
5645
|
});
|
|
5613
5646
|
}
|
|
5614
5647
|
if (stereo) {
|
|
5615
5648
|
result = enableStereoOpus(result);
|
|
5616
|
-
logger$
|
|
5649
|
+
logger$17.debug("[RTCPeerConnectionController] Applied stereo Opus to SDP");
|
|
5617
5650
|
}
|
|
5618
5651
|
return Promise.resolve(result);
|
|
5619
5652
|
}
|
|
@@ -5669,25 +5702,25 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5669
5702
|
...this.peerConnection.getConfiguration(),
|
|
5670
5703
|
iceTransportPolicy: "relay"
|
|
5671
5704
|
});
|
|
5672
|
-
logger$
|
|
5705
|
+
logger$17.debug("[RTCPeerConnectionController] ICE transport policy set to relay-only");
|
|
5673
5706
|
} catch (error) {
|
|
5674
|
-
logger$
|
|
5707
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to set relay-only policy:", error);
|
|
5675
5708
|
}
|
|
5676
5709
|
this.setupEventListeners();
|
|
5677
5710
|
this._isNegotiating$.next(true);
|
|
5678
|
-
logger$
|
|
5711
|
+
logger$17.debug(`[RTCPeerConnectionController] Triggering ICE restart${relayOnly ? " (relay-only)" : ""}.`);
|
|
5679
5712
|
try {
|
|
5680
5713
|
const offer = await this.peerConnection.createOffer({ iceRestart: true });
|
|
5681
5714
|
await this.setLocalDescription(offer);
|
|
5682
5715
|
} catch (error) {
|
|
5683
|
-
logger$
|
|
5716
|
+
logger$17.error("[RTCPeerConnectionController] ICE restart offer failed:", error);
|
|
5684
5717
|
this._errors$.next(toError(error));
|
|
5685
5718
|
this.negotiationEnded();
|
|
5686
5719
|
if (policyChanged) this.restoreIceTransportPolicy();
|
|
5687
5720
|
throw error;
|
|
5688
5721
|
}
|
|
5689
5722
|
if (policyChanged) (0, rxjs.firstValueFrom)((0, rxjs.race)(this._iceGatheringState$.pipe((0, rxjs.filter)((state) => state === "complete"), (0, rxjs.take)(1)), (0, rxjs.timer)(ICE_GATHERING_COMPLETE_TIMEOUT_MS).pipe((0, rxjs.map)(() => "timeout")))).then(() => this.restoreIceTransportPolicy()).catch((error) => {
|
|
5690
|
-
logger$
|
|
5723
|
+
logger$17.warn("[RTCPeerConnectionController] Error waiting for ICE gathering to complete:", error);
|
|
5691
5724
|
this.restoreIceTransportPolicy();
|
|
5692
5725
|
});
|
|
5693
5726
|
}
|
|
@@ -5697,9 +5730,9 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5697
5730
|
...this.peerConnection.getConfiguration(),
|
|
5698
5731
|
iceTransportPolicy: this.options.relayOnly ? "relay" : "all"
|
|
5699
5732
|
});
|
|
5700
|
-
logger$
|
|
5733
|
+
logger$17.debug("[RTCPeerConnectionController] ICE transport policy restored");
|
|
5701
5734
|
} catch (error) {
|
|
5702
|
-
logger$
|
|
5735
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to restore ICE transport policy:", error);
|
|
5703
5736
|
}
|
|
5704
5737
|
}
|
|
5705
5738
|
/**
|
|
@@ -5711,13 +5744,13 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5711
5744
|
await this.setupRemoteTracks();
|
|
5712
5745
|
}
|
|
5713
5746
|
async setupLocalTracks() {
|
|
5714
|
-
logger$
|
|
5747
|
+
logger$17.debug("[RTCPeerConnectionController] Setting up local tracks/transceivers.");
|
|
5715
5748
|
const localStream = this.localStream ?? await this.localStreamController.buildLocalStream();
|
|
5716
5749
|
if (this.transceiverController?.useAddStream ?? false) {
|
|
5717
|
-
logger$
|
|
5750
|
+
logger$17.warn("[RTCPeerConnectionController] Using deprecated addStream API to add local stream.");
|
|
5718
5751
|
this.peerConnection?.addStream(localStream);
|
|
5719
5752
|
if (!this.isNegotiating) {
|
|
5720
|
-
logger$
|
|
5753
|
+
logger$17.debug("[RTCPeerConnectionController] Forcing negotiationneeded after local tracks setup.");
|
|
5721
5754
|
this.negotiationNeeded$.next();
|
|
5722
5755
|
}
|
|
5723
5756
|
return;
|
|
@@ -5733,7 +5766,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5733
5766
|
const transceivers = (kind === "audio" ? this.transceiverController?.audioTransceivers : this.transceiverController?.videoTransceivers) ?? [];
|
|
5734
5767
|
await this.transceiverController?.setupTransceiverSender(track, localStream, transceivers[index]);
|
|
5735
5768
|
} else {
|
|
5736
|
-
logger$
|
|
5769
|
+
logger$17.debug(`[RTCPeerConnectionController] Using addTrack for local ${kind} track:`, track.id);
|
|
5737
5770
|
this.peerConnection?.addTrack(track, localStream);
|
|
5738
5771
|
}
|
|
5739
5772
|
}
|
|
@@ -5750,7 +5783,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5750
5783
|
async setupRemoteTracks() {
|
|
5751
5784
|
if (!this.peerConnection) throw new require_operators.DependencyError("RTCPeerConnection is not initialized");
|
|
5752
5785
|
this.peerConnection.ontrack = (event) => {
|
|
5753
|
-
logger$
|
|
5786
|
+
logger$17.debug("[RTCPeerConnectionController] Remote track received:", event.track.kind);
|
|
5754
5787
|
if (event.streams[0]) this._remoteStream$.next(event.streams[0]);
|
|
5755
5788
|
else {
|
|
5756
5789
|
const existingTracks = this._remoteStream$.value?.getTracks() ?? [];
|
|
@@ -5774,7 +5807,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5774
5807
|
try {
|
|
5775
5808
|
stream = await this.getUserMedia({ audio: constraints });
|
|
5776
5809
|
} catch (error) {
|
|
5777
|
-
logger$
|
|
5810
|
+
logger$17.error("[RTCPeerConnectionController] Failed to re-acquire mic for pipeline restore:", error);
|
|
5778
5811
|
this._errors$.next(toError(error));
|
|
5779
5812
|
return;
|
|
5780
5813
|
}
|
|
@@ -5796,7 +5829,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5796
5829
|
try {
|
|
5797
5830
|
this._localAudioPipeline = new LocalAudioPipeline();
|
|
5798
5831
|
} catch (error) {
|
|
5799
|
-
logger$
|
|
5832
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to create LocalAudioPipeline:", error);
|
|
5800
5833
|
return null;
|
|
5801
5834
|
}
|
|
5802
5835
|
this.subscribeTo(this.localStreamController.localAudioTracks$, () => {
|
|
@@ -5818,7 +5851,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5818
5851
|
try {
|
|
5819
5852
|
await sender.replaceTrack(this._localAudioPipeline.outputTrack);
|
|
5820
5853
|
} catch (error) {
|
|
5821
|
-
logger$
|
|
5854
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to route audio sender through pipeline:", error);
|
|
5822
5855
|
}
|
|
5823
5856
|
}
|
|
5824
5857
|
/**
|
|
@@ -5834,9 +5867,9 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5834
5867
|
try {
|
|
5835
5868
|
const localStream = this.localStreamController.addTrack(track);
|
|
5836
5869
|
this.peerConnection.addTrack(track, localStream);
|
|
5837
|
-
logger$
|
|
5870
|
+
logger$17.debug(`[RTCPeerConnectionController] ${track.kind} track added:`, track.id);
|
|
5838
5871
|
} catch (error) {
|
|
5839
|
-
logger$
|
|
5872
|
+
logger$17.error(`[RTCPeerConnectionController] Failed to add ${track.kind} track:`, error);
|
|
5840
5873
|
this._errors$.next(toError(error));
|
|
5841
5874
|
throw error;
|
|
5842
5875
|
}
|
|
@@ -5853,15 +5886,15 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5853
5886
|
}
|
|
5854
5887
|
const sender = this.peerConnection.getSenders().find((sender$1) => sender$1.track?.id === trackId);
|
|
5855
5888
|
if (!sender) {
|
|
5856
|
-
logger$
|
|
5889
|
+
logger$17.debug(`[RTCPeerConnectionController] track not found: ${trackId}`);
|
|
5857
5890
|
return;
|
|
5858
5891
|
}
|
|
5859
5892
|
try {
|
|
5860
5893
|
this.peerConnection.removeTrack(sender);
|
|
5861
5894
|
this.localStreamController.removeTrack(trackId);
|
|
5862
|
-
logger$
|
|
5895
|
+
logger$17.debug(`[RTCPeerConnectionController] ${sender.track?.kind} track removed:`, trackId);
|
|
5863
5896
|
} catch (error) {
|
|
5864
|
-
logger$
|
|
5897
|
+
logger$17.error(`[RTCPeerConnectionController] Failed to remove ${sender.track?.kind} track:`, error);
|
|
5865
5898
|
this._errors$.next(toError(error));
|
|
5866
5899
|
throw error;
|
|
5867
5900
|
}
|
|
@@ -5888,7 +5921,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5888
5921
|
async replaceAudioTrackWithConstraints(constraints) {
|
|
5889
5922
|
const senders = this.peerConnection?.getSenders().filter((s) => s.track?.kind === "audio" && s.track.readyState === "live");
|
|
5890
5923
|
if (!senders || senders.length === 0) {
|
|
5891
|
-
logger$
|
|
5924
|
+
logger$17.warn("[RTCPeerConnectionController] No live audio sender to replace");
|
|
5892
5925
|
return;
|
|
5893
5926
|
}
|
|
5894
5927
|
for (const sender of senders) {
|
|
@@ -5906,7 +5939,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5906
5939
|
const newTrack = (await this.getUserMedia({ audio: mergedConstraints })).getAudioTracks()[0];
|
|
5907
5940
|
await sender.replaceTrack(newTrack);
|
|
5908
5941
|
this.localStreamController.addTrack(newTrack);
|
|
5909
|
-
logger$
|
|
5942
|
+
logger$17.debug(`[RTCPeerConnectionController] Audio track replaced for server-pushed params. New track: ${newTrack.id}`);
|
|
5910
5943
|
}
|
|
5911
5944
|
}
|
|
5912
5945
|
/**
|
|
@@ -5914,7 +5947,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5914
5947
|
* Completes all observables to prevent memory leaks.
|
|
5915
5948
|
*/
|
|
5916
5949
|
destroy() {
|
|
5917
|
-
logger$
|
|
5950
|
+
logger$17.debug(`[RTCPeerConnectionController] Destroying RTCPeerConnectionController. ${this.propose}`);
|
|
5918
5951
|
this.removeConnectionTimer();
|
|
5919
5952
|
this._iceGatheringController?.destroy();
|
|
5920
5953
|
this._localAudioPipeline?.destroy();
|
|
@@ -5940,7 +5973,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5940
5973
|
}
|
|
5941
5974
|
stopRemoteTracks() {
|
|
5942
5975
|
this._remoteStream$.value?.getTracks().forEach((track) => {
|
|
5943
|
-
logger$
|
|
5976
|
+
logger$17.debug(`[RTCPeerConnectionController] Stopping remote track: ${track.kind}`);
|
|
5944
5977
|
track.stop();
|
|
5945
5978
|
});
|
|
5946
5979
|
}
|
|
@@ -5957,7 +5990,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5957
5990
|
...params,
|
|
5958
5991
|
sdp: finalRemote
|
|
5959
5992
|
};
|
|
5960
|
-
logger$
|
|
5993
|
+
logger$17.debug("[RTCPeerConnectionController] Setting remote description:", answer);
|
|
5961
5994
|
return this.peerConnection.setRemoteDescription(answer);
|
|
5962
5995
|
}
|
|
5963
5996
|
};
|
|
@@ -5972,7 +6005,11 @@ function isVertoInviteMessage(value) {
|
|
|
5972
6005
|
const msg = value;
|
|
5973
6006
|
return msg.method === "verto.invite" && isObject(msg.params) && hasProperty(msg.params, "sdp") && hasProperty(msg.params, "callID");
|
|
5974
6007
|
}
|
|
5975
|
-
|
|
6008
|
+
/**
|
|
6009
|
+
* Guards inbound `verto.bye` frames (server → client). Outbound bye messages are
|
|
6010
|
+
* built locally and never type-guarded, so only the inbound shape is asserted.
|
|
6011
|
+
*/
|
|
6012
|
+
function isVertoByeInboundMessage(value) {
|
|
5976
6013
|
if (!isVertoMethodMessage(value)) return false;
|
|
5977
6014
|
return value.method === "verto.bye";
|
|
5978
6015
|
}
|
|
@@ -5992,10 +6029,18 @@ function isVertoMediaParamsInnerParams(value) {
|
|
|
5992
6029
|
function isVertoPingInnerParams(value) {
|
|
5993
6030
|
return isObject(value) && hasProperty(value, "jsonrpc") && value.jsonrpc === "2.0" && hasProperty(value, "method") && value.method === "verto.ping";
|
|
5994
6031
|
}
|
|
6032
|
+
/**
|
|
6033
|
+
* Guards the params of an inbound `verto.bye` frame (top-level `callID`). Use
|
|
6034
|
+
* this to recognise the bye payload extracted by `vertoBye$`, e.g. when racing
|
|
6035
|
+
* it against a boolean answer/reject signal.
|
|
6036
|
+
*/
|
|
6037
|
+
function isVertoByeInboundParamsGuard(value) {
|
|
6038
|
+
return isObject(value) && hasProperty(value, "callID") && typeof value.callID === "string";
|
|
6039
|
+
}
|
|
5995
6040
|
|
|
5996
6041
|
//#endregion
|
|
5997
6042
|
//#region src/managers/VertoManager.ts
|
|
5998
|
-
const logger$
|
|
6043
|
+
const logger$16 = require_operators.getLogger();
|
|
5999
6044
|
/**
|
|
6000
6045
|
* Decide what value goes on the `node_id` field of a `webrtc.verto` envelope.
|
|
6001
6046
|
*
|
|
@@ -6051,7 +6096,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6051
6096
|
try {
|
|
6052
6097
|
await this.executeVerto(vertoModifyMessage);
|
|
6053
6098
|
} catch (error) {
|
|
6054
|
-
logger$
|
|
6099
|
+
logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto hold:", error);
|
|
6055
6100
|
throw error;
|
|
6056
6101
|
}
|
|
6057
6102
|
}
|
|
@@ -6064,7 +6109,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6064
6109
|
try {
|
|
6065
6110
|
await this.executeVerto(vertoModifyMessage);
|
|
6066
6111
|
} catch (error) {
|
|
6067
|
-
logger$
|
|
6112
|
+
logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto unhold:", error);
|
|
6068
6113
|
throw error;
|
|
6069
6114
|
}
|
|
6070
6115
|
}
|
|
@@ -6117,7 +6162,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6117
6162
|
if (event.member_id) this.setSelfIdIfNull(event.member_id);
|
|
6118
6163
|
});
|
|
6119
6164
|
this.subscribeTo(this.vertoMedia$, (event) => {
|
|
6120
|
-
logger$
|
|
6165
|
+
logger$16.debug("[WebRTCManager] Received Verto media event (early media SDP):", event);
|
|
6121
6166
|
const { sdp, callID } = event;
|
|
6122
6167
|
this.emitMainSignalingStatus(callID, "ringing");
|
|
6123
6168
|
this._rtcPeerConnectionsMap.get(callID)?.updateAnswerStatus({
|
|
@@ -6126,7 +6171,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6126
6171
|
});
|
|
6127
6172
|
});
|
|
6128
6173
|
this.subscribeTo(this.vertoAnswer$, (event) => {
|
|
6129
|
-
logger$
|
|
6174
|
+
logger$16.debug("[WebRTCManager] Received Verto answer event:", event);
|
|
6130
6175
|
const { sdp, callID } = event;
|
|
6131
6176
|
this.emitMainSignalingStatus(callID, "connecting");
|
|
6132
6177
|
this._rtcPeerConnectionsMap.get(callID)?.updateAnswerStatus({
|
|
@@ -6135,7 +6180,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6135
6180
|
});
|
|
6136
6181
|
});
|
|
6137
6182
|
this.subscribeTo(this.vertoMediaParams$, (event) => {
|
|
6138
|
-
logger$
|
|
6183
|
+
logger$16.debug("[WebRTCManager] Received Verto mediaParams event:", event);
|
|
6139
6184
|
const { mediaParams, callID } = event;
|
|
6140
6185
|
const rtcPeerConnController = this._rtcPeerConnectionsMap.get(callID);
|
|
6141
6186
|
const { audio, video } = mediaParams;
|
|
@@ -6149,7 +6194,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6149
6194
|
timestamp: Date.now()
|
|
6150
6195
|
});
|
|
6151
6196
|
} catch (error) {
|
|
6152
|
-
logger$
|
|
6197
|
+
logger$16.warn("[WebRTCManager] Error applying server-pushed media params:", error);
|
|
6153
6198
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
6154
6199
|
}
|
|
6155
6200
|
})();
|
|
@@ -6171,13 +6216,13 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6171
6216
|
*/
|
|
6172
6217
|
setNodeIdIfNull(nodeId) {
|
|
6173
6218
|
if (!this._nodeId$.value && nodeId) {
|
|
6174
|
-
logger$
|
|
6219
|
+
logger$16.debug(`[WebRTCManager] Early node_id set: ${nodeId}`);
|
|
6175
6220
|
this._nodeId$.next(nodeId);
|
|
6176
6221
|
}
|
|
6177
6222
|
}
|
|
6178
6223
|
setSelfIdIfNull(selfId) {
|
|
6179
6224
|
if (!this._selfId$.value && selfId) {
|
|
6180
|
-
logger$
|
|
6225
|
+
logger$16.debug(`[WebRTCManager] Early selfId set: ${selfId}`);
|
|
6181
6226
|
this._selfId$.next(selfId);
|
|
6182
6227
|
}
|
|
6183
6228
|
}
|
|
@@ -6186,7 +6231,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6186
6231
|
const vertoPongMessage = VertoPong({ ...vertoPing });
|
|
6187
6232
|
await this.executeVerto(vertoPongMessage);
|
|
6188
6233
|
} catch (error) {
|
|
6189
|
-
logger$
|
|
6234
|
+
logger$16.warn("[WebRTCManager] Call might disconnect, error sending Verto pong:", error);
|
|
6190
6235
|
this.onError?.(new require_operators.VertoPongError(error));
|
|
6191
6236
|
}
|
|
6192
6237
|
}
|
|
@@ -6196,7 +6241,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6196
6241
|
if (audio) await this.mainPeerConnection.updateSendersConstraints("audio", audio);
|
|
6197
6242
|
if (video) await this.mainPeerConnection.updateSendersConstraints("video", video);
|
|
6198
6243
|
} catch (error) {
|
|
6199
|
-
logger$
|
|
6244
|
+
logger$16.warn("[WebRTCManager] Error updating media constraints:", error);
|
|
6200
6245
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
6201
6246
|
throw error;
|
|
6202
6247
|
}
|
|
@@ -6226,20 +6271,20 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6226
6271
|
try {
|
|
6227
6272
|
const pc = this.mainPeerConnection.peerConnection;
|
|
6228
6273
|
if (!pc) {
|
|
6229
|
-
logger$
|
|
6274
|
+
logger$16.warn("[WebRTCManager] No peer connection for keyframe request");
|
|
6230
6275
|
return;
|
|
6231
6276
|
}
|
|
6232
6277
|
const videoReceiver = pc.getReceivers().find((r) => r.track.kind === "video");
|
|
6233
6278
|
if (!videoReceiver) {
|
|
6234
|
-
logger$
|
|
6279
|
+
logger$16.warn("[WebRTCManager] No video receiver for keyframe request");
|
|
6235
6280
|
return;
|
|
6236
6281
|
}
|
|
6237
6282
|
if (typeof videoReceiver.requestKeyFrame === "function") {
|
|
6238
6283
|
videoReceiver.requestKeyFrame();
|
|
6239
|
-
logger$
|
|
6240
|
-
} else logger$
|
|
6284
|
+
logger$16.debug("[WebRTCManager] Keyframe requested via RTCRtpReceiver.requestKeyFrame()");
|
|
6285
|
+
} else logger$16.debug("[WebRTCManager] requestKeyFrame() not supported, skipping");
|
|
6241
6286
|
} catch (error) {
|
|
6242
|
-
logger$
|
|
6287
|
+
logger$16.warn("[WebRTCManager] Keyframe request failed (non-fatal):", error);
|
|
6243
6288
|
}
|
|
6244
6289
|
}
|
|
6245
6290
|
/**
|
|
@@ -6257,13 +6302,13 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6257
6302
|
try {
|
|
6258
6303
|
const controller = this.mainPeerConnection;
|
|
6259
6304
|
if (!controller.peerConnection) {
|
|
6260
|
-
logger$
|
|
6305
|
+
logger$16.warn("[WebRTCManager] No peer connection for ICE restart");
|
|
6261
6306
|
return;
|
|
6262
6307
|
}
|
|
6263
6308
|
await controller.triggerIceRestart(relayOnly);
|
|
6264
|
-
logger$
|
|
6309
|
+
logger$16.info(`[WebRTCManager] ICE restart initiated${relayOnly ? " (relay-only)" : ""}`);
|
|
6265
6310
|
} catch (error) {
|
|
6266
|
-
logger$
|
|
6311
|
+
logger$16.error("[WebRTCManager] ICE restart failed:", error);
|
|
6267
6312
|
throw error;
|
|
6268
6313
|
}
|
|
6269
6314
|
}
|
|
@@ -6281,13 +6326,13 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6281
6326
|
const entries = Array.from(this._rtcPeerConnectionsMap.entries());
|
|
6282
6327
|
for (const [id, controller] of entries) try {
|
|
6283
6328
|
if (!controller.peerConnection) {
|
|
6284
|
-
logger$
|
|
6329
|
+
logger$16.debug(`[WebRTCManager] No peer connection for leg ${id}, skipping ICE restart`);
|
|
6285
6330
|
continue;
|
|
6286
6331
|
}
|
|
6287
6332
|
await controller.triggerIceRestart(relayOnly);
|
|
6288
|
-
logger$
|
|
6333
|
+
logger$16.info(`[WebRTCManager] ICE restart initiated for leg ${id}${relayOnly ? " (relay-only)" : ""}`);
|
|
6289
6334
|
} catch (error) {
|
|
6290
|
-
logger$
|
|
6335
|
+
logger$16.warn(`[WebRTCManager] ICE restart failed for leg ${id}:`, error);
|
|
6291
6336
|
}
|
|
6292
6337
|
}
|
|
6293
6338
|
/**
|
|
@@ -6299,7 +6344,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6299
6344
|
requestKeyframeAll() {
|
|
6300
6345
|
for (const [id, controller] of this._rtcPeerConnectionsMap) {
|
|
6301
6346
|
if (controller.isScreenShare) {
|
|
6302
|
-
logger$
|
|
6347
|
+
logger$16.debug(`[WebRTCManager] Skipping keyframe for send-only screen share leg ${id}`);
|
|
6303
6348
|
continue;
|
|
6304
6349
|
}
|
|
6305
6350
|
try {
|
|
@@ -6309,10 +6354,10 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6309
6354
|
if (!videoReceiver) continue;
|
|
6310
6355
|
if (typeof videoReceiver.requestKeyFrame === "function") {
|
|
6311
6356
|
videoReceiver.requestKeyFrame();
|
|
6312
|
-
logger$
|
|
6357
|
+
logger$16.debug(`[WebRTCManager] Keyframe requested for leg ${id}`);
|
|
6313
6358
|
}
|
|
6314
6359
|
} catch (error) {
|
|
6315
|
-
logger$
|
|
6360
|
+
logger$16.warn(`[WebRTCManager] Keyframe request failed for leg ${id} (non-fatal):`, error);
|
|
6316
6361
|
}
|
|
6317
6362
|
}
|
|
6318
6363
|
}
|
|
@@ -6329,7 +6374,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6329
6374
|
return this.cachedObservable("vertoMediaParams$", () => this.webRtcCallSession.webrtcMessages$.pipe(require_operators.filterAs(isVertoMediaParamsInnerParams, "params"), (0, rxjs.takeUntil)(this.destroyed$)));
|
|
6330
6375
|
}
|
|
6331
6376
|
get vertoBye$() {
|
|
6332
|
-
return this.cachedObservable("vertoBye$", () => this.webRtcCallSession.webrtcMessages$.pipe(require_operators.filterAs(
|
|
6377
|
+
return this.cachedObservable("vertoBye$", () => this.webRtcCallSession.webrtcMessages$.pipe(require_operators.filterAs(isVertoByeInboundMessage, "params"), (0, rxjs.takeUntil)(this.destroyed$)));
|
|
6333
6378
|
}
|
|
6334
6379
|
get vertoAttach$() {
|
|
6335
6380
|
return this.cachedObservable("vertoAttach$", () => this.webRtcCallSession.webrtcMessages$.pipe(require_operators.filterAs(isVertoAttachMessage, "params"), (0, rxjs.takeUntil)(this.destroyed$)));
|
|
@@ -6373,7 +6418,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6373
6418
|
default:
|
|
6374
6419
|
}
|
|
6375
6420
|
} catch (error) {
|
|
6376
|
-
logger$
|
|
6421
|
+
logger$16.error(`[WebRTCManager] Error sending Verto ${vertoMethod}:`, error);
|
|
6377
6422
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
6378
6423
|
if (vertoMethod === "verto.modify") this.onModifyFailed?.();
|
|
6379
6424
|
}
|
|
@@ -6388,7 +6433,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6388
6433
|
sdp
|
|
6389
6434
|
});
|
|
6390
6435
|
} catch (error) {
|
|
6391
|
-
logger$
|
|
6436
|
+
logger$16.warn("[WebRTCManager] Error processing modify response:", error);
|
|
6392
6437
|
const modifyError = error instanceof Error ? error : new Error(String(error), { cause: error });
|
|
6393
6438
|
this.onError?.(modifyError);
|
|
6394
6439
|
}
|
|
@@ -6398,7 +6443,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6398
6443
|
const rtcPeerConnController = this._rtcPeerConnectionsMap.get(callId);
|
|
6399
6444
|
if (!rtcPeerConnController) {
|
|
6400
6445
|
const signalingError = new require_operators.DependencyError(`Cannot emit signaling status, RTCPeerConnectionController not found for callID: ${callId}`);
|
|
6401
|
-
logger$
|
|
6446
|
+
logger$16.error("[WebRTCManager] Failed to emit signaling status:", {
|
|
6402
6447
|
callId,
|
|
6403
6448
|
status,
|
|
6404
6449
|
signalingError
|
|
@@ -6414,7 +6459,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6414
6459
|
this._nodeId$.next(require_operators.getValueFrom(response, "result.node_id") ?? null);
|
|
6415
6460
|
const memberId = require_operators.getValueFrom(response, "result.result.result.memberID") ?? null;
|
|
6416
6461
|
const callId = require_operators.getValueFrom(response, "result.result.result.callID") ?? null;
|
|
6417
|
-
logger$
|
|
6462
|
+
logger$16.debug("[WebRTCManager] Verto invite response:", {
|
|
6418
6463
|
callId,
|
|
6419
6464
|
memberId,
|
|
6420
6465
|
response
|
|
@@ -6424,14 +6469,14 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6424
6469
|
if (callId) {
|
|
6425
6470
|
this.webRtcCallSession.addCallId(callId);
|
|
6426
6471
|
this.attachManager.attach(this.buildAttachableCall(callId));
|
|
6427
|
-
} else logger$
|
|
6472
|
+
} else logger$16.warn("[WebRTCManager] Cannot attach call, missing callId:", {
|
|
6428
6473
|
nodeId: this.nodeId,
|
|
6429
6474
|
callId
|
|
6430
6475
|
});
|
|
6431
|
-
logger$
|
|
6432
|
-
logger$
|
|
6476
|
+
logger$16.info("[WebRTCManager] Verto invite successful");
|
|
6477
|
+
logger$16.debug(`[WebRTCManager] nodeid: ${this._nodeId$.value}, selfId: ${this._selfId$.value}`);
|
|
6433
6478
|
} else {
|
|
6434
|
-
logger$
|
|
6479
|
+
logger$16.error("[WebRTCManager] Verto invite failed:", response);
|
|
6435
6480
|
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");
|
|
6436
6481
|
this.onError?.(inviteError);
|
|
6437
6482
|
}
|
|
@@ -6476,17 +6521,17 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6476
6521
|
if (options.initOffer) this.handleInboundAnswer(rtcPeerConnController);
|
|
6477
6522
|
}
|
|
6478
6523
|
async handleInboundAnswer(rtcPeerConnController) {
|
|
6479
|
-
logger$
|
|
6524
|
+
logger$16.debug("[WebRTCManager] Waiting for inbound call to be accepted or rejected");
|
|
6480
6525
|
const vertoByeOrAccepted = await (0, rxjs.firstValueFrom)((0, rxjs.race)(this.vertoBye$, this.webRtcCallSession.answered$).pipe((0, rxjs.takeUntil)(this.destroyed$))).catch(() => null);
|
|
6481
6526
|
if (vertoByeOrAccepted === null) {
|
|
6482
|
-
logger$
|
|
6527
|
+
logger$16.debug("[WebRTCManager] Inbound answer handler aborted (destroyed).");
|
|
6483
6528
|
return;
|
|
6484
6529
|
}
|
|
6485
|
-
if (
|
|
6486
|
-
logger$
|
|
6530
|
+
if (isVertoByeInboundParamsGuard(vertoByeOrAccepted)) {
|
|
6531
|
+
logger$16.info("[WebRTCManager] Inbound call ended by remote before answer.");
|
|
6487
6532
|
this.callSession?.destroy();
|
|
6488
6533
|
} else if (!vertoByeOrAccepted) {
|
|
6489
|
-
logger$
|
|
6534
|
+
logger$16.info("[WebRTCManager] Inbound call rejected by user.");
|
|
6490
6535
|
try {
|
|
6491
6536
|
await this.bye("USER_BUSY");
|
|
6492
6537
|
} finally {
|
|
@@ -6494,19 +6539,19 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6494
6539
|
this.callSession?.destroy();
|
|
6495
6540
|
}
|
|
6496
6541
|
} else {
|
|
6497
|
-
logger$
|
|
6542
|
+
logger$16.debug("[WebRTCManager] Inbound call accepted, creating SDP answer");
|
|
6498
6543
|
const answerOptions = this.webRtcCallSession.answerMediaOptions;
|
|
6499
6544
|
try {
|
|
6500
6545
|
await rtcPeerConnController.acceptInbound(answerOptions);
|
|
6501
6546
|
} catch (error) {
|
|
6502
|
-
logger$
|
|
6547
|
+
logger$16.error("[WebRTCManager] Error creating inbound answer:", error);
|
|
6503
6548
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
6504
6549
|
}
|
|
6505
6550
|
}
|
|
6506
6551
|
}
|
|
6507
6552
|
setupVertoAttachHandler() {
|
|
6508
6553
|
this.subscribeTo(this.vertoAttach$, async (vertoAttach) => {
|
|
6509
|
-
logger$
|
|
6554
|
+
logger$16.debug("[WebRTCManager] Received Verto attach event for existing call:", vertoAttach);
|
|
6510
6555
|
const { callID } = vertoAttach;
|
|
6511
6556
|
await this.attachManager.attach({
|
|
6512
6557
|
nodeId: this.nodeId ?? void 0,
|
|
@@ -6578,17 +6623,17 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6578
6623
|
};
|
|
6579
6624
|
}
|
|
6580
6625
|
async sendLocalDescriptionOnceAccepted(vertoMessageRequest, rtcPeerConnectionController) {
|
|
6581
|
-
logger$
|
|
6626
|
+
logger$16.debug("[WebRTCManager] Waiting for call to be accepted or ended before sending answer");
|
|
6582
6627
|
const vertoByeOrAccepted = await (0, rxjs.firstValueFrom)((0, rxjs.race)(this.vertoBye$, this.webRtcCallSession.answered$).pipe((0, rxjs.takeUntil)(this.destroyed$))).catch(() => null);
|
|
6583
6628
|
if (vertoByeOrAccepted === null) {
|
|
6584
|
-
logger$
|
|
6629
|
+
logger$16.debug("[WebRTCManager] Destroyed while waiting for call acceptance");
|
|
6585
6630
|
return;
|
|
6586
6631
|
}
|
|
6587
|
-
if (
|
|
6588
|
-
logger$
|
|
6632
|
+
if (isVertoByeInboundParamsGuard(vertoByeOrAccepted)) {
|
|
6633
|
+
logger$16.info("[WebRTCManager] Call ended before answer was sent.");
|
|
6589
6634
|
this.callSession?.destroy();
|
|
6590
6635
|
} else if (!vertoByeOrAccepted) {
|
|
6591
|
-
logger$
|
|
6636
|
+
logger$16.info("[WebRTCManager] Call was not accepted, sending verto.bye.");
|
|
6592
6637
|
try {
|
|
6593
6638
|
await this.bye("USER_BUSY");
|
|
6594
6639
|
} finally {
|
|
@@ -6596,14 +6641,14 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6596
6641
|
this.callSession?.destroy();
|
|
6597
6642
|
}
|
|
6598
6643
|
} else {
|
|
6599
|
-
logger$
|
|
6644
|
+
logger$16.debug("[WebRTCManager] Call accepted, sending answer");
|
|
6600
6645
|
try {
|
|
6601
6646
|
this.emitMainSignalingStatus(rtcPeerConnectionController.id, "connecting");
|
|
6602
6647
|
await this.sendLocalDescription(vertoMessageRequest, rtcPeerConnectionController);
|
|
6603
6648
|
await rtcPeerConnectionController.updateAnswerStatus({ status: "sent" });
|
|
6604
6649
|
await this.attachManager.attach(this.buildAttachableCall());
|
|
6605
6650
|
} catch (error) {
|
|
6606
|
-
logger$
|
|
6651
|
+
logger$16.error("[WebRTCManager] Error sending Verto answer:", error);
|
|
6607
6652
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
6608
6653
|
await rtcPeerConnectionController.updateAnswerStatus({ status: "failed" });
|
|
6609
6654
|
}
|
|
@@ -6702,10 +6747,10 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6702
6747
|
});
|
|
6703
6748
|
await (0, rxjs.firstValueFrom)(rtcPeerConnController.connectionState$.pipe((0, rxjs.filter)((state) => state === "connected"), (0, rxjs.take)(1), (0, rxjs.timeout)(this._screenShareTimeoutMs), (0, rxjs.takeUntil)(this.destroyed$)));
|
|
6704
6749
|
this._screenShareStatus$.next("started");
|
|
6705
|
-
logger$
|
|
6750
|
+
logger$16.info("[WebRTCManager] Screen share started successfully.");
|
|
6706
6751
|
return rtcPeerConnController.id;
|
|
6707
6752
|
} catch (error) {
|
|
6708
|
-
logger$
|
|
6753
|
+
logger$16.warn("[WebRTCManager] Error initializing additional peer connection:", error);
|
|
6709
6754
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
6710
6755
|
if (rtcPeerConnController) rtcPeerConnController.destroy();
|
|
6711
6756
|
this._screenShareStatus$.next("none");
|
|
@@ -6724,9 +6769,9 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6724
6769
|
if (removeTrack) return this.mainPeerConnection.stopTrackSender(removeTrack, { updateTransceiverDirection: true });
|
|
6725
6770
|
}
|
|
6726
6771
|
async removeScreenMedia() {
|
|
6727
|
-
if (!["starting", "started"].includes(this._screenShareStatus$.value)) logger$
|
|
6772
|
+
if (!["starting", "started"].includes(this._screenShareStatus$.value)) logger$16.warn("[WebRTCManager] No active screen share to stop.");
|
|
6728
6773
|
if (!this._screenShareId) {
|
|
6729
|
-
logger$
|
|
6774
|
+
logger$16.debug("[WebRTCManager] No screen share peer connection found.");
|
|
6730
6775
|
return;
|
|
6731
6776
|
}
|
|
6732
6777
|
this._screenShareStatus$.next("stopping");
|
|
@@ -6755,7 +6800,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6755
6800
|
dialogParams: this.dialogParams(rtcPeerConnController)
|
|
6756
6801
|
}));
|
|
6757
6802
|
} catch (error) {
|
|
6758
|
-
logger$
|
|
6803
|
+
logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto bye:", error);
|
|
6759
6804
|
throw error;
|
|
6760
6805
|
}
|
|
6761
6806
|
}
|
|
@@ -6773,7 +6818,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6773
6818
|
try {
|
|
6774
6819
|
await this.executeVerto(vertoInfoMessage);
|
|
6775
6820
|
} catch (error) {
|
|
6776
|
-
logger$
|
|
6821
|
+
logger$16.warn("[WebRTCManager] Error sending DTMF digits:", error);
|
|
6777
6822
|
throw error;
|
|
6778
6823
|
}
|
|
6779
6824
|
}
|
|
@@ -6784,10 +6829,10 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6784
6829
|
action: "transfer"
|
|
6785
6830
|
});
|
|
6786
6831
|
try {
|
|
6787
|
-
logger$
|
|
6832
|
+
logger$16.debug("[WebRTCManager] Transferring call with options:", options);
|
|
6788
6833
|
await this.executeVerto(message);
|
|
6789
6834
|
} catch (error) {
|
|
6790
|
-
logger$
|
|
6835
|
+
logger$16.error("[WebRTCManager] Error transferring call:", error);
|
|
6791
6836
|
throw error;
|
|
6792
6837
|
}
|
|
6793
6838
|
}
|
|
@@ -6803,7 +6848,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6803
6848
|
|
|
6804
6849
|
//#endregion
|
|
6805
6850
|
//#region src/controllers/RemoteAudioMeter.ts
|
|
6806
|
-
const logger$
|
|
6851
|
+
const logger$15 = require_operators.getLogger();
|
|
6807
6852
|
/**
|
|
6808
6853
|
* Read-only audio level meter for a remote MediaStream. Attaches an
|
|
6809
6854
|
* AnalyserNode to a MediaStreamAudioSourceNode so it observes the stream
|
|
@@ -6838,7 +6883,7 @@ var RemoteAudioMeter = class extends Destroyable {
|
|
|
6838
6883
|
try {
|
|
6839
6884
|
this._source.disconnect();
|
|
6840
6885
|
} catch (error) {
|
|
6841
|
-
logger$
|
|
6886
|
+
logger$15.debug("[RemoteAudioMeter] source disconnect warning:", error);
|
|
6842
6887
|
}
|
|
6843
6888
|
this._source = null;
|
|
6844
6889
|
this._stream = null;
|
|
@@ -6855,7 +6900,7 @@ var RemoteAudioMeter = class extends Destroyable {
|
|
|
6855
6900
|
this._source = null;
|
|
6856
6901
|
}
|
|
6857
6902
|
this._audioContext.close().catch((error) => {
|
|
6858
|
-
logger$
|
|
6903
|
+
logger$15.debug("[RemoteAudioMeter] audio context close warning:", error);
|
|
6859
6904
|
});
|
|
6860
6905
|
super.destroy();
|
|
6861
6906
|
}
|
|
@@ -6879,7 +6924,7 @@ var RemoteAudioMeter = class extends Destroyable {
|
|
|
6879
6924
|
*
|
|
6880
6925
|
* Extends Destroyable for automatic RxJS cleanup.
|
|
6881
6926
|
*/
|
|
6882
|
-
const logger$
|
|
6927
|
+
const logger$14 = require_operators.getLogger();
|
|
6883
6928
|
const DEFAULT_POLLING_INTERVAL_MS = 1e3;
|
|
6884
6929
|
const DEFAULT_BASELINE_SAMPLES = 10;
|
|
6885
6930
|
const DEFAULT_NO_AUDIO_PACKET_THRESHOLD_MS = 2e3;
|
|
@@ -6969,9 +7014,9 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
6969
7014
|
const now = Date.now();
|
|
6970
7015
|
this.lastAudioPacketChangeTime = now;
|
|
6971
7016
|
this.lastVideoPacketChangeTime = now;
|
|
6972
|
-
logger$
|
|
7017
|
+
logger$14.debug("[RTCStatsMonitor] Starting stats monitoring");
|
|
6973
7018
|
this.subscribeTo((0, rxjs.interval)(this.pollingIntervalMs).pipe((0, rxjs.filter)(() => this.running), (0, rxjs.switchMap)(() => (0, rxjs.from)(this.peerConnection.getStats()).pipe((0, rxjs.catchError)((err) => {
|
|
6974
|
-
logger$
|
|
7019
|
+
logger$14.warn("[RTCStatsMonitor] Failed to get stats:", err);
|
|
6975
7020
|
return rxjs.EMPTY;
|
|
6976
7021
|
}))), (0, rxjs.filter)(() => this.running), (0, rxjs.map)((report) => this.extractSample(report))), (sample) => this._sample$.next(sample));
|
|
6977
7022
|
this.subscribeTo(this._sample$.pipe((0, rxjs.take)(this.baselineSampleCount), (0, rxjs.toArray)(), (0, rxjs.map)((samples) => ({
|
|
@@ -6979,7 +7024,7 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
6979
7024
|
jitter: samples.reduce((s, b) => s + b.audioJitter, 0) / samples.length,
|
|
6980
7025
|
ready: true
|
|
6981
7026
|
}))), (baseline) => {
|
|
6982
|
-
logger$
|
|
7027
|
+
logger$14.debug(`[RTCStatsMonitor] Baseline established: rtt=${baseline.rtt.toFixed(1)}ms, jitter=${baseline.jitter.toFixed(1)}ms`);
|
|
6983
7028
|
this._baseline$.next(baseline);
|
|
6984
7029
|
});
|
|
6985
7030
|
this.subscribeTo(this._sample$.pipe((0, rxjs.scan)((acc, sample) => ({
|
|
@@ -7016,10 +7061,10 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
7016
7061
|
stop() {
|
|
7017
7062
|
if (!this.running) return;
|
|
7018
7063
|
this.running = false;
|
|
7019
|
-
logger$
|
|
7064
|
+
logger$14.debug("[RTCStatsMonitor] Stopping stats monitoring");
|
|
7020
7065
|
}
|
|
7021
7066
|
destroy() {
|
|
7022
|
-
logger$
|
|
7067
|
+
logger$14.debug("[RTCStatsMonitor] Destroying RTCStatsMonitor");
|
|
7023
7068
|
this.stop();
|
|
7024
7069
|
super.destroy();
|
|
7025
7070
|
}
|
|
@@ -7147,7 +7192,7 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
7147
7192
|
|
|
7148
7193
|
//#endregion
|
|
7149
7194
|
//#region src/managers/CallRecoveryManager.ts
|
|
7150
|
-
const logger$
|
|
7195
|
+
const logger$13 = require_operators.getLogger();
|
|
7151
7196
|
const DEFAULT_DEBOUNCE_TIME_MS = 2e3;
|
|
7152
7197
|
const DEFAULT_COOLDOWN_MS = 1e4;
|
|
7153
7198
|
const DEFAULT_ICE_GRACE_PERIOD_MS = 3e3;
|
|
@@ -7244,10 +7289,10 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
7244
7289
|
*/
|
|
7245
7290
|
async requestIceRestart() {
|
|
7246
7291
|
if (this._recoveryState$.value === "recovering") {
|
|
7247
|
-
logger$
|
|
7292
|
+
logger$13.info("CallRecoveryManager: manual ICE restart skipped — recovery already in progress");
|
|
7248
7293
|
return;
|
|
7249
7294
|
}
|
|
7250
|
-
logger$
|
|
7295
|
+
logger$13.info("CallRecoveryManager: manual ICE restart requested");
|
|
7251
7296
|
this.transitionTo("recovering");
|
|
7252
7297
|
await this.executeIceRestart(false);
|
|
7253
7298
|
this.startCooldown();
|
|
@@ -7263,7 +7308,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
7263
7308
|
* WebSocket reconnect or call state recovers to 'connected'.
|
|
7264
7309
|
*/
|
|
7265
7310
|
reset() {
|
|
7266
|
-
logger$
|
|
7311
|
+
logger$13.info("CallRecoveryManager: resetting counters");
|
|
7267
7312
|
this._attemptCount = 0;
|
|
7268
7313
|
this._keyframeBurstCount = 0;
|
|
7269
7314
|
this._keyframeBurstStart = 0;
|
|
@@ -7278,7 +7323,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
7278
7323
|
*/
|
|
7279
7324
|
notifyModifyFailed() {
|
|
7280
7325
|
if (this._recoveryState$.value === "cooldown" || this._recoveryState$.value === "idle") {
|
|
7281
|
-
logger$
|
|
7326
|
+
logger$13.info("CallRecoveryManager: verto.modify failed — re-entering recovery");
|
|
7282
7327
|
this._cooldownUntil = 0;
|
|
7283
7328
|
this.transitionTo("idle");
|
|
7284
7329
|
this.pushTrigger({
|
|
@@ -7302,7 +7347,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
7302
7347
|
reason: `bandwidth ${bitrateKbps}kbps below threshold ${this._config.degradationBitrateThreshold}kbps`,
|
|
7303
7348
|
timestamp: Date.now()
|
|
7304
7349
|
});
|
|
7305
|
-
logger$
|
|
7350
|
+
logger$13.warn(`CallRecoveryManager: disabling video — bandwidth ${bitrateKbps}kbps < ${this._config.degradationBitrateThreshold}kbps`);
|
|
7306
7351
|
} else if (wasConstrained && bitrateKbps >= this._config.degradationRecoveryThreshold) {
|
|
7307
7352
|
this._bandwidthConstrained$.next(false);
|
|
7308
7353
|
this._callbacks.enableVideo();
|
|
@@ -7311,7 +7356,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
7311
7356
|
reason: `bandwidth ${bitrateKbps}kbps recovered above ${this._config.degradationRecoveryThreshold}kbps`,
|
|
7312
7357
|
timestamp: Date.now()
|
|
7313
7358
|
});
|
|
7314
|
-
logger$
|
|
7359
|
+
logger$13.info(`CallRecoveryManager: restoring video — bandwidth ${bitrateKbps}kbps >= ${this._config.degradationRecoveryThreshold}kbps`);
|
|
7315
7360
|
}
|
|
7316
7361
|
}
|
|
7317
7362
|
/**
|
|
@@ -7330,14 +7375,14 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
7330
7375
|
handleWebSocketReconnect() {
|
|
7331
7376
|
const pcState = this._callbacks.getPeerConnectionState();
|
|
7332
7377
|
if (pcState === "connected" || pcState === "completed") {
|
|
7333
|
-
logger$
|
|
7378
|
+
logger$13.info("CallRecoveryManager: signal-only reconnect — peer connection still alive");
|
|
7334
7379
|
this.emitEvent({
|
|
7335
7380
|
action: "signal_reconnect",
|
|
7336
7381
|
reason: "WebSocket reconnected, peer connection still connected",
|
|
7337
7382
|
timestamp: Date.now()
|
|
7338
7383
|
});
|
|
7339
7384
|
} else {
|
|
7340
|
-
logger$
|
|
7385
|
+
logger$13.info("CallRecoveryManager: full reconnect — peer connection also down");
|
|
7341
7386
|
this.emitEvent({
|
|
7342
7387
|
action: "full_reconnect",
|
|
7343
7388
|
reason: "WebSocket reconnected, peer connection not connected — ICE restart needed",
|
|
@@ -7361,7 +7406,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
7361
7406
|
}), (0, rxjs.debounceTime)(this._config.debounceTimeMs), (0, rxjs.withLatestFrom)(this._inputs.signalingReady$), (0, rxjs.filter)(([, signalingReady]) => this.passGateChecks(signalingReady)), (0, rxjs.map)(([trigger]) => trigger), (0, rxjs.exhaustMap)((trigger) => this.executeTieredRecovery(trigger)), (0, rxjs.takeUntil)((0, rxjs.merge)(this._destroyed$, this._pipelineStop$))), {
|
|
7362
7407
|
next: () => {},
|
|
7363
7408
|
error: (err) => {
|
|
7364
|
-
logger$
|
|
7409
|
+
logger$13.error("CallRecoveryManager: pipeline error", err);
|
|
7365
7410
|
this.transitionTo("idle");
|
|
7366
7411
|
}
|
|
7367
7412
|
});
|
|
@@ -7388,27 +7433,27 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
7388
7433
|
reason: `no packet loss for ${this._config.packetLossRecoveryDelaySec}s — restoring video`,
|
|
7389
7434
|
timestamp: Date.now()
|
|
7390
7435
|
});
|
|
7391
|
-
logger$
|
|
7436
|
+
logger$13.info(`CallRecoveryManager: restoring video — no packet loss for ${this._config.packetLossRecoveryDelaySec}s`);
|
|
7392
7437
|
});
|
|
7393
7438
|
}
|
|
7394
7439
|
passGateChecks(signalingReady) {
|
|
7395
7440
|
if (this._callbacks.isNegotiating()) {
|
|
7396
|
-
logger$
|
|
7441
|
+
logger$13.debug("CallRecoveryManager: gate blocked — negotiation in progress");
|
|
7397
7442
|
this.transitionTo("idle");
|
|
7398
7443
|
return false;
|
|
7399
7444
|
}
|
|
7400
7445
|
if (!signalingReady) {
|
|
7401
|
-
logger$
|
|
7446
|
+
logger$13.debug("CallRecoveryManager: gate blocked — signaling not ready");
|
|
7402
7447
|
this.transitionTo("idle");
|
|
7403
7448
|
return false;
|
|
7404
7449
|
}
|
|
7405
7450
|
if (!this._callbacks.isCallConnected()) {
|
|
7406
|
-
logger$
|
|
7451
|
+
logger$13.debug("CallRecoveryManager: gate blocked — call not connected");
|
|
7407
7452
|
this.transitionTo("idle");
|
|
7408
7453
|
return false;
|
|
7409
7454
|
}
|
|
7410
7455
|
if (this.isCooldownActive()) {
|
|
7411
|
-
logger$
|
|
7456
|
+
logger$13.debug("CallRecoveryManager: gate blocked — cooldown active");
|
|
7412
7457
|
this.transitionTo("cooldown");
|
|
7413
7458
|
return false;
|
|
7414
7459
|
}
|
|
@@ -7419,9 +7464,9 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
7419
7464
|
}
|
|
7420
7465
|
executeTieredRecovery(trigger) {
|
|
7421
7466
|
this.transitionTo("recovering");
|
|
7422
|
-
logger$
|
|
7467
|
+
logger$13.info(`CallRecoveryManager: starting tiered recovery — source=${trigger.source} detail=${trigger.detail}`);
|
|
7423
7468
|
return (0, rxjs.from)(this.runTiers(trigger)).pipe((0, rxjs.tap)(() => this.startCooldown()), (0, rxjs.catchError)((err) => {
|
|
7424
|
-
logger$
|
|
7469
|
+
logger$13.error("CallRecoveryManager: tiered recovery failed", err);
|
|
7425
7470
|
this.startCooldown();
|
|
7426
7471
|
return rxjs.EMPTY;
|
|
7427
7472
|
}));
|
|
@@ -7429,7 +7474,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
7429
7474
|
async runTiers(trigger) {
|
|
7430
7475
|
this.executeKeyframe(trigger.detail);
|
|
7431
7476
|
if (trigger.issueType && DEGRADATION_ONLY_ISSUES.has(trigger.issueType)) {
|
|
7432
|
-
logger$
|
|
7477
|
+
logger$13.debug(`CallRecoveryManager: degradation-only issue (${trigger.issueType}) — Tier 1 only, skipping ICE restart`);
|
|
7433
7478
|
return;
|
|
7434
7479
|
}
|
|
7435
7480
|
if (this._attemptCount < this._config.maxAttempts) {
|
|
@@ -7446,13 +7491,13 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
7446
7491
|
maxAttempts: this._config.maxAttempts,
|
|
7447
7492
|
timestamp: Date.now()
|
|
7448
7493
|
});
|
|
7449
|
-
logger$
|
|
7494
|
+
logger$13.warn("CallRecoveryManager: max recovery attempts reached");
|
|
7450
7495
|
}
|
|
7451
7496
|
}
|
|
7452
7497
|
executeKeyframe(reason) {
|
|
7453
7498
|
const now = Date.now();
|
|
7454
7499
|
if (now < this._keyframeCooldownUntil) {
|
|
7455
|
-
logger$
|
|
7500
|
+
logger$13.debug("CallRecoveryManager: keyframe request skipped — cooldown active");
|
|
7456
7501
|
return;
|
|
7457
7502
|
}
|
|
7458
7503
|
if (now - this._keyframeBurstStart > this._config.keyframeBurstWindowMs) {
|
|
@@ -7461,7 +7506,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
7461
7506
|
}
|
|
7462
7507
|
if (this._keyframeBurstCount >= this._config.keyframeMaxBurst) {
|
|
7463
7508
|
this._keyframeCooldownUntil = now + this._config.keyframeCooldownMs;
|
|
7464
|
-
logger$
|
|
7509
|
+
logger$13.debug(`CallRecoveryManager: keyframe burst limit reached (${this._config.keyframeMaxBurst}), cooldown until ${this._keyframeCooldownUntil}`);
|
|
7465
7510
|
return;
|
|
7466
7511
|
}
|
|
7467
7512
|
this._keyframeBurstCount += 1;
|
|
@@ -7471,12 +7516,12 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
7471
7516
|
reason,
|
|
7472
7517
|
timestamp: now
|
|
7473
7518
|
});
|
|
7474
|
-
logger$
|
|
7519
|
+
logger$13.debug(`CallRecoveryManager: keyframe requested (burst ${this._keyframeBurstCount}/${this._config.keyframeMaxBurst})`);
|
|
7475
7520
|
}
|
|
7476
7521
|
async executeIceRestart(relayOnly) {
|
|
7477
7522
|
this._attemptCount += 1;
|
|
7478
7523
|
const tier = relayOnly ? "Tier 3 (relay-only)" : "Tier 2 (standard)";
|
|
7479
|
-
logger$
|
|
7524
|
+
logger$13.info(`CallRecoveryManager: ${tier} ICE restart — attempt ${this._attemptCount}/${this._config.maxAttempts}`);
|
|
7480
7525
|
this.emitEvent({
|
|
7481
7526
|
action: "reinvite_started",
|
|
7482
7527
|
reason: `${tier} ICE restart`,
|
|
@@ -7493,7 +7538,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
7493
7538
|
maxAttempts: this._config.maxAttempts,
|
|
7494
7539
|
timestamp: Date.now()
|
|
7495
7540
|
});
|
|
7496
|
-
logger$
|
|
7541
|
+
logger$13.info(`CallRecoveryManager: ${tier} ICE restart succeeded`);
|
|
7497
7542
|
this._attemptCount = 0;
|
|
7498
7543
|
return true;
|
|
7499
7544
|
}
|
|
@@ -7504,7 +7549,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
7504
7549
|
maxAttempts: this._config.maxAttempts,
|
|
7505
7550
|
timestamp: Date.now()
|
|
7506
7551
|
});
|
|
7507
|
-
logger$
|
|
7552
|
+
logger$13.warn(`CallRecoveryManager: ${tier} ICE restart failed`);
|
|
7508
7553
|
return false;
|
|
7509
7554
|
} catch {
|
|
7510
7555
|
this.emitEvent({
|
|
@@ -7514,7 +7559,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
7514
7559
|
maxAttempts: this._config.maxAttempts,
|
|
7515
7560
|
timestamp: Date.now()
|
|
7516
7561
|
});
|
|
7517
|
-
logger$
|
|
7562
|
+
logger$13.warn(`CallRecoveryManager: ${tier} ICE restart timed out`);
|
|
7518
7563
|
return false;
|
|
7519
7564
|
}
|
|
7520
7565
|
}
|
|
@@ -7535,7 +7580,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
7535
7580
|
transitionTo(state) {
|
|
7536
7581
|
const prev = this._recoveryState$.value;
|
|
7537
7582
|
if (prev !== state) {
|
|
7538
|
-
logger$
|
|
7583
|
+
logger$13.debug(`CallRecoveryManager: state ${prev} -> ${state}`);
|
|
7539
7584
|
this._recoveryState$.next(state);
|
|
7540
7585
|
}
|
|
7541
7586
|
}
|
|
@@ -7637,7 +7682,13 @@ function mosToQualityLevel(mos) {
|
|
|
7637
7682
|
|
|
7638
7683
|
//#endregion
|
|
7639
7684
|
//#region src/core/entities/Call.ts
|
|
7640
|
-
const logger$
|
|
7685
|
+
const logger$12 = require_operators.getLogger();
|
|
7686
|
+
/**
|
|
7687
|
+
* Verto method for setting member layout positions. Its gateway DTO requires a
|
|
7688
|
+
* `targets` array whose entries are `{ target, position }` (NOT bare targets),
|
|
7689
|
+
* so {@link WebRTCCall.buildMethodParams} special-cases it. See issue #19400.
|
|
7690
|
+
*/
|
|
7691
|
+
const POSITION_SET_METHOD = "call.member.position.set";
|
|
7641
7692
|
/**
|
|
7642
7693
|
* Ratio between the critical and warning RTT spike multipliers.
|
|
7643
7694
|
* Warning threshold = baseline * warningMultiplier (default 3x)
|
|
@@ -7655,7 +7706,7 @@ const fromDestinationParams = (destination) => {
|
|
|
7655
7706
|
});
|
|
7656
7707
|
return params;
|
|
7657
7708
|
} catch (error) {
|
|
7658
|
-
logger$
|
|
7709
|
+
logger$12.warn(`Failed to parse destination URI: ${destination}`, error);
|
|
7659
7710
|
return {};
|
|
7660
7711
|
}
|
|
7661
7712
|
};
|
|
@@ -7845,7 +7896,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
7845
7896
|
* @throws {JSONRPCError} If the RPC call returns an error.
|
|
7846
7897
|
*/
|
|
7847
7898
|
async executeMethod(target, method, args) {
|
|
7848
|
-
const params = this.buildMethodParams(target, args);
|
|
7899
|
+
const params = this.buildMethodParams(target, args, method);
|
|
7849
7900
|
const request = buildRPCRequest({
|
|
7850
7901
|
method,
|
|
7851
7902
|
params
|
|
@@ -7855,16 +7906,20 @@ var WebRTCCall = class extends Destroyable {
|
|
|
7855
7906
|
if (isJSONRPCErrorResponse(response)) throw new require_operators.JSONRPCError(parseInt(response.result?.code ?? "0"), `Error response from method ${method}: ${response.result?.code} ${response.result?.message}`, void 0, void 0, request.id);
|
|
7856
7907
|
return response;
|
|
7857
7908
|
} catch (error) {
|
|
7858
|
-
logger$
|
|
7909
|
+
logger$12.error(`[Call] Error executing method ${method} with params`, params, error);
|
|
7859
7910
|
throw error;
|
|
7860
7911
|
}
|
|
7861
7912
|
}
|
|
7862
|
-
buildMethodParams(target, args) {
|
|
7913
|
+
buildMethodParams(target, args, method) {
|
|
7863
7914
|
const self = {
|
|
7864
7915
|
node_id: this.nodeId ?? "",
|
|
7865
7916
|
call_id: this.id,
|
|
7866
7917
|
member_id: this.vertoManager.selfId ?? ""
|
|
7867
7918
|
};
|
|
7919
|
+
if (method === POSITION_SET_METHOD) return {
|
|
7920
|
+
...args,
|
|
7921
|
+
self
|
|
7922
|
+
};
|
|
7868
7923
|
if (typeof target === "object") return {
|
|
7869
7924
|
...args,
|
|
7870
7925
|
self,
|
|
@@ -8058,9 +8113,9 @@ var WebRTCCall = class extends Destroyable {
|
|
|
8058
8113
|
*/
|
|
8059
8114
|
initResilienceSubsystems() {
|
|
8060
8115
|
const pc = this.rtcPeerConnection;
|
|
8061
|
-
logger$
|
|
8116
|
+
logger$12.debug(`[Call] initResilienceSubsystems: pc=${pc ? "exists" : "undefined"}, connectionState=${pc?.connectionState}`);
|
|
8062
8117
|
if (!pc) {
|
|
8063
|
-
logger$
|
|
8118
|
+
logger$12.warn("[Call] No peer connection available, skipping resilience init");
|
|
8064
8119
|
return;
|
|
8065
8120
|
}
|
|
8066
8121
|
try {
|
|
@@ -8095,14 +8150,14 @@ var WebRTCCall = class extends Destroyable {
|
|
|
8095
8150
|
disableVideo: () => {
|
|
8096
8151
|
try {
|
|
8097
8152
|
this.vertoManager.muteMainVideoInputDevice();
|
|
8098
|
-
logger$
|
|
8153
|
+
logger$12.debug("[Call] Recovery manager disabled video");
|
|
8099
8154
|
} catch {
|
|
8100
|
-
logger$
|
|
8155
|
+
logger$12.debug("[Call] Recovery manager failed to disable video");
|
|
8101
8156
|
}
|
|
8102
8157
|
},
|
|
8103
8158
|
enableVideo: () => {
|
|
8104
8159
|
this.vertoManager.unmuteMainVideoInputDevice().catch(() => {
|
|
8105
|
-
logger$
|
|
8160
|
+
logger$12.debug("[Call] Recovery manager failed to enable video");
|
|
8106
8161
|
});
|
|
8107
8162
|
},
|
|
8108
8163
|
isNegotiating: () => this.vertoManager.mainPeerConnection.isNegotiating,
|
|
@@ -8152,7 +8207,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
8152
8207
|
this.subscribeTo(this._recoveryManager.recoveryEvent$, (event) => {
|
|
8153
8208
|
this._recoveryEvent$.next(event);
|
|
8154
8209
|
if (event.action === "max_attempts_reached") {
|
|
8155
|
-
logger$
|
|
8210
|
+
logger$12.warn("[Call] All recovery attempts exhausted, terminating call");
|
|
8156
8211
|
this.emitError({
|
|
8157
8212
|
kind: "network",
|
|
8158
8213
|
fatal: true,
|
|
@@ -8172,13 +8227,13 @@ var WebRTCCall = class extends Destroyable {
|
|
|
8172
8227
|
else if (event.type === "online") this._recoveryManager?.handleWebSocketReconnect();
|
|
8173
8228
|
});
|
|
8174
8229
|
this.subscribeTo(this.clientSession.authenticated$.pipe((0, rxjs.skip)(1), (0, rxjs.filter)(Boolean)), () => {
|
|
8175
|
-
logger$
|
|
8230
|
+
logger$12.debug("[Call] WebSocket reconnected — notifying recovery manager");
|
|
8176
8231
|
this._recoveryManager?.handleWebSocketReconnect();
|
|
8177
8232
|
});
|
|
8178
8233
|
this._statsMonitor.start();
|
|
8179
|
-
logger$
|
|
8234
|
+
logger$12.debug("[Call] Resilience subsystems initialized for call", this.id);
|
|
8180
8235
|
} catch (error) {
|
|
8181
|
-
logger$
|
|
8236
|
+
logger$12.warn("[Call] Failed to initialize resilience subsystems:", error);
|
|
8182
8237
|
}
|
|
8183
8238
|
}
|
|
8184
8239
|
/**
|
|
@@ -8261,19 +8316,19 @@ var WebRTCCall = class extends Destroyable {
|
|
|
8261
8316
|
}
|
|
8262
8317
|
isCallSessionEvent(event) {
|
|
8263
8318
|
try {
|
|
8264
|
-
logger$
|
|
8319
|
+
logger$12.debug("[Call] Checking if event is for this call session:", event);
|
|
8265
8320
|
const callId = require_operators.getValueFrom(event, "params.params.callID") ?? require_operators.getValueFrom(event, "params.call_id");
|
|
8266
8321
|
const roomSessionId = require_operators.getValueFrom(event, "params.room_session_id");
|
|
8267
|
-
logger$
|
|
8322
|
+
logger$12.debug(`[Call] Extracted session identifiers callID: ${callId} and roomSessionID: ${roomSessionId} from event:`);
|
|
8268
8323
|
return callId === this.id || !!callId && this.callEventsManager.isCallIdValid(callId) || !!roomSessionId && this.callEventsManager.isRoomSessionIdValid(roomSessionId);
|
|
8269
8324
|
} catch (error) {
|
|
8270
|
-
logger$
|
|
8325
|
+
logger$12.error("[Call] Error checking if event is for this call session:", error);
|
|
8271
8326
|
return false;
|
|
8272
8327
|
}
|
|
8273
8328
|
}
|
|
8274
8329
|
get callSessionEvents$() {
|
|
8275
8330
|
return this.cachedObservable("callSessionEvents$", () => this.clientSession.signalingEvent$.pipe((0, rxjs.filter)((event) => this.isCallSessionEvent(event)), (0, rxjs.tap)((event) => {
|
|
8276
|
-
logger$
|
|
8331
|
+
logger$12.debug("[Call] Received call session event:", event);
|
|
8277
8332
|
}), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.share)()));
|
|
8278
8333
|
}
|
|
8279
8334
|
/** Observable of call-updated events. */
|
|
@@ -8343,16 +8398,16 @@ var WebRTCCall = class extends Destroyable {
|
|
|
8343
8398
|
this._customSubscriptions.set(eventType, filtered$);
|
|
8344
8399
|
}, (error) => {
|
|
8345
8400
|
this._customSubscriptions.delete(eventType);
|
|
8346
|
-
logger$
|
|
8401
|
+
logger$12.warn(`[Call] verto.subscribe for '${eventType}' failed, not caching:`, error);
|
|
8347
8402
|
});
|
|
8348
8403
|
this._customSubscriptions.set(eventType, filtered$);
|
|
8349
8404
|
return filtered$;
|
|
8350
8405
|
}
|
|
8351
8406
|
get webrtcMessages$() {
|
|
8352
|
-
return this.cachedObservable("webrtcMessages$", () => this.callSessionEvents$.pipe(require_operators.filterAs(isWebrtcMessageMetadata, "params"), (0, rxjs.tap)((event) => logger$
|
|
8407
|
+
return this.cachedObservable("webrtcMessages$", () => this.callSessionEvents$.pipe(require_operators.filterAs(isWebrtcMessageMetadata, "params"), (0, rxjs.tap)((event) => logger$12.debug("[Call] Event is a WebRTC message event:", event)), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.share)()));
|
|
8353
8408
|
}
|
|
8354
8409
|
get callEvent$() {
|
|
8355
|
-
return this.cachedObservable("callEvent$", () => this.callSessionEvents$.pipe(require_operators.filterAs(isSignalwireCallMetadata, "params"), (0, rxjs.tap)((event) => logger$
|
|
8410
|
+
return this.cachedObservable("callEvent$", () => this.callSessionEvents$.pipe(require_operators.filterAs(isSignalwireCallMetadata, "params"), (0, rxjs.tap)((event) => logger$12.debug("[Call] Event is a call event:", event)), (0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.share)()));
|
|
8356
8411
|
}
|
|
8357
8412
|
get layoutEvent$() {
|
|
8358
8413
|
return this.cachedObservable("layoutEvent$", () => this.callEvent$.pipe(require_operators.filterAs(isLayoutChangedMetadata, "params")));
|
|
@@ -8430,10 +8485,21 @@ var WebRTCCall = class extends Destroyable {
|
|
|
8430
8485
|
return this.deferEmission(this._answered$.asObservable());
|
|
8431
8486
|
}
|
|
8432
8487
|
/**
|
|
8433
|
-
* Sets the call layout and participant positions.
|
|
8488
|
+
* Sets the call layout and, optionally, individual participant positions.
|
|
8489
|
+
*
|
|
8490
|
+
* The gateway `call.layout.set` DTO has **no** `positions` member, so when
|
|
8491
|
+
* `positions` is provided this method issues a `call.member.position.set`
|
|
8492
|
+
* request per member (via {@link Participant.setPosition}, which keys each
|
|
8493
|
+
* position by that member's own call context) alongside `call.layout.set`
|
|
8494
|
+
* (issue #19400, Flag #6).
|
|
8495
|
+
*
|
|
8496
|
+
* **These operations are NOT atomic.** The layout is applied first, then each
|
|
8497
|
+
* member position sequentially, so members may briefly flash into their
|
|
8498
|
+
* default slots before being moved to the requested positions.
|
|
8434
8499
|
*
|
|
8435
8500
|
* @param layout - Layout name (must be one of {@link layouts}).
|
|
8436
|
-
* @param positions -
|
|
8501
|
+
* @param positions - Optional map of member IDs to {@link VideoPosition} values.
|
|
8502
|
+
* When omitted or empty, only the layout is changed.
|
|
8437
8503
|
* @throws {InvalidParams} If the layout is not in the available {@link layouts}.
|
|
8438
8504
|
*
|
|
8439
8505
|
* @example
|
|
@@ -8446,10 +8512,17 @@ var WebRTCCall = class extends Destroyable {
|
|
|
8446
8512
|
async setLayout(layout, positions) {
|
|
8447
8513
|
if (!this.layouts.includes(layout)) throw new require_operators.InvalidParams(`Layout ${layout} is not available in the current call layouts: ${this.layouts.join(", ")}`);
|
|
8448
8514
|
const selfId = await (0, rxjs.firstValueFrom)(this.selfId$.pipe((0, rxjs.filter)((id) => id !== null)));
|
|
8449
|
-
await this.executeMethod(selfId, "call.layout.set", {
|
|
8450
|
-
|
|
8451
|
-
|
|
8452
|
-
|
|
8515
|
+
await this.executeMethod(selfId, "call.layout.set", { layout });
|
|
8516
|
+
const positionEntries = Object.entries(positions ?? {});
|
|
8517
|
+
if (positionEntries.length === 0) return;
|
|
8518
|
+
for (const [memberId, position] of positionEntries) {
|
|
8519
|
+
const participant = this.participants.find((p) => p.id === memberId);
|
|
8520
|
+
if (!participant) {
|
|
8521
|
+
logger$12.warn(`[Call] setLayout: member ${memberId} not found in participants; skipping position ${position}`);
|
|
8522
|
+
continue;
|
|
8523
|
+
}
|
|
8524
|
+
await participant.setPosition(position);
|
|
8525
|
+
}
|
|
8453
8526
|
}
|
|
8454
8527
|
/**
|
|
8455
8528
|
* Transfers the call to another destination.
|
|
@@ -8481,7 +8554,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
8481
8554
|
setLocalMicrophoneGain(value) {
|
|
8482
8555
|
const pipeline = this.vertoManager.ensureLocalAudioPipeline();
|
|
8483
8556
|
if (!pipeline) {
|
|
8484
|
-
logger$
|
|
8557
|
+
logger$12.warn("[Call] setLocalMicrophoneGain: audio pipeline unavailable");
|
|
8485
8558
|
return;
|
|
8486
8559
|
}
|
|
8487
8560
|
const percent = Math.max(0, Math.min(200, value));
|
|
@@ -8526,7 +8599,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
8526
8599
|
enablePushToTalk() {
|
|
8527
8600
|
const pipeline = this.vertoManager.ensureLocalAudioPipeline();
|
|
8528
8601
|
if (!pipeline) {
|
|
8529
|
-
logger$
|
|
8602
|
+
logger$12.warn("[Call] enablePushToTalk: audio pipeline unavailable");
|
|
8530
8603
|
return;
|
|
8531
8604
|
}
|
|
8532
8605
|
pipeline.setPTTActive(false);
|
|
@@ -8688,7 +8761,7 @@ var CallFactory = class {
|
|
|
8688
8761
|
|
|
8689
8762
|
//#endregion
|
|
8690
8763
|
//#region src/behaviors/Collection.ts
|
|
8691
|
-
const logger$
|
|
8764
|
+
const logger$11 = require_operators.getLogger();
|
|
8692
8765
|
var Fetcher = class {
|
|
8693
8766
|
constructor(endpoint, params, http) {
|
|
8694
8767
|
this.endpoint = endpoint;
|
|
@@ -8712,7 +8785,7 @@ var Fetcher = class {
|
|
|
8712
8785
|
this.hasMore = !!this.nextUrl;
|
|
8713
8786
|
return result.data.filter(this.filter).map(this.mapper);
|
|
8714
8787
|
}
|
|
8715
|
-
logger$
|
|
8788
|
+
logger$11.error("Failed to fetch entity");
|
|
8716
8789
|
return [];
|
|
8717
8790
|
}
|
|
8718
8791
|
async id(v) {
|
|
@@ -8788,7 +8861,7 @@ var EntityCollection = class extends Destroyable {
|
|
|
8788
8861
|
this._hasMore$.next(this.fetchController.hasMore ?? false);
|
|
8789
8862
|
this._loading$.next(false);
|
|
8790
8863
|
} catch (error) {
|
|
8791
|
-
logger$
|
|
8864
|
+
logger$11.error(`Failed to fetch initial collection data`, error);
|
|
8792
8865
|
this._hasMore$.next(this.fetchController.hasMore ?? false);
|
|
8793
8866
|
this._loading$.next(false);
|
|
8794
8867
|
this.onError?.(new require_operators.CollectionFetchError("fetchMore", error));
|
|
@@ -8802,7 +8875,7 @@ var EntityCollection = class extends Destroyable {
|
|
|
8802
8875
|
if (data) this.upsertData(data);
|
|
8803
8876
|
return data;
|
|
8804
8877
|
} catch (error) {
|
|
8805
|
-
logger$
|
|
8878
|
+
logger$11.error(`Failed to fetch data for (${String(key)}:${String(value)}) :`, error);
|
|
8806
8879
|
this._loading$.next(false);
|
|
8807
8880
|
this.onError?.(new require_operators.CollectionFetchError(`tryFetch(${String(key)})`, error));
|
|
8808
8881
|
}
|
|
@@ -9049,13 +9122,13 @@ var Address = class extends Destroyable {
|
|
|
9049
9122
|
|
|
9050
9123
|
//#endregion
|
|
9051
9124
|
//#region src/core/utils.ts
|
|
9052
|
-
const logger$
|
|
9125
|
+
const logger$10 = require_operators.getLogger();
|
|
9053
9126
|
const isRPCConnectResult = (e) => {
|
|
9054
|
-
logger$
|
|
9127
|
+
logger$10.debug("isRPCConnectResult check:", e);
|
|
9055
9128
|
if (!e || typeof e !== "object") return false;
|
|
9056
9129
|
const result = e;
|
|
9057
9130
|
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";
|
|
9058
|
-
logger$
|
|
9131
|
+
logger$10.debug("isRPCConnectResult check result:", is);
|
|
9059
9132
|
return is;
|
|
9060
9133
|
};
|
|
9061
9134
|
var PendingRPC = class PendingRPC {
|
|
@@ -9064,7 +9137,7 @@ var PendingRPC = class PendingRPC {
|
|
|
9064
9137
|
}
|
|
9065
9138
|
constructor(request, responses$, options) {
|
|
9066
9139
|
this.id = (0, uuid.v4)();
|
|
9067
|
-
logger$
|
|
9140
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}: method:${request.method}] Creating PendingRPC`);
|
|
9068
9141
|
this.request = request;
|
|
9069
9142
|
const timeoutMs = options?.timeoutMs ?? PendingRPC.defaultTimeoutMs;
|
|
9070
9143
|
const signal = options?.signal;
|
|
@@ -9090,22 +9163,22 @@ var PendingRPC = class PendingRPC {
|
|
|
9090
9163
|
isSettled = true;
|
|
9091
9164
|
if (response.error) {
|
|
9092
9165
|
const rpcError = new require_operators.JSONRPCError(response.error.code, response.error.message, response.error.data, void 0, request.id);
|
|
9093
|
-
logger$
|
|
9166
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with RPC error:`, rpcError);
|
|
9094
9167
|
reject(rpcError);
|
|
9095
9168
|
} else {
|
|
9096
|
-
logger$
|
|
9169
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Resolving promise with response:`, response);
|
|
9097
9170
|
resolve(response);
|
|
9098
9171
|
}
|
|
9099
9172
|
subscription.unsubscribe();
|
|
9100
9173
|
},
|
|
9101
9174
|
error: (error) => {
|
|
9102
|
-
logger$
|
|
9175
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with error:`, error);
|
|
9103
9176
|
isSettled = true;
|
|
9104
9177
|
reject(error);
|
|
9105
9178
|
subscription.unsubscribe();
|
|
9106
9179
|
},
|
|
9107
9180
|
complete: () => {
|
|
9108
|
-
logger$
|
|
9181
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Observable completed`);
|
|
9109
9182
|
if (!isSettled) reject(new require_operators.RPCTimeoutError(request.id, timeoutMs));
|
|
9110
9183
|
subscription.unsubscribe();
|
|
9111
9184
|
}
|
|
@@ -9125,7 +9198,7 @@ var PendingRPC = class PendingRPC {
|
|
|
9125
9198
|
|
|
9126
9199
|
//#endregion
|
|
9127
9200
|
//#region src/managers/ClientSessionManager.ts
|
|
9128
|
-
const logger$
|
|
9201
|
+
const logger$9 = require_operators.getLogger();
|
|
9129
9202
|
const getAddressSearchURI = (options) => {
|
|
9130
9203
|
const to = options.to?.split("?")[0];
|
|
9131
9204
|
const from$8 = options.from?.startsWith("subscriber://") ? options.from.replace("subscriber://", "") : options.from;
|
|
@@ -9223,7 +9296,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
9223
9296
|
try {
|
|
9224
9297
|
return await this.transport.execute(request, options);
|
|
9225
9298
|
} catch (error) {
|
|
9226
|
-
logger$
|
|
9299
|
+
logger$9.debug("[Session] Execute Error", error);
|
|
9227
9300
|
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
9228
9301
|
throw error;
|
|
9229
9302
|
}
|
|
@@ -9237,13 +9310,13 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
9237
9310
|
return true;
|
|
9238
9311
|
}
|
|
9239
9312
|
setupMessageHandlers() {
|
|
9240
|
-
logger$
|
|
9313
|
+
logger$9.debug("[Session] Setting up message handlers");
|
|
9241
9314
|
this.subscribeTo(this.authStateEvent$, async (authStateEvent) => {
|
|
9242
|
-
logger$
|
|
9315
|
+
logger$9.debug("[Session] Authorization state event received:", authStateEvent);
|
|
9243
9316
|
try {
|
|
9244
9317
|
await this.updateAuthorizationStateInStorage(authStateEvent.authorization_state);
|
|
9245
9318
|
} catch (error) {
|
|
9246
|
-
logger$
|
|
9319
|
+
logger$9.error("[Session] Failed to handle authorization state update:", error);
|
|
9247
9320
|
this._errors$.next(new require_operators.AuthStateHandlerError(error));
|
|
9248
9321
|
}
|
|
9249
9322
|
});
|
|
@@ -9251,29 +9324,29 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
9251
9324
|
if (this._authState$.value.kind === "authenticated") this._authState$.next({ kind: "unauthenticated" });
|
|
9252
9325
|
});
|
|
9253
9326
|
this.subscribeTo(this.transport.connectionStatus$.pipe((0, rxjs.filter)((status) => status === "connected"), (0, rxjs.exhaustMap)(() => {
|
|
9254
|
-
logger$
|
|
9327
|
+
logger$9.debug("[Session] Connection established, initiating authentication");
|
|
9255
9328
|
return (0, rxjs.from)(this.authenticate()).pipe((0, rxjs.catchError)((error) => {
|
|
9256
9329
|
this.handleAuthenticationError(error).catch((err) => {
|
|
9257
|
-
logger$
|
|
9330
|
+
logger$9.error("[Session] Error handling authentication failure:", err);
|
|
9258
9331
|
});
|
|
9259
9332
|
return rxjs.EMPTY;
|
|
9260
9333
|
}));
|
|
9261
9334
|
})), void 0);
|
|
9262
9335
|
this.subscribeTo(this.vertoInvite$, async (invite) => {
|
|
9263
|
-
logger$
|
|
9336
|
+
logger$9.debug("[Session] Verto invite received:", invite);
|
|
9264
9337
|
try {
|
|
9265
9338
|
await this.createInboundCall(invite);
|
|
9266
9339
|
} catch (error) {
|
|
9267
|
-
logger$
|
|
9340
|
+
logger$9.error("[Session] Error handling Verto invite:", error);
|
|
9268
9341
|
this._errors$.next(new require_operators.VertoInviteHandlerError(error));
|
|
9269
9342
|
}
|
|
9270
9343
|
});
|
|
9271
9344
|
this.subscribeTo(this.vertoAttach$, async (attach) => {
|
|
9272
|
-
logger$
|
|
9345
|
+
logger$9.debug("[Session] Verto attach received:", attach);
|
|
9273
9346
|
try {
|
|
9274
9347
|
await this.handleVertoAttach(attach);
|
|
9275
9348
|
} catch (error) {
|
|
9276
|
-
logger$
|
|
9349
|
+
logger$9.error("[Session] Error handling Verto attach:", error);
|
|
9277
9350
|
this._errors$.next(new require_operators.VertoAttachHandlerError(error));
|
|
9278
9351
|
}
|
|
9279
9352
|
});
|
|
@@ -9283,36 +9356,36 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
9283
9356
|
const storedState = await this.storage.getItem(this.authorizationStateKey);
|
|
9284
9357
|
this.authorizationState$.next(storedState ?? void 0);
|
|
9285
9358
|
} catch (error) {
|
|
9286
|
-
logger$
|
|
9359
|
+
logger$9.error("Failed to retrieve authorization state from storage:", error);
|
|
9287
9360
|
this.authorizationState$.next(void 0);
|
|
9288
9361
|
}
|
|
9289
9362
|
}
|
|
9290
9363
|
async updateAuthorizationStateInStorage(authorizationState) {
|
|
9291
9364
|
if (!authorizationState) {
|
|
9292
|
-
logger$
|
|
9365
|
+
logger$9.debug("[Session] Removing authorization state from storage");
|
|
9293
9366
|
try {
|
|
9294
9367
|
await this.storage.removeItem(this.authorizationStateKey);
|
|
9295
9368
|
this.authorizationState$.next(void 0);
|
|
9296
9369
|
} catch (error) {
|
|
9297
|
-
logger$
|
|
9370
|
+
logger$9.error("Failed to remove authorization state from storage:", error);
|
|
9298
9371
|
throw error;
|
|
9299
9372
|
}
|
|
9300
9373
|
return;
|
|
9301
9374
|
}
|
|
9302
9375
|
try {
|
|
9303
|
-
logger$
|
|
9376
|
+
logger$9.debug("[Session] Updating authorization state in storage");
|
|
9304
9377
|
await this.storage.setItem(this.authorizationStateKey, authorizationState);
|
|
9305
9378
|
this.authorizationState$.next(authorizationState);
|
|
9306
9379
|
} catch (error) {
|
|
9307
|
-
logger$
|
|
9380
|
+
logger$9.error("Failed to retrieve authorization state from storage:", error);
|
|
9308
9381
|
throw error;
|
|
9309
9382
|
}
|
|
9310
9383
|
}
|
|
9311
9384
|
get authStateEvent$() {
|
|
9312
9385
|
return this.cachedObservable("authStateEvent$", () => this.signalingEvent$.pipe((0, rxjs.tap)((msg) => {
|
|
9313
|
-
logger$
|
|
9386
|
+
logger$9.debug("[Session] Received incoming message:", msg);
|
|
9314
9387
|
}), require_operators.filterAs(isSignalwireAuthorizationStateMetadata, "params"), (0, rxjs.tap)((event) => {
|
|
9315
|
-
logger$
|
|
9388
|
+
logger$9.debug("[Session] Authorization state event received:", event.authorization_state);
|
|
9316
9389
|
})));
|
|
9317
9390
|
}
|
|
9318
9391
|
get signalingEvent$() {
|
|
@@ -9350,42 +9423,72 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
9350
9423
|
await (0, rxjs.firstValueFrom)(this.authenticated$.pipe((0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.filter)(Boolean), (0, rxjs.take)(1), (0, rxjs.timeout)({ first: 15e3 })));
|
|
9351
9424
|
}
|
|
9352
9425
|
async handleAuthenticationError(error) {
|
|
9353
|
-
logger$
|
|
9426
|
+
logger$9.error("Authentication error:", error);
|
|
9354
9427
|
const isRecoverableAuthError = error instanceof require_operators.JSONRPCError && (error.code === RPC_ERROR_REQUESTER_VALIDATION_FAILED || error.code === RPC_ERROR_INVALID_PARAMS || error.code === RPC_ERROR_AUTHENTICATION_FAILED);
|
|
9355
9428
|
const hasStoredState = await (0, rxjs.firstValueFrom)(this.authorizationState$.pipe((0, rxjs.take)(1))) !== void 0;
|
|
9356
9429
|
if (isRecoverableAuthError && hasStoredState) {
|
|
9357
|
-
logger$
|
|
9430
|
+
logger$9.debug("[Session] Recoverable auth error — cleaning up stored state and reconnecting fresh");
|
|
9358
9431
|
try {
|
|
9359
9432
|
await this.cleanupStoredConnectionParams();
|
|
9360
9433
|
} catch (cleanupError) {
|
|
9361
|
-
logger$
|
|
9434
|
+
logger$9.error("Failed to cleanup stored connection params:", cleanupError);
|
|
9362
9435
|
} finally {
|
|
9363
9436
|
this.transport.reconnect();
|
|
9364
9437
|
}
|
|
9365
9438
|
} else this._errors$.next(error);
|
|
9366
9439
|
}
|
|
9440
|
+
/**
|
|
9441
|
+
* Clear the resume state (authorization_state + protocol) only.
|
|
9442
|
+
*
|
|
9443
|
+
* This is the stale-auth-state recovery helper used by handleAuthError:
|
|
9444
|
+
* the server rejected a reconnect, so the resume state is discarded and a
|
|
9445
|
+
* fresh connect follows. Attach records are deliberately preserved — the
|
|
9446
|
+
* session lives on through the reconnect and reattachCalls() needs the
|
|
9447
|
+
* stored call references afterwards. Do NOT add detachAll() here.
|
|
9448
|
+
*
|
|
9449
|
+
* For public teardown (disconnect/destroy), use {@link teardownSessionState}
|
|
9450
|
+
* instead, which clears the attach records as well.
|
|
9451
|
+
*/
|
|
9367
9452
|
async cleanupStoredConnectionParams() {
|
|
9368
9453
|
await this.transport.setProtocol(void 0);
|
|
9369
9454
|
await this.updateAuthorizationStateInStorage(void 0);
|
|
9370
9455
|
this._authorization$.next(void 0);
|
|
9371
9456
|
}
|
|
9457
|
+
/**
|
|
9458
|
+
* Public-teardown helper for disconnect()/destroy(). Clears the resume
|
|
9459
|
+
* state (authorization_state + protocol) AND the attach records as one
|
|
9460
|
+
* atomic unit.
|
|
9461
|
+
*
|
|
9462
|
+
* The two stores are coupled: the backend only honors attach records
|
|
9463
|
+
* within the session identified by the resume state, so ending the
|
|
9464
|
+
* session must clear both. Clearing one without the other strands records
|
|
9465
|
+
* no future session can honor (disconnect) or revives a session the
|
|
9466
|
+
* developer explicitly ended (destroy).
|
|
9467
|
+
*
|
|
9468
|
+
* Distinct from {@link cleanupStoredConnectionParams}, which keeps the
|
|
9469
|
+
* attach records for the stale-auth-state recovery path.
|
|
9470
|
+
*/
|
|
9471
|
+
async teardownSessionState() {
|
|
9472
|
+
await this.cleanupStoredConnectionParams();
|
|
9473
|
+
await this.attachManager.detachAll();
|
|
9474
|
+
}
|
|
9372
9475
|
async updateAuthState(authorization_state) {
|
|
9373
9476
|
try {
|
|
9374
9477
|
await this.storage.setItem(this.authorizationStateKey, authorization_state);
|
|
9375
9478
|
} catch (error) {
|
|
9376
|
-
logger$
|
|
9479
|
+
logger$9.error("Failed to update authorization state in storage:", error);
|
|
9377
9480
|
this._errors$.next(new require_operators.AuthStateHandlerError(error));
|
|
9378
9481
|
}
|
|
9379
9482
|
}
|
|
9380
9483
|
async reauthenticate(token, dpopToken, options) {
|
|
9381
|
-
logger$
|
|
9484
|
+
logger$9.debug("[Session] Re-authenticating session");
|
|
9382
9485
|
try {
|
|
9383
9486
|
let resolvedDpopToken = dpopToken;
|
|
9384
9487
|
if (!resolvedDpopToken && this.dpopManager?.initialized) try {
|
|
9385
9488
|
resolvedDpopToken = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
|
|
9386
9489
|
} catch (error) {
|
|
9387
9490
|
if (this.clientBound) throw error;
|
|
9388
|
-
logger$
|
|
9491
|
+
logger$9.warn("[Session] Failed to create DPoP proof for reauthenticate:", error);
|
|
9389
9492
|
}
|
|
9390
9493
|
const request = RPCReauthenticate({
|
|
9391
9494
|
project: this._authorization$.value?.project_id ?? "",
|
|
@@ -9393,24 +9496,24 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
9393
9496
|
...resolvedDpopToken ? { dpop_token: resolvedDpopToken } : {}
|
|
9394
9497
|
});
|
|
9395
9498
|
await (0, rxjs.lastValueFrom)((0, rxjs.from)(this.transport.execute(request)).pipe(require_operators.throwOnRPCError(), (0, rxjs.take)(1), (0, rxjs.catchError)((err) => {
|
|
9396
|
-
logger$
|
|
9499
|
+
logger$9.error("[Session] Re-authentication RPC failed:", err);
|
|
9397
9500
|
throw err;
|
|
9398
9501
|
})));
|
|
9399
9502
|
if (options?.clientBound) this._wasClientBound = true;
|
|
9400
|
-
logger$
|
|
9503
|
+
logger$9.debug("[Session] Re-authentication successful, updating stored auth state");
|
|
9401
9504
|
} catch (error) {
|
|
9402
|
-
logger$
|
|
9505
|
+
logger$9.error("[Session] Re-authentication failed:", error);
|
|
9403
9506
|
this._errors$.next(new require_operators.AuthStateHandlerError(error));
|
|
9404
9507
|
throw error;
|
|
9405
9508
|
}
|
|
9406
9509
|
}
|
|
9407
9510
|
async authenticate() {
|
|
9408
|
-
logger$
|
|
9511
|
+
logger$9.debug("[Session] Starting authentication process");
|
|
9409
9512
|
const persistedParams = await (0, rxjs.firstValueFrom)((0, rxjs.combineLatest)({
|
|
9410
9513
|
protocol: this.transport.protocol$,
|
|
9411
9514
|
authorization_state: this.authorizationState$
|
|
9412
9515
|
}).pipe((0, rxjs.take)(1)));
|
|
9413
|
-
logger$
|
|
9516
|
+
logger$9.debug("[Session] Persisted params:\n", {
|
|
9414
9517
|
protocol: persistedParams.protocol,
|
|
9415
9518
|
authStateLength: persistedParams.authorization_state?.length
|
|
9416
9519
|
});
|
|
@@ -9418,16 +9521,16 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
9418
9521
|
const storedToken = this.getCredential().token;
|
|
9419
9522
|
const isReconnect = hasReconnectState && storedToken;
|
|
9420
9523
|
let dpopToken;
|
|
9421
|
-
if (isReconnect) logger$
|
|
9524
|
+
if (isReconnect) logger$9.debug("[Session] Reconnecting with stored jwt_token + authorization_state");
|
|
9422
9525
|
else if (this.onBeforeReconnect && this.clientBound) {
|
|
9423
|
-
logger$
|
|
9526
|
+
logger$9.debug("[Session] Refreshing credentials before fresh connect");
|
|
9424
9527
|
await this.onBeforeReconnect();
|
|
9425
9528
|
}
|
|
9426
9529
|
if ((!isReconnect || this.clientBound) && this.dpopManager?.initialized) try {
|
|
9427
9530
|
dpopToken = await this.dpopManager.createRpcProof({ method: "signalwire.connect" });
|
|
9428
9531
|
} catch (error) {
|
|
9429
9532
|
if (this.clientBound) throw error;
|
|
9430
|
-
logger$
|
|
9533
|
+
logger$9.warn("[Session] Failed to create DPoP proof for connect, proceeding without:", error);
|
|
9431
9534
|
}
|
|
9432
9535
|
const rpcConnectRequest = RPCConnect({
|
|
9433
9536
|
authentication: isReconnect ? { jwt_token: storedToken } : this.authentication,
|
|
@@ -9444,12 +9547,12 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
9444
9547
|
} : {}
|
|
9445
9548
|
});
|
|
9446
9549
|
const response = await (0, rxjs.lastValueFrom)((0, rxjs.from)(this.transport.execute(rpcConnectRequest)).pipe(require_operators.throwOnRPCError(), (0, rxjs.map)((res) => res.result), (0, rxjs.filter)(isRPCConnectResult), (0, rxjs.tap)(() => {
|
|
9447
|
-
logger$
|
|
9550
|
+
logger$9.debug("[Session] Response passed filter, processing authentication result");
|
|
9448
9551
|
}), (0, rxjs.take)(1), (0, rxjs.catchError)((err) => {
|
|
9449
|
-
logger$
|
|
9552
|
+
logger$9.error("[Session] Authentication RPC failed:", err);
|
|
9450
9553
|
throw err;
|
|
9451
9554
|
})));
|
|
9452
|
-
logger$
|
|
9555
|
+
logger$9.debug("[Session] Processing authentication result:", {
|
|
9453
9556
|
hasProtocol: !!response.protocol,
|
|
9454
9557
|
hasAuthorization: !!response.authorization,
|
|
9455
9558
|
hasIceServers: !!response.ice_servers
|
|
@@ -9458,12 +9561,12 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
9458
9561
|
this._authorization$.next(response.authorization);
|
|
9459
9562
|
this._iceServers$.next(response.ice_servers ?? []);
|
|
9460
9563
|
this._authState$.next({ kind: "authenticated" });
|
|
9461
|
-
logger$
|
|
9564
|
+
logger$9.debug("[Session] Authentication completed successfully");
|
|
9462
9565
|
}
|
|
9463
9566
|
async disconnect() {
|
|
9464
9567
|
this.transport.disconnect();
|
|
9465
9568
|
this._authState$.next({ kind: "unauthenticated" });
|
|
9466
|
-
await this.
|
|
9569
|
+
await this.teardownSessionState();
|
|
9467
9570
|
}
|
|
9468
9571
|
async createInboundCall(invite) {
|
|
9469
9572
|
const callSession = await this.createCall({
|
|
@@ -9494,11 +9597,11 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
9494
9597
|
async handleVertoAttach(attach) {
|
|
9495
9598
|
const { callID } = attach;
|
|
9496
9599
|
if (callID in this._calls$.value) {
|
|
9497
|
-
logger$
|
|
9600
|
+
logger$9.debug(`[Session] Verto attach for existing call ${callID}, deferring to per-call handler`);
|
|
9498
9601
|
return;
|
|
9499
9602
|
}
|
|
9500
9603
|
const storedOptions = await this.attachManager.consumePendingAttachment(callID);
|
|
9501
|
-
logger$
|
|
9604
|
+
logger$9.debug(`[Session] Creating reattached call for callID: ${callID}`);
|
|
9502
9605
|
const callSession = await this.createCall({
|
|
9503
9606
|
nodeId: attach.node_id,
|
|
9504
9607
|
callId: callID,
|
|
@@ -9529,7 +9632,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
9529
9632
|
});
|
|
9530
9633
|
return callSession;
|
|
9531
9634
|
} catch (error) {
|
|
9532
|
-
logger$
|
|
9635
|
+
logger$9.error("[Session] Error creating outbound call:", error);
|
|
9533
9636
|
callSession?.destroy();
|
|
9534
9637
|
const callError = new require_operators.CallCreateError(error instanceof rxjs.TimeoutError ? "Call create timeout" : "Call creation failed", error, "outbound");
|
|
9535
9638
|
this._errors$.next(callError);
|
|
@@ -9547,7 +9650,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
9547
9650
|
address = this._directory.get(addressId);
|
|
9548
9651
|
if (!address) throw new require_operators.DependencyError(`Address ID: ${addressId} not found`);
|
|
9549
9652
|
} catch {
|
|
9550
|
-
logger$
|
|
9653
|
+
logger$9.warn(`[Session] Directory lookup failed for ${addressURI}, proceeding with raw URI`);
|
|
9551
9654
|
}
|
|
9552
9655
|
const callSession = this.callFactory.createCall(address, { ...options });
|
|
9553
9656
|
this.subscribeTo(callSession.status$.pipe((0, rxjs.filter)((status) => status === "destroyed"), (0, rxjs.take)(1)), () => {
|
|
@@ -9556,7 +9659,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
9556
9659
|
});
|
|
9557
9660
|
return callSession;
|
|
9558
9661
|
} catch (error) {
|
|
9559
|
-
logger$
|
|
9662
|
+
logger$9.error("[Session] Error creating call session:", error);
|
|
9560
9663
|
throw new require_operators.CallCreateError("Call create error", error, options.initOffer ? "inbound" : "outbound");
|
|
9561
9664
|
}
|
|
9562
9665
|
}
|
|
@@ -9604,7 +9707,7 @@ const isString = (obj) => typeof obj === "string";
|
|
|
9604
9707
|
|
|
9605
9708
|
//#endregion
|
|
9606
9709
|
//#region src/managers/ConversationsManager.ts
|
|
9607
|
-
const logger$
|
|
9710
|
+
const logger$8 = require_operators.getLogger();
|
|
9608
9711
|
var ConversationMessagesFetcher = class extends Fetcher {
|
|
9609
9712
|
constructor(groupId, http) {
|
|
9610
9713
|
super(`/api/fabric/conversations/${groupId}/messages`, "page_size=100", http);
|
|
@@ -9644,13 +9747,13 @@ var ConversationsManager = class {
|
|
|
9644
9747
|
}
|
|
9645
9748
|
throw new require_operators.ConversationError("Join Failed - Unexpected response");
|
|
9646
9749
|
} catch (error) {
|
|
9647
|
-
logger$
|
|
9750
|
+
logger$8.error("[ConversationsManager] Failed to join conversation:", error);
|
|
9648
9751
|
throw error;
|
|
9649
9752
|
}
|
|
9650
9753
|
}
|
|
9651
9754
|
async getConversationMessageCollection(addressId) {
|
|
9652
9755
|
const groupId = this.groupIds.get(addressId) ?? await this.join(addressId);
|
|
9653
|
-
return Promise.resolve(new ConversationMessageCollection(groupId, this.clientSession.signalingEvent$.pipe(require_operators.filterAs(isConversationMessageMetadata, "params"), (0, rxjs.tap)((event) => logger$
|
|
9756
|
+
return Promise.resolve(new ConversationMessageCollection(groupId, this.clientSession.signalingEvent$.pipe(require_operators.filterAs(isConversationMessageMetadata, "params"), (0, rxjs.tap)((event) => logger$8.debug("[ConversationsManager ] Conversation Event:", event)), (0, rxjs.map)((params) => ({ ...params }))), this.http, this.onError));
|
|
9654
9757
|
}
|
|
9655
9758
|
async sendText(text, destinationAddressId) {
|
|
9656
9759
|
const groupId = this.groupIds.get(destinationAddressId) ?? await this.join(destinationAddressId);
|
|
@@ -9667,7 +9770,7 @@ var ConversationsManager = class {
|
|
|
9667
9770
|
})).ok) return;
|
|
9668
9771
|
throw new require_operators.ConversationError("Send Text Failed - Unexpected response");
|
|
9669
9772
|
} catch (error) {
|
|
9670
|
-
logger$
|
|
9773
|
+
logger$8.error("[ConversationsManager] Failed to send text message:", error);
|
|
9671
9774
|
throw error;
|
|
9672
9775
|
}
|
|
9673
9776
|
}
|
|
@@ -9675,7 +9778,7 @@ var ConversationsManager = class {
|
|
|
9675
9778
|
|
|
9676
9779
|
//#endregion
|
|
9677
9780
|
//#region src/managers/DeviceTokenManager.ts
|
|
9678
|
-
const logger$
|
|
9781
|
+
const logger$7 = require_operators.getLogger();
|
|
9679
9782
|
/**
|
|
9680
9783
|
* Resolves the token expiry timestamp (epoch seconds) using a 3-tier priority chain:
|
|
9681
9784
|
* 1. `data.expires_at` — server-provided absolute timestamp
|
|
@@ -9685,7 +9788,7 @@ const logger$6 = require_operators.getLogger();
|
|
|
9685
9788
|
function resolveExpiresAt(data) {
|
|
9686
9789
|
if (data.expires_at) return data.expires_at;
|
|
9687
9790
|
if (data.expires_in) return Math.floor(Date.now() / 1e3) + data.expires_in;
|
|
9688
|
-
logger$
|
|
9791
|
+
logger$7.warn("[DeviceToken] Could not determine token expiry, using default");
|
|
9689
9792
|
return Math.floor(Date.now() / 1e3) + DEVICE_TOKEN_DEFAULT_EXPIRE_IN;
|
|
9690
9793
|
}
|
|
9691
9794
|
/**
|
|
@@ -9718,11 +9821,12 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
9718
9821
|
this.getCredential = getCredential;
|
|
9719
9822
|
this._currentToken$ = this.createBehaviorSubject(null);
|
|
9720
9823
|
this._refreshInProgress = false;
|
|
9824
|
+
this._paused = false;
|
|
9721
9825
|
this._effectiveExpireIn = DEVICE_TOKEN_DEFAULT_EXPIRE_IN;
|
|
9722
9826
|
this.subscribeTo(this._currentToken$.pipe((0, rxjs.filter)(Boolean), (0, rxjs.switchMap)((tokenData) => {
|
|
9723
9827
|
const expiresAt = resolveExpiresAt(tokenData);
|
|
9724
9828
|
const refreshIn = Math.max(expiresAt * 1e3 - Date.now() - DEVICE_TOKEN_REFRESH_BUFFER_MS, 1e3);
|
|
9725
|
-
logger$
|
|
9829
|
+
logger$7.debug(`[DeviceToken] Scheduling Client Bound SAT refresh in ${refreshIn}ms`);
|
|
9726
9830
|
return (0, rxjs.timer)(refreshIn);
|
|
9727
9831
|
})), () => {
|
|
9728
9832
|
this.executeRefresh();
|
|
@@ -9736,7 +9840,12 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
9736
9840
|
* Activates the Client Bound SAT flow when the user's token has
|
|
9737
9841
|
* `sat:refresh` scope.
|
|
9738
9842
|
*
|
|
9739
|
-
*
|
|
9843
|
+
* Returns an {@link ActivationResult} indicating whether the manager
|
|
9844
|
+
* took ownership of refresh duties. The caller must use the `activated`
|
|
9845
|
+
* boolean to decide whether to keep its own refresh path armed — when
|
|
9846
|
+
* `activated` is `false`, the caller is responsible for refresh.
|
|
9847
|
+
*
|
|
9848
|
+
* Steps on success:
|
|
9740
9849
|
* 1. Check user's `sat_claims` for `sat:refresh` scope
|
|
9741
9850
|
* 2. Call `/api/fabric/subscriber/devices/token` with a DPoP proof
|
|
9742
9851
|
* 3. Reauthenticate the session with the Client Bound SAT + DPoP proof
|
|
@@ -9745,32 +9854,51 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
9745
9854
|
async activate(user, session, updateCredential) {
|
|
9746
9855
|
const { satClaims } = user;
|
|
9747
9856
|
if (!satClaims?.scope?.includes(SAT_REFRESH_SCOPE)) {
|
|
9748
|
-
logger$
|
|
9749
|
-
return
|
|
9857
|
+
logger$7.debug("[DeviceToken] No sat:refresh scope, skipping Client Bound SAT activation");
|
|
9858
|
+
return {
|
|
9859
|
+
activated: false,
|
|
9860
|
+
reason: "no-scope"
|
|
9861
|
+
};
|
|
9750
9862
|
}
|
|
9751
9863
|
this._session = session;
|
|
9752
9864
|
this._updateCredential = updateCredential;
|
|
9753
9865
|
try {
|
|
9866
|
+
const cached = this._currentToken$.value;
|
|
9867
|
+
if (cached && this.isTokenFresh(cached)) {
|
|
9868
|
+
logger$7.debug("[DeviceToken] Reusing cached Client Bound SAT — skipping /devices/token");
|
|
9869
|
+
const rpcProof$1 = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
|
|
9870
|
+
await session.reauthenticate(cached.token, rpcProof$1, { clientBound: true });
|
|
9871
|
+
updateCredential({ token: cached.token });
|
|
9872
|
+
return { activated: true };
|
|
9873
|
+
}
|
|
9754
9874
|
const tokenData = await this.obtainToken();
|
|
9755
9875
|
if (!tokenData.expires_at && !tokenData.expires_in && satClaims.expires_at) tokenData.expires_at = satClaims.expires_at;
|
|
9756
9876
|
this._effectiveExpireIn = resolveExpireIn(tokenData);
|
|
9757
9877
|
const rpcProof = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
|
|
9758
9878
|
await session.reauthenticate(tokenData.token, rpcProof, { clientBound: true });
|
|
9759
9879
|
updateCredential({ token: tokenData.token });
|
|
9760
|
-
logger$
|
|
9880
|
+
logger$7.info("[DeviceToken] Client Bound SAT activated successfully");
|
|
9761
9881
|
this._currentToken$.next(tokenData);
|
|
9882
|
+
return { activated: true };
|
|
9762
9883
|
} catch (error) {
|
|
9763
|
-
logger$
|
|
9884
|
+
logger$7.error("[DeviceToken] Failed to activate Client Bound SAT:", error);
|
|
9764
9885
|
this.errorHandler(new require_operators.DPoPInitError(error, "Failed to activate Client Bound SAT"));
|
|
9765
|
-
|
|
9766
|
-
|
|
9767
|
-
|
|
9768
|
-
|
|
9769
|
-
expires_at: expiresAt
|
|
9770
|
-
});
|
|
9886
|
+
return {
|
|
9887
|
+
activated: false,
|
|
9888
|
+
reason: "endpoint-failed"
|
|
9889
|
+
};
|
|
9771
9890
|
}
|
|
9772
9891
|
}
|
|
9773
9892
|
/**
|
|
9893
|
+
* Returns true when the cached token has enough headroom before expiry to
|
|
9894
|
+
* be safely reused on reactivation. The headroom matches the refresh
|
|
9895
|
+
* buffer, so a token within the refresh window is treated as stale (the
|
|
9896
|
+
* reactive pipeline is about to refresh it anyway).
|
|
9897
|
+
*/
|
|
9898
|
+
isTokenFresh(token) {
|
|
9899
|
+
return resolveExpiresAt(token) * 1e3 - Date.now() > DEVICE_TOKEN_REFRESH_BUFFER_MS;
|
|
9900
|
+
}
|
|
9901
|
+
/**
|
|
9774
9902
|
* Obtains a Client Bound SAT from `/api/fabric/subscriber/devices/token`.
|
|
9775
9903
|
* Returns the full {@link DeviceTokenResponse} including expiry metadata.
|
|
9776
9904
|
*/
|
|
@@ -9800,7 +9928,7 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
9800
9928
|
* handled by the reactive pipeline).
|
|
9801
9929
|
*/
|
|
9802
9930
|
async refreshToken(session, currentToken, updateCredential) {
|
|
9803
|
-
logger$
|
|
9931
|
+
logger$7.debug("[DeviceToken] Refreshing Client Bound SAT");
|
|
9804
9932
|
const dpopProof = await this.dpopManager.createHttpProof({
|
|
9805
9933
|
method: "POST",
|
|
9806
9934
|
uri: DEVICE_REFRESH_ENDPOINT,
|
|
@@ -9822,7 +9950,7 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
9822
9950
|
const rpcProof = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
|
|
9823
9951
|
await session.reauthenticate(data.token, rpcProof);
|
|
9824
9952
|
updateCredential({ token: data.token });
|
|
9825
|
-
logger$
|
|
9953
|
+
logger$7.info("[DeviceToken] Client Bound SAT refreshed successfully");
|
|
9826
9954
|
return data;
|
|
9827
9955
|
}
|
|
9828
9956
|
/**
|
|
@@ -9831,18 +9959,22 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
9831
9959
|
* On all retries exhausted, emits to `errorHandler`.
|
|
9832
9960
|
*/
|
|
9833
9961
|
async executeRefresh() {
|
|
9962
|
+
if (this._paused) {
|
|
9963
|
+
logger$7.debug("[DeviceToken] Manager paused, skipping refresh");
|
|
9964
|
+
return;
|
|
9965
|
+
}
|
|
9834
9966
|
if (this._refreshInProgress) {
|
|
9835
|
-
logger$
|
|
9967
|
+
logger$7.debug("[DeviceToken] Refresh already in progress, skipping");
|
|
9836
9968
|
return;
|
|
9837
9969
|
}
|
|
9838
9970
|
const session = this._session;
|
|
9839
9971
|
const updateCredential = this._updateCredential;
|
|
9840
9972
|
if (!session || !updateCredential) {
|
|
9841
|
-
logger$
|
|
9973
|
+
logger$7.warn("[DeviceToken] Cannot refresh: session or updateCredential not set");
|
|
9842
9974
|
return;
|
|
9843
9975
|
}
|
|
9844
9976
|
if (!session.authenticated) {
|
|
9845
|
-
logger$
|
|
9977
|
+
logger$7.debug("[DeviceToken] Session not authenticated, deferring refresh");
|
|
9846
9978
|
return;
|
|
9847
9979
|
}
|
|
9848
9980
|
this._refreshInProgress = true;
|
|
@@ -9852,7 +9984,7 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
9852
9984
|
const newTokenData = await this.retryRefresh(session, currentToken, updateCredential);
|
|
9853
9985
|
this._currentToken$.next(newTokenData);
|
|
9854
9986
|
} catch (error) {
|
|
9855
|
-
logger$
|
|
9987
|
+
logger$7.error("[DeviceToken] Automatic Client Bound SAT refresh failed:", error);
|
|
9856
9988
|
this.errorHandler(error instanceof require_operators.TokenRefreshError ? error : new require_operators.TokenRefreshError("Automatic token refresh failed", error));
|
|
9857
9989
|
} finally {
|
|
9858
9990
|
this._refreshInProgress = false;
|
|
@@ -9870,18 +10002,215 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
9870
10002
|
lastError = error;
|
|
9871
10003
|
if (attempt < DEVICE_TOKEN_REFRESH_MAX_RETRIES - 1) {
|
|
9872
10004
|
const delay = DEVICE_TOKEN_REFRESH_RETRY_BASE_MS * Math.pow(2, attempt);
|
|
9873
|
-
logger$
|
|
10005
|
+
logger$7.warn(`[DeviceToken] Refresh attempt ${attempt + 1} failed, retrying in ${delay}ms`);
|
|
9874
10006
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
9875
10007
|
}
|
|
9876
10008
|
}
|
|
9877
10009
|
throw lastError instanceof Error ? lastError : new require_operators.TokenRefreshError("All refresh retries exhausted", lastError);
|
|
9878
10010
|
}
|
|
10011
|
+
/**
|
|
10012
|
+
* Stops the reactive refresh pipeline from firing. Use when the underlying
|
|
10013
|
+
* session is being torn down (e.g., during {@link SignalWire.disconnect})
|
|
10014
|
+
* so a scheduled refresh cannot fire against a destroyed session.
|
|
10015
|
+
*
|
|
10016
|
+
* The manager's state (cached token, effective TTL, subscriptions) is
|
|
10017
|
+
* preserved — call {@link resume} to re-enable firing after reconnect.
|
|
10018
|
+
*/
|
|
10019
|
+
pause() {
|
|
10020
|
+
this._paused = true;
|
|
10021
|
+
}
|
|
10022
|
+
/** Re-enables the reactive refresh pipeline after a previous {@link pause}. */
|
|
10023
|
+
resume() {
|
|
10024
|
+
this._paused = false;
|
|
10025
|
+
}
|
|
9879
10026
|
/** Cleans up the manager, cancelling the reactive pipeline and all subscriptions. */
|
|
9880
10027
|
destroy() {
|
|
9881
10028
|
super.destroy();
|
|
9882
10029
|
}
|
|
9883
10030
|
};
|
|
9884
10031
|
|
|
10032
|
+
//#endregion
|
|
10033
|
+
//#region src/managers/CredentialRefreshCoordinator.ts
|
|
10034
|
+
const logger$6 = require_operators.getLogger();
|
|
10035
|
+
const defaultDeviceTokenManagerFactory = (dpopManager, http, errorHandler, getCredential) => new DeviceTokenManager(dpopManager, http, errorHandler, getCredential);
|
|
10036
|
+
/**
|
|
10037
|
+
* Centralizes credential-refresh ownership across the two competing
|
|
10038
|
+
* mechanisms — developer-provided `CredentialProvider.refresh()` and the
|
|
10039
|
+
* Client Bound SAT path via {@link DeviceTokenManager}.
|
|
10040
|
+
*
|
|
10041
|
+
* Maintains the invariant: **at most one refresh mechanism is armed at a
|
|
10042
|
+
* time, and at least one is armed whenever the current credential has an
|
|
10043
|
+
* `expiry_at`**.
|
|
10044
|
+
*
|
|
10045
|
+
* Replaces the previous design where refresh state was distributed across
|
|
10046
|
+
* `SignalWire` (timer field, scheduler method, activation helper) and
|
|
10047
|
+
* `DeviceTokenManager` (reactive pipeline). Centralizing the invariant in
|
|
10048
|
+
* one component eliminates the bug class that produced issue #19074.
|
|
10049
|
+
*
|
|
10050
|
+
* Race-safety:
|
|
10051
|
+
* - `_activating` flag prevents overlapping `activate()` calls from racing.
|
|
10052
|
+
* - `_activationGeneration` lets late resolutions detect they've been
|
|
10053
|
+
* preempted by a newer activation (e.g., reconnect during in-flight
|
|
10054
|
+
* `obtainToken`).
|
|
10055
|
+
*/
|
|
10056
|
+
var CredentialRefreshCoordinator = class extends Destroyable {
|
|
10057
|
+
constructor(dpopManager, deps) {
|
|
10058
|
+
super();
|
|
10059
|
+
this.deps = deps;
|
|
10060
|
+
this._activating = false;
|
|
10061
|
+
this._activationGeneration = 0;
|
|
10062
|
+
if (dpopManager?.initialized) this._deviceTokenManager = (deps.deviceTokenManagerFactory ?? defaultDeviceTokenManagerFactory)(dpopManager, deps.http, (error) => deps.notifier.onError(error), () => deps.store.read());
|
|
10063
|
+
}
|
|
10064
|
+
/** True when the Client Bound SAT path is available (DPoP initialized). */
|
|
10065
|
+
get clientBoundSATAvailable() {
|
|
10066
|
+
return this._deviceTokenManager !== void 0;
|
|
10067
|
+
}
|
|
10068
|
+
/** True when the developer-provided refresh timer is currently armed. */
|
|
10069
|
+
get developerRefreshArmed() {
|
|
10070
|
+
return this._developerTimerId !== void 0;
|
|
10071
|
+
}
|
|
10072
|
+
/**
|
|
10073
|
+
* Arms the developer-provided refresh timer to fire shortly before
|
|
10074
|
+
* `expiresAt`. Replaces any previously scheduled developer refresh.
|
|
10075
|
+
*
|
|
10076
|
+
* Idempotent — multiple calls just reschedule. On retry exhaustion,
|
|
10077
|
+
* invokes `deps.onRefreshExhausted` so the orchestrator can disconnect.
|
|
10078
|
+
*/
|
|
10079
|
+
scheduleDeveloperRefresh(provider, expiresAt, attempt = 0) {
|
|
10080
|
+
if (this._developerTimerId !== void 0) clearTimeout(this._developerTimerId);
|
|
10081
|
+
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);
|
|
10082
|
+
this._developerTimerId = setTimeout(async () => {
|
|
10083
|
+
try {
|
|
10084
|
+
if (!provider.refresh) throw new require_operators.InvalidCredentialsError("Credential provider does not support refresh");
|
|
10085
|
+
const newCredentials = await provider.refresh();
|
|
10086
|
+
this.deps.store.write(newCredentials);
|
|
10087
|
+
this.deps.store.persist(newCredentials);
|
|
10088
|
+
logger$6.info("[Coordinator] Credentials refreshed successfully.");
|
|
10089
|
+
if (newCredentials.expiry_at) this.scheduleDeveloperRefresh(provider, newCredentials.expiry_at, 0);
|
|
10090
|
+
} catch (error) {
|
|
10091
|
+
const nextAttempt = attempt + 1;
|
|
10092
|
+
logger$6.error(`[Coordinator] Credential refresh failed (attempt ${nextAttempt}/${CREDENTIAL_REFRESH_MAX_RETRIES}):`, error);
|
|
10093
|
+
this.deps.notifier.onError(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
10094
|
+
if (nextAttempt < CREDENTIAL_REFRESH_MAX_RETRIES) this.scheduleDeveloperRefresh(provider, expiresAt, nextAttempt);
|
|
10095
|
+
else {
|
|
10096
|
+
logger$6.error("[Coordinator] Credential refresh exhausted all retries. Disconnecting.");
|
|
10097
|
+
this.deps.notifier.onError(new require_operators.TokenRefreshError("Credential refresh failed after max retries"));
|
|
10098
|
+
this.deps.notifier.onRefreshExhausted();
|
|
10099
|
+
}
|
|
10100
|
+
}
|
|
10101
|
+
}, refreshInterval);
|
|
10102
|
+
}
|
|
10103
|
+
/**
|
|
10104
|
+
* Cancels any scheduled developer-provided refresh. Idempotent.
|
|
10105
|
+
*
|
|
10106
|
+
* @internal Used by the coordinator's own activation flow. External
|
|
10107
|
+
* callers should use {@link suspend} for disconnect-time quiescence —
|
|
10108
|
+
* `suspend()` also pauses the internal Client Bound SAT pipeline.
|
|
10109
|
+
*/
|
|
10110
|
+
cancelDeveloperRefresh() {
|
|
10111
|
+
if (this._developerTimerId !== void 0) {
|
|
10112
|
+
clearTimeout(this._developerTimerId);
|
|
10113
|
+
this._developerTimerId = void 0;
|
|
10114
|
+
}
|
|
10115
|
+
}
|
|
10116
|
+
/**
|
|
10117
|
+
* Suspends both refresh paths — cancels the developer timer and pauses
|
|
10118
|
+
* the internal reactive pipeline. Use when the underlying session is
|
|
10119
|
+
* being torn down (e.g., {@link SignalWire.disconnect}). The next
|
|
10120
|
+
* {@link activate} call re-enables the internal pipeline.
|
|
10121
|
+
*
|
|
10122
|
+
* The internal manager's cached token survives — see
|
|
10123
|
+
* {@link DeviceTokenManager.pause} — so a subsequent reconnect can
|
|
10124
|
+
* skip the `/devices/token` exchange entirely.
|
|
10125
|
+
*/
|
|
10126
|
+
suspend() {
|
|
10127
|
+
this.cancelDeveloperRefresh();
|
|
10128
|
+
this._deviceTokenManager?.pause();
|
|
10129
|
+
}
|
|
10130
|
+
/**
|
|
10131
|
+
* Asks the Client Bound SAT path to take over refresh. If it accepts,
|
|
10132
|
+
* the developer-provided timer (if any) is cancelled. If it declines, the
|
|
10133
|
+
* developer timer remains armed and a `credential_refresh_fallback`
|
|
10134
|
+
* warning is emitted.
|
|
10135
|
+
*
|
|
10136
|
+
* **Idempotent** — re-entrant calls during an in-flight activation are
|
|
10137
|
+
* dropped. Use `_activationGeneration` to detect stale resolutions
|
|
10138
|
+
* (e.g., a reconnect-triggered activate() that races with an earlier one).
|
|
10139
|
+
*/
|
|
10140
|
+
async activate(user, session) {
|
|
10141
|
+
if (this._activating) {
|
|
10142
|
+
logger$6.debug("[Coordinator] activate() in flight; ignoring re-entrant call");
|
|
10143
|
+
return;
|
|
10144
|
+
}
|
|
10145
|
+
if (!this._deviceTokenManager) return;
|
|
10146
|
+
this._deviceTokenManager.resume();
|
|
10147
|
+
const generation = ++this._activationGeneration;
|
|
10148
|
+
this._activating = true;
|
|
10149
|
+
try {
|
|
10150
|
+
const result = await this.withActivationTimeout(this._deviceTokenManager.activate(user, session, (cred) => this.deps.store.merge(cred)));
|
|
10151
|
+
if (generation !== this._activationGeneration) {
|
|
10152
|
+
logger$6.debug("[Coordinator] activate() result discarded (preempted by newer activation)");
|
|
10153
|
+
return;
|
|
10154
|
+
}
|
|
10155
|
+
if (result.activated) {
|
|
10156
|
+
this.cancelDeveloperRefresh();
|
|
10157
|
+
logger$6.debug("[Coordinator] Developer refresh disabled — Client Bound SAT owns refresh");
|
|
10158
|
+
return;
|
|
10159
|
+
}
|
|
10160
|
+
logger$6.warn(`[SignalWire] [SW-REFRESH-FALLBACK] Client Bound SAT declined (reason=${result.reason}); using developer-provided refresh handler.`);
|
|
10161
|
+
this.deps.notifier.onWarning({
|
|
10162
|
+
code: "credential_refresh_fallback",
|
|
10163
|
+
source: "CredentialProvider",
|
|
10164
|
+
reason: result.reason,
|
|
10165
|
+
message: `Client Bound SAT activation declined (${result.reason}); using developer-provided refresh.`
|
|
10166
|
+
});
|
|
10167
|
+
} finally {
|
|
10168
|
+
this._activating = false;
|
|
10169
|
+
}
|
|
10170
|
+
}
|
|
10171
|
+
destroy() {
|
|
10172
|
+
this.cancelDeveloperRefresh();
|
|
10173
|
+
this._deviceTokenManager?.destroy();
|
|
10174
|
+
this._deviceTokenManager = void 0;
|
|
10175
|
+
super.destroy();
|
|
10176
|
+
}
|
|
10177
|
+
/**
|
|
10178
|
+
* Races the manager's `activate()` against a hard timeout. A wedged HTTP
|
|
10179
|
+
* layer (e.g., proxy issues) could otherwise hang the activation
|
|
10180
|
+
* indefinitely, leaving the session with no refresh mechanism while the
|
|
10181
|
+
* `_activating` guard blocks subsequent retries.
|
|
10182
|
+
*
|
|
10183
|
+
* On timeout, the manager is paused immediately. The inner `activate()`
|
|
10184
|
+
* may still complete in the background and emit to `_currentToken$`,
|
|
10185
|
+
* which would normally arm the reactive refresh pipeline; pausing
|
|
10186
|
+
* prevents that pipeline from firing while the developer refresh path
|
|
10187
|
+
* is the active mechanism — preserving the "at most one mechanism
|
|
10188
|
+
* armed" invariant. The next `activate()` call resumes the manager.
|
|
10189
|
+
*/
|
|
10190
|
+
async withActivationTimeout(inner) {
|
|
10191
|
+
return new Promise((resolve) => {
|
|
10192
|
+
const timer$3 = setTimeout(() => {
|
|
10193
|
+
this._deviceTokenManager?.pause();
|
|
10194
|
+
resolve({
|
|
10195
|
+
activated: false,
|
|
10196
|
+
reason: "activation-timeout"
|
|
10197
|
+
});
|
|
10198
|
+
}, CREDENTIAL_ACTIVATE_TIMEOUT_MS);
|
|
10199
|
+
inner.then((result) => {
|
|
10200
|
+
clearTimeout(timer$3);
|
|
10201
|
+
resolve(result);
|
|
10202
|
+
}, (error) => {
|
|
10203
|
+
clearTimeout(timer$3);
|
|
10204
|
+
this.deps.notifier.onError(error instanceof Error ? error : new Error(String(error)));
|
|
10205
|
+
resolve({
|
|
10206
|
+
activated: false,
|
|
10207
|
+
reason: "endpoint-failed"
|
|
10208
|
+
});
|
|
10209
|
+
});
|
|
10210
|
+
});
|
|
10211
|
+
}
|
|
10212
|
+
};
|
|
10213
|
+
|
|
9885
10214
|
//#endregion
|
|
9886
10215
|
//#region src/managers/DiagnosticsCollector.ts
|
|
9887
10216
|
const logger$5 = require_operators.getLogger();
|
|
@@ -10399,6 +10728,10 @@ var TransportManager = class extends Destroyable {
|
|
|
10399
10728
|
if (!isSignalwireRequest(message)) return true;
|
|
10400
10729
|
const eventChannel = message.params.event_channel;
|
|
10401
10730
|
if (!eventChannel) return true;
|
|
10731
|
+
if (message.params.event_type.startsWith("conversation.")) {
|
|
10732
|
+
logger$2.debug(`[Transport] Received conversation event: ${message.params.event_type} (event_channel: ${eventChannel})`);
|
|
10733
|
+
return true;
|
|
10734
|
+
}
|
|
10402
10735
|
const currentProtocol = this._currentProtocol;
|
|
10403
10736
|
if (!currentProtocol) return true;
|
|
10404
10737
|
if (!eventChannel.includes(currentProtocol)) {
|
|
@@ -10591,6 +10924,7 @@ var SignalWire = class extends Destroyable {
|
|
|
10591
10924
|
this._isConnected$ = this.createBehaviorSubject(false);
|
|
10592
10925
|
this._isRegistered$ = this.createBehaviorSubject(false);
|
|
10593
10926
|
this._errors$ = this.createReplaySubject(1);
|
|
10927
|
+
this._warnings$ = this.createReplaySubject(10);
|
|
10594
10928
|
this._options = {};
|
|
10595
10929
|
this._deps = new DependencyContainer();
|
|
10596
10930
|
this._credentialProvider = credentialProvider;
|
|
@@ -10650,6 +10984,27 @@ var SignalWire = class extends Destroyable {
|
|
|
10650
10984
|
*/
|
|
10651
10985
|
async resolveCredentials() {
|
|
10652
10986
|
const fingerprint = await this.initDPoP();
|
|
10987
|
+
this._refreshCoordinator = new CredentialRefreshCoordinator(this._dpopManager, {
|
|
10988
|
+
http: this._deps.http,
|
|
10989
|
+
notifier: {
|
|
10990
|
+
onError: (error) => this._errors$.next(error),
|
|
10991
|
+
onWarning: (warning) => this._warnings$.next(warning),
|
|
10992
|
+
onRefreshExhausted: () => void this.disconnect()
|
|
10993
|
+
},
|
|
10994
|
+
store: {
|
|
10995
|
+
read: () => this._deps.credential,
|
|
10996
|
+
write: (credential) => {
|
|
10997
|
+
this._deps.credential = credential;
|
|
10998
|
+
},
|
|
10999
|
+
merge: (partial) => {
|
|
11000
|
+
this._deps.credential = {
|
|
11001
|
+
...this._deps.credential,
|
|
11002
|
+
...partial
|
|
11003
|
+
};
|
|
11004
|
+
},
|
|
11005
|
+
persist: (credential) => this.persistCredential(credential)
|
|
11006
|
+
}
|
|
11007
|
+
});
|
|
10653
11008
|
if (this._credentialProvider) return this.validateCredentials(this._credentialProvider, void 0, fingerprint);
|
|
10654
11009
|
for (const scope of this._deps.persistSession ? ["local", "session"] : ["session"]) try {
|
|
10655
11010
|
const cached = await this._deps.storage.getItem("sw:cached_credential", scope);
|
|
@@ -10679,7 +11034,16 @@ var SignalWire = class extends Destroyable {
|
|
|
10679
11034
|
logger$1.error("[SignalWire] Provided credentials have expired.");
|
|
10680
11035
|
throw new require_operators.InvalidCredentialsError("Provided credentials have expired.");
|
|
10681
11036
|
}
|
|
10682
|
-
if (_credentials.expiry_at && credentialProvider?.refresh) this.
|
|
11037
|
+
if (_credentials.expiry_at && credentialProvider?.refresh) this._refreshCoordinator?.scheduleDeveloperRefresh(credentialProvider, _credentials.expiry_at);
|
|
11038
|
+
else if (_credentials.expiry_at && !credentialProvider?.refresh) {
|
|
11039
|
+
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.`);
|
|
11040
|
+
this._warnings$.next({
|
|
11041
|
+
code: "credential_no_refresh_handler",
|
|
11042
|
+
source: "CredentialProvider",
|
|
11043
|
+
message: "Credential has expiry_at but no refresh handler. Session will terminate at expiry unless the SAT carries 'sat:refresh' scope.",
|
|
11044
|
+
expiresAt: _credentials.expiry_at
|
|
11045
|
+
});
|
|
11046
|
+
}
|
|
10683
11047
|
this._deps.credential = _credentials;
|
|
10684
11048
|
this.persistCredential(_credentials);
|
|
10685
11049
|
if (this.isConnected && this._clientSession.authenticated && _credentials.token) try {
|
|
@@ -10690,35 +11054,6 @@ var SignalWire = class extends Destroyable {
|
|
|
10690
11054
|
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
10691
11055
|
}
|
|
10692
11056
|
}
|
|
10693
|
-
/**
|
|
10694
|
-
* Schedules credential refresh with exponential backoff retry on failure.
|
|
10695
|
-
* On success, resets attempt counter and schedules the next refresh.
|
|
10696
|
-
* After exhausting retries, emits TokenRefreshError and disconnects.
|
|
10697
|
-
*/
|
|
10698
|
-
scheduleCredentialRefresh(credentialProvider, expiresAt, attempt = 0) {
|
|
10699
|
-
if (this._refreshTimerId !== void 0) clearTimeout(this._refreshTimerId);
|
|
10700
|
-
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);
|
|
10701
|
-
this._refreshTimerId = setTimeout(async () => {
|
|
10702
|
-
try {
|
|
10703
|
-
if (!credentialProvider.refresh) throw new require_operators.InvalidCredentialsError("Credential provider does not support refresh");
|
|
10704
|
-
const newCredentials = await credentialProvider.refresh();
|
|
10705
|
-
this._deps.credential = newCredentials;
|
|
10706
|
-
this.persistCredential(newCredentials);
|
|
10707
|
-
logger$1.info("[SignalWire] Credentials refreshed successfully.");
|
|
10708
|
-
if (newCredentials.expiry_at) this.scheduleCredentialRefresh(credentialProvider, newCredentials.expiry_at, 0);
|
|
10709
|
-
} catch (error) {
|
|
10710
|
-
const nextAttempt = attempt + 1;
|
|
10711
|
-
logger$1.error(`[SignalWire] Credential refresh failed (attempt ${nextAttempt}/${CREDENTIAL_REFRESH_MAX_RETRIES}):`, error);
|
|
10712
|
-
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
10713
|
-
if (nextAttempt < CREDENTIAL_REFRESH_MAX_RETRIES) this.scheduleCredentialRefresh(credentialProvider, expiresAt, nextAttempt);
|
|
10714
|
-
else {
|
|
10715
|
-
logger$1.error("[SignalWire] Credential refresh exhausted all retries. Disconnecting.");
|
|
10716
|
-
this._errors$.next(new require_operators.TokenRefreshError("Credential refresh failed after max retries"));
|
|
10717
|
-
this.disconnect();
|
|
10718
|
-
}
|
|
10719
|
-
}
|
|
10720
|
-
}, refreshInterval);
|
|
10721
|
-
}
|
|
10722
11057
|
/** Persist credential to localStorage when persistSession is enabled. */
|
|
10723
11058
|
persistCredential(credential) {
|
|
10724
11059
|
if (!credential.token) return;
|
|
@@ -10812,6 +11147,7 @@ var SignalWire = class extends Destroyable {
|
|
|
10812
11147
|
logger$1.debug("[SignalWire] Credential expired, refreshing before reconnect");
|
|
10813
11148
|
const newCredentials = await this._credentialProvider.authenticate(fingerprint ? { fingerprint } : void 0);
|
|
10814
11149
|
this._deps.credential = newCredentials;
|
|
11150
|
+
if (newCredentials.expiry_at && this._credentialProvider.refresh) this._refreshCoordinator?.scheduleDeveloperRefresh(this._credentialProvider, newCredentials.expiry_at);
|
|
10815
11151
|
logger$1.debug("[SignalWire] Credential refreshed successfully for reconnect");
|
|
10816
11152
|
} catch (error) {
|
|
10817
11153
|
logger$1.error("[SignalWire] Failed to refresh credentials for reconnect:", error);
|
|
@@ -10823,33 +11159,12 @@ var SignalWire = class extends Destroyable {
|
|
|
10823
11159
|
this._errors$.next(error);
|
|
10824
11160
|
});
|
|
10825
11161
|
await this._clientSession.connect();
|
|
10826
|
-
|
|
10827
|
-
if (this._refreshTimerId) {
|
|
10828
|
-
clearTimeout(this._refreshTimerId);
|
|
10829
|
-
this._refreshTimerId = void 0;
|
|
10830
|
-
logger$1.debug("[SignalWire] Developer refresh disabled — Client Bound SAT activation starting");
|
|
10831
|
-
}
|
|
10832
|
-
this._deviceTokenManager = new DeviceTokenManager(this._dpopManager, this._deps.http, (error) => this._errors$.next(error), () => this._deps.credential);
|
|
10833
|
-
await this._deviceTokenManager.activate(this._deps.user, this._clientSession, (cred) => {
|
|
10834
|
-
this._deps.credential = {
|
|
10835
|
-
...this._deps.credential,
|
|
10836
|
-
...cred
|
|
10837
|
-
};
|
|
10838
|
-
});
|
|
10839
|
-
}
|
|
11162
|
+
await this._refreshCoordinator?.activate(this._deps.user, this._clientSession);
|
|
10840
11163
|
this.subscribeTo(this._clientSession.authenticated$.pipe((0, rxjs.skip)(1), (0, rxjs.filter)(Boolean)), async () => {
|
|
10841
11164
|
try {
|
|
10842
|
-
|
|
10843
|
-
await this._deviceTokenManager.activate(this._deps.user, this._clientSession, (cred) => {
|
|
10844
|
-
this._deps.credential = {
|
|
10845
|
-
...this._deps.credential,
|
|
10846
|
-
...cred
|
|
10847
|
-
};
|
|
10848
|
-
});
|
|
10849
|
-
logger$1.debug("[SignalWire] Client Bound SAT re-activated after reconnect");
|
|
10850
|
-
}
|
|
11165
|
+
await this._refreshCoordinator?.activate(this._deps.user, this._clientSession);
|
|
10851
11166
|
} catch (error) {
|
|
10852
|
-
logger$1.error("[SignalWire]
|
|
11167
|
+
logger$1.error("[SignalWire] Refresh re-arm after reconnect failed (non-fatal):", error);
|
|
10853
11168
|
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
10854
11169
|
}
|
|
10855
11170
|
try {
|
|
@@ -10935,6 +11250,19 @@ var SignalWire = class extends Destroyable {
|
|
|
10935
11250
|
get errors$() {
|
|
10936
11251
|
return this.deferEmission(this._errors$.asObservable());
|
|
10937
11252
|
}
|
|
11253
|
+
/**
|
|
11254
|
+
* Observable stream of non-fatal SDK warnings.
|
|
11255
|
+
*
|
|
11256
|
+
* Subscribe to detect SDK behaviors that affect session liveness or developer-facing
|
|
11257
|
+
* contracts but do not warrant disconnection — e.g., a fallback from Client Bound SAT
|
|
11258
|
+
* refresh to the developer-provided `refresh()` because the SAT lacks `sat:refresh`
|
|
11259
|
+
* scope. Discriminated by `code`.
|
|
11260
|
+
*
|
|
11261
|
+
* Independent from {@link errors$}: existing error consumers are not notified.
|
|
11262
|
+
*/
|
|
11263
|
+
get warnings$() {
|
|
11264
|
+
return this.deferEmission(this._warnings$.asObservable());
|
|
11265
|
+
}
|
|
10938
11266
|
/** Platform WebRTC capabilities detected at construction time. */
|
|
10939
11267
|
get platformCapabilities() {
|
|
10940
11268
|
this._platformCapabilities ??= detectPlatformCapabilities(this._options.webRTCApiProvider);
|
|
@@ -11003,7 +11331,7 @@ var SignalWire = class extends Destroyable {
|
|
|
11003
11331
|
logger$1.warn("[SignalWire] Failed to initialize VisibilityController:", error);
|
|
11004
11332
|
}
|
|
11005
11333
|
try {
|
|
11006
|
-
this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "
|
|
11334
|
+
this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "4.0.0-rc.1" });
|
|
11007
11335
|
} catch (error) {
|
|
11008
11336
|
logger$1.warn("[SignalWire] Failed to initialize DiagnosticsCollector:", error);
|
|
11009
11337
|
}
|
|
@@ -11011,14 +11339,19 @@ var SignalWire = class extends Destroyable {
|
|
|
11011
11339
|
/**
|
|
11012
11340
|
* Disconnects the WebSocket and tears down the current session.
|
|
11013
11341
|
*
|
|
11342
|
+
* Ends the session identified by the protocol and clears its persisted
|
|
11343
|
+
* resume state (`authorization_state` + protocol) and attach records
|
|
11344
|
+
* together — a later {@link connect} with the same credentials starts a
|
|
11345
|
+
* fresh session and cannot reattach to the ended session's calls.
|
|
11346
|
+
* Credentials and device preferences are preserved. To temporarily stop
|
|
11347
|
+
* receiving inbound calls while keeping the session alive, use
|
|
11348
|
+
* `unregister()` instead.
|
|
11349
|
+
*
|
|
11014
11350
|
* The client can be reconnected by calling {@link connect} again,
|
|
11015
11351
|
* which creates a fresh transport and session.
|
|
11016
11352
|
*/
|
|
11017
11353
|
async disconnect() {
|
|
11018
|
-
|
|
11019
|
-
clearTimeout(this._refreshTimerId);
|
|
11020
|
-
this._refreshTimerId = void 0;
|
|
11021
|
-
}
|
|
11354
|
+
this._refreshCoordinator?.suspend();
|
|
11022
11355
|
this._diagnosticsCollector?.record("connection", "disconnected");
|
|
11023
11356
|
await this.teardownTransportAndSession();
|
|
11024
11357
|
this._isConnected$.next(false);
|
|
@@ -11405,15 +11738,20 @@ var SignalWire = class extends Destroyable {
|
|
|
11405
11738
|
prefs.preferredVideoInput = null;
|
|
11406
11739
|
await this._deviceController.clearDeviceState();
|
|
11407
11740
|
}
|
|
11408
|
-
/**
|
|
11741
|
+
/**
|
|
11742
|
+
* Destroys the client, clearing timers and releasing all resources.
|
|
11743
|
+
*
|
|
11744
|
+
* Intentionally destroying the client ends its session: the resume state
|
|
11745
|
+
* (`authorization_state` + protocol) and the attach records are both
|
|
11746
|
+
* cleared. Credentials and device preferences are preserved — use
|
|
11747
|
+
* {@link resetToDefaults} for a full wipe. To temporarily stop receiving
|
|
11748
|
+
* inbound calls while keeping the session alive, use `unregister()`.
|
|
11749
|
+
*/
|
|
11409
11750
|
destroy() {
|
|
11410
|
-
|
|
11411
|
-
|
|
11412
|
-
this._refreshTimerId = void 0;
|
|
11413
|
-
}
|
|
11414
|
-
this._deviceTokenManager?.destroy();
|
|
11751
|
+
this._refreshCoordinator?.destroy();
|
|
11752
|
+
this._refreshCoordinator = void 0;
|
|
11415
11753
|
this._dpopManager?.destroy();
|
|
11416
|
-
|
|
11754
|
+
this._clientSession.teardownSessionState();
|
|
11417
11755
|
this._transport.destroy();
|
|
11418
11756
|
this._clientSession.destroy();
|
|
11419
11757
|
try {
|
|
@@ -11527,7 +11865,7 @@ var StaticCredentialProvider = class {
|
|
|
11527
11865
|
/**
|
|
11528
11866
|
* Library version from package.json, injected at build time.
|
|
11529
11867
|
*/
|
|
11530
|
-
const version = "
|
|
11868
|
+
const version = "4.0.0-rc.1";
|
|
11531
11869
|
/**
|
|
11532
11870
|
* Flag indicating the library has been loaded and is ready to use.
|
|
11533
11871
|
* For UMD builds: `window.SignalWire.ready`
|
|
@@ -11549,7 +11887,7 @@ const ready = true;
|
|
|
11549
11887
|
*/
|
|
11550
11888
|
const emitReadyEvent = () => {
|
|
11551
11889
|
if (typeof window !== "undefined") {
|
|
11552
|
-
const event = new CustomEvent("signalwire:js:ready", { detail: { version: "
|
|
11890
|
+
const event = new CustomEvent("signalwire:js:ready", { detail: { version: "4.0.0-rc.1" } });
|
|
11553
11891
|
window.dispatchEvent(event);
|
|
11554
11892
|
}
|
|
11555
11893
|
};
|