@signalwire/js 4.0.0-dev-20260515133934 → 4.0.0-dev-20260714201720
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 +1072 -607
- package/dist/browser.mjs.map +1 -1
- package/dist/browser.umd.js +1072 -606
- package/dist/browser.umd.js.map +1 -1
- package/dist/index.cjs +970 -668
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +266 -34
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +266 -34
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +900 -599
- package/dist/index.mjs.map +1 -1
- package/dist/operators/index.cjs +1 -1
- package/dist/operators/index.mjs +1 -1
- package/dist/{operators-D6a2J1KA.cjs → operators-B8ipd4Xl.cjs} +561 -1
- package/dist/operators-B8ipd4Xl.cjs.map +1 -0
- package/dist/{operators-CX_lCCJm.mjs → operators-BlUtq-t0.mjs} +166 -2
- package/dist/operators-BlUtq-t0.mjs.map +1 -0
- package/package.json +1 -1
- package/dist/operators-CX_lCCJm.mjs.map +0 -1
- package/dist/operators-D6a2J1KA.cjs.map +0 -1
package/dist/browser.mjs
CHANGED
|
@@ -9015,6 +9015,143 @@ var Destroyable = class {
|
|
|
9015
9015
|
}
|
|
9016
9016
|
};
|
|
9017
9017
|
|
|
9018
|
+
//#endregion
|
|
9019
|
+
//#region src/core/constants.ts
|
|
9020
|
+
const INVITE_VERSION = 1e3;
|
|
9021
|
+
const DEFAULT_ICE_CANDIDATE_TIMEOUT_MS = 600;
|
|
9022
|
+
const DEFAULT_ICE_GATHERING_TIMEOUT_MS = 6e3;
|
|
9023
|
+
const DEFAULT_RECONNECT_CALLS_TIMEOUT_MS = 300 * 1e3;
|
|
9024
|
+
const DEFAULT_CONNECTION_TIMEOUT_MS = 1e4;
|
|
9025
|
+
const DEFAULT_RECONNECT_DELAY_MIN_MS = 100;
|
|
9026
|
+
const DEFAULT_RECONNECT_DELAY_MAX_MS = 3e3;
|
|
9027
|
+
const DEFAULT_DEVICE_DEBOUNCE_TIME_MS = 1500;
|
|
9028
|
+
const DEFAULT_DEVICE_POLLING_INTERVAL_MS = 0;
|
|
9029
|
+
const PREFERENCES_STORAGE_KEY = "sw:preferences";
|
|
9030
|
+
/** Scope value that enables automatic token refresh. */
|
|
9031
|
+
const SAT_REFRESH_SCOPE = "sat:refresh";
|
|
9032
|
+
/** API endpoints for device token operations. */
|
|
9033
|
+
const DEVICE_TOKEN_ENDPOINT = "/api/fabric/subscriber/devices/token";
|
|
9034
|
+
const DEVICE_REFRESH_ENDPOINT = "/api/fabric/subscriber/devices/refresh";
|
|
9035
|
+
/** Default device token TTL in seconds (15 minutes). */
|
|
9036
|
+
const DEVICE_TOKEN_DEFAULT_EXPIRE_IN = 900;
|
|
9037
|
+
/** Buffer time in milliseconds before expiry to trigger refresh. */
|
|
9038
|
+
const DEVICE_TOKEN_REFRESH_BUFFER_MS = 3e4;
|
|
9039
|
+
/** Maximum retry attempts for device token refresh on transient failure. */
|
|
9040
|
+
const DEVICE_TOKEN_REFRESH_MAX_RETRIES = 3;
|
|
9041
|
+
/** Base delay in milliseconds for exponential backoff on refresh retry. */
|
|
9042
|
+
const DEVICE_TOKEN_REFRESH_RETRY_BASE_MS = 1e3;
|
|
9043
|
+
/** Maximum retry attempts for developer credential refresh on transient failure. */
|
|
9044
|
+
const CREDENTIAL_REFRESH_MAX_RETRIES = 5;
|
|
9045
|
+
/** Base delay in milliseconds for exponential backoff on credential refresh retry. */
|
|
9046
|
+
const CREDENTIAL_REFRESH_RETRY_BASE_MS = 1e3;
|
|
9047
|
+
/** Maximum delay in milliseconds for credential refresh backoff. */
|
|
9048
|
+
const CREDENTIAL_REFRESH_MAX_DELAY_MS = 3e4;
|
|
9049
|
+
/** Buffer in milliseconds before token expiry to trigger refresh. */
|
|
9050
|
+
const CREDENTIAL_REFRESH_BUFFER_MS = 5e3;
|
|
9051
|
+
/**
|
|
9052
|
+
* Maximum time the coordinator will wait for `DeviceTokenManager.activate()`
|
|
9053
|
+
* to resolve before treating the activation as failed and falling back to
|
|
9054
|
+
* the developer-provided refresh path. Prevents a wedged HTTP layer from
|
|
9055
|
+
* leaving the session with no active refresh mechanism.
|
|
9056
|
+
*/
|
|
9057
|
+
const CREDENTIAL_ACTIVATE_TIMEOUT_MS = 3e4;
|
|
9058
|
+
/** JSON-RPC error code for requester validation failure (corrupted auth state). */
|
|
9059
|
+
const RPC_ERROR_REQUESTER_VALIDATION_FAILED = -32003;
|
|
9060
|
+
/** JSON-RPC error code for invalid params (e.g., missing authentication block). */
|
|
9061
|
+
const RPC_ERROR_INVALID_PARAMS = -32602;
|
|
9062
|
+
/** JSON-RPC error code for authentication failure (invalid token, missing DPoP, etc.). */
|
|
9063
|
+
const RPC_ERROR_AUTHENTICATION_FAILED = -32002;
|
|
9064
|
+
/** Error names browsers use for a media permission denial (user or policy). */
|
|
9065
|
+
const MEDIA_ACCESS_DENIAL_NAMES = [
|
|
9066
|
+
"NotAllowedError",
|
|
9067
|
+
"SecurityError",
|
|
9068
|
+
"PermissionDeniedError"
|
|
9069
|
+
];
|
|
9070
|
+
/** Default polling interval for RTCPeerConnection.getStats() in milliseconds. */
|
|
9071
|
+
const DEFAULT_STATS_POLLING_INTERVAL_MS = 1e3;
|
|
9072
|
+
/** Number of initial samples used to build a baseline for spike detection. */
|
|
9073
|
+
const DEFAULT_STATS_BASELINE_SAMPLES = 10;
|
|
9074
|
+
/** Duration in ms with no inbound audio packets before emitting a critical issue. */
|
|
9075
|
+
const DEFAULT_STATS_NO_PACKET_THRESHOLD_MS = 2e3;
|
|
9076
|
+
/** Multiplier applied to baseline RTT to detect a warning-level RTT spike. */
|
|
9077
|
+
const DEFAULT_STATS_RTT_SPIKE_MULTIPLIER = 3;
|
|
9078
|
+
/** Packet loss fraction (0-1) above which a warning is emitted. */
|
|
9079
|
+
const DEFAULT_STATS_PACKET_LOSS_THRESHOLD = .05;
|
|
9080
|
+
/** Multiplier applied to baseline jitter to detect a jitter spike. */
|
|
9081
|
+
const DEFAULT_STATS_JITTER_SPIKE_MULTIPLIER = 4;
|
|
9082
|
+
/** Number of seconds of metrics history to retain. */
|
|
9083
|
+
const DEFAULT_STATS_HISTORY_SIZE = 30;
|
|
9084
|
+
/** Maximum keyframe requests allowed within a single burst window. */
|
|
9085
|
+
const DEFAULT_KEYFRAME_MAX_BURST$1 = 3;
|
|
9086
|
+
/** Duration of the keyframe burst window in milliseconds. */
|
|
9087
|
+
const DEFAULT_KEYFRAME_BURST_WINDOW_MS$1 = 3e3;
|
|
9088
|
+
/** Cooldown period in ms after burst limit is reached before allowing more keyframes. */
|
|
9089
|
+
const DEFAULT_KEYFRAME_COOLDOWN_MS$1 = 1e4;
|
|
9090
|
+
/** Minimum time between re-INVITE attempts in milliseconds. */
|
|
9091
|
+
const DEFAULT_REINVITE_DEBOUNCE_TIME_MS = 1e4;
|
|
9092
|
+
/** Maximum number of re-INVITE attempts per call. */
|
|
9093
|
+
const DEFAULT_REINVITE_MAX_ATTEMPTS = 3;
|
|
9094
|
+
/** Timeout for a single re-INVITE attempt in milliseconds. */
|
|
9095
|
+
const DEFAULT_REINVITE_TIMEOUT_MS = 5e3;
|
|
9096
|
+
/** Debounce window in ms to collapse multiple detection signals into one trigger. */
|
|
9097
|
+
const DEFAULT_RECOVERY_DEBOUNCE_TIME_MS = 2e3;
|
|
9098
|
+
/** Cooldown period in ms between recovery attempts. */
|
|
9099
|
+
const DEFAULT_RECOVERY_COOLDOWN_MS = 1e4;
|
|
9100
|
+
/** Grace period in ms before treating ICE 'disconnected' as a failure. */
|
|
9101
|
+
const DEFAULT_ICE_DISCONNECTED_GRACE_PERIOD_MS = 3e3;
|
|
9102
|
+
/** Timeout for a single ICE restart attempt in milliseconds. */
|
|
9103
|
+
const DEFAULT_ICE_RESTART_TIMEOUT_MS$1 = 5e3;
|
|
9104
|
+
/** Maximum recovery attempts before emitting 'max_attempts_reached'. */
|
|
9105
|
+
const DEFAULT_MAX_RECOVERY_ATTEMPTS = 3;
|
|
9106
|
+
/** Upper bound in ms for waiting on iceGatheringState === 'complete' after an ICE restart. */
|
|
9107
|
+
const ICE_GATHERING_COMPLETE_TIMEOUT_MS = 1e4;
|
|
9108
|
+
/** Upper bound in ms for waiting on RTCPeerConnection.connectionState === 'connected' after a recovery ICE restart. */
|
|
9109
|
+
const PEER_CONNECTION_RECOVERY_WAIT_MS = 5e3;
|
|
9110
|
+
/** Polling interval in ms while waiting for RTCPeerConnection.connectionState to transition. */
|
|
9111
|
+
const PEER_CONNECTION_RECOVERY_POLL_MS = 100;
|
|
9112
|
+
/** Polling interval for LocalAudioPipeline.level$ (ms). ~30fps is smooth for meters. */
|
|
9113
|
+
const AUDIO_LEVEL_POLL_INTERVAL_MS = 33;
|
|
9114
|
+
/** RMS level threshold (0..1) above which the local participant is considered speaking. */
|
|
9115
|
+
const VAD_THRESHOLD = .03;
|
|
9116
|
+
/** Hold window in ms below the threshold before speaking$ flips back to false. */
|
|
9117
|
+
const VAD_HOLD_MS = 250;
|
|
9118
|
+
/** Whether to persist device selections to storage by default. */
|
|
9119
|
+
const DEFAULT_PERSIST_DEVICE_SELECTION = true;
|
|
9120
|
+
/** Whether to auto-apply device changes to active calls by default. */
|
|
9121
|
+
const DEFAULT_SYNC_DEVICES_TO_ACTIVE_CALLS = true;
|
|
9122
|
+
/** Storage keys for persisted device selections. */
|
|
9123
|
+
const DEVICE_STORAGE_KEY_AUDIO_INPUT = "sw:device:audioinput";
|
|
9124
|
+
const DEVICE_STORAGE_KEY_AUDIO_OUTPUT = "sw:device:audiooutput";
|
|
9125
|
+
const DEVICE_STORAGE_KEY_VIDEO_INPUT = "sw:device:videoinput";
|
|
9126
|
+
/** Whether to auto-mute video when the tab becomes hidden. */
|
|
9127
|
+
const DEFAULT_AUTO_MUTE_VIDEO_ON_HIDDEN = false;
|
|
9128
|
+
/** Whether to re-enumerate devices when the page becomes visible. */
|
|
9129
|
+
const DEFAULT_REFRESH_DEVICES_ON_VISIBLE = true;
|
|
9130
|
+
/** Whether to check peer connection health when the page becomes visible. */
|
|
9131
|
+
const DEFAULT_CHECK_CONNECTION_ON_VISIBLE = true;
|
|
9132
|
+
/** Whether automatic video degradation on low bandwidth is enabled. */
|
|
9133
|
+
const DEFAULT_ENABLE_AUTO_DEGRADATION = true;
|
|
9134
|
+
/** Bitrate in kbps below which video is automatically disabled. */
|
|
9135
|
+
const DEFAULT_DEGRADATION_BITRATE_THRESHOLD_KBPS = 150;
|
|
9136
|
+
/** Bitrate in kbps above which video is automatically re-enabled (hysteresis). */
|
|
9137
|
+
const DEFAULT_DEGRADATION_RECOVERY_THRESHOLD_KBPS = 300;
|
|
9138
|
+
/** Whether relay-only escalation is enabled as a last-resort recovery tier. */
|
|
9139
|
+
const DEFAULT_ENABLE_RELAY_FALLBACK = true;
|
|
9140
|
+
/** Whether to listen for browser online/offline/connection events. */
|
|
9141
|
+
const DEFAULT_ENABLE_NETWORK_CHANGE_DETECTION = true;
|
|
9142
|
+
/** Whether to intercept server-sent media-timeout hangups and attempt recovery. */
|
|
9143
|
+
const DEFAULT_ENABLE_SERVER_HANGUP_INTERCEPTION = true;
|
|
9144
|
+
/** Default video track constraints applied when video is enabled without explicit constraints. */
|
|
9145
|
+
const DEFAULT_VIDEO_CONSTRAINTS = {
|
|
9146
|
+
width: { ideal: 1280 },
|
|
9147
|
+
height: { ideal: 720 },
|
|
9148
|
+
aspectRatio: 16 / 9
|
|
9149
|
+
};
|
|
9150
|
+
/** Whether stereo Opus is enabled by default. */
|
|
9151
|
+
const DEFAULT_STEREO_AUDIO = false;
|
|
9152
|
+
/** Max average bitrate for stereo Opus in bits per second. */
|
|
9153
|
+
const DEFAULT_STEREO_MAX_AVERAGE_BITRATE = 51e4;
|
|
9154
|
+
|
|
9018
9155
|
//#endregion
|
|
9019
9156
|
//#region src/core/errors.ts
|
|
9020
9157
|
var UnexpectedError = class extends Error {
|
|
@@ -9224,6 +9361,33 @@ var MediaTrackError = class extends Error {
|
|
|
9224
9361
|
this.name = "MediaTrackError";
|
|
9225
9362
|
}
|
|
9226
9363
|
};
|
|
9364
|
+
/** True when a `getUserMedia`/`getDisplayMedia` rejection is a permission denial. */
|
|
9365
|
+
function isMediaAccessDenial(originalError) {
|
|
9366
|
+
return originalError instanceof Error && MEDIA_ACCESS_DENIAL_NAMES.includes(originalError.name);
|
|
9367
|
+
}
|
|
9368
|
+
/**
|
|
9369
|
+
* Failure to acquire local media (camera, microphone, or screen capture)
|
|
9370
|
+
* via `getUserMedia`/`getDisplayMedia`.
|
|
9371
|
+
*
|
|
9372
|
+
* Non-fatal by default: screenshare and additional-device failures never
|
|
9373
|
+
* end the call, and main-connection failures degrade to receive-only when
|
|
9374
|
+
* possible. The wrapping site sets `fatal` to `true` only when the call
|
|
9375
|
+
* cannot continue (receive-only fallback disabled or no receive intent).
|
|
9376
|
+
*/
|
|
9377
|
+
var MediaAccessError = class extends Error {
|
|
9378
|
+
constructor(operation, media, originalError, fatal = false) {
|
|
9379
|
+
super(`Media access ${isMediaAccessDenial(originalError) ? "denied" : "failed"} for ${operation} (${media})`, { cause: originalError instanceof Error ? originalError : void 0 });
|
|
9380
|
+
this.operation = operation;
|
|
9381
|
+
this.media = media;
|
|
9382
|
+
this.originalError = originalError;
|
|
9383
|
+
this.fatal = fatal;
|
|
9384
|
+
this.name = "MediaAccessError";
|
|
9385
|
+
}
|
|
9386
|
+
/** True when the underlying failure is a permission denial (user or policy). */
|
|
9387
|
+
get denied() {
|
|
9388
|
+
return isMediaAccessDenial(this.originalError);
|
|
9389
|
+
}
|
|
9390
|
+
};
|
|
9227
9391
|
var DPoPInitError = class extends Error {
|
|
9228
9392
|
constructor(originalError, message = "Failed to initialize DPoP key pair") {
|
|
9229
9393
|
super(message, { cause: originalError instanceof Error ? originalError : void 0 });
|
|
@@ -9464,9 +9628,9 @@ var require_loglevel = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
9464
9628
|
defaultLogger$1 = new Logger();
|
|
9465
9629
|
defaultLogger$1.getLogger = function getLogger$1(name) {
|
|
9466
9630
|
if (typeof name !== "symbol" && typeof name !== "string" || name === "") throw new TypeError("You must supply a name when creating a logger.");
|
|
9467
|
-
var logger$
|
|
9468
|
-
if (!logger$
|
|
9469
|
-
return logger$
|
|
9631
|
+
var logger$33 = _loggersByName[name];
|
|
9632
|
+
if (!logger$33) logger$33 = _loggersByName[name] = new Logger(name, defaultLogger$1.methodFactory);
|
|
9633
|
+
return logger$33;
|
|
9470
9634
|
};
|
|
9471
9635
|
var _log = typeof window !== undefinedType ? window.log : void 0;
|
|
9472
9636
|
defaultLogger$1.noConflict = function() {
|
|
@@ -9502,8 +9666,8 @@ const defaultLoggerLevel = defaultLogger.levels.WARN;
|
|
|
9502
9666
|
defaultLogger.setLevel(defaultLoggerLevel);
|
|
9503
9667
|
let userLogger = null;
|
|
9504
9668
|
/** Replace the built-in logger with a custom implementation. Pass `null` to restore defaults. */
|
|
9505
|
-
const setLogger = (logger$
|
|
9506
|
-
userLogger = logger$
|
|
9669
|
+
const setLogger = (logger$33) => {
|
|
9670
|
+
userLogger = logger$33;
|
|
9507
9671
|
};
|
|
9508
9672
|
let debugOptions = {};
|
|
9509
9673
|
/** Configure debug options (e.g., `{ logWsTraffic: true }`). */
|
|
@@ -9547,8 +9711,8 @@ const wsTraffic = (options) => {
|
|
|
9547
9711
|
loggerInstance.debug(`${options.type.toUpperCase()}: \n`, msg, "\n");
|
|
9548
9712
|
};
|
|
9549
9713
|
const getLogger = () => {
|
|
9550
|
-
const logger$
|
|
9551
|
-
return new Proxy(logger$
|
|
9714
|
+
const logger$33 = getLoggerInstance();
|
|
9715
|
+
return new Proxy(logger$33, { get(_target, prop, _receiver) {
|
|
9552
9716
|
if (prop === "wsTraffic") return wsTraffic;
|
|
9553
9717
|
const instance = getLoggerInstance();
|
|
9554
9718
|
const value = Reflect.get(instance, prop);
|
|
@@ -9600,7 +9764,7 @@ const asyncRetry = async ({ asyncCallable, maxRetries: retries = DEFAULT_MAX_RET
|
|
|
9600
9764
|
|
|
9601
9765
|
//#endregion
|
|
9602
9766
|
//#region src/controllers/HTTPRequestController.ts
|
|
9603
|
-
const logger$
|
|
9767
|
+
const logger$32 = getLogger();
|
|
9604
9768
|
const GET_PARAMS = {
|
|
9605
9769
|
method: "GET",
|
|
9606
9770
|
headers: { Accept: "application/json" }
|
|
@@ -9664,7 +9828,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9664
9828
|
this._responses$.next(response);
|
|
9665
9829
|
return response;
|
|
9666
9830
|
} catch (error) {
|
|
9667
|
-
logger$
|
|
9831
|
+
logger$32.error("[HTTPRequestController] Request error:", error);
|
|
9668
9832
|
this._status$.next("error");
|
|
9669
9833
|
const err = error instanceof Error ? error : new Error("HTTP request failed", { cause: error });
|
|
9670
9834
|
this._errors$.next(err);
|
|
@@ -9691,7 +9855,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9691
9855
|
const url = this.buildURL(request.url);
|
|
9692
9856
|
const headers = this.buildHeaders(request.headers);
|
|
9693
9857
|
const timeout$5 = request.timeout ?? this.requestTimeout;
|
|
9694
|
-
logger$
|
|
9858
|
+
logger$32.debug("[HTTPRequestController] Executing request:", {
|
|
9695
9859
|
method: request.method,
|
|
9696
9860
|
url,
|
|
9697
9861
|
headers: Object.keys(headers).reduce((acc, key) => {
|
|
@@ -9711,7 +9875,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9711
9875
|
});
|
|
9712
9876
|
clearTimeout(timeoutId);
|
|
9713
9877
|
const httpResponse = await this.convertResponse(response);
|
|
9714
|
-
logger$
|
|
9878
|
+
logger$32.debug("[HTTPRequestController] Response received:", {
|
|
9715
9879
|
status: response.status,
|
|
9716
9880
|
statusText: response.statusText,
|
|
9717
9881
|
headers: [...response.headers.entries()],
|
|
@@ -9721,7 +9885,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9721
9885
|
} catch (error) {
|
|
9722
9886
|
clearTimeout(timeoutId);
|
|
9723
9887
|
if (error instanceof Error && error.name === "AbortError") throw new RequestTimeoutError(`Request timeout after ${timeout$5}ms`, { cause: error });
|
|
9724
|
-
logger$
|
|
9888
|
+
logger$32.error("[HTTPRequestController] Request failed:", error);
|
|
9725
9889
|
throw error;
|
|
9726
9890
|
}
|
|
9727
9891
|
}
|
|
@@ -9735,8 +9899,8 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9735
9899
|
const credential = this.getCredential();
|
|
9736
9900
|
if (credential.token) {
|
|
9737
9901
|
headers.Authorization = `Bearer ${credential.token}`;
|
|
9738
|
-
logger$
|
|
9739
|
-
} else logger$
|
|
9902
|
+
logger$32.debug("[HTTPRequestController] Using Bearer token auth, token length:", credential.token.length);
|
|
9903
|
+
} else logger$32.warn("[HTTPRequestController] No credentials available for authentication");
|
|
9740
9904
|
return headers;
|
|
9741
9905
|
}
|
|
9742
9906
|
/**
|
|
@@ -9873,130 +10037,6 @@ var DeviceHistoryManager = class {
|
|
|
9873
10037
|
}
|
|
9874
10038
|
};
|
|
9875
10039
|
|
|
9876
|
-
//#endregion
|
|
9877
|
-
//#region src/core/constants.ts
|
|
9878
|
-
const INVITE_VERSION = 1e3;
|
|
9879
|
-
const DEFAULT_ICE_CANDIDATE_TIMEOUT_MS = 600;
|
|
9880
|
-
const DEFAULT_ICE_GATHERING_TIMEOUT_MS = 6e3;
|
|
9881
|
-
const DEFAULT_RECONNECT_CALLS_TIMEOUT_MS = 300 * 1e3;
|
|
9882
|
-
const DEFAULT_CONNECTION_TIMEOUT_MS = 1e4;
|
|
9883
|
-
const DEFAULT_RECONNECT_DELAY_MIN_MS = 100;
|
|
9884
|
-
const DEFAULT_RECONNECT_DELAY_MAX_MS = 3e3;
|
|
9885
|
-
const DEFAULT_DEVICE_DEBOUNCE_TIME_MS = 1500;
|
|
9886
|
-
const DEFAULT_DEVICE_POLLING_INTERVAL_MS = 0;
|
|
9887
|
-
const PREFERENCES_STORAGE_KEY = "sw:preferences";
|
|
9888
|
-
/** Scope value that enables automatic token refresh. */
|
|
9889
|
-
const SAT_REFRESH_SCOPE = "sat:refresh";
|
|
9890
|
-
/** API endpoints for device token operations. */
|
|
9891
|
-
const DEVICE_TOKEN_ENDPOINT = "/api/fabric/subscriber/devices/token";
|
|
9892
|
-
const DEVICE_REFRESH_ENDPOINT = "/api/fabric/subscriber/devices/refresh";
|
|
9893
|
-
/** Default device token TTL in seconds (15 minutes). */
|
|
9894
|
-
const DEVICE_TOKEN_DEFAULT_EXPIRE_IN = 900;
|
|
9895
|
-
/** Buffer time in milliseconds before expiry to trigger refresh. */
|
|
9896
|
-
const DEVICE_TOKEN_REFRESH_BUFFER_MS = 3e4;
|
|
9897
|
-
/** Maximum retry attempts for device token refresh on transient failure. */
|
|
9898
|
-
const DEVICE_TOKEN_REFRESH_MAX_RETRIES = 3;
|
|
9899
|
-
/** Base delay in milliseconds for exponential backoff on refresh retry. */
|
|
9900
|
-
const DEVICE_TOKEN_REFRESH_RETRY_BASE_MS = 1e3;
|
|
9901
|
-
/** Maximum retry attempts for developer credential refresh on transient failure. */
|
|
9902
|
-
const CREDENTIAL_REFRESH_MAX_RETRIES = 5;
|
|
9903
|
-
/** Base delay in milliseconds for exponential backoff on credential refresh retry. */
|
|
9904
|
-
const CREDENTIAL_REFRESH_RETRY_BASE_MS = 1e3;
|
|
9905
|
-
/** Maximum delay in milliseconds for credential refresh backoff. */
|
|
9906
|
-
const CREDENTIAL_REFRESH_MAX_DELAY_MS = 3e4;
|
|
9907
|
-
/** Buffer in milliseconds before token expiry to trigger refresh. */
|
|
9908
|
-
const CREDENTIAL_REFRESH_BUFFER_MS = 5e3;
|
|
9909
|
-
/** JSON-RPC error code for requester validation failure (corrupted auth state). */
|
|
9910
|
-
const RPC_ERROR_REQUESTER_VALIDATION_FAILED = -32003;
|
|
9911
|
-
/** JSON-RPC error code for invalid params (e.g., missing authentication block). */
|
|
9912
|
-
const RPC_ERROR_INVALID_PARAMS = -32602;
|
|
9913
|
-
/** JSON-RPC error code for authentication failure (invalid token, missing DPoP, etc.). */
|
|
9914
|
-
const RPC_ERROR_AUTHENTICATION_FAILED = -32002;
|
|
9915
|
-
/** Default polling interval for RTCPeerConnection.getStats() in milliseconds. */
|
|
9916
|
-
const DEFAULT_STATS_POLLING_INTERVAL_MS = 1e3;
|
|
9917
|
-
/** Number of initial samples used to build a baseline for spike detection. */
|
|
9918
|
-
const DEFAULT_STATS_BASELINE_SAMPLES = 10;
|
|
9919
|
-
/** Duration in ms with no inbound audio packets before emitting a critical issue. */
|
|
9920
|
-
const DEFAULT_STATS_NO_PACKET_THRESHOLD_MS = 2e3;
|
|
9921
|
-
/** Multiplier applied to baseline RTT to detect a warning-level RTT spike. */
|
|
9922
|
-
const DEFAULT_STATS_RTT_SPIKE_MULTIPLIER = 3;
|
|
9923
|
-
/** Packet loss fraction (0-1) above which a warning is emitted. */
|
|
9924
|
-
const DEFAULT_STATS_PACKET_LOSS_THRESHOLD = .05;
|
|
9925
|
-
/** Multiplier applied to baseline jitter to detect a jitter spike. */
|
|
9926
|
-
const DEFAULT_STATS_JITTER_SPIKE_MULTIPLIER = 4;
|
|
9927
|
-
/** Number of seconds of metrics history to retain. */
|
|
9928
|
-
const DEFAULT_STATS_HISTORY_SIZE = 30;
|
|
9929
|
-
/** Maximum keyframe requests allowed within a single burst window. */
|
|
9930
|
-
const DEFAULT_KEYFRAME_MAX_BURST$1 = 3;
|
|
9931
|
-
/** Duration of the keyframe burst window in milliseconds. */
|
|
9932
|
-
const DEFAULT_KEYFRAME_BURST_WINDOW_MS$1 = 3e3;
|
|
9933
|
-
/** Cooldown period in ms after burst limit is reached before allowing more keyframes. */
|
|
9934
|
-
const DEFAULT_KEYFRAME_COOLDOWN_MS$1 = 1e4;
|
|
9935
|
-
/** Minimum time between re-INVITE attempts in milliseconds. */
|
|
9936
|
-
const DEFAULT_REINVITE_DEBOUNCE_TIME_MS = 1e4;
|
|
9937
|
-
/** Maximum number of re-INVITE attempts per call. */
|
|
9938
|
-
const DEFAULT_REINVITE_MAX_ATTEMPTS = 3;
|
|
9939
|
-
/** Timeout for a single re-INVITE attempt in milliseconds. */
|
|
9940
|
-
const DEFAULT_REINVITE_TIMEOUT_MS = 5e3;
|
|
9941
|
-
/** Debounce window in ms to collapse multiple detection signals into one trigger. */
|
|
9942
|
-
const DEFAULT_RECOVERY_DEBOUNCE_TIME_MS = 2e3;
|
|
9943
|
-
/** Cooldown period in ms between recovery attempts. */
|
|
9944
|
-
const DEFAULT_RECOVERY_COOLDOWN_MS = 1e4;
|
|
9945
|
-
/** Grace period in ms before treating ICE 'disconnected' as a failure. */
|
|
9946
|
-
const DEFAULT_ICE_DISCONNECTED_GRACE_PERIOD_MS = 3e3;
|
|
9947
|
-
/** Timeout for a single ICE restart attempt in milliseconds. */
|
|
9948
|
-
const DEFAULT_ICE_RESTART_TIMEOUT_MS$1 = 5e3;
|
|
9949
|
-
/** Maximum recovery attempts before emitting 'max_attempts_reached'. */
|
|
9950
|
-
const DEFAULT_MAX_RECOVERY_ATTEMPTS = 3;
|
|
9951
|
-
/** Upper bound in ms for waiting on iceGatheringState === 'complete' after an ICE restart. */
|
|
9952
|
-
const ICE_GATHERING_COMPLETE_TIMEOUT_MS = 1e4;
|
|
9953
|
-
/** Upper bound in ms for waiting on RTCPeerConnection.connectionState === 'connected' after a recovery ICE restart. */
|
|
9954
|
-
const PEER_CONNECTION_RECOVERY_WAIT_MS = 5e3;
|
|
9955
|
-
/** Polling interval in ms while waiting for RTCPeerConnection.connectionState to transition. */
|
|
9956
|
-
const PEER_CONNECTION_RECOVERY_POLL_MS = 100;
|
|
9957
|
-
/** Polling interval for LocalAudioPipeline.level$ (ms). ~30fps is smooth for meters. */
|
|
9958
|
-
const AUDIO_LEVEL_POLL_INTERVAL_MS = 33;
|
|
9959
|
-
/** RMS level threshold (0..1) above which the local participant is considered speaking. */
|
|
9960
|
-
const VAD_THRESHOLD = .03;
|
|
9961
|
-
/** Hold window in ms below the threshold before speaking$ flips back to false. */
|
|
9962
|
-
const VAD_HOLD_MS = 250;
|
|
9963
|
-
/** Whether to persist device selections to storage by default. */
|
|
9964
|
-
const DEFAULT_PERSIST_DEVICE_SELECTION = true;
|
|
9965
|
-
/** Whether to auto-apply device changes to active calls by default. */
|
|
9966
|
-
const DEFAULT_SYNC_DEVICES_TO_ACTIVE_CALLS = true;
|
|
9967
|
-
/** Storage keys for persisted device selections. */
|
|
9968
|
-
const DEVICE_STORAGE_KEY_AUDIO_INPUT = "sw:device:audioinput";
|
|
9969
|
-
const DEVICE_STORAGE_KEY_AUDIO_OUTPUT = "sw:device:audiooutput";
|
|
9970
|
-
const DEVICE_STORAGE_KEY_VIDEO_INPUT = "sw:device:videoinput";
|
|
9971
|
-
/** Whether to auto-mute video when the tab becomes hidden. */
|
|
9972
|
-
const DEFAULT_AUTO_MUTE_VIDEO_ON_HIDDEN = false;
|
|
9973
|
-
/** Whether to re-enumerate devices when the page becomes visible. */
|
|
9974
|
-
const DEFAULT_REFRESH_DEVICES_ON_VISIBLE = true;
|
|
9975
|
-
/** Whether to check peer connection health when the page becomes visible. */
|
|
9976
|
-
const DEFAULT_CHECK_CONNECTION_ON_VISIBLE = true;
|
|
9977
|
-
/** Whether automatic video degradation on low bandwidth is enabled. */
|
|
9978
|
-
const DEFAULT_ENABLE_AUTO_DEGRADATION = true;
|
|
9979
|
-
/** Bitrate in kbps below which video is automatically disabled. */
|
|
9980
|
-
const DEFAULT_DEGRADATION_BITRATE_THRESHOLD_KBPS = 150;
|
|
9981
|
-
/** Bitrate in kbps above which video is automatically re-enabled (hysteresis). */
|
|
9982
|
-
const DEFAULT_DEGRADATION_RECOVERY_THRESHOLD_KBPS = 300;
|
|
9983
|
-
/** Whether relay-only escalation is enabled as a last-resort recovery tier. */
|
|
9984
|
-
const DEFAULT_ENABLE_RELAY_FALLBACK = true;
|
|
9985
|
-
/** Whether to listen for browser online/offline/connection events. */
|
|
9986
|
-
const DEFAULT_ENABLE_NETWORK_CHANGE_DETECTION = true;
|
|
9987
|
-
/** Whether to intercept server-sent media-timeout hangups and attempt recovery. */
|
|
9988
|
-
const DEFAULT_ENABLE_SERVER_HANGUP_INTERCEPTION = true;
|
|
9989
|
-
/** Default video track constraints applied when video is enabled without explicit constraints. */
|
|
9990
|
-
const DEFAULT_VIDEO_CONSTRAINTS = {
|
|
9991
|
-
width: { ideal: 1280 },
|
|
9992
|
-
height: { ideal: 720 },
|
|
9993
|
-
aspectRatio: 16 / 9
|
|
9994
|
-
};
|
|
9995
|
-
/** Whether stereo Opus is enabled by default. */
|
|
9996
|
-
const DEFAULT_STEREO_AUDIO = false;
|
|
9997
|
-
/** Max average bitrate for stereo Opus in bits per second. */
|
|
9998
|
-
const DEFAULT_STEREO_MAX_AVERAGE_BITRATE = 51e4;
|
|
9999
|
-
|
|
10000
10040
|
//#endregion
|
|
10001
10041
|
//#region src/utils/time.ts
|
|
10002
10042
|
function fromSecToMs(seconds) {
|
|
@@ -10008,7 +10048,7 @@ function fromMsToSec(milliseconds) {
|
|
|
10008
10048
|
|
|
10009
10049
|
//#endregion
|
|
10010
10050
|
//#region src/containers/PreferencesContainer.ts
|
|
10011
|
-
const logger$
|
|
10051
|
+
const logger$31 = getLogger();
|
|
10012
10052
|
var PreferencesContainer = class PreferencesContainer {
|
|
10013
10053
|
static get instance() {
|
|
10014
10054
|
this._instance ??= new PreferencesContainer();
|
|
@@ -10670,7 +10710,7 @@ var ClientPreferences = class {
|
|
|
10670
10710
|
if (!this._storage) return;
|
|
10671
10711
|
const data = collectStoredPreferences();
|
|
10672
10712
|
this._storage.setItem(PREFERENCES_STORAGE_KEY, data, "local").catch((error) => {
|
|
10673
|
-
logger$
|
|
10713
|
+
logger$31.error(`[ClientPreferences] Failed to save preferences: ${String(error)}`);
|
|
10674
10714
|
});
|
|
10675
10715
|
}
|
|
10676
10716
|
/** Loads preferences from storage and applies them to the container. */
|
|
@@ -10679,7 +10719,7 @@ var ClientPreferences = class {
|
|
|
10679
10719
|
this._storage.getItem(PREFERENCES_STORAGE_KEY, "local").then((stored) => {
|
|
10680
10720
|
if (stored) applyStoredPreferences(stored);
|
|
10681
10721
|
}).catch((error) => {
|
|
10682
|
-
logger$
|
|
10722
|
+
logger$31.error(`[ClientPreferences] Failed to load preferences: ${String(error)}`);
|
|
10683
10723
|
});
|
|
10684
10724
|
}
|
|
10685
10725
|
};
|
|
@@ -10701,7 +10741,7 @@ function toError(value) {
|
|
|
10701
10741
|
//#endregion
|
|
10702
10742
|
//#region src/controllers/NavigatorDeviceController.ts
|
|
10703
10743
|
var import_cjs$29 = require_cjs();
|
|
10704
|
-
const logger$
|
|
10744
|
+
const logger$30 = getLogger();
|
|
10705
10745
|
/** Maps a device kind to its storage key. */
|
|
10706
10746
|
const DEVICE_STORAGE_KEYS = {
|
|
10707
10747
|
audioinput: DEVICE_STORAGE_KEY_AUDIO_INPUT,
|
|
@@ -10723,7 +10763,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10723
10763
|
super();
|
|
10724
10764
|
this.webRTCApiProvider = webRTCApiProvider;
|
|
10725
10765
|
this.deviceChangeHandler = () => {
|
|
10726
|
-
logger$
|
|
10766
|
+
logger$30.debug("[DeviceController] Device change detected");
|
|
10727
10767
|
this.enumerateDevices();
|
|
10728
10768
|
};
|
|
10729
10769
|
this._devicesState$ = this.createBehaviorSubject(initialDevicesState);
|
|
@@ -10788,13 +10828,13 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10788
10828
|
return this.cachedObservable("videoInputDevices$", () => this._devicesState$.pipe((0, import_cjs$29.map)((state) => state.videoinput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$)));
|
|
10789
10829
|
}
|
|
10790
10830
|
get selectedAudioInputDevice$() {
|
|
10791
|
-
return this.cachedObservable("selectedAudioInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.audioinput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$
|
|
10831
|
+
return this.cachedObservable("selectedAudioInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.audioinput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$30.debug("[DeviceController] Selected audio input device changed:", info))));
|
|
10792
10832
|
}
|
|
10793
10833
|
get selectedAudioOutputDevice$() {
|
|
10794
|
-
return this.cachedObservable("selectedAudioOutputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.audiooutput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$
|
|
10834
|
+
return this.cachedObservable("selectedAudioOutputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.audiooutput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$30.debug("[DeviceController] Selected audio output device changed:", info))));
|
|
10795
10835
|
}
|
|
10796
10836
|
get selectedVideoInputDevice$() {
|
|
10797
|
-
return this.cachedObservable("selectedVideoInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.videoinput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$
|
|
10837
|
+
return this.cachedObservable("selectedVideoInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.videoinput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$30.debug("[DeviceController] Selected video input device changed:", info))));
|
|
10798
10838
|
}
|
|
10799
10839
|
get selectedAudioInputDevice() {
|
|
10800
10840
|
if (this._audioInputDisabled$.value) return null;
|
|
@@ -10869,7 +10909,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10869
10909
|
if (device) this.persistDeviceSelection("audioinput", device);
|
|
10870
10910
|
}
|
|
10871
10911
|
selectVideoInputDevice(device) {
|
|
10872
|
-
logger$
|
|
10912
|
+
logger$30.debug("[DeviceController] Setting selected video input device:", device);
|
|
10873
10913
|
if (this._videoInputDisabled$.value && device) this._videoInputDisabled$.next(false);
|
|
10874
10914
|
const previous = this._selectedDevicesState$.value.videoinput;
|
|
10875
10915
|
if (previous && previous.deviceId !== device?.deviceId) this._deviceHistory.push("videoinput", previous);
|
|
@@ -10926,7 +10966,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10926
10966
|
}
|
|
10927
10967
|
const fromHistory = this._deviceHistory.findInHistory(kind, devices);
|
|
10928
10968
|
if (fromHistory) {
|
|
10929
|
-
logger$
|
|
10969
|
+
logger$30.debug(`[DeviceController] Device disappeared, falling back to history: ${fromHistory.label}`);
|
|
10930
10970
|
this.emitDeviceRecovered(kind, selected, fromHistory, "device_disconnected");
|
|
10931
10971
|
return fromHistory;
|
|
10932
10972
|
}
|
|
@@ -10979,7 +11019,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10979
11019
|
try {
|
|
10980
11020
|
await this._storageManager.setItem(DEVICE_STORAGE_KEYS[kind], stored, "local");
|
|
10981
11021
|
} catch (error) {
|
|
10982
|
-
logger$
|
|
11022
|
+
logger$30.error(`[DeviceController] Failed to persist device selection for ${kind}:`, error);
|
|
10983
11023
|
}
|
|
10984
11024
|
}
|
|
10985
11025
|
async loadPersistedDevices() {
|
|
@@ -10995,7 +11035,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10995
11035
|
[kind]: stored
|
|
10996
11036
|
};
|
|
10997
11037
|
} catch (error) {
|
|
10998
|
-
logger$
|
|
11038
|
+
logger$30.error(`[DeviceController] Failed to load persisted device for ${kind}:`, error);
|
|
10999
11039
|
}
|
|
11000
11040
|
}
|
|
11001
11041
|
/** Clears device history, persisted selections, and re-enumerates devices. */
|
|
@@ -11013,7 +11053,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
11013
11053
|
this.disableDeviceMonitoring();
|
|
11014
11054
|
this.webRTCApiProvider.mediaDevices.addEventListener("devicechange", this.deviceChangeHandler);
|
|
11015
11055
|
if (PreferencesContainer.instance.devicePollingInterval > 0) this._devicesPoolingSubscription = (0, import_cjs$29.interval)(PreferencesContainer.instance.devicePollingInterval).subscribe(() => {
|
|
11016
|
-
logger$
|
|
11056
|
+
logger$30.debug("[DeviceController] Polling devices due to interval");
|
|
11017
11057
|
this.enumerateDevices();
|
|
11018
11058
|
});
|
|
11019
11059
|
this.enumerateDevices();
|
|
@@ -11039,13 +11079,13 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
11039
11079
|
videoinput: []
|
|
11040
11080
|
});
|
|
11041
11081
|
this._devicesState$.next(devicesByKind);
|
|
11042
|
-
logger$
|
|
11082
|
+
logger$30.debug("[DeviceController] Devices enumerated:", {
|
|
11043
11083
|
audioInputs: devicesByKind.audioinput.length,
|
|
11044
11084
|
audioOutputs: devicesByKind.audiooutput.length,
|
|
11045
11085
|
videoInputs: devicesByKind.videoinput.length
|
|
11046
11086
|
});
|
|
11047
11087
|
} catch (error) {
|
|
11048
|
-
logger$
|
|
11088
|
+
logger$30.error("[DeviceController] Failed to enumerate devices:", error);
|
|
11049
11089
|
this._errors$.next(toError(error));
|
|
11050
11090
|
}
|
|
11051
11091
|
}
|
|
@@ -11061,7 +11101,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
11061
11101
|
stream.getTracks().forEach((t) => t.stop());
|
|
11062
11102
|
return capabilities;
|
|
11063
11103
|
} catch (error) {
|
|
11064
|
-
logger$
|
|
11104
|
+
logger$30.error("[DeviceController] Failed to get device capabilities:", error);
|
|
11065
11105
|
this._errors$.next(toError(error));
|
|
11066
11106
|
throw error;
|
|
11067
11107
|
}
|
|
@@ -11312,7 +11352,7 @@ var DependencyContainer = class {
|
|
|
11312
11352
|
|
|
11313
11353
|
//#endregion
|
|
11314
11354
|
//#region src/controllers/CryptoController.ts
|
|
11315
|
-
const logger$
|
|
11355
|
+
const logger$29 = getLogger();
|
|
11316
11356
|
const DPOP_DB_NAME = "sw-dpop";
|
|
11317
11357
|
const DPOP_DB_VERSION = 1;
|
|
11318
11358
|
const DPOP_STORE_NAME = "keys";
|
|
@@ -11371,7 +11411,7 @@ async function loadKeyPairFromDB() {
|
|
|
11371
11411
|
tx.oncomplete = () => db.close();
|
|
11372
11412
|
});
|
|
11373
11413
|
} catch (error) {
|
|
11374
|
-
logger$
|
|
11414
|
+
logger$29.warn("[DPoP] Failed to load key pair from IndexedDB:", error);
|
|
11375
11415
|
return null;
|
|
11376
11416
|
}
|
|
11377
11417
|
}
|
|
@@ -11391,7 +11431,7 @@ async function saveKeyPairToDB(keyPair) {
|
|
|
11391
11431
|
};
|
|
11392
11432
|
});
|
|
11393
11433
|
} catch (error) {
|
|
11394
|
-
logger$
|
|
11434
|
+
logger$29.warn("[DPoP] Failed to save key pair to IndexedDB:", error);
|
|
11395
11435
|
}
|
|
11396
11436
|
}
|
|
11397
11437
|
async function deleteKeyPairFromDB() {
|
|
@@ -11410,7 +11450,7 @@ async function deleteKeyPairFromDB() {
|
|
|
11410
11450
|
};
|
|
11411
11451
|
});
|
|
11412
11452
|
} catch (error) {
|
|
11413
|
-
logger$
|
|
11453
|
+
logger$29.warn("[DPoP] Failed to delete key pair from IndexedDB:", error);
|
|
11414
11454
|
}
|
|
11415
11455
|
}
|
|
11416
11456
|
/**
|
|
@@ -11470,13 +11510,13 @@ var CryptoController = class {
|
|
|
11470
11510
|
this._publicJwk = await crypto.subtle.exportKey("jwk", stored.publicKey);
|
|
11471
11511
|
this._fingerprint = await computeJwkThumbprint(this._publicJwk);
|
|
11472
11512
|
this._initialized = true;
|
|
11473
|
-
logger$
|
|
11513
|
+
logger$29.debug("[DPoP] Key pair restored from IndexedDB, fingerprint:", this._fingerprint);
|
|
11474
11514
|
return this._fingerprint;
|
|
11475
11515
|
} catch (error) {
|
|
11476
|
-
logger$
|
|
11516
|
+
logger$29.warn("[DPoP] Stored key pair unusable, generating new one:", error);
|
|
11477
11517
|
await deleteKeyPairFromDB();
|
|
11478
11518
|
}
|
|
11479
|
-
logger$
|
|
11519
|
+
logger$29.debug("[DPoP] Generating RSA key pair");
|
|
11480
11520
|
this._keyPair = await crypto.subtle.generateKey({
|
|
11481
11521
|
name: "RSASSA-PKCS1-v1_5",
|
|
11482
11522
|
modulusLength: 2048,
|
|
@@ -11491,7 +11531,7 @@ var CryptoController = class {
|
|
|
11491
11531
|
this._fingerprint = await computeJwkThumbprint(this._publicJwk);
|
|
11492
11532
|
this._initialized = true;
|
|
11493
11533
|
await saveKeyPairToDB(this._keyPair);
|
|
11494
|
-
logger$
|
|
11534
|
+
logger$29.debug("[DPoP] Key pair generated and persisted, fingerprint:", this._fingerprint);
|
|
11495
11535
|
return this._fingerprint;
|
|
11496
11536
|
}
|
|
11497
11537
|
/**
|
|
@@ -11557,7 +11597,7 @@ var CryptoController = class {
|
|
|
11557
11597
|
this._fingerprint = null;
|
|
11558
11598
|
this._initialized = false;
|
|
11559
11599
|
deleteKeyPairFromDB();
|
|
11560
|
-
logger$
|
|
11600
|
+
logger$29.debug("[DPoP] Controller destroyed");
|
|
11561
11601
|
}
|
|
11562
11602
|
get publicJwk() {
|
|
11563
11603
|
if (!this._publicJwk) throw new DPoPInitError("CryptoController not initialized. Call init() first.");
|
|
@@ -11581,7 +11621,7 @@ var CryptoController = class {
|
|
|
11581
11621
|
//#endregion
|
|
11582
11622
|
//#region src/controllers/NetworkMonitor.ts
|
|
11583
11623
|
var import_cjs$28 = require_cjs();
|
|
11584
|
-
const logger$
|
|
11624
|
+
const logger$28 = getLogger();
|
|
11585
11625
|
/**
|
|
11586
11626
|
* Safely check whether we are running in a browser environment
|
|
11587
11627
|
* with `window` and the relevant event targets.
|
|
@@ -11638,7 +11678,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11638
11678
|
}
|
|
11639
11679
|
attachListeners() {
|
|
11640
11680
|
if (!hasBrowserNetworkEvents()) {
|
|
11641
|
-
logger$
|
|
11681
|
+
logger$28.debug("NetworkMonitor: no browser environment detected, skipping event listeners");
|
|
11642
11682
|
return;
|
|
11643
11683
|
}
|
|
11644
11684
|
window.addEventListener("online", this._onOnline);
|
|
@@ -11646,7 +11686,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11646
11686
|
const connection = getNetworkConnection();
|
|
11647
11687
|
if (connection) connection.addEventListener("change", this._onConnectionChange);
|
|
11648
11688
|
this._listenersAttached = true;
|
|
11649
|
-
logger$
|
|
11689
|
+
logger$28.debug("NetworkMonitor: event listeners attached");
|
|
11650
11690
|
}
|
|
11651
11691
|
removeListeners() {
|
|
11652
11692
|
if (!this._listenersAttached) return;
|
|
@@ -11657,10 +11697,10 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11657
11697
|
if (connection) connection.removeEventListener("change", this._onConnectionChange);
|
|
11658
11698
|
}
|
|
11659
11699
|
this._listenersAttached = false;
|
|
11660
|
-
logger$
|
|
11700
|
+
logger$28.debug("NetworkMonitor: event listeners removed");
|
|
11661
11701
|
}
|
|
11662
11702
|
handleOnline() {
|
|
11663
|
-
logger$
|
|
11703
|
+
logger$28.info("NetworkMonitor: browser went online");
|
|
11664
11704
|
this._isOnline$.next(true);
|
|
11665
11705
|
this._networkChange$.next({
|
|
11666
11706
|
type: "online",
|
|
@@ -11669,7 +11709,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11669
11709
|
});
|
|
11670
11710
|
}
|
|
11671
11711
|
handleOffline() {
|
|
11672
|
-
logger$
|
|
11712
|
+
logger$28.info("NetworkMonitor: browser went offline");
|
|
11673
11713
|
this._isOnline$.next(false);
|
|
11674
11714
|
this._networkChange$.next({
|
|
11675
11715
|
type: "offline",
|
|
@@ -11678,7 +11718,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11678
11718
|
}
|
|
11679
11719
|
handleConnectionChange() {
|
|
11680
11720
|
const networkType = getNetworkType();
|
|
11681
|
-
logger$
|
|
11721
|
+
logger$28.info(`NetworkMonitor: connection changed — effectiveType=${networkType ?? "unknown"}`);
|
|
11682
11722
|
this._networkChange$.next({
|
|
11683
11723
|
type: "connection_change",
|
|
11684
11724
|
timestamp: Date.now(),
|
|
@@ -11794,7 +11834,7 @@ function getNavigatorMediaDevices() {
|
|
|
11794
11834
|
//#endregion
|
|
11795
11835
|
//#region src/controllers/PreflightRunner.ts
|
|
11796
11836
|
var import_cjs$27 = require_cjs();
|
|
11797
|
-
const logger$
|
|
11837
|
+
const logger$27 = getLogger();
|
|
11798
11838
|
const DEFAULT_MEDIA_TEST_DURATION_S = 10;
|
|
11799
11839
|
const ICE_GATHERING_TIMEOUT_MS = 1e4;
|
|
11800
11840
|
const SIGNALING_RTT_TIMEOUT_MS = 5e3;
|
|
@@ -11843,7 +11883,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11843
11883
|
if (!this._options.skipMediaTest) try {
|
|
11844
11884
|
bandwidth = await this.testMediaBandwidth(destination);
|
|
11845
11885
|
} catch (error) {
|
|
11846
|
-
logger$
|
|
11886
|
+
logger$27.warn("[PreflightRunner] Media bandwidth test failed:", error);
|
|
11847
11887
|
warnings.push("Media bandwidth test failed");
|
|
11848
11888
|
}
|
|
11849
11889
|
return {
|
|
@@ -11855,7 +11895,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11855
11895
|
warnings
|
|
11856
11896
|
};
|
|
11857
11897
|
} catch (error) {
|
|
11858
|
-
logger$
|
|
11898
|
+
logger$27.error("[PreflightRunner] Preflight test failed:", error);
|
|
11859
11899
|
throw new PreflightError("preflight", error instanceof Error ? error : new Error(String(error)));
|
|
11860
11900
|
} finally {
|
|
11861
11901
|
this.destroy();
|
|
@@ -11886,7 +11926,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11886
11926
|
if (track.kind === "video" && track.readyState === "live") videoWorking = true;
|
|
11887
11927
|
}
|
|
11888
11928
|
} catch (error) {
|
|
11889
|
-
logger$
|
|
11929
|
+
logger$27.warn("[PreflightRunner] Device test failed:", error);
|
|
11890
11930
|
} finally {
|
|
11891
11931
|
if (audioStream) audioStream.getTracks().forEach((t) => t.stop());
|
|
11892
11932
|
}
|
|
@@ -11944,7 +11984,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11944
11984
|
rttMs
|
|
11945
11985
|
};
|
|
11946
11986
|
} catch (error) {
|
|
11947
|
-
logger$
|
|
11987
|
+
logger$27.warn("[PreflightRunner] ICE connectivity test failed:", error);
|
|
11948
11988
|
return {
|
|
11949
11989
|
type: "failed",
|
|
11950
11990
|
turnReachable: false,
|
|
@@ -11992,7 +12032,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11992
12032
|
//#endregion
|
|
11993
12033
|
//#region src/controllers/VisibilityController.ts
|
|
11994
12034
|
var import_cjs$26 = require_cjs();
|
|
11995
|
-
const logger$
|
|
12035
|
+
const logger$26 = getLogger();
|
|
11996
12036
|
/**
|
|
11997
12037
|
* Checks whether the document visibility API is available.
|
|
11998
12038
|
*/
|
|
@@ -12029,8 +12069,8 @@ var VisibilityController = class extends Destroyable {
|
|
|
12029
12069
|
this._boundHandler = this._handleVisibilityChange.bind(this);
|
|
12030
12070
|
if (this._hasVisibilityApi) {
|
|
12031
12071
|
document.addEventListener("visibilitychange", this._boundHandler);
|
|
12032
|
-
logger$
|
|
12033
|
-
} else logger$
|
|
12072
|
+
logger$26.debug("VisibilityController: listening for visibilitychange events");
|
|
12073
|
+
} else logger$26.debug("VisibilityController: document visibility API not available, defaulting to visible");
|
|
12034
12074
|
}
|
|
12035
12075
|
/**
|
|
12036
12076
|
* Observable of the current visibility state.
|
|
@@ -12055,7 +12095,7 @@ var VisibilityController = class extends Destroyable {
|
|
|
12055
12095
|
destroy() {
|
|
12056
12096
|
if (this._hasVisibilityApi) {
|
|
12057
12097
|
document.removeEventListener("visibilitychange", this._boundHandler);
|
|
12058
|
-
logger$
|
|
12098
|
+
logger$26.debug("VisibilityController: removed visibilitychange listener");
|
|
12059
12099
|
}
|
|
12060
12100
|
super.destroy();
|
|
12061
12101
|
}
|
|
@@ -12073,7 +12113,7 @@ var VisibilityController = class extends Destroyable {
|
|
|
12073
12113
|
timestamp: Date.now()
|
|
12074
12114
|
};
|
|
12075
12115
|
this._visibilityChange$.next(changeEvent);
|
|
12076
|
-
logger$
|
|
12116
|
+
logger$26.debug("VisibilityController: visibility changed", {
|
|
12077
12117
|
from: previousState,
|
|
12078
12118
|
to: newState
|
|
12079
12119
|
});
|
|
@@ -12314,7 +12354,7 @@ const RPCEventAckResponse = (id) => makeRPCResponse({
|
|
|
12314
12354
|
|
|
12315
12355
|
//#endregion
|
|
12316
12356
|
//#region src/managers/AttachManager.ts
|
|
12317
|
-
const logger$
|
|
12357
|
+
const logger$25 = getLogger();
|
|
12318
12358
|
var AttachManager = class {
|
|
12319
12359
|
constructor(storage, deviceController, reconnectCallsTimeout, attachKey) {
|
|
12320
12360
|
this.storage = storage;
|
|
@@ -12335,7 +12375,7 @@ var AttachManager = class {
|
|
|
12335
12375
|
try {
|
|
12336
12376
|
return await this.storage.getItem(this.attachKey) ?? {};
|
|
12337
12377
|
} catch (error) {
|
|
12338
|
-
logger$
|
|
12378
|
+
logger$25.warn("[AttachManager] Failed to retrieve attached calls from storage", error);
|
|
12339
12379
|
return {};
|
|
12340
12380
|
}
|
|
12341
12381
|
}
|
|
@@ -12343,7 +12383,7 @@ var AttachManager = class {
|
|
|
12343
12383
|
try {
|
|
12344
12384
|
await this.storage.setItem(this.attachKey, attached);
|
|
12345
12385
|
} catch (error) {
|
|
12346
|
-
logger$
|
|
12386
|
+
logger$25.warn("[AttachManager] Failed to write attached calls to storage", error);
|
|
12347
12387
|
}
|
|
12348
12388
|
}
|
|
12349
12389
|
/**
|
|
@@ -12362,7 +12402,7 @@ var AttachManager = class {
|
|
|
12362
12402
|
}
|
|
12363
12403
|
async attach(call) {
|
|
12364
12404
|
if (!call.to) {
|
|
12365
|
-
logger$
|
|
12405
|
+
logger$25.warn("[AttachManager] Skip attach for calls with no destination");
|
|
12366
12406
|
return;
|
|
12367
12407
|
}
|
|
12368
12408
|
const destination = call.to;
|
|
@@ -12415,15 +12455,15 @@ var AttachManager = class {
|
|
|
12415
12455
|
callId,
|
|
12416
12456
|
...options
|
|
12417
12457
|
});
|
|
12418
|
-
logger$
|
|
12458
|
+
logger$25.info(`[AttachManager] Reattached call ${callId} (attempt ${attempt})`);
|
|
12419
12459
|
succeeded = true;
|
|
12420
12460
|
break;
|
|
12421
12461
|
} catch (error) {
|
|
12422
|
-
logger$
|
|
12462
|
+
logger$25.warn(`[AttachManager] Reattach attempt ${attempt}/3 failed for call ${callId}:`, error);
|
|
12423
12463
|
if (attempt < 3) await new Promise((r) => setTimeout(r, (attempt + 1) * 1e3));
|
|
12424
12464
|
}
|
|
12425
12465
|
if (!succeeded) {
|
|
12426
|
-
logger$
|
|
12466
|
+
logger$25.warn(`[AttachManager] Reattach failed after 3 attempts for call ${callId}, removing reference`);
|
|
12427
12467
|
await this.detach({
|
|
12428
12468
|
id: callId,
|
|
12429
12469
|
mediaDirections: attachment.mediaDirections
|
|
@@ -13360,7 +13400,9 @@ const DEFAULT_MEMBER_CAPABILITIES = {
|
|
|
13360
13400
|
position: false,
|
|
13361
13401
|
meta: false,
|
|
13362
13402
|
remove: false,
|
|
13363
|
-
audioFlags: false
|
|
13403
|
+
audioFlags: false,
|
|
13404
|
+
denoise: false,
|
|
13405
|
+
lowbitrate: false
|
|
13364
13406
|
};
|
|
13365
13407
|
/**
|
|
13366
13408
|
* Default call capabilities with no permissions
|
|
@@ -13433,7 +13475,9 @@ function computeMemberCapabilities(flags, memberType) {
|
|
|
13433
13475
|
position: hasBooleanCapability(flags, memberType, null, "position"),
|
|
13434
13476
|
meta: hasBooleanCapability(flags, memberType, null, "meta"),
|
|
13435
13477
|
remove: hasBooleanCapability(flags, memberType, null, "remove"),
|
|
13436
|
-
audioFlags: hasBooleanCapability(flags, memberType, null, "audioflags")
|
|
13478
|
+
audioFlags: hasBooleanCapability(flags, memberType, null, "audioflags"),
|
|
13479
|
+
denoise: hasBooleanCapability(flags, memberType, null, "denoise"),
|
|
13480
|
+
lowbitrate: hasBooleanCapability(flags, memberType, null, "lowbitrate")
|
|
13437
13481
|
};
|
|
13438
13482
|
}
|
|
13439
13483
|
/**
|
|
@@ -13590,7 +13634,7 @@ function toggleHandraiseMethod(is) {
|
|
|
13590
13634
|
|
|
13591
13635
|
//#endregion
|
|
13592
13636
|
//#region src/core/entities/Participant.ts
|
|
13593
|
-
const logger$
|
|
13637
|
+
const logger$24 = getLogger();
|
|
13594
13638
|
const initialState = {};
|
|
13595
13639
|
/**
|
|
13596
13640
|
* Represents a participant in a call.
|
|
@@ -13816,6 +13860,10 @@ var Participant = class extends Destroyable {
|
|
|
13816
13860
|
get nodeId() {
|
|
13817
13861
|
return this._state$.value.node_id;
|
|
13818
13862
|
}
|
|
13863
|
+
/** Call ID for this participant's leg, or `undefined` if not available. */
|
|
13864
|
+
get callId() {
|
|
13865
|
+
return this._state$.value.call_id;
|
|
13866
|
+
}
|
|
13819
13867
|
/** @internal */
|
|
13820
13868
|
get value() {
|
|
13821
13869
|
return this._state$.value;
|
|
@@ -13877,8 +13925,9 @@ var Participant = class extends Destroyable {
|
|
|
13877
13925
|
noise_suppression: !this.noiseSuppression
|
|
13878
13926
|
});
|
|
13879
13927
|
}
|
|
13928
|
+
/** Toggles low-bitrate mode for this participant's media. */
|
|
13880
13929
|
async toggleLowbitrate() {
|
|
13881
|
-
|
|
13930
|
+
await this.executeMethod(this.id, "call.lowbitrate.set", { lowbitrate: !this.lowbitrate });
|
|
13882
13931
|
}
|
|
13883
13932
|
/**
|
|
13884
13933
|
* Adjusts the **conference-only** microphone energy gate / sensitivity level
|
|
@@ -13925,10 +13974,27 @@ var Participant = class extends Destroyable {
|
|
|
13925
13974
|
}
|
|
13926
13975
|
/**
|
|
13927
13976
|
* Sets the participant's position in the video layout.
|
|
13977
|
+
*
|
|
13978
|
+
* Requires the `member.position` capability. The gateway keys positions by the
|
|
13979
|
+
* **target member's own** `call_id`/`node_id` (see issue #19400 and the legacy
|
|
13980
|
+
* `setPositions` implementation), so this sends the participant's own call
|
|
13981
|
+
* context — matching {@link Participant.remove}. A resolved promise does not
|
|
13982
|
+
* guarantee a visible change: the backend silently returns `200` (no-op) for
|
|
13983
|
+
* non-conference targets.
|
|
13984
|
+
*
|
|
13928
13985
|
* @param value - The {@link VideoPosition} to assign (e.g. `'auto'`, `'reserved-0'`).
|
|
13929
13986
|
*/
|
|
13930
13987
|
async setPosition(value) {
|
|
13931
|
-
|
|
13988
|
+
const state = this._state$.value;
|
|
13989
|
+
const target = {
|
|
13990
|
+
member_id: this.id,
|
|
13991
|
+
call_id: state.call_id ?? "",
|
|
13992
|
+
node_id: state.node_id ?? ""
|
|
13993
|
+
};
|
|
13994
|
+
await this.executeMethod(target, "call.member.position.set", { targets: [{
|
|
13995
|
+
target,
|
|
13996
|
+
position: value
|
|
13997
|
+
}] });
|
|
13932
13998
|
}
|
|
13933
13999
|
/** Removes this participant from the call. */
|
|
13934
14000
|
async remove() {
|
|
@@ -14018,12 +14084,21 @@ var SelfParticipant = class extends Participant {
|
|
|
14018
14084
|
noise_suppression: true
|
|
14019
14085
|
});
|
|
14020
14086
|
}
|
|
14021
|
-
/**
|
|
14087
|
+
/**
|
|
14088
|
+
* Starts sharing the local screen.
|
|
14089
|
+
*
|
|
14090
|
+
* The call is unaffected when acquisition fails.
|
|
14091
|
+
*
|
|
14092
|
+
* @throws The raw `getDisplayMedia` error. A dismissed picker or a
|
|
14093
|
+
* permission denial rejects with a `NotAllowedError` `DOMException` —
|
|
14094
|
+
* inspect `error.name` to tell benign cancels apart from real failures.
|
|
14095
|
+
*/
|
|
14022
14096
|
async startScreenShare() {
|
|
14023
14097
|
try {
|
|
14024
14098
|
await this.vertoManager.addScreenMedia();
|
|
14025
14099
|
} catch (error) {
|
|
14026
|
-
logger$
|
|
14100
|
+
logger$24.error("[Participant.startScreenShare] Screen share error:", error);
|
|
14101
|
+
throw error;
|
|
14027
14102
|
}
|
|
14028
14103
|
}
|
|
14029
14104
|
/** Observable of the current screen share status. */
|
|
@@ -14038,12 +14113,20 @@ var SelfParticipant = class extends Participant {
|
|
|
14038
14113
|
async stopScreenShare() {
|
|
14039
14114
|
return this.vertoManager.removeScreenMedia();
|
|
14040
14115
|
}
|
|
14041
|
-
/**
|
|
14116
|
+
/**
|
|
14117
|
+
* Adds an additional media input device to the call.
|
|
14118
|
+
*
|
|
14119
|
+
* The call is unaffected when acquisition fails.
|
|
14120
|
+
*
|
|
14121
|
+
* @throws The raw `getUserMedia` error (e.g. `NotAllowedError` on
|
|
14122
|
+
* permission denial) — inspect `error.name` to decide how to react.
|
|
14123
|
+
*/
|
|
14042
14124
|
async addAdditionalDevice(options) {
|
|
14043
14125
|
try {
|
|
14044
14126
|
await this.vertoManager.addInputDevice(options);
|
|
14045
14127
|
} catch (error) {
|
|
14046
|
-
logger$
|
|
14128
|
+
logger$24.error("[Participant.addAdditionalDevice] Additional device error:", error);
|
|
14129
|
+
throw error;
|
|
14047
14130
|
}
|
|
14048
14131
|
}
|
|
14049
14132
|
/** Removes an additional media input device by ID. */
|
|
@@ -14105,7 +14188,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14105
14188
|
*/
|
|
14106
14189
|
exitStudioModeIfActive() {
|
|
14107
14190
|
if (this._studioAudio$.value) {
|
|
14108
|
-
logger$
|
|
14191
|
+
logger$24.debug("[SelfParticipant] Exiting studio audio mode due to individual flag toggle");
|
|
14109
14192
|
this._studioAudio$.next(false);
|
|
14110
14193
|
}
|
|
14111
14194
|
}
|
|
@@ -14129,7 +14212,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14129
14212
|
try {
|
|
14130
14213
|
await super.mute();
|
|
14131
14214
|
} catch (error) {
|
|
14132
|
-
logger$
|
|
14215
|
+
logger$24.warn("[Participant.toggleAudioInput] Server Error while muting audio input, proceeding with local toggle anyway", error);
|
|
14133
14216
|
} finally {
|
|
14134
14217
|
this.vertoManager.muteMainAudioInputDevice();
|
|
14135
14218
|
}
|
|
@@ -14139,7 +14222,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14139
14222
|
try {
|
|
14140
14223
|
await super.unmute();
|
|
14141
14224
|
} catch (error) {
|
|
14142
|
-
logger$
|
|
14225
|
+
logger$24.warn("[Participant.toggleAudioInput] Server Error while unmuting audio input, proceeding with local toggle anyway", error);
|
|
14143
14226
|
} finally {
|
|
14144
14227
|
await this.vertoManager.unmuteMainAudioInputDevice();
|
|
14145
14228
|
}
|
|
@@ -14149,7 +14232,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14149
14232
|
try {
|
|
14150
14233
|
await super.muteVideo();
|
|
14151
14234
|
} catch (error) {
|
|
14152
|
-
logger$
|
|
14235
|
+
logger$24.warn("[Participant.toggleVideoInput] Server Error while muting video input, proceeding with local toggle anyway", error);
|
|
14153
14236
|
} finally {
|
|
14154
14237
|
this.vertoManager.muteMainVideoInputDevice();
|
|
14155
14238
|
}
|
|
@@ -14159,7 +14242,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14159
14242
|
try {
|
|
14160
14243
|
await super.unmuteVideo();
|
|
14161
14244
|
} catch (error) {
|
|
14162
|
-
logger$
|
|
14245
|
+
logger$24.warn("[Participant.toggleVideoInput] Server Error while unmuting video input, proceeding with local toggle anyway", error);
|
|
14163
14246
|
} finally {
|
|
14164
14247
|
await this.vertoManager.unmuteMainVideoInputDevice();
|
|
14165
14248
|
}
|
|
@@ -14364,7 +14447,7 @@ function filterAs(predicate, resultPath) {
|
|
|
14364
14447
|
//#endregion
|
|
14365
14448
|
//#region src/operators/throwOnRPCError.ts
|
|
14366
14449
|
var import_cjs$21 = require_cjs();
|
|
14367
|
-
const logger$
|
|
14450
|
+
const logger$23 = getLogger();
|
|
14368
14451
|
/**
|
|
14369
14452
|
* RxJS operator that throws a {@link JSONRPCError} when the RPC response contains an error.
|
|
14370
14453
|
* Passes successful responses through unchanged.
|
|
@@ -14372,14 +14455,14 @@ const logger$22 = getLogger();
|
|
|
14372
14455
|
function throwOnRPCError() {
|
|
14373
14456
|
return (0, import_cjs$21.map)((response) => {
|
|
14374
14457
|
if (response.error) {
|
|
14375
|
-
logger$
|
|
14458
|
+
logger$23.error("[throwOnRPCError] RPC error response:", {
|
|
14376
14459
|
code: response.error.code,
|
|
14377
14460
|
message: response.error.message,
|
|
14378
14461
|
data: response.error.data
|
|
14379
14462
|
});
|
|
14380
14463
|
throw new JSONRPCError(response.error.code, response.error.message, response.error.data);
|
|
14381
14464
|
}
|
|
14382
|
-
logger$
|
|
14465
|
+
logger$23.debug("[throwOnRPCError] RPC successful response:", response);
|
|
14383
14466
|
return response;
|
|
14384
14467
|
});
|
|
14385
14468
|
}
|
|
@@ -14387,7 +14470,7 @@ function throwOnRPCError() {
|
|
|
14387
14470
|
//#endregion
|
|
14388
14471
|
//#region src/managers/CallEventsManager.ts
|
|
14389
14472
|
var import_cjs$20 = require_cjs();
|
|
14390
|
-
const logger$
|
|
14473
|
+
const logger$22 = getLogger();
|
|
14391
14474
|
const initialSessionState = {};
|
|
14392
14475
|
/** @internal */
|
|
14393
14476
|
var CallEventsManager = class extends Destroyable {
|
|
@@ -14491,7 +14574,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14491
14574
|
}
|
|
14492
14575
|
initSubscriptions() {
|
|
14493
14576
|
this.subscribeTo(this.callJoinedEvent$, (callJoinedEvent) => {
|
|
14494
|
-
logger$
|
|
14577
|
+
logger$22.debug("[CallEventsManager] Handling call.joined event for call/session IDs:", {
|
|
14495
14578
|
callId: callJoinedEvent.call_id,
|
|
14496
14579
|
roomSessionId: callJoinedEvent.room_session_id
|
|
14497
14580
|
});
|
|
@@ -14518,19 +14601,19 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14518
14601
|
if (this._self$.value?.capabilities.setLayout) this.updateLayouts();
|
|
14519
14602
|
});
|
|
14520
14603
|
this.subscribeTo(this.memberUpdates$, (member) => {
|
|
14521
|
-
logger$
|
|
14604
|
+
logger$22.debug("[CallEventsManager] Handling member update event for member ID:", member);
|
|
14522
14605
|
this.upsertParticipant(member);
|
|
14523
14606
|
});
|
|
14524
14607
|
this.subscribeTo(this.webRtcCallSession.memberLeft$, (memberLeftEvent) => {
|
|
14525
|
-
logger$
|
|
14608
|
+
logger$22.debug("[CallEventsManager] Handling member.left event for member ID:", memberLeftEvent.member.member_id);
|
|
14526
14609
|
const participants = { ...this._participants$.value };
|
|
14527
14610
|
if (memberLeftEvent.member.member_id in participants) {
|
|
14528
14611
|
delete participants[memberLeftEvent.member.member_id];
|
|
14529
14612
|
this._participants$.next(participants);
|
|
14530
|
-
} else logger$
|
|
14613
|
+
} else logger$22.warn(`[CallEventsManager] Received member.left event for unknown member ID: ${memberLeftEvent.member.member_id}`);
|
|
14531
14614
|
});
|
|
14532
14615
|
this.subscribeTo(this.webRtcCallSession.callUpdated$, (callUpdatedEvent) => {
|
|
14533
|
-
logger$
|
|
14616
|
+
logger$22.debug("[CallEventsManager] Handling call.updated event:", callUpdatedEvent);
|
|
14534
14617
|
const roomSession = callUpdatedEvent.room_session;
|
|
14535
14618
|
this._sessionState$.next({
|
|
14536
14619
|
...this._sessionState$.value,
|
|
@@ -14545,7 +14628,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14545
14628
|
});
|
|
14546
14629
|
});
|
|
14547
14630
|
this.subscribeTo(this.layoutChangedEvent$, (layoutChangedEvent) => {
|
|
14548
|
-
logger$
|
|
14631
|
+
logger$22.debug("[CallEventsManager] Handling layout.changed event:", layoutChangedEvent);
|
|
14549
14632
|
this._sessionState$.next({
|
|
14550
14633
|
...this._sessionState$.value,
|
|
14551
14634
|
layout_name: layoutChangedEvent.id,
|
|
@@ -14555,10 +14638,10 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14555
14638
|
});
|
|
14556
14639
|
}
|
|
14557
14640
|
updateParticipantPositions(layoutChangedEvent) {
|
|
14558
|
-
if (Object.keys(this._participants$.value).length > 0 && !layoutChangedEvent.layers.some((layer) => !!layer.member_id)) logger$
|
|
14641
|
+
if (Object.keys(this._participants$.value).length > 0 && !layoutChangedEvent.layers.some((layer) => !!layer.member_id)) logger$22.warn("[CallEventsManager] No layers with member_id found in layout.changed event. Nothing to update.");
|
|
14559
14642
|
layoutChangedEvent.layers.filter((layer) => !!layer.member_id).filter((layer) => {
|
|
14560
14643
|
if (!(layer.member_id in this._participants$.value)) {
|
|
14561
|
-
logger$
|
|
14644
|
+
logger$22.warn(`[CallEventsManager] Skipping layout layer for unknown member_id: ${layer.member_id}`);
|
|
14562
14645
|
return false;
|
|
14563
14646
|
}
|
|
14564
14647
|
return true;
|
|
@@ -14581,7 +14664,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14581
14664
|
layouts: response.result.layouts
|
|
14582
14665
|
});
|
|
14583
14666
|
}).catch((error) => {
|
|
14584
|
-
logger$
|
|
14667
|
+
logger$22.error("[CallEventsManager] Error fetching layouts:", error);
|
|
14585
14668
|
});
|
|
14586
14669
|
}
|
|
14587
14670
|
updateParticipants(members) {
|
|
@@ -14597,7 +14680,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14597
14680
|
}
|
|
14598
14681
|
const participant = this._participants$.value[member.member_id];
|
|
14599
14682
|
const oldValue = participant.value;
|
|
14600
|
-
logger$
|
|
14683
|
+
logger$22.debug("[CallEventsManager] Updating participant:", member.member_id, {
|
|
14601
14684
|
oldValue,
|
|
14602
14685
|
newValue: member
|
|
14603
14686
|
});
|
|
@@ -14610,17 +14693,17 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14610
14693
|
}
|
|
14611
14694
|
get callJoinedEvent$() {
|
|
14612
14695
|
return this.cachedObservable("callJoinedEvent$", () => this.webRtcCallSession.callEvent$.pipe((0, import_cjs$20.filter)(isCallJoinedPayload), (0, import_cjs$20.tap)((event) => {
|
|
14613
|
-
logger$
|
|
14696
|
+
logger$22.debug("[CallEventsManager] Call joined event:", event);
|
|
14614
14697
|
})));
|
|
14615
14698
|
}
|
|
14616
14699
|
get layoutChangedEvent$() {
|
|
14617
14700
|
return this.cachedObservable("layoutChangedEvent$", () => this.webRtcCallSession.callEvent$.pipe(filterAs(isLayoutChangedPayload, "layout"), (0, import_cjs$20.tap)((event) => {
|
|
14618
|
-
logger$
|
|
14701
|
+
logger$22.debug("[CallEventsManager] Layout changed event:", event);
|
|
14619
14702
|
})));
|
|
14620
14703
|
}
|
|
14621
14704
|
get memberUpdates$() {
|
|
14622
14705
|
return this.cachedObservable("memberUpdates$", () => (0, import_cjs$20.merge)(this.webRtcCallSession.memberJoined$, this.webRtcCallSession.memberUpdated$, this.webRtcCallSession.memberTalking$).pipe((0, import_cjs$20.map)((event) => event.member), (0, import_cjs$20.tap)((event) => {
|
|
14623
|
-
logger$
|
|
14706
|
+
logger$22.debug("[CallEventsManager] Member update event:", event);
|
|
14624
14707
|
})));
|
|
14625
14708
|
}
|
|
14626
14709
|
destroy() {
|
|
@@ -14877,7 +14960,7 @@ function appendStereoParams(fmtpLine, maxBitrate) {
|
|
|
14877
14960
|
//#endregion
|
|
14878
14961
|
//#region src/controllers/ICEGatheringController.ts
|
|
14879
14962
|
var import_cjs$19 = require_cjs();
|
|
14880
|
-
const logger$
|
|
14963
|
+
const logger$21 = getLogger();
|
|
14881
14964
|
var ICEGatheringController = class extends Destroyable {
|
|
14882
14965
|
constructor(peerConnection, peerConnectionControllerNegotiating$, options = {}) {
|
|
14883
14966
|
super();
|
|
@@ -14885,23 +14968,23 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14885
14968
|
this.peerConnectionControllerNegotiating$ = peerConnectionControllerNegotiating$;
|
|
14886
14969
|
this.onicegatheringstatechangeHandler = () => {
|
|
14887
14970
|
const { iceGatheringState } = this.peerConnection;
|
|
14888
|
-
logger$
|
|
14971
|
+
logger$21.debug(`[ICEGatheringController] ICE gathering state changed to: ${iceGatheringState}`);
|
|
14889
14972
|
if (iceGatheringState === "gathering") this._iceCandidatesState.next({
|
|
14890
14973
|
state: "gathering",
|
|
14891
14974
|
validSDP: false
|
|
14892
14975
|
});
|
|
14893
14976
|
};
|
|
14894
14977
|
this.onicecandidateHandler = (event) => {
|
|
14895
|
-
logger$
|
|
14978
|
+
logger$21.debug("[ICEGatheringController] ICE candidate event received:", event.candidate);
|
|
14896
14979
|
this.removeTimer("iceCandidateTimer");
|
|
14897
14980
|
if (event.candidate) this.iceCandidateTimer = setTimeout(() => {
|
|
14898
14981
|
if (this.peerConnection.iceGatheringState !== "complete") {
|
|
14899
|
-
logger$
|
|
14982
|
+
logger$21.warn("[ICEGatheringController] ICE candidate timeout, using current SDP");
|
|
14900
14983
|
this.handleICECandidateTimeout();
|
|
14901
14984
|
}
|
|
14902
14985
|
}, this.iceCandidateTimeout);
|
|
14903
14986
|
else {
|
|
14904
|
-
logger$
|
|
14987
|
+
logger$21.debug("[ICEGatheringController] ICE gathering completed: null candidate received");
|
|
14905
14988
|
this.removeTimer("iceGatheringTimer");
|
|
14906
14989
|
this.handleICEGatheringComplete();
|
|
14907
14990
|
}
|
|
@@ -14919,7 +15002,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14919
15002
|
this.setupEventListeners();
|
|
14920
15003
|
this.iceGatheringTimer = setTimeout(() => {
|
|
14921
15004
|
if (this.peerConnection.iceGatheringState !== "complete") {
|
|
14922
|
-
logger$
|
|
15005
|
+
logger$21.warn("[ICEGatheringController] ICE gathering timeout, using current SDP");
|
|
14923
15006
|
this.handleICEGatheringTimeout();
|
|
14924
15007
|
}
|
|
14925
15008
|
}, this.iceGatheringTimeout);
|
|
@@ -14946,9 +15029,9 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14946
15029
|
this.relayOnly = value;
|
|
14947
15030
|
}
|
|
14948
15031
|
handleICEGatheringComplete() {
|
|
14949
|
-
logger$
|
|
14950
|
-
logger$
|
|
14951
|
-
logger$
|
|
15032
|
+
logger$21.debug("[ICEGatheringController] Handling ICE gathering complete");
|
|
15033
|
+
logger$21.debug(`[ICEGatheringController] Checking ICE gathering state: ${this.peerConnection.iceGatheringState}`);
|
|
15034
|
+
logger$21.debug("[ICEGatheringController] ICE gathering complete");
|
|
14952
15035
|
this._iceCandidatesState.next({
|
|
14953
15036
|
state: "complete",
|
|
14954
15037
|
validSDP: this.hasValidLocalDescriptionSDP
|
|
@@ -14964,21 +15047,21 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14964
15047
|
this.removeTimer("iceGatheringTimer");
|
|
14965
15048
|
const validSDP = this.hasValidLocalDescriptionSDP;
|
|
14966
15049
|
if (validSDP) {
|
|
14967
|
-
logger$
|
|
15050
|
+
logger$21.debug("[ICEGatheringController] Local SDP is valid");
|
|
14968
15051
|
this._iceCandidatesState.next({
|
|
14969
15052
|
state: "timeout",
|
|
14970
15053
|
validSDP
|
|
14971
15054
|
});
|
|
14972
15055
|
this.stopGathering();
|
|
14973
|
-
} else logger$
|
|
15056
|
+
} else logger$21.debug("### ICE gathering timeout\n", this.peerConnection.localDescription?.sdp);
|
|
14974
15057
|
}
|
|
14975
15058
|
handleICECandidateTimeout() {
|
|
14976
15059
|
if (this.iceCandidateTimer) this.removeTimer("iceCandidateTimer");
|
|
14977
|
-
logger$
|
|
15060
|
+
logger$21.warn("[ICEGatheringController] ICE candidate timeout");
|
|
14978
15061
|
const validSDP = this.hasValidLocalDescriptionSDP;
|
|
14979
15062
|
if (!validSDP && !this.relayOnly) this.restartICEGatheringWithRelayOnly();
|
|
14980
15063
|
else {
|
|
14981
|
-
logger$
|
|
15064
|
+
logger$21.debug("[ICEGatheringController] Using current SDP due to ICE candidate timeout");
|
|
14982
15065
|
this._iceCandidatesState.next({
|
|
14983
15066
|
state: "timeout",
|
|
14984
15067
|
validSDP
|
|
@@ -14987,7 +15070,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14987
15070
|
}
|
|
14988
15071
|
}
|
|
14989
15072
|
restartICEGatheringWithRelayOnly() {
|
|
14990
|
-
logger$
|
|
15073
|
+
logger$21.debug("[ICEGatheringController] Restarting ICE gathering with relay-only candidates");
|
|
14991
15074
|
this.relayOnly = true;
|
|
14992
15075
|
this.peerConnection.setConfiguration({
|
|
14993
15076
|
...this.peerConnection.getConfiguration(),
|
|
@@ -15002,7 +15085,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
15002
15085
|
}
|
|
15003
15086
|
}
|
|
15004
15087
|
clearAllTimers() {
|
|
15005
|
-
logger$
|
|
15088
|
+
logger$21.debug("[ICEGatheringController] Clearing all timers");
|
|
15006
15089
|
this.removeTimer("iceGatheringTimer");
|
|
15007
15090
|
this.removeTimer("iceCandidateTimer");
|
|
15008
15091
|
}
|
|
@@ -15011,7 +15094,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
15011
15094
|
this.peerConnection.removeEventListener("icecandidate", this.onicecandidateHandler);
|
|
15012
15095
|
}
|
|
15013
15096
|
destroy() {
|
|
15014
|
-
logger$
|
|
15097
|
+
logger$21.debug("[ICEGatheringController] Destroying ICEGatheringController");
|
|
15015
15098
|
this.clearAllTimers();
|
|
15016
15099
|
this.removeEventListeners();
|
|
15017
15100
|
super.destroy();
|
|
@@ -15021,7 +15104,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
15021
15104
|
//#endregion
|
|
15022
15105
|
//#region src/controllers/LocalAudioPipeline.ts
|
|
15023
15106
|
var import_cjs$18 = require_cjs();
|
|
15024
|
-
const logger$
|
|
15107
|
+
const logger$20 = getLogger();
|
|
15025
15108
|
/**
|
|
15026
15109
|
* Web Audio pipeline for the local microphone stream.
|
|
15027
15110
|
*
|
|
@@ -15133,7 +15216,7 @@ var LocalAudioPipeline = class extends Destroyable {
|
|
|
15133
15216
|
try {
|
|
15134
15217
|
this._inputSource.disconnect();
|
|
15135
15218
|
} catch (error) {
|
|
15136
|
-
logger$
|
|
15219
|
+
logger$20.debug("[LocalAudioPipeline] input disconnect warning:", error);
|
|
15137
15220
|
}
|
|
15138
15221
|
this._inputSource = null;
|
|
15139
15222
|
}
|
|
@@ -15143,7 +15226,7 @@ var LocalAudioPipeline = class extends Destroyable {
|
|
|
15143
15226
|
this._inputSource = this._audioContext.createMediaStreamSource(this._inputStream);
|
|
15144
15227
|
this._inputSource.connect(this._gainNode);
|
|
15145
15228
|
if (this._audioContext.state === "suspended") this._audioContext.resume().catch((error) => {
|
|
15146
|
-
logger$
|
|
15229
|
+
logger$20.warn("[LocalAudioPipeline] AudioContext resume failed:", error);
|
|
15147
15230
|
});
|
|
15148
15231
|
}
|
|
15149
15232
|
destroy() {
|
|
@@ -15158,7 +15241,7 @@ var LocalAudioPipeline = class extends Destroyable {
|
|
|
15158
15241
|
this._analyser.disconnect();
|
|
15159
15242
|
} catch {}
|
|
15160
15243
|
this._audioContext.close().catch((error) => {
|
|
15161
|
-
logger$
|
|
15244
|
+
logger$20.debug("[LocalAudioPipeline] audio context close warning:", error);
|
|
15162
15245
|
});
|
|
15163
15246
|
super.destroy();
|
|
15164
15247
|
}
|
|
@@ -15185,7 +15268,7 @@ var LocalAudioPipeline = class extends Destroyable {
|
|
|
15185
15268
|
//#endregion
|
|
15186
15269
|
//#region src/controllers/LocalStreamController.ts
|
|
15187
15270
|
var import_cjs$17 = require_cjs();
|
|
15188
|
-
const logger$
|
|
15271
|
+
const logger$19 = getLogger();
|
|
15189
15272
|
var LocalStreamController = class extends Destroyable {
|
|
15190
15273
|
constructor(options) {
|
|
15191
15274
|
super();
|
|
@@ -15223,26 +15306,26 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15223
15306
|
* Build the local media stream based on the provided options.
|
|
15224
15307
|
*/
|
|
15225
15308
|
async buildLocalStream() {
|
|
15226
|
-
logger$
|
|
15309
|
+
logger$19.debug("[LocalStreamController] Building local media stream.");
|
|
15227
15310
|
let stream;
|
|
15228
15311
|
if (this.options.inputAudioStream ?? this.options.inputVideoStream) {
|
|
15229
15312
|
const tracks = [...this.options.inputAudioStream?.getTracks() ?? [], ...this.options.inputVideoStream?.getTracks() ?? []];
|
|
15230
15313
|
stream = new MediaStream(tracks);
|
|
15231
15314
|
} else if (this.options.propose === "screenshare") {
|
|
15232
|
-
logger$
|
|
15315
|
+
logger$19.debug("[LocalStreamController] Requesting display media for screen sharing with audio:", Boolean(this.options.inputAudioDeviceConstraints));
|
|
15233
15316
|
stream = await this.options.getDisplayMedia({
|
|
15234
15317
|
video: true,
|
|
15235
15318
|
audio: Boolean(this.options.inputAudioDeviceConstraints)
|
|
15236
15319
|
});
|
|
15237
|
-
logger$
|
|
15320
|
+
logger$19.debug("[LocalStreamController] Screen share media obtained:", stream);
|
|
15238
15321
|
} else {
|
|
15239
15322
|
const constraints = {
|
|
15240
15323
|
audio: this.options.inputAudioDeviceConstraints,
|
|
15241
15324
|
video: this.options.inputVideoDeviceConstraints
|
|
15242
15325
|
};
|
|
15243
|
-
logger$
|
|
15326
|
+
logger$19.debug("[LocalStreamController] Requesting user media with constraints:", constraints);
|
|
15244
15327
|
stream = await this.options.getUserMedia(constraints);
|
|
15245
|
-
logger$
|
|
15328
|
+
logger$19.debug("[LocalStreamController] User media obtained:", stream);
|
|
15246
15329
|
}
|
|
15247
15330
|
this._localStream$.next(stream);
|
|
15248
15331
|
this._localAudioTracks$.next(stream.getAudioTracks());
|
|
@@ -15261,7 +15344,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15261
15344
|
this._localStream$.next(localStream);
|
|
15262
15345
|
if (track.kind === "video") this._localVideoTracks$.next(localStream.getVideoTracks());
|
|
15263
15346
|
else this._localAudioTracks$.next(localStream.getAudioTracks());
|
|
15264
|
-
logger$
|
|
15347
|
+
logger$19.debug(`[LocalStreamController] ${track.kind} track added:`, track.id);
|
|
15265
15348
|
return localStream;
|
|
15266
15349
|
}
|
|
15267
15350
|
/**
|
|
@@ -15273,7 +15356,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15273
15356
|
const stream = this._localStream$.value;
|
|
15274
15357
|
const track = stream?.getTracks().find((t) => t.id === trackId);
|
|
15275
15358
|
if (!track) {
|
|
15276
|
-
logger$
|
|
15359
|
+
logger$19.debug(`[LocalStreamController] track not found: ${trackId}`);
|
|
15277
15360
|
return;
|
|
15278
15361
|
}
|
|
15279
15362
|
track.removeEventListener("ended", this.mediaTrackEndedHandler);
|
|
@@ -15282,7 +15365,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15282
15365
|
this._localStream$.next(stream);
|
|
15283
15366
|
if (track.kind === "video") this._localVideoTracks$.next(stream?.getVideoTracks() ?? []);
|
|
15284
15367
|
else this._localAudioTracks$.next(stream?.getAudioTracks() ?? []);
|
|
15285
|
-
logger$
|
|
15368
|
+
logger$19.debug(`[LocalStreamController] ${track.kind} track removed:`, trackId);
|
|
15286
15369
|
return track;
|
|
15287
15370
|
}
|
|
15288
15371
|
/**
|
|
@@ -15317,7 +15400,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15317
15400
|
*/
|
|
15318
15401
|
stopAllTracks() {
|
|
15319
15402
|
this._localStream$.value?.getTracks().forEach((track) => {
|
|
15320
|
-
logger$
|
|
15403
|
+
logger$19.debug(`[LocalStreamController] Stopping local track: ${track.kind}`);
|
|
15321
15404
|
track.removeEventListener("ended", this.mediaTrackEndedHandler);
|
|
15322
15405
|
track.stop();
|
|
15323
15406
|
});
|
|
@@ -15333,7 +15416,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15333
15416
|
|
|
15334
15417
|
//#endregion
|
|
15335
15418
|
//#region src/controllers/TransceiverController.ts
|
|
15336
|
-
const logger$
|
|
15419
|
+
const logger$18 = getLogger();
|
|
15337
15420
|
const getDirection = (send, recv) => {
|
|
15338
15421
|
if (send && recv) return "sendrecv";
|
|
15339
15422
|
else if (send && !recv) return "sendonly";
|
|
@@ -15441,7 +15524,7 @@ var TransceiverController = class extends Destroyable {
|
|
|
15441
15524
|
sendEncodings: isAudio ? void 0 : this.sendEncodings,
|
|
15442
15525
|
streams: direction === "recvonly" ? void 0 : [localStream]
|
|
15443
15526
|
};
|
|
15444
|
-
logger$
|
|
15527
|
+
logger$18.debug(`[TransceiverController] Setting up transceiver sender for local ${track.kind} track:`, {
|
|
15445
15528
|
transceiver,
|
|
15446
15529
|
transceiverParams
|
|
15447
15530
|
});
|
|
@@ -15449,11 +15532,11 @@ var TransceiverController = class extends Destroyable {
|
|
|
15449
15532
|
await transceiver.sender.replaceTrack(track);
|
|
15450
15533
|
transceiver.direction = transceiverParams.direction;
|
|
15451
15534
|
if (transceiverParams.streams?.some((stream) => Boolean(stream))) {
|
|
15452
|
-
logger$
|
|
15535
|
+
logger$18.debug(`[TransceiverController] Setting streams for transceiver sender for local ${track.kind} track:`, transceiverParams.streams);
|
|
15453
15536
|
transceiver.sender.setStreams(...transceiverParams.streams);
|
|
15454
15537
|
}
|
|
15455
15538
|
} else {
|
|
15456
|
-
logger$
|
|
15539
|
+
logger$18.debug(`[TransceiverController] Adding new transceiver for local ${track.kind} track:`, track.id);
|
|
15457
15540
|
this.peerConnection.addTransceiver(track, transceiverParams);
|
|
15458
15541
|
}
|
|
15459
15542
|
}
|
|
@@ -15467,13 +15550,13 @@ var TransceiverController = class extends Destroyable {
|
|
|
15467
15550
|
if (options.updateTransceiverDirection) transceiver.direction = "inactive";
|
|
15468
15551
|
}
|
|
15469
15552
|
} catch (error) {
|
|
15470
|
-
logger$
|
|
15553
|
+
logger$18.error("[TransceiverController] stopTrackSender error", kind, error);
|
|
15471
15554
|
this.options.onError?.(new MediaTrackError("stopTrackSender", kind, error));
|
|
15472
15555
|
}
|
|
15473
15556
|
}
|
|
15474
15557
|
async restoreTrackSender(kind) {
|
|
15475
15558
|
try {
|
|
15476
|
-
logger$
|
|
15559
|
+
logger$18.debug("[TransceiverController] restoreTrackSender called", kind);
|
|
15477
15560
|
const constraints = {};
|
|
15478
15561
|
const transceivers = this.transceiverByKind(kind);
|
|
15479
15562
|
for (const transceiver of transceivers) {
|
|
@@ -15483,23 +15566,23 @@ var TransceiverController = class extends Destroyable {
|
|
|
15483
15566
|
if (trackKind === "audio" || trackKind === "video") constraints[trackKind] = this.getConstraintsFor(trackKind);
|
|
15484
15567
|
}
|
|
15485
15568
|
}
|
|
15486
|
-
logger$
|
|
15569
|
+
logger$18.debug("[TransceiverController] restoreTrackSender constraints:", constraints);
|
|
15487
15570
|
if (Object.keys(constraints).length === 0) {
|
|
15488
|
-
logger$
|
|
15571
|
+
logger$18.warn("[TransceiverController] restoreTrackSender: no tracks need restoration", kind);
|
|
15489
15572
|
return;
|
|
15490
15573
|
}
|
|
15491
15574
|
const newTracks = (await this.options.getUserMedia(constraints)).getTracks();
|
|
15492
|
-
logger$
|
|
15575
|
+
logger$18.debug("[TransceiverController] restoreTrackSender new tracks:", newTracks);
|
|
15493
15576
|
for (const newTrack of newTracks) {
|
|
15494
15577
|
this.options.localStreamController.addTrack(newTrack);
|
|
15495
15578
|
const trackKind = newTrack.kind;
|
|
15496
15579
|
const transceiverOfKind = this.transceiverByKind(trackKind)[0];
|
|
15497
15580
|
transceiverOfKind.direction = trackKind === "audio" ? this.audioDirection : this.videoDirection;
|
|
15498
|
-
logger$
|
|
15581
|
+
logger$18.debug("[TransceiverController] restoreTrackSender setting direction for", trackKind, transceiverOfKind.direction);
|
|
15499
15582
|
await transceiverOfKind.sender.replaceTrack(newTrack);
|
|
15500
15583
|
}
|
|
15501
15584
|
} catch (error) {
|
|
15502
|
-
logger$
|
|
15585
|
+
logger$18.error("[TransceiverController] restoreTrackSender error", kind, error);
|
|
15503
15586
|
this.options.onError?.(new MediaTrackError("restoreTrackSender", kind, error));
|
|
15504
15587
|
}
|
|
15505
15588
|
}
|
|
@@ -15540,14 +15623,14 @@ var TransceiverController = class extends Destroyable {
|
|
|
15540
15623
|
};
|
|
15541
15624
|
try {
|
|
15542
15625
|
await track.applyConstraints(constraintsToApply);
|
|
15543
|
-
logger$
|
|
15544
|
-
logger$
|
|
15626
|
+
logger$18.debug(`[TransceiverController] Updated ${kind} sender constraints:`, constraintsToApply);
|
|
15627
|
+
logger$18.debug(`[TransceiverController] Updated ${kind} sender constraints:`, track.getConstraints());
|
|
15545
15628
|
} catch (error) {
|
|
15546
|
-
logger$
|
|
15629
|
+
logger$18.warn(`[TransceiverController] applyConstraints failed for ${kind} track ${track.id}, attempting track replacement fallback:`, error);
|
|
15547
15630
|
try {
|
|
15548
15631
|
await this.replaceTrackFallback(sender, track, kind, constraintsToApply);
|
|
15549
15632
|
} catch (fallbackError) {
|
|
15550
|
-
logger$
|
|
15633
|
+
logger$18.warn(`[TransceiverController] Track replacement fallback also failed for ${kind} track:`, fallbackError);
|
|
15551
15634
|
this.options.onError?.(new MediaTrackError("updateSendersConstraints", kind, fallbackError));
|
|
15552
15635
|
}
|
|
15553
15636
|
}
|
|
@@ -15575,7 +15658,7 @@ var TransceiverController = class extends Destroyable {
|
|
|
15575
15658
|
if (!newTrack) throw new MediaTrackError("replaceTrackFallback", kind, /* @__PURE__ */ new Error("getUserMedia returned no track of the requested kind"));
|
|
15576
15659
|
await sender.replaceTrack(newTrack);
|
|
15577
15660
|
this.options.localStreamController.addTrack(newTrack);
|
|
15578
|
-
logger$
|
|
15661
|
+
logger$18.debug(`[TransceiverController] Track replacement fallback succeeded for ${kind}. New track: ${newTrack.id}`);
|
|
15579
15662
|
}
|
|
15580
15663
|
getMediaDirections() {
|
|
15581
15664
|
if (this.peerConnection.connectionState === "connected") return this.peerConnection.getTransceivers().reduce((acc, transceiver) => {
|
|
@@ -15606,7 +15689,7 @@ var TransceiverController = class extends Destroyable {
|
|
|
15606
15689
|
//#endregion
|
|
15607
15690
|
//#region src/controllers/RTCPeerConnectionController.ts
|
|
15608
15691
|
var import_cjs$16 = require_cjs();
|
|
15609
|
-
const logger$
|
|
15692
|
+
const logger$17 = getLogger();
|
|
15610
15693
|
var RTCPeerConnectionController = class extends Destroyable {
|
|
15611
15694
|
constructor(options = {}, remoteSessionDescription, deviceController) {
|
|
15612
15695
|
super();
|
|
@@ -15622,43 +15705,43 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15622
15705
|
this.oniceconnectionstatechangeHandler = () => {
|
|
15623
15706
|
if (this.peerConnection) {
|
|
15624
15707
|
const { iceConnectionState } = this.peerConnection;
|
|
15625
|
-
logger$
|
|
15708
|
+
logger$17.debug(`[RTCPeerConnectionController] ICE connection state changed to: ${iceConnectionState}`);
|
|
15626
15709
|
this._iceConnectionState$.next(this.peerConnection.iceConnectionState);
|
|
15627
15710
|
}
|
|
15628
15711
|
};
|
|
15629
15712
|
this.onconnectionstatechangeHandler = () => {
|
|
15630
15713
|
if (this.peerConnection) {
|
|
15631
15714
|
const { connectionState } = this.peerConnection;
|
|
15632
|
-
logger$
|
|
15715
|
+
logger$17.debug(`[RTCPeerConnectionController] Connection state changed to: ${connectionState}`);
|
|
15633
15716
|
if (connectionState === "connected") this.removeConnectionTimer();
|
|
15634
15717
|
this._connectionState$.next(this.peerConnection.connectionState);
|
|
15635
15718
|
}
|
|
15636
15719
|
};
|
|
15637
15720
|
this.onsignalingstatechangeHandler = () => {
|
|
15638
|
-
logger$
|
|
15721
|
+
logger$17.debug(`[RTCPeerConnectionController] Signaling state changed to: ${this.peerConnection?.signalingState}`);
|
|
15639
15722
|
};
|
|
15640
15723
|
this.onicegatheringstatechangeHandler = () => {
|
|
15641
15724
|
if (this.peerConnection) this._iceGatheringState$.next(this.peerConnection.iceGatheringState);
|
|
15642
15725
|
};
|
|
15643
15726
|
this.onnegotiationneededHandler = (event) => {
|
|
15644
|
-
logger$
|
|
15727
|
+
logger$17.debug("[RTCPeerConnectionController] Negotiation needed event received.", event);
|
|
15645
15728
|
this.negotiationNeeded$.next();
|
|
15646
15729
|
};
|
|
15647
15730
|
this.updateSelectedInputDevice = async (kind, deviceInfo) => {
|
|
15648
15731
|
try {
|
|
15649
15732
|
const { localStream } = this;
|
|
15650
15733
|
if (!localStream) {
|
|
15651
|
-
logger$
|
|
15734
|
+
logger$17.warn("[RTCPeerConnectionController] No local stream available to update input device.");
|
|
15652
15735
|
return;
|
|
15653
15736
|
}
|
|
15654
|
-
logger$
|
|
15737
|
+
logger$17.debug(`[RTCPeerConnectionController] Updating selected ${kind} input device:`, localStream.getTracks());
|
|
15655
15738
|
const track = localStream.getTracks().find((track$1) => track$1.kind === kind);
|
|
15656
15739
|
if (track) {
|
|
15657
15740
|
this.transceiverController?.stopTrackSender(kind);
|
|
15658
15741
|
this.localStreamController.removeTrack(track.id);
|
|
15659
|
-
logger$
|
|
15742
|
+
logger$17.debug(`[RTCPeerConnectionController] Stopped existing ${kind} track: ${track.id}`, localStream.getTracks());
|
|
15660
15743
|
if (!deviceInfo) {
|
|
15661
|
-
logger$
|
|
15744
|
+
logger$17.debug(`[RTCPeerConnectionController] ${kind} input device selected: none`);
|
|
15662
15745
|
return;
|
|
15663
15746
|
}
|
|
15664
15747
|
const streamTrack = (await this.getUserMedia({ [kind]: {
|
|
@@ -15666,16 +15749,16 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15666
15749
|
...this.deviceController.deviceInfoToConstraints(deviceInfo)
|
|
15667
15750
|
} })).getTracks().find((t) => t.kind === kind);
|
|
15668
15751
|
if (streamTrack) {
|
|
15669
|
-
logger$
|
|
15752
|
+
logger$17.debug(`[RTCPeerConnectionController] Adding new ${kind} track: ${streamTrack.id}`);
|
|
15670
15753
|
this.localStreamController.addTrack(streamTrack);
|
|
15671
15754
|
await this.transceiverController?.replaceSenderTrack(kind, streamTrack);
|
|
15672
|
-
logger$
|
|
15755
|
+
logger$17.debug(`[RTCPeerConnectionController] Added new ${kind} track: ${streamTrack.id}`, this.localStream?.getTracks());
|
|
15673
15756
|
}
|
|
15674
15757
|
}
|
|
15675
|
-
logger$
|
|
15758
|
+
logger$17.debug(`[RTCPeerConnectionController] ${kind} input device selected:`, deviceInfo?.label);
|
|
15676
15759
|
} catch (error) {
|
|
15677
|
-
logger$
|
|
15678
|
-
this._errors$.next(
|
|
15760
|
+
logger$17.error(`[RTCPeerConnectionController] Failed to select ${kind} input device:`, error);
|
|
15761
|
+
this._errors$.next(new MediaTrackError("updateSelectedInputDevice", kind, error));
|
|
15679
15762
|
throw error;
|
|
15680
15763
|
}
|
|
15681
15764
|
};
|
|
@@ -15897,7 +15980,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15897
15980
|
case "main":
|
|
15898
15981
|
default: return {
|
|
15899
15982
|
...options,
|
|
15900
|
-
offerToReceiveAudio: true,
|
|
15983
|
+
offerToReceiveAudio: this.options.receiveAudio ?? true,
|
|
15901
15984
|
offerToReceiveVideo: this.options.receiveVideo ?? Boolean(this.inputVideoDeviceConstraints)
|
|
15902
15985
|
};
|
|
15903
15986
|
}
|
|
@@ -15924,15 +16007,15 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15924
16007
|
this.setupPeerConnection();
|
|
15925
16008
|
this.subscribeTo(this.negotiationNeeded$.pipe((0, import_cjs$16.auditTime)(0), (0, import_cjs$16.exhaustMap)(async () => this.startNegotiation())), {
|
|
15926
16009
|
next: () => {
|
|
15927
|
-
logger$
|
|
16010
|
+
logger$17.debug("[RTCPeerConnectionController] Start Negotiation completed successfully");
|
|
15928
16011
|
},
|
|
15929
16012
|
error: (error) => {
|
|
15930
|
-
logger$
|
|
16013
|
+
logger$17.error("[RTCPeerConnectionController] Start Negotiation error:", error);
|
|
15931
16014
|
this._errors$.next(toError(error));
|
|
15932
16015
|
}
|
|
15933
16016
|
});
|
|
15934
16017
|
this.subscribeTo((0, import_cjs$16.merge)(this.deviceController.selectedAudioInputDevice$.pipe((0, import_cjs$16.map)((deviceInfo) => ["audio", deviceInfo])), this.deviceController.selectedVideoInputDevice$.pipe((0, import_cjs$16.map)((deviceInfo) => ["video", deviceInfo]))).pipe((0, import_cjs$16.skipWhile)(() => !this.localStreamController.localStream)), async ([kind, deviceInfo]) => {
|
|
15935
|
-
logger$
|
|
16018
|
+
logger$17.debug(`[RTCPeerConnectionController] Selected input device changed for:`, {
|
|
15936
16019
|
kind,
|
|
15937
16020
|
deviceInfo
|
|
15938
16021
|
});
|
|
@@ -15949,7 +16032,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15949
16032
|
this._initialized$.next(true);
|
|
15950
16033
|
}
|
|
15951
16034
|
} catch (error) {
|
|
15952
|
-
logger$
|
|
16035
|
+
logger$17.error("[RTCPeerConnectionController] Initialization error:", error);
|
|
15953
16036
|
this._errors$.next(toError(error));
|
|
15954
16037
|
this.destroy();
|
|
15955
16038
|
}
|
|
@@ -15981,22 +16064,22 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15981
16064
|
}
|
|
15982
16065
|
async startNegotiation() {
|
|
15983
16066
|
if (this.isNegotiating) {
|
|
15984
|
-
logger$
|
|
16067
|
+
logger$17.debug("[RTCPeerConnectionController] Negotiation already in progress, skipping.");
|
|
15985
16068
|
return;
|
|
15986
16069
|
}
|
|
15987
16070
|
this.setupEventListeners();
|
|
15988
16071
|
if (this.type === "answer") {
|
|
15989
|
-
logger$
|
|
16072
|
+
logger$17.debug("[RTCPeerConnectionController] This is an answer type still, skipping offer creation.");
|
|
15990
16073
|
return;
|
|
15991
16074
|
}
|
|
15992
16075
|
this._isNegotiating$.next(true);
|
|
15993
|
-
logger$
|
|
16076
|
+
logger$17.debug("[RTCPeerConnectionController] Starting negotiation.");
|
|
15994
16077
|
try {
|
|
15995
16078
|
const { offerOptions } = this;
|
|
15996
|
-
logger$
|
|
16079
|
+
logger$17.debug("[RTCPeerConnectionController] Creating offer with options:", offerOptions);
|
|
15997
16080
|
await this.createOffer(offerOptions);
|
|
15998
16081
|
} catch (error) {
|
|
15999
|
-
logger$
|
|
16082
|
+
logger$17.error("[RTCPeerConnectionController] Error during negotiation:", error);
|
|
16000
16083
|
this._errors$.next(toError(error));
|
|
16001
16084
|
}
|
|
16002
16085
|
}
|
|
@@ -16012,14 +16095,14 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16012
16095
|
let readyToConnect = status !== "failed";
|
|
16013
16096
|
try {
|
|
16014
16097
|
if (status === "received" && sdp) {
|
|
16015
|
-
logger$
|
|
16098
|
+
logger$17.debug("[RTCPeerConnectionController] Received answer SDP:", sdp);
|
|
16016
16099
|
await this._setRemoteDescription({
|
|
16017
16100
|
type: "answer",
|
|
16018
16101
|
sdp
|
|
16019
16102
|
});
|
|
16020
16103
|
}
|
|
16021
16104
|
} catch (error) {
|
|
16022
|
-
logger$
|
|
16105
|
+
logger$17.error("[RTCPeerConnectionController] Error updating answer status:", error);
|
|
16023
16106
|
this._errors$.next(toError(error));
|
|
16024
16107
|
readyToConnect = false;
|
|
16025
16108
|
} finally {
|
|
@@ -16038,7 +16121,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16038
16121
|
await this.handleOfferReceived();
|
|
16039
16122
|
break;
|
|
16040
16123
|
case "failed":
|
|
16041
|
-
logger$
|
|
16124
|
+
logger$17.error("[RTCPeerConnectionController] Offer failed to be processed by remote.");
|
|
16042
16125
|
break;
|
|
16043
16126
|
case "sent":
|
|
16044
16127
|
default:
|
|
@@ -16051,13 +16134,14 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16051
16134
|
*/
|
|
16052
16135
|
async acceptInbound(mediaOverrides) {
|
|
16053
16136
|
if (mediaOverrides) {
|
|
16054
|
-
const { audio, video, receiveAudio, receiveVideo } = mediaOverrides;
|
|
16137
|
+
const { audio, video, receiveAudio, receiveVideo, fallbackToReceiveOnly } = mediaOverrides;
|
|
16055
16138
|
this.options = {
|
|
16056
16139
|
...this.options,
|
|
16057
16140
|
...audio !== void 0 ? { audio } : {},
|
|
16058
16141
|
...video !== void 0 ? { video } : {},
|
|
16059
16142
|
...receiveAudio !== void 0 ? { receiveAudio } : {},
|
|
16060
|
-
...receiveVideo !== void 0 ? { receiveVideo } : {}
|
|
16143
|
+
...receiveVideo !== void 0 ? { receiveVideo } : {},
|
|
16144
|
+
...fallbackToReceiveOnly !== void 0 ? { fallbackToReceiveOnly } : {}
|
|
16061
16145
|
};
|
|
16062
16146
|
this.transceiverController?.updateOptions({
|
|
16063
16147
|
receiveAudio: this.receiveAudio,
|
|
@@ -16070,7 +16154,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16070
16154
|
}
|
|
16071
16155
|
await this.setupLocalTracks();
|
|
16072
16156
|
const { answerOptions } = this;
|
|
16073
|
-
logger$
|
|
16157
|
+
logger$17.debug("[RTCPeerConnectionController] Creating inbound answer with options:", answerOptions);
|
|
16074
16158
|
await this.createAnswer(answerOptions);
|
|
16075
16159
|
}
|
|
16076
16160
|
async handleOfferReceived() {
|
|
@@ -16078,7 +16162,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16078
16162
|
this._isNegotiating$.next(true);
|
|
16079
16163
|
await this._setRemoteDescription(this.sdpInit);
|
|
16080
16164
|
const { answerOptions } = this;
|
|
16081
|
-
logger$
|
|
16165
|
+
logger$17.debug("[RTCPeerConnectionController] Creating answer with options:", answerOptions);
|
|
16082
16166
|
await this.createAnswer(answerOptions);
|
|
16083
16167
|
}
|
|
16084
16168
|
readyToConnect() {
|
|
@@ -16086,7 +16170,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16086
16170
|
this.connectionTimer = setTimeout(() => {
|
|
16087
16171
|
this.removeConnectionTimer();
|
|
16088
16172
|
if (this.peerConnection?.connectionState !== "connected") {
|
|
16089
|
-
logger$
|
|
16173
|
+
logger$17.debug("[RTCPeerConnectionController] Connection timeout, restarting ICE gathering with relay only.");
|
|
16090
16174
|
this.iceGatheringController.restartICEGatheringWithRelayOnly();
|
|
16091
16175
|
}
|
|
16092
16176
|
}, this.connectionTimeout);
|
|
@@ -16108,14 +16192,14 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16108
16192
|
const stereo = this.options.stereo ?? PreferencesContainer.instance.stereoAudio;
|
|
16109
16193
|
if (preferredAudioCodecs.length > 0 || preferredVideoCodecs.length > 0) {
|
|
16110
16194
|
result = setCodecPreferences(result, preferredAudioCodecs, preferredVideoCodecs);
|
|
16111
|
-
logger$
|
|
16195
|
+
logger$17.debug("[RTCPeerConnectionController] Applied codec preferences to SDP", {
|
|
16112
16196
|
preferredAudioCodecs,
|
|
16113
16197
|
preferredVideoCodecs
|
|
16114
16198
|
});
|
|
16115
16199
|
}
|
|
16116
16200
|
if (stereo) {
|
|
16117
16201
|
result = enableStereoOpus(result);
|
|
16118
|
-
logger$
|
|
16202
|
+
logger$17.debug("[RTCPeerConnectionController] Applied stereo Opus to SDP");
|
|
16119
16203
|
}
|
|
16120
16204
|
return Promise.resolve(result);
|
|
16121
16205
|
}
|
|
@@ -16171,25 +16255,25 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16171
16255
|
...this.peerConnection.getConfiguration(),
|
|
16172
16256
|
iceTransportPolicy: "relay"
|
|
16173
16257
|
});
|
|
16174
|
-
logger$
|
|
16258
|
+
logger$17.debug("[RTCPeerConnectionController] ICE transport policy set to relay-only");
|
|
16175
16259
|
} catch (error) {
|
|
16176
|
-
logger$
|
|
16260
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to set relay-only policy:", error);
|
|
16177
16261
|
}
|
|
16178
16262
|
this.setupEventListeners();
|
|
16179
16263
|
this._isNegotiating$.next(true);
|
|
16180
|
-
logger$
|
|
16264
|
+
logger$17.debug(`[RTCPeerConnectionController] Triggering ICE restart${relayOnly ? " (relay-only)" : ""}.`);
|
|
16181
16265
|
try {
|
|
16182
16266
|
const offer = await this.peerConnection.createOffer({ iceRestart: true });
|
|
16183
16267
|
await this.setLocalDescription(offer);
|
|
16184
16268
|
} catch (error) {
|
|
16185
|
-
logger$
|
|
16269
|
+
logger$17.error("[RTCPeerConnectionController] ICE restart offer failed:", error);
|
|
16186
16270
|
this._errors$.next(toError(error));
|
|
16187
16271
|
this.negotiationEnded();
|
|
16188
16272
|
if (policyChanged) this.restoreIceTransportPolicy();
|
|
16189
16273
|
throw error;
|
|
16190
16274
|
}
|
|
16191
16275
|
if (policyChanged) (0, import_cjs$16.firstValueFrom)((0, import_cjs$16.race)(this._iceGatheringState$.pipe((0, import_cjs$16.filter)((state) => state === "complete"), (0, import_cjs$16.take)(1)), (0, import_cjs$16.timer)(ICE_GATHERING_COMPLETE_TIMEOUT_MS).pipe((0, import_cjs$16.map)(() => "timeout")))).then(() => this.restoreIceTransportPolicy()).catch((error) => {
|
|
16192
|
-
logger$
|
|
16276
|
+
logger$17.warn("[RTCPeerConnectionController] Error waiting for ICE gathering to complete:", error);
|
|
16193
16277
|
this.restoreIceTransportPolicy();
|
|
16194
16278
|
});
|
|
16195
16279
|
}
|
|
@@ -16199,9 +16283,9 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16199
16283
|
...this.peerConnection.getConfiguration(),
|
|
16200
16284
|
iceTransportPolicy: this.options.relayOnly ? "relay" : "all"
|
|
16201
16285
|
});
|
|
16202
|
-
logger$
|
|
16286
|
+
logger$17.debug("[RTCPeerConnectionController] ICE transport policy restored");
|
|
16203
16287
|
} catch (error) {
|
|
16204
|
-
logger$
|
|
16288
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to restore ICE transport policy:", error);
|
|
16205
16289
|
}
|
|
16206
16290
|
}
|
|
16207
16291
|
/**
|
|
@@ -16213,13 +16297,25 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16213
16297
|
await this.setupRemoteTracks();
|
|
16214
16298
|
}
|
|
16215
16299
|
async setupLocalTracks() {
|
|
16216
|
-
logger$
|
|
16217
|
-
|
|
16300
|
+
logger$17.debug("[RTCPeerConnectionController] Setting up local tracks/transceivers.");
|
|
16301
|
+
if (this.hasNoLocalMediaToSend()) {
|
|
16302
|
+
if (!this.receiveAudio && !this.receiveVideo) throw new InvalidParams("Call requests no media: enable audio/video or receiveAudio/receiveVideo");
|
|
16303
|
+
logger$17.debug("[RTCPeerConnectionController] No local media requested; negotiating receive-only.");
|
|
16304
|
+
this.setupReceiveOnlyTransceivers();
|
|
16305
|
+
return;
|
|
16306
|
+
}
|
|
16307
|
+
let localStream;
|
|
16308
|
+
try {
|
|
16309
|
+
localStream = this.localStream ?? await this.localStreamController.buildLocalStream();
|
|
16310
|
+
} catch (error) {
|
|
16311
|
+
this.handleLocalMediaFailure(error);
|
|
16312
|
+
return;
|
|
16313
|
+
}
|
|
16218
16314
|
if (this.transceiverController?.useAddStream ?? false) {
|
|
16219
|
-
logger$
|
|
16315
|
+
logger$17.warn("[RTCPeerConnectionController] Using deprecated addStream API to add local stream.");
|
|
16220
16316
|
this.peerConnection?.addStream(localStream);
|
|
16221
16317
|
if (!this.isNegotiating) {
|
|
16222
|
-
logger$
|
|
16318
|
+
logger$17.debug("[RTCPeerConnectionController] Forcing negotiationneeded after local tracks setup.");
|
|
16223
16319
|
this.negotiationNeeded$.next();
|
|
16224
16320
|
}
|
|
16225
16321
|
return;
|
|
@@ -16235,12 +16331,54 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16235
16331
|
const transceivers = (kind === "audio" ? this.transceiverController?.audioTransceivers : this.transceiverController?.videoTransceivers) ?? [];
|
|
16236
16332
|
await this.transceiverController?.setupTransceiverSender(track, localStream, transceivers[index]);
|
|
16237
16333
|
} else {
|
|
16238
|
-
logger$
|
|
16334
|
+
logger$17.debug(`[RTCPeerConnectionController] Using addTrack for local ${kind} track:`, track.id);
|
|
16239
16335
|
this.peerConnection?.addTrack(track, localStream);
|
|
16240
16336
|
}
|
|
16241
16337
|
}
|
|
16242
16338
|
}
|
|
16243
16339
|
}
|
|
16340
|
+
/** True for a main connection with no local media to send. */
|
|
16341
|
+
hasNoLocalMediaToSend() {
|
|
16342
|
+
const hasInputStreams = Boolean(this.options.inputAudioStream ?? this.options.inputVideoStream);
|
|
16343
|
+
return this.propose === "main" && !this.localStream && !hasInputStreams && !this.inputAudioDeviceConstraints && !this.inputVideoDeviceConstraints;
|
|
16344
|
+
}
|
|
16345
|
+
/** The media kinds this connection wants to send: 'audiovideo' | 'video' | 'audio'. */
|
|
16346
|
+
get requestedMediaKinds() {
|
|
16347
|
+
const wantsAudio = Boolean(this.inputAudioDeviceConstraints);
|
|
16348
|
+
const wantsVideo = Boolean(this.inputVideoDeviceConstraints);
|
|
16349
|
+
if (wantsAudio && wantsVideo) return "audiovideo";
|
|
16350
|
+
return wantsVideo ? "video" : "audio";
|
|
16351
|
+
}
|
|
16352
|
+
/**
|
|
16353
|
+
* Handle a local media acquisition failure with a typed, semantically
|
|
16354
|
+
* accurate MediaAccessError created at the acquisition site:
|
|
16355
|
+
* - Auxiliary connections (screenshare / additional-device) throw a
|
|
16356
|
+
* non-fatal error — VertoManager surfaces it and the call is unaffected.
|
|
16357
|
+
* - The main connection degrades to receive-only when allowed (default),
|
|
16358
|
+
* otherwise fails with a fatal error.
|
|
16359
|
+
*/
|
|
16360
|
+
handleLocalMediaFailure(error) {
|
|
16361
|
+
if (this.propose === "screenshare") throw new MediaAccessError("startScreenShare", "screen", error, false);
|
|
16362
|
+
if (this.propose === "additional-device") throw new MediaAccessError("addInputDevice", this.requestedMediaKinds, error, false);
|
|
16363
|
+
const canReceive = this.receiveAudio || this.receiveVideo;
|
|
16364
|
+
if (!((this.options.fallbackToReceiveOnly ?? true) && canReceive)) throw new MediaAccessError("acquireLocalMedia", this.requestedMediaKinds, error, true);
|
|
16365
|
+
logger$17.warn("[RTCPeerConnectionController] Local media unavailable; continuing receive-only:", error);
|
|
16366
|
+
this._errors$.next(new MediaAccessError("acquireLocalMedia", this.requestedMediaKinds, error, false));
|
|
16367
|
+
this.setupReceiveOnlyTransceivers();
|
|
16368
|
+
}
|
|
16369
|
+
/**
|
|
16370
|
+
* Negotiate receive-only m-lines when there are no local tracks to send.
|
|
16371
|
+
* Only offer-type connections add transceivers — answer-type connections
|
|
16372
|
+
* reuse the transceivers created from the remote offer.
|
|
16373
|
+
*/
|
|
16374
|
+
setupReceiveOnlyTransceivers() {
|
|
16375
|
+
if (this.type !== "offer") return;
|
|
16376
|
+
if (this.transceiverController?.useAddTransceivers ?? false) {
|
|
16377
|
+
this.peerConnection?.addTransceiver("audio", { direction: this.receiveAudio ? "recvonly" : "inactive" });
|
|
16378
|
+
this.peerConnection?.addTransceiver("video", { direction: this.receiveVideo ? "recvonly" : "inactive" });
|
|
16379
|
+
}
|
|
16380
|
+
if (!this.isNegotiating) this.negotiationNeeded$.next();
|
|
16381
|
+
}
|
|
16244
16382
|
async getUserMedia(constraints) {
|
|
16245
16383
|
return (this.options.webRTCApiProvider?.mediaDevices ?? navigator.mediaDevices).getUserMedia(constraints);
|
|
16246
16384
|
}
|
|
@@ -16252,7 +16390,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16252
16390
|
async setupRemoteTracks() {
|
|
16253
16391
|
if (!this.peerConnection) throw new DependencyError("RTCPeerConnection is not initialized");
|
|
16254
16392
|
this.peerConnection.ontrack = (event) => {
|
|
16255
|
-
logger$
|
|
16393
|
+
logger$17.debug("[RTCPeerConnectionController] Remote track received:", event.track.kind);
|
|
16256
16394
|
if (event.streams[0]) this._remoteStream$.next(event.streams[0]);
|
|
16257
16395
|
else {
|
|
16258
16396
|
const existingTracks = this._remoteStream$.value?.getTracks() ?? [];
|
|
@@ -16276,8 +16414,8 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16276
16414
|
try {
|
|
16277
16415
|
stream = await this.getUserMedia({ audio: constraints });
|
|
16278
16416
|
} catch (error) {
|
|
16279
|
-
logger$
|
|
16280
|
-
this._errors$.next(
|
|
16417
|
+
logger$17.error("[RTCPeerConnectionController] Failed to re-acquire mic for pipeline restore:", error);
|
|
16418
|
+
this._errors$.next(new MediaTrackError("restoreAudioPipelineInput", "audio", error));
|
|
16281
16419
|
return;
|
|
16282
16420
|
}
|
|
16283
16421
|
const newTrack = stream.getAudioTracks().at(0);
|
|
@@ -16298,7 +16436,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16298
16436
|
try {
|
|
16299
16437
|
this._localAudioPipeline = new LocalAudioPipeline();
|
|
16300
16438
|
} catch (error) {
|
|
16301
|
-
logger$
|
|
16439
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to create LocalAudioPipeline:", error);
|
|
16302
16440
|
return null;
|
|
16303
16441
|
}
|
|
16304
16442
|
this.subscribeTo(this.localStreamController.localAudioTracks$, () => {
|
|
@@ -16320,7 +16458,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16320
16458
|
try {
|
|
16321
16459
|
await sender.replaceTrack(this._localAudioPipeline.outputTrack);
|
|
16322
16460
|
} catch (error) {
|
|
16323
|
-
logger$
|
|
16461
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to route audio sender through pipeline:", error);
|
|
16324
16462
|
}
|
|
16325
16463
|
}
|
|
16326
16464
|
/**
|
|
@@ -16336,10 +16474,10 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16336
16474
|
try {
|
|
16337
16475
|
const localStream = this.localStreamController.addTrack(track);
|
|
16338
16476
|
this.peerConnection.addTrack(track, localStream);
|
|
16339
|
-
logger$
|
|
16477
|
+
logger$17.debug(`[RTCPeerConnectionController] ${track.kind} track added:`, track.id);
|
|
16340
16478
|
} catch (error) {
|
|
16341
|
-
logger$
|
|
16342
|
-
this._errors$.next(
|
|
16479
|
+
logger$17.error(`[RTCPeerConnectionController] Failed to add ${track.kind} track:`, error);
|
|
16480
|
+
this._errors$.next(new MediaTrackError("addLocalTrack", track.kind, error));
|
|
16343
16481
|
throw error;
|
|
16344
16482
|
}
|
|
16345
16483
|
}
|
|
@@ -16355,16 +16493,16 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16355
16493
|
}
|
|
16356
16494
|
const sender = this.peerConnection.getSenders().find((sender$1) => sender$1.track?.id === trackId);
|
|
16357
16495
|
if (!sender) {
|
|
16358
|
-
logger$
|
|
16496
|
+
logger$17.debug(`[RTCPeerConnectionController] track not found: ${trackId}`);
|
|
16359
16497
|
return;
|
|
16360
16498
|
}
|
|
16361
16499
|
try {
|
|
16362
16500
|
this.peerConnection.removeTrack(sender);
|
|
16363
16501
|
this.localStreamController.removeTrack(trackId);
|
|
16364
|
-
logger$
|
|
16502
|
+
logger$17.debug(`[RTCPeerConnectionController] ${sender.track?.kind} track removed:`, trackId);
|
|
16365
16503
|
} catch (error) {
|
|
16366
|
-
logger$
|
|
16367
|
-
this._errors$.next(
|
|
16504
|
+
logger$17.error(`[RTCPeerConnectionController] Failed to remove ${sender.track?.kind} track:`, error);
|
|
16505
|
+
this._errors$.next(new MediaTrackError("removeLocalTrack", sender.track?.kind ?? "unknown", error));
|
|
16368
16506
|
throw error;
|
|
16369
16507
|
}
|
|
16370
16508
|
}
|
|
@@ -16390,7 +16528,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16390
16528
|
async replaceAudioTrackWithConstraints(constraints) {
|
|
16391
16529
|
const senders = this.peerConnection?.getSenders().filter((s) => s.track?.kind === "audio" && s.track.readyState === "live");
|
|
16392
16530
|
if (!senders || senders.length === 0) {
|
|
16393
|
-
logger$
|
|
16531
|
+
logger$17.warn("[RTCPeerConnectionController] No live audio sender to replace");
|
|
16394
16532
|
return;
|
|
16395
16533
|
}
|
|
16396
16534
|
for (const sender of senders) {
|
|
@@ -16408,7 +16546,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16408
16546
|
const newTrack = (await this.getUserMedia({ audio: mergedConstraints })).getAudioTracks()[0];
|
|
16409
16547
|
await sender.replaceTrack(newTrack);
|
|
16410
16548
|
this.localStreamController.addTrack(newTrack);
|
|
16411
|
-
logger$
|
|
16549
|
+
logger$17.debug(`[RTCPeerConnectionController] Audio track replaced for server-pushed params. New track: ${newTrack.id}`);
|
|
16412
16550
|
}
|
|
16413
16551
|
}
|
|
16414
16552
|
/**
|
|
@@ -16416,7 +16554,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16416
16554
|
* Completes all observables to prevent memory leaks.
|
|
16417
16555
|
*/
|
|
16418
16556
|
destroy() {
|
|
16419
|
-
logger$
|
|
16557
|
+
logger$17.debug(`[RTCPeerConnectionController] Destroying RTCPeerConnectionController. ${this.propose}`);
|
|
16420
16558
|
this.removeConnectionTimer();
|
|
16421
16559
|
this._iceGatheringController?.destroy();
|
|
16422
16560
|
this._localAudioPipeline?.destroy();
|
|
@@ -16442,7 +16580,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16442
16580
|
}
|
|
16443
16581
|
stopRemoteTracks() {
|
|
16444
16582
|
this._remoteStream$.value?.getTracks().forEach((track) => {
|
|
16445
|
-
logger$
|
|
16583
|
+
logger$17.debug(`[RTCPeerConnectionController] Stopping remote track: ${track.kind}`);
|
|
16446
16584
|
track.stop();
|
|
16447
16585
|
});
|
|
16448
16586
|
}
|
|
@@ -16459,7 +16597,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16459
16597
|
...params,
|
|
16460
16598
|
sdp: finalRemote
|
|
16461
16599
|
};
|
|
16462
|
-
logger$
|
|
16600
|
+
logger$17.debug("[RTCPeerConnectionController] Setting remote description:", answer);
|
|
16463
16601
|
return this.peerConnection.setRemoteDescription(answer);
|
|
16464
16602
|
}
|
|
16465
16603
|
};
|
|
@@ -16474,7 +16612,11 @@ function isVertoInviteMessage(value) {
|
|
|
16474
16612
|
const msg = value;
|
|
16475
16613
|
return msg.method === "verto.invite" && isObject(msg.params) && hasProperty(msg.params, "sdp") && hasProperty(msg.params, "callID");
|
|
16476
16614
|
}
|
|
16477
|
-
|
|
16615
|
+
/**
|
|
16616
|
+
* Guards inbound `verto.bye` frames (server → client). Outbound bye messages are
|
|
16617
|
+
* built locally and never type-guarded, so only the inbound shape is asserted.
|
|
16618
|
+
*/
|
|
16619
|
+
function isVertoByeInboundMessage(value) {
|
|
16478
16620
|
if (!isVertoMethodMessage(value)) return false;
|
|
16479
16621
|
return value.method === "verto.bye";
|
|
16480
16622
|
}
|
|
@@ -16494,11 +16636,19 @@ function isVertoMediaParamsInnerParams(value) {
|
|
|
16494
16636
|
function isVertoPingInnerParams(value) {
|
|
16495
16637
|
return isObject(value) && hasProperty(value, "jsonrpc") && value.jsonrpc === "2.0" && hasProperty(value, "method") && value.method === "verto.ping";
|
|
16496
16638
|
}
|
|
16639
|
+
/**
|
|
16640
|
+
* Guards the params of an inbound `verto.bye` frame (top-level `callID`). Use
|
|
16641
|
+
* this to recognise the bye payload extracted by `vertoBye$`, e.g. when racing
|
|
16642
|
+
* it against a boolean answer/reject signal.
|
|
16643
|
+
*/
|
|
16644
|
+
function isVertoByeInboundParamsGuard(value) {
|
|
16645
|
+
return isObject(value) && hasProperty(value, "callID") && typeof value.callID === "string";
|
|
16646
|
+
}
|
|
16497
16647
|
|
|
16498
16648
|
//#endregion
|
|
16499
16649
|
//#region src/managers/VertoManager.ts
|
|
16500
16650
|
var import_cjs$15 = require_cjs();
|
|
16501
|
-
const logger$
|
|
16651
|
+
const logger$16 = getLogger();
|
|
16502
16652
|
/**
|
|
16503
16653
|
* Decide what value goes on the `node_id` field of a `webrtc.verto` envelope.
|
|
16504
16654
|
*
|
|
@@ -16554,7 +16704,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16554
16704
|
try {
|
|
16555
16705
|
await this.executeVerto(vertoModifyMessage);
|
|
16556
16706
|
} catch (error) {
|
|
16557
|
-
logger$
|
|
16707
|
+
logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto hold:", error);
|
|
16558
16708
|
throw error;
|
|
16559
16709
|
}
|
|
16560
16710
|
}
|
|
@@ -16567,7 +16717,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16567
16717
|
try {
|
|
16568
16718
|
await this.executeVerto(vertoModifyMessage);
|
|
16569
16719
|
} catch (error) {
|
|
16570
|
-
logger$
|
|
16720
|
+
logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto unhold:", error);
|
|
16571
16721
|
throw error;
|
|
16572
16722
|
}
|
|
16573
16723
|
}
|
|
@@ -16620,7 +16770,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16620
16770
|
if (event.member_id) this.setSelfIdIfNull(event.member_id);
|
|
16621
16771
|
});
|
|
16622
16772
|
this.subscribeTo(this.vertoMedia$, (event) => {
|
|
16623
|
-
logger$
|
|
16773
|
+
logger$16.debug("[WebRTCManager] Received Verto media event (early media SDP):", event);
|
|
16624
16774
|
const { sdp, callID } = event;
|
|
16625
16775
|
this.emitMainSignalingStatus(callID, "ringing");
|
|
16626
16776
|
this._rtcPeerConnectionsMap.get(callID)?.updateAnswerStatus({
|
|
@@ -16629,7 +16779,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16629
16779
|
});
|
|
16630
16780
|
});
|
|
16631
16781
|
this.subscribeTo(this.vertoAnswer$, (event) => {
|
|
16632
|
-
logger$
|
|
16782
|
+
logger$16.debug("[WebRTCManager] Received Verto answer event:", event);
|
|
16633
16783
|
const { sdp, callID } = event;
|
|
16634
16784
|
this.emitMainSignalingStatus(callID, "connecting");
|
|
16635
16785
|
this._rtcPeerConnectionsMap.get(callID)?.updateAnswerStatus({
|
|
@@ -16638,7 +16788,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16638
16788
|
});
|
|
16639
16789
|
});
|
|
16640
16790
|
this.subscribeTo(this.vertoMediaParams$, (event) => {
|
|
16641
|
-
logger$
|
|
16791
|
+
logger$16.debug("[WebRTCManager] Received Verto mediaParams event:", event);
|
|
16642
16792
|
const { mediaParams, callID } = event;
|
|
16643
16793
|
const rtcPeerConnController = this._rtcPeerConnectionsMap.get(callID);
|
|
16644
16794
|
const { audio, video } = mediaParams;
|
|
@@ -16652,7 +16802,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16652
16802
|
timestamp: Date.now()
|
|
16653
16803
|
});
|
|
16654
16804
|
} catch (error) {
|
|
16655
|
-
logger$
|
|
16805
|
+
logger$16.warn("[WebRTCManager] Error applying server-pushed media params:", error);
|
|
16656
16806
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
16657
16807
|
}
|
|
16658
16808
|
})();
|
|
@@ -16674,13 +16824,13 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16674
16824
|
*/
|
|
16675
16825
|
setNodeIdIfNull(nodeId) {
|
|
16676
16826
|
if (!this._nodeId$.value && nodeId) {
|
|
16677
|
-
logger$
|
|
16827
|
+
logger$16.debug(`[WebRTCManager] Early node_id set: ${nodeId}`);
|
|
16678
16828
|
this._nodeId$.next(nodeId);
|
|
16679
16829
|
}
|
|
16680
16830
|
}
|
|
16681
16831
|
setSelfIdIfNull(selfId) {
|
|
16682
16832
|
if (!this._selfId$.value && selfId) {
|
|
16683
|
-
logger$
|
|
16833
|
+
logger$16.debug(`[WebRTCManager] Early selfId set: ${selfId}`);
|
|
16684
16834
|
this._selfId$.next(selfId);
|
|
16685
16835
|
}
|
|
16686
16836
|
}
|
|
@@ -16689,7 +16839,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16689
16839
|
const vertoPongMessage = VertoPong({ ...vertoPing });
|
|
16690
16840
|
await this.executeVerto(vertoPongMessage);
|
|
16691
16841
|
} catch (error) {
|
|
16692
|
-
logger$
|
|
16842
|
+
logger$16.warn("[WebRTCManager] Call might disconnect, error sending Verto pong:", error);
|
|
16693
16843
|
this.onError?.(new VertoPongError(error));
|
|
16694
16844
|
}
|
|
16695
16845
|
}
|
|
@@ -16699,7 +16849,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16699
16849
|
if (audio) await this.mainPeerConnection.updateSendersConstraints("audio", audio);
|
|
16700
16850
|
if (video) await this.mainPeerConnection.updateSendersConstraints("video", video);
|
|
16701
16851
|
} catch (error) {
|
|
16702
|
-
logger$
|
|
16852
|
+
logger$16.warn("[WebRTCManager] Error updating media constraints:", error);
|
|
16703
16853
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
16704
16854
|
throw error;
|
|
16705
16855
|
}
|
|
@@ -16729,20 +16879,20 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16729
16879
|
try {
|
|
16730
16880
|
const pc = this.mainPeerConnection.peerConnection;
|
|
16731
16881
|
if (!pc) {
|
|
16732
|
-
logger$
|
|
16882
|
+
logger$16.warn("[WebRTCManager] No peer connection for keyframe request");
|
|
16733
16883
|
return;
|
|
16734
16884
|
}
|
|
16735
16885
|
const videoReceiver = pc.getReceivers().find((r) => r.track.kind === "video");
|
|
16736
16886
|
if (!videoReceiver) {
|
|
16737
|
-
logger$
|
|
16887
|
+
logger$16.warn("[WebRTCManager] No video receiver for keyframe request");
|
|
16738
16888
|
return;
|
|
16739
16889
|
}
|
|
16740
16890
|
if (typeof videoReceiver.requestKeyFrame === "function") {
|
|
16741
16891
|
videoReceiver.requestKeyFrame();
|
|
16742
|
-
logger$
|
|
16743
|
-
} else logger$
|
|
16892
|
+
logger$16.debug("[WebRTCManager] Keyframe requested via RTCRtpReceiver.requestKeyFrame()");
|
|
16893
|
+
} else logger$16.debug("[WebRTCManager] requestKeyFrame() not supported, skipping");
|
|
16744
16894
|
} catch (error) {
|
|
16745
|
-
logger$
|
|
16895
|
+
logger$16.warn("[WebRTCManager] Keyframe request failed (non-fatal):", error);
|
|
16746
16896
|
}
|
|
16747
16897
|
}
|
|
16748
16898
|
/**
|
|
@@ -16760,13 +16910,13 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16760
16910
|
try {
|
|
16761
16911
|
const controller = this.mainPeerConnection;
|
|
16762
16912
|
if (!controller.peerConnection) {
|
|
16763
|
-
logger$
|
|
16913
|
+
logger$16.warn("[WebRTCManager] No peer connection for ICE restart");
|
|
16764
16914
|
return;
|
|
16765
16915
|
}
|
|
16766
16916
|
await controller.triggerIceRestart(relayOnly);
|
|
16767
|
-
logger$
|
|
16917
|
+
logger$16.info(`[WebRTCManager] ICE restart initiated${relayOnly ? " (relay-only)" : ""}`);
|
|
16768
16918
|
} catch (error) {
|
|
16769
|
-
logger$
|
|
16919
|
+
logger$16.error("[WebRTCManager] ICE restart failed:", error);
|
|
16770
16920
|
throw error;
|
|
16771
16921
|
}
|
|
16772
16922
|
}
|
|
@@ -16784,13 +16934,13 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16784
16934
|
const entries = Array.from(this._rtcPeerConnectionsMap.entries());
|
|
16785
16935
|
for (const [id, controller] of entries) try {
|
|
16786
16936
|
if (!controller.peerConnection) {
|
|
16787
|
-
logger$
|
|
16937
|
+
logger$16.debug(`[WebRTCManager] No peer connection for leg ${id}, skipping ICE restart`);
|
|
16788
16938
|
continue;
|
|
16789
16939
|
}
|
|
16790
16940
|
await controller.triggerIceRestart(relayOnly);
|
|
16791
|
-
logger$
|
|
16941
|
+
logger$16.info(`[WebRTCManager] ICE restart initiated for leg ${id}${relayOnly ? " (relay-only)" : ""}`);
|
|
16792
16942
|
} catch (error) {
|
|
16793
|
-
logger$
|
|
16943
|
+
logger$16.warn(`[WebRTCManager] ICE restart failed for leg ${id}:`, error);
|
|
16794
16944
|
}
|
|
16795
16945
|
}
|
|
16796
16946
|
/**
|
|
@@ -16802,7 +16952,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16802
16952
|
requestKeyframeAll() {
|
|
16803
16953
|
for (const [id, controller] of this._rtcPeerConnectionsMap) {
|
|
16804
16954
|
if (controller.isScreenShare) {
|
|
16805
|
-
logger$
|
|
16955
|
+
logger$16.debug(`[WebRTCManager] Skipping keyframe for send-only screen share leg ${id}`);
|
|
16806
16956
|
continue;
|
|
16807
16957
|
}
|
|
16808
16958
|
try {
|
|
@@ -16812,10 +16962,10 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16812
16962
|
if (!videoReceiver) continue;
|
|
16813
16963
|
if (typeof videoReceiver.requestKeyFrame === "function") {
|
|
16814
16964
|
videoReceiver.requestKeyFrame();
|
|
16815
|
-
logger$
|
|
16965
|
+
logger$16.debug(`[WebRTCManager] Keyframe requested for leg ${id}`);
|
|
16816
16966
|
}
|
|
16817
16967
|
} catch (error) {
|
|
16818
|
-
logger$
|
|
16968
|
+
logger$16.warn(`[WebRTCManager] Keyframe request failed for leg ${id} (non-fatal):`, error);
|
|
16819
16969
|
}
|
|
16820
16970
|
}
|
|
16821
16971
|
}
|
|
@@ -16832,7 +16982,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16832
16982
|
return this.cachedObservable("vertoMediaParams$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoMediaParamsInnerParams, "params"), (0, import_cjs$15.takeUntil)(this.destroyed$)));
|
|
16833
16983
|
}
|
|
16834
16984
|
get vertoBye$() {
|
|
16835
|
-
return this.cachedObservable("vertoBye$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(
|
|
16985
|
+
return this.cachedObservable("vertoBye$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoByeInboundMessage, "params"), (0, import_cjs$15.takeUntil)(this.destroyed$)));
|
|
16836
16986
|
}
|
|
16837
16987
|
get vertoAttach$() {
|
|
16838
16988
|
return this.cachedObservable("vertoAttach$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoAttachMessage, "params"), (0, import_cjs$15.takeUntil)(this.destroyed$)));
|
|
@@ -16876,7 +17026,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16876
17026
|
default:
|
|
16877
17027
|
}
|
|
16878
17028
|
} catch (error) {
|
|
16879
|
-
logger$
|
|
17029
|
+
logger$16.error(`[WebRTCManager] Error sending Verto ${vertoMethod}:`, error);
|
|
16880
17030
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
16881
17031
|
if (vertoMethod === "verto.modify") this.onModifyFailed?.();
|
|
16882
17032
|
}
|
|
@@ -16891,7 +17041,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16891
17041
|
sdp
|
|
16892
17042
|
});
|
|
16893
17043
|
} catch (error) {
|
|
16894
|
-
logger$
|
|
17044
|
+
logger$16.warn("[WebRTCManager] Error processing modify response:", error);
|
|
16895
17045
|
const modifyError = error instanceof Error ? error : new Error(String(error), { cause: error });
|
|
16896
17046
|
this.onError?.(modifyError);
|
|
16897
17047
|
}
|
|
@@ -16901,7 +17051,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16901
17051
|
const rtcPeerConnController = this._rtcPeerConnectionsMap.get(callId);
|
|
16902
17052
|
if (!rtcPeerConnController) {
|
|
16903
17053
|
const signalingError = new DependencyError(`Cannot emit signaling status, RTCPeerConnectionController not found for callID: ${callId}`);
|
|
16904
|
-
logger$
|
|
17054
|
+
logger$16.error("[WebRTCManager] Failed to emit signaling status:", {
|
|
16905
17055
|
callId,
|
|
16906
17056
|
status,
|
|
16907
17057
|
signalingError
|
|
@@ -16917,7 +17067,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16917
17067
|
this._nodeId$.next(getValueFrom(response, "result.node_id") ?? null);
|
|
16918
17068
|
const memberId = getValueFrom(response, "result.result.result.memberID") ?? null;
|
|
16919
17069
|
const callId = getValueFrom(response, "result.result.result.callID") ?? null;
|
|
16920
|
-
logger$
|
|
17070
|
+
logger$16.debug("[WebRTCManager] Verto invite response:", {
|
|
16921
17071
|
callId,
|
|
16922
17072
|
memberId,
|
|
16923
17073
|
response
|
|
@@ -16927,14 +17077,14 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16927
17077
|
if (callId) {
|
|
16928
17078
|
this.webRtcCallSession.addCallId(callId);
|
|
16929
17079
|
this.attachManager.attach(this.buildAttachableCall(callId));
|
|
16930
|
-
} else logger$
|
|
17080
|
+
} else logger$16.warn("[WebRTCManager] Cannot attach call, missing callId:", {
|
|
16931
17081
|
nodeId: this.nodeId,
|
|
16932
17082
|
callId
|
|
16933
17083
|
});
|
|
16934
|
-
logger$
|
|
16935
|
-
logger$
|
|
17084
|
+
logger$16.info("[WebRTCManager] Verto invite successful");
|
|
17085
|
+
logger$16.debug(`[WebRTCManager] nodeid: ${this._nodeId$.value}, selfId: ${this._selfId$.value}`);
|
|
16936
17086
|
} else {
|
|
16937
|
-
logger$
|
|
17087
|
+
logger$16.error("[WebRTCManager] Verto invite failed:", response);
|
|
16938
17088
|
const inviteError = response.error ? new JSONRPCError(response.error.code, response.error.message, response.error.data) : /* @__PURE__ */ new Error("Verto invite failed: unexpected response");
|
|
16939
17089
|
this.onError?.(inviteError);
|
|
16940
17090
|
}
|
|
@@ -16961,6 +17111,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16961
17111
|
inputVideoStream: options.inputVideoStream,
|
|
16962
17112
|
receiveAudio: options.receiveAudio,
|
|
16963
17113
|
receiveVideo: options.receiveVideo,
|
|
17114
|
+
fallbackToReceiveOnly: options.fallbackToReceiveOnly,
|
|
16964
17115
|
webRTCApiProvider: this.webRTCApiProvider,
|
|
16965
17116
|
preferredVideoCodecs: options.preferredVideoCodecs,
|
|
16966
17117
|
preferredAudioCodecs: options.preferredAudioCodecs,
|
|
@@ -16979,17 +17130,17 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16979
17130
|
if (options.initOffer) this.handleInboundAnswer(rtcPeerConnController);
|
|
16980
17131
|
}
|
|
16981
17132
|
async handleInboundAnswer(rtcPeerConnController) {
|
|
16982
|
-
logger$
|
|
17133
|
+
logger$16.debug("[WebRTCManager] Waiting for inbound call to be accepted or rejected");
|
|
16983
17134
|
const vertoByeOrAccepted = await (0, import_cjs$15.firstValueFrom)((0, import_cjs$15.race)(this.vertoBye$, this.webRtcCallSession.answered$).pipe((0, import_cjs$15.takeUntil)(this.destroyed$))).catch(() => null);
|
|
16984
17135
|
if (vertoByeOrAccepted === null) {
|
|
16985
|
-
logger$
|
|
17136
|
+
logger$16.debug("[WebRTCManager] Inbound answer handler aborted (destroyed).");
|
|
16986
17137
|
return;
|
|
16987
17138
|
}
|
|
16988
|
-
if (
|
|
16989
|
-
logger$
|
|
17139
|
+
if (isVertoByeInboundParamsGuard(vertoByeOrAccepted)) {
|
|
17140
|
+
logger$16.info("[WebRTCManager] Inbound call ended by remote before answer.");
|
|
16990
17141
|
this.callSession?.destroy();
|
|
16991
17142
|
} else if (!vertoByeOrAccepted) {
|
|
16992
|
-
logger$
|
|
17143
|
+
logger$16.info("[WebRTCManager] Inbound call rejected by user.");
|
|
16993
17144
|
try {
|
|
16994
17145
|
await this.bye("USER_BUSY");
|
|
16995
17146
|
} finally {
|
|
@@ -16997,19 +17148,19 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16997
17148
|
this.callSession?.destroy();
|
|
16998
17149
|
}
|
|
16999
17150
|
} else {
|
|
17000
|
-
logger$
|
|
17151
|
+
logger$16.debug("[WebRTCManager] Inbound call accepted, creating SDP answer");
|
|
17001
17152
|
const answerOptions = this.webRtcCallSession.answerMediaOptions;
|
|
17002
17153
|
try {
|
|
17003
17154
|
await rtcPeerConnController.acceptInbound(answerOptions);
|
|
17004
17155
|
} catch (error) {
|
|
17005
|
-
logger$
|
|
17156
|
+
logger$16.error("[WebRTCManager] Error creating inbound answer:", error);
|
|
17006
17157
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
17007
17158
|
}
|
|
17008
17159
|
}
|
|
17009
17160
|
}
|
|
17010
17161
|
setupVertoAttachHandler() {
|
|
17011
17162
|
this.subscribeTo(this.vertoAttach$, async (vertoAttach) => {
|
|
17012
|
-
logger$
|
|
17163
|
+
logger$16.debug("[WebRTCManager] Received Verto attach event for existing call:", vertoAttach);
|
|
17013
17164
|
const { callID } = vertoAttach;
|
|
17014
17165
|
await this.attachManager.attach({
|
|
17015
17166
|
nodeId: this.nodeId ?? void 0,
|
|
@@ -17081,17 +17232,17 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17081
17232
|
};
|
|
17082
17233
|
}
|
|
17083
17234
|
async sendLocalDescriptionOnceAccepted(vertoMessageRequest, rtcPeerConnectionController) {
|
|
17084
|
-
logger$
|
|
17235
|
+
logger$16.debug("[WebRTCManager] Waiting for call to be accepted or ended before sending answer");
|
|
17085
17236
|
const vertoByeOrAccepted = await (0, import_cjs$15.firstValueFrom)((0, import_cjs$15.race)(this.vertoBye$, this.webRtcCallSession.answered$).pipe((0, import_cjs$15.takeUntil)(this.destroyed$))).catch(() => null);
|
|
17086
17237
|
if (vertoByeOrAccepted === null) {
|
|
17087
|
-
logger$
|
|
17238
|
+
logger$16.debug("[WebRTCManager] Destroyed while waiting for call acceptance");
|
|
17088
17239
|
return;
|
|
17089
17240
|
}
|
|
17090
|
-
if (
|
|
17091
|
-
logger$
|
|
17241
|
+
if (isVertoByeInboundParamsGuard(vertoByeOrAccepted)) {
|
|
17242
|
+
logger$16.info("[WebRTCManager] Call ended before answer was sent.");
|
|
17092
17243
|
this.callSession?.destroy();
|
|
17093
17244
|
} else if (!vertoByeOrAccepted) {
|
|
17094
|
-
logger$
|
|
17245
|
+
logger$16.info("[WebRTCManager] Call was not accepted, sending verto.bye.");
|
|
17095
17246
|
try {
|
|
17096
17247
|
await this.bye("USER_BUSY");
|
|
17097
17248
|
} finally {
|
|
@@ -17099,14 +17250,14 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17099
17250
|
this.callSession?.destroy();
|
|
17100
17251
|
}
|
|
17101
17252
|
} else {
|
|
17102
|
-
logger$
|
|
17253
|
+
logger$16.debug("[WebRTCManager] Call accepted, sending answer");
|
|
17103
17254
|
try {
|
|
17104
17255
|
this.emitMainSignalingStatus(rtcPeerConnectionController.id, "connecting");
|
|
17105
17256
|
await this.sendLocalDescription(vertoMessageRequest, rtcPeerConnectionController);
|
|
17106
17257
|
await rtcPeerConnectionController.updateAnswerStatus({ status: "sent" });
|
|
17107
17258
|
await this.attachManager.attach(this.buildAttachableCall());
|
|
17108
17259
|
} catch (error) {
|
|
17109
|
-
logger$
|
|
17260
|
+
logger$16.error("[WebRTCManager] Error sending Verto answer:", error);
|
|
17110
17261
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
17111
17262
|
await rtcPeerConnectionController.updateAnswerStatus({ status: "failed" });
|
|
17112
17263
|
}
|
|
@@ -17187,9 +17338,11 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17187
17338
|
await this.initAdditionalPeerConnection("screenshare", options);
|
|
17188
17339
|
}
|
|
17189
17340
|
async initAdditionalPeerConnection(propose, options) {
|
|
17341
|
+
const isScreenShare = propose === "screenshare";
|
|
17342
|
+
let firstPeerConnectionError;
|
|
17190
17343
|
let rtcPeerConnController = null;
|
|
17191
17344
|
try {
|
|
17192
|
-
this._screenShareStatus$.next("starting");
|
|
17345
|
+
if (isScreenShare) this._screenShareStatus$.next("starting");
|
|
17193
17346
|
rtcPeerConnController = new RTCPeerConnectionController({
|
|
17194
17347
|
...options,
|
|
17195
17348
|
...this.RTCPeerConnectionConfig,
|
|
@@ -17197,21 +17350,27 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17197
17350
|
webRTCApiProvider: this.webRTCApiProvider
|
|
17198
17351
|
}, void 0, this.deviceController);
|
|
17199
17352
|
this.setupLocalDescriptionHandler(rtcPeerConnController);
|
|
17200
|
-
if (
|
|
17353
|
+
if (isScreenShare) this._screenShareId = rtcPeerConnController.id;
|
|
17201
17354
|
this._rtcPeerConnectionsMap.set(rtcPeerConnController.id, rtcPeerConnController);
|
|
17202
17355
|
this._rtcPeerConnections$.next(Array.from(this._rtcPeerConnectionsMap.values()));
|
|
17203
17356
|
this.subscribeTo(rtcPeerConnController.errors$, (error) => {
|
|
17204
|
-
|
|
17357
|
+
firstPeerConnectionError ??= error;
|
|
17358
|
+
this.onError?.(error, { fatal: false });
|
|
17205
17359
|
});
|
|
17206
17360
|
await (0, import_cjs$15.firstValueFrom)(rtcPeerConnController.connectionState$.pipe((0, import_cjs$15.filter)((state) => state === "connected"), (0, import_cjs$15.take)(1), (0, import_cjs$15.timeout)(this._screenShareTimeoutMs), (0, import_cjs$15.takeUntil)(this.destroyed$)));
|
|
17207
|
-
this._screenShareStatus$.next("started");
|
|
17208
|
-
logger$
|
|
17361
|
+
if (isScreenShare) this._screenShareStatus$.next("started");
|
|
17362
|
+
logger$16.info(`[WebRTCManager] Additional peer connection connected (${propose}).`);
|
|
17209
17363
|
return rtcPeerConnController.id;
|
|
17210
17364
|
} catch (error) {
|
|
17211
|
-
logger$
|
|
17212
|
-
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
17365
|
+
logger$16.warn("[WebRTCManager] Error initializing additional peer connection:", error);
|
|
17213
17366
|
if (rtcPeerConnController) rtcPeerConnController.destroy();
|
|
17214
|
-
this._screenShareStatus$.next("none");
|
|
17367
|
+
if (isScreenShare) this._screenShareStatus$.next("none");
|
|
17368
|
+
if (firstPeerConnectionError) throw firstPeerConnectionError instanceof MediaAccessError && firstPeerConnectionError.originalError instanceof Error ? firstPeerConnectionError.originalError : firstPeerConnectionError;
|
|
17369
|
+
if (error instanceof import_cjs$15.EmptyError) {
|
|
17370
|
+
logger$16.debug("[WebRTCManager] Additional peer connection aborted before connecting.");
|
|
17371
|
+
return;
|
|
17372
|
+
}
|
|
17373
|
+
throw error instanceof Error ? error : new Error(String(error), { cause: error });
|
|
17215
17374
|
}
|
|
17216
17375
|
}
|
|
17217
17376
|
async removeInputDevices(id) {
|
|
@@ -17227,9 +17386,9 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17227
17386
|
if (removeTrack) return this.mainPeerConnection.stopTrackSender(removeTrack, { updateTransceiverDirection: true });
|
|
17228
17387
|
}
|
|
17229
17388
|
async removeScreenMedia() {
|
|
17230
|
-
if (!["starting", "started"].includes(this._screenShareStatus$.value)) logger$
|
|
17389
|
+
if (!["starting", "started"].includes(this._screenShareStatus$.value)) logger$16.warn("[WebRTCManager] No active screen share to stop.");
|
|
17231
17390
|
if (!this._screenShareId) {
|
|
17232
|
-
logger$
|
|
17391
|
+
logger$16.debug("[WebRTCManager] No screen share peer connection found.");
|
|
17233
17392
|
return;
|
|
17234
17393
|
}
|
|
17235
17394
|
this._screenShareStatus$.next("stopping");
|
|
@@ -17258,7 +17417,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17258
17417
|
dialogParams: this.dialogParams(rtcPeerConnController)
|
|
17259
17418
|
}));
|
|
17260
17419
|
} catch (error) {
|
|
17261
|
-
logger$
|
|
17420
|
+
logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto bye:", error);
|
|
17262
17421
|
throw error;
|
|
17263
17422
|
}
|
|
17264
17423
|
}
|
|
@@ -17276,7 +17435,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17276
17435
|
try {
|
|
17277
17436
|
await this.executeVerto(vertoInfoMessage);
|
|
17278
17437
|
} catch (error) {
|
|
17279
|
-
logger$
|
|
17438
|
+
logger$16.warn("[WebRTCManager] Error sending DTMF digits:", error);
|
|
17280
17439
|
throw error;
|
|
17281
17440
|
}
|
|
17282
17441
|
}
|
|
@@ -17287,10 +17446,10 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17287
17446
|
action: "transfer"
|
|
17288
17447
|
});
|
|
17289
17448
|
try {
|
|
17290
|
-
logger$
|
|
17449
|
+
logger$16.debug("[WebRTCManager] Transferring call with options:", options);
|
|
17291
17450
|
await this.executeVerto(message);
|
|
17292
17451
|
} catch (error) {
|
|
17293
|
-
logger$
|
|
17452
|
+
logger$16.error("[WebRTCManager] Error transferring call:", error);
|
|
17294
17453
|
throw error;
|
|
17295
17454
|
}
|
|
17296
17455
|
}
|
|
@@ -17307,7 +17466,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17307
17466
|
//#endregion
|
|
17308
17467
|
//#region src/controllers/RemoteAudioMeter.ts
|
|
17309
17468
|
var import_cjs$14 = require_cjs();
|
|
17310
|
-
const logger$
|
|
17469
|
+
const logger$15 = getLogger();
|
|
17311
17470
|
/**
|
|
17312
17471
|
* Read-only audio level meter for a remote MediaStream. Attaches an
|
|
17313
17472
|
* AnalyserNode to a MediaStreamAudioSourceNode so it observes the stream
|
|
@@ -17342,7 +17501,7 @@ var RemoteAudioMeter = class extends Destroyable {
|
|
|
17342
17501
|
try {
|
|
17343
17502
|
this._source.disconnect();
|
|
17344
17503
|
} catch (error) {
|
|
17345
|
-
logger$
|
|
17504
|
+
logger$15.debug("[RemoteAudioMeter] source disconnect warning:", error);
|
|
17346
17505
|
}
|
|
17347
17506
|
this._source = null;
|
|
17348
17507
|
this._stream = null;
|
|
@@ -17359,7 +17518,7 @@ var RemoteAudioMeter = class extends Destroyable {
|
|
|
17359
17518
|
this._source = null;
|
|
17360
17519
|
}
|
|
17361
17520
|
this._audioContext.close().catch((error) => {
|
|
17362
|
-
logger$
|
|
17521
|
+
logger$15.debug("[RemoteAudioMeter] audio context close warning:", error);
|
|
17363
17522
|
});
|
|
17364
17523
|
super.destroy();
|
|
17365
17524
|
}
|
|
@@ -17378,7 +17537,7 @@ var RemoteAudioMeter = class extends Destroyable {
|
|
|
17378
17537
|
//#endregion
|
|
17379
17538
|
//#region src/controllers/RTCStatsMonitor.ts
|
|
17380
17539
|
var import_cjs$13 = require_cjs();
|
|
17381
|
-
const logger$
|
|
17540
|
+
const logger$14 = getLogger();
|
|
17382
17541
|
const DEFAULT_POLLING_INTERVAL_MS = 1e3;
|
|
17383
17542
|
const DEFAULT_BASELINE_SAMPLES = 10;
|
|
17384
17543
|
const DEFAULT_NO_AUDIO_PACKET_THRESHOLD_MS = 2e3;
|
|
@@ -17468,9 +17627,9 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
17468
17627
|
const now = Date.now();
|
|
17469
17628
|
this.lastAudioPacketChangeTime = now;
|
|
17470
17629
|
this.lastVideoPacketChangeTime = now;
|
|
17471
|
-
logger$
|
|
17630
|
+
logger$14.debug("[RTCStatsMonitor] Starting stats monitoring");
|
|
17472
17631
|
this.subscribeTo((0, import_cjs$13.interval)(this.pollingIntervalMs).pipe((0, import_cjs$13.filter)(() => this.running), (0, import_cjs$13.switchMap)(() => (0, import_cjs$13.from)(this.peerConnection.getStats()).pipe((0, import_cjs$13.catchError)((err) => {
|
|
17473
|
-
logger$
|
|
17632
|
+
logger$14.warn("[RTCStatsMonitor] Failed to get stats:", err);
|
|
17474
17633
|
return import_cjs$13.EMPTY;
|
|
17475
17634
|
}))), (0, import_cjs$13.filter)(() => this.running), (0, import_cjs$13.map)((report) => this.extractSample(report))), (sample$1) => this._sample$.next(sample$1));
|
|
17476
17635
|
this.subscribeTo(this._sample$.pipe((0, import_cjs$13.take)(this.baselineSampleCount), (0, import_cjs$13.toArray)(), (0, import_cjs$13.map)((samples) => ({
|
|
@@ -17478,7 +17637,7 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
17478
17637
|
jitter: samples.reduce((s, b) => s + b.audioJitter, 0) / samples.length,
|
|
17479
17638
|
ready: true
|
|
17480
17639
|
}))), (baseline) => {
|
|
17481
|
-
logger$
|
|
17640
|
+
logger$14.debug(`[RTCStatsMonitor] Baseline established: rtt=${baseline.rtt.toFixed(1)}ms, jitter=${baseline.jitter.toFixed(1)}ms`);
|
|
17482
17641
|
this._baseline$.next(baseline);
|
|
17483
17642
|
});
|
|
17484
17643
|
this.subscribeTo(this._sample$.pipe((0, import_cjs$13.scan)((acc, sample$1) => ({
|
|
@@ -17515,10 +17674,10 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
17515
17674
|
stop() {
|
|
17516
17675
|
if (!this.running) return;
|
|
17517
17676
|
this.running = false;
|
|
17518
|
-
logger$
|
|
17677
|
+
logger$14.debug("[RTCStatsMonitor] Stopping stats monitoring");
|
|
17519
17678
|
}
|
|
17520
17679
|
destroy() {
|
|
17521
|
-
logger$
|
|
17680
|
+
logger$14.debug("[RTCStatsMonitor] Destroying RTCStatsMonitor");
|
|
17522
17681
|
this.stop();
|
|
17523
17682
|
super.destroy();
|
|
17524
17683
|
}
|
|
@@ -17647,7 +17806,7 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
17647
17806
|
//#endregion
|
|
17648
17807
|
//#region src/managers/CallRecoveryManager.ts
|
|
17649
17808
|
var import_cjs$12 = require_cjs();
|
|
17650
|
-
const logger$
|
|
17809
|
+
const logger$13 = getLogger();
|
|
17651
17810
|
const DEFAULT_DEBOUNCE_TIME_MS = 2e3;
|
|
17652
17811
|
const DEFAULT_COOLDOWN_MS = 1e4;
|
|
17653
17812
|
const DEFAULT_ICE_GRACE_PERIOD_MS = 3e3;
|
|
@@ -17744,10 +17903,10 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17744
17903
|
*/
|
|
17745
17904
|
async requestIceRestart() {
|
|
17746
17905
|
if (this._recoveryState$.value === "recovering") {
|
|
17747
|
-
logger$
|
|
17906
|
+
logger$13.info("CallRecoveryManager: manual ICE restart skipped — recovery already in progress");
|
|
17748
17907
|
return;
|
|
17749
17908
|
}
|
|
17750
|
-
logger$
|
|
17909
|
+
logger$13.info("CallRecoveryManager: manual ICE restart requested");
|
|
17751
17910
|
this.transitionTo("recovering");
|
|
17752
17911
|
await this.executeIceRestart(false);
|
|
17753
17912
|
this.startCooldown();
|
|
@@ -17763,7 +17922,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17763
17922
|
* WebSocket reconnect or call state recovers to 'connected'.
|
|
17764
17923
|
*/
|
|
17765
17924
|
reset() {
|
|
17766
|
-
logger$
|
|
17925
|
+
logger$13.info("CallRecoveryManager: resetting counters");
|
|
17767
17926
|
this._attemptCount = 0;
|
|
17768
17927
|
this._keyframeBurstCount = 0;
|
|
17769
17928
|
this._keyframeBurstStart = 0;
|
|
@@ -17778,7 +17937,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17778
17937
|
*/
|
|
17779
17938
|
notifyModifyFailed() {
|
|
17780
17939
|
if (this._recoveryState$.value === "cooldown" || this._recoveryState$.value === "idle") {
|
|
17781
|
-
logger$
|
|
17940
|
+
logger$13.info("CallRecoveryManager: verto.modify failed — re-entering recovery");
|
|
17782
17941
|
this._cooldownUntil = 0;
|
|
17783
17942
|
this.transitionTo("idle");
|
|
17784
17943
|
this.pushTrigger({
|
|
@@ -17802,7 +17961,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17802
17961
|
reason: `bandwidth ${bitrateKbps}kbps below threshold ${this._config.degradationBitrateThreshold}kbps`,
|
|
17803
17962
|
timestamp: Date.now()
|
|
17804
17963
|
});
|
|
17805
|
-
logger$
|
|
17964
|
+
logger$13.warn(`CallRecoveryManager: disabling video — bandwidth ${bitrateKbps}kbps < ${this._config.degradationBitrateThreshold}kbps`);
|
|
17806
17965
|
} else if (wasConstrained && bitrateKbps >= this._config.degradationRecoveryThreshold) {
|
|
17807
17966
|
this._bandwidthConstrained$.next(false);
|
|
17808
17967
|
this._callbacks.enableVideo();
|
|
@@ -17811,7 +17970,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17811
17970
|
reason: `bandwidth ${bitrateKbps}kbps recovered above ${this._config.degradationRecoveryThreshold}kbps`,
|
|
17812
17971
|
timestamp: Date.now()
|
|
17813
17972
|
});
|
|
17814
|
-
logger$
|
|
17973
|
+
logger$13.info(`CallRecoveryManager: restoring video — bandwidth ${bitrateKbps}kbps >= ${this._config.degradationRecoveryThreshold}kbps`);
|
|
17815
17974
|
}
|
|
17816
17975
|
}
|
|
17817
17976
|
/**
|
|
@@ -17830,14 +17989,14 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17830
17989
|
handleWebSocketReconnect() {
|
|
17831
17990
|
const pcState = this._callbacks.getPeerConnectionState();
|
|
17832
17991
|
if (pcState === "connected" || pcState === "completed") {
|
|
17833
|
-
logger$
|
|
17992
|
+
logger$13.info("CallRecoveryManager: signal-only reconnect — peer connection still alive");
|
|
17834
17993
|
this.emitEvent({
|
|
17835
17994
|
action: "signal_reconnect",
|
|
17836
17995
|
reason: "WebSocket reconnected, peer connection still connected",
|
|
17837
17996
|
timestamp: Date.now()
|
|
17838
17997
|
});
|
|
17839
17998
|
} else {
|
|
17840
|
-
logger$
|
|
17999
|
+
logger$13.info("CallRecoveryManager: full reconnect — peer connection also down");
|
|
17841
18000
|
this.emitEvent({
|
|
17842
18001
|
action: "full_reconnect",
|
|
17843
18002
|
reason: "WebSocket reconnected, peer connection not connected — ICE restart needed",
|
|
@@ -17861,7 +18020,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17861
18020
|
}), (0, import_cjs$12.debounceTime)(this._config.debounceTimeMs), (0, import_cjs$12.withLatestFrom)(this._inputs.signalingReady$), (0, import_cjs$12.filter)(([, signalingReady]) => this.passGateChecks(signalingReady)), (0, import_cjs$12.map)(([trigger]) => trigger), (0, import_cjs$12.exhaustMap)((trigger) => this.executeTieredRecovery(trigger)), (0, import_cjs$12.takeUntil)((0, import_cjs$12.merge)(this._destroyed$, this._pipelineStop$))), {
|
|
17862
18021
|
next: () => {},
|
|
17863
18022
|
error: (err) => {
|
|
17864
|
-
logger$
|
|
18023
|
+
logger$13.error("CallRecoveryManager: pipeline error", err);
|
|
17865
18024
|
this.transitionTo("idle");
|
|
17866
18025
|
}
|
|
17867
18026
|
});
|
|
@@ -17888,27 +18047,27 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17888
18047
|
reason: `no packet loss for ${this._config.packetLossRecoveryDelaySec}s — restoring video`,
|
|
17889
18048
|
timestamp: Date.now()
|
|
17890
18049
|
});
|
|
17891
|
-
logger$
|
|
18050
|
+
logger$13.info(`CallRecoveryManager: restoring video — no packet loss for ${this._config.packetLossRecoveryDelaySec}s`);
|
|
17892
18051
|
});
|
|
17893
18052
|
}
|
|
17894
18053
|
passGateChecks(signalingReady) {
|
|
17895
18054
|
if (this._callbacks.isNegotiating()) {
|
|
17896
|
-
logger$
|
|
18055
|
+
logger$13.debug("CallRecoveryManager: gate blocked — negotiation in progress");
|
|
17897
18056
|
this.transitionTo("idle");
|
|
17898
18057
|
return false;
|
|
17899
18058
|
}
|
|
17900
18059
|
if (!signalingReady) {
|
|
17901
|
-
logger$
|
|
18060
|
+
logger$13.debug("CallRecoveryManager: gate blocked — signaling not ready");
|
|
17902
18061
|
this.transitionTo("idle");
|
|
17903
18062
|
return false;
|
|
17904
18063
|
}
|
|
17905
18064
|
if (!this._callbacks.isCallConnected()) {
|
|
17906
|
-
logger$
|
|
18065
|
+
logger$13.debug("CallRecoveryManager: gate blocked — call not connected");
|
|
17907
18066
|
this.transitionTo("idle");
|
|
17908
18067
|
return false;
|
|
17909
18068
|
}
|
|
17910
18069
|
if (this.isCooldownActive()) {
|
|
17911
|
-
logger$
|
|
18070
|
+
logger$13.debug("CallRecoveryManager: gate blocked — cooldown active");
|
|
17912
18071
|
this.transitionTo("cooldown");
|
|
17913
18072
|
return false;
|
|
17914
18073
|
}
|
|
@@ -17919,9 +18078,9 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17919
18078
|
}
|
|
17920
18079
|
executeTieredRecovery(trigger) {
|
|
17921
18080
|
this.transitionTo("recovering");
|
|
17922
|
-
logger$
|
|
18081
|
+
logger$13.info(`CallRecoveryManager: starting tiered recovery — source=${trigger.source} detail=${trigger.detail}`);
|
|
17923
18082
|
return (0, import_cjs$12.from)(this.runTiers(trigger)).pipe((0, import_cjs$12.tap)(() => this.startCooldown()), (0, import_cjs$12.catchError)((err) => {
|
|
17924
|
-
logger$
|
|
18083
|
+
logger$13.error("CallRecoveryManager: tiered recovery failed", err);
|
|
17925
18084
|
this.startCooldown();
|
|
17926
18085
|
return import_cjs$12.EMPTY;
|
|
17927
18086
|
}));
|
|
@@ -17929,7 +18088,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17929
18088
|
async runTiers(trigger) {
|
|
17930
18089
|
this.executeKeyframe(trigger.detail);
|
|
17931
18090
|
if (trigger.issueType && DEGRADATION_ONLY_ISSUES.has(trigger.issueType)) {
|
|
17932
|
-
logger$
|
|
18091
|
+
logger$13.debug(`CallRecoveryManager: degradation-only issue (${trigger.issueType}) — Tier 1 only, skipping ICE restart`);
|
|
17933
18092
|
return;
|
|
17934
18093
|
}
|
|
17935
18094
|
if (this._attemptCount < this._config.maxAttempts) {
|
|
@@ -17946,13 +18105,13 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17946
18105
|
maxAttempts: this._config.maxAttempts,
|
|
17947
18106
|
timestamp: Date.now()
|
|
17948
18107
|
});
|
|
17949
|
-
logger$
|
|
18108
|
+
logger$13.warn("CallRecoveryManager: max recovery attempts reached");
|
|
17950
18109
|
}
|
|
17951
18110
|
}
|
|
17952
18111
|
executeKeyframe(reason) {
|
|
17953
18112
|
const now = Date.now();
|
|
17954
18113
|
if (now < this._keyframeCooldownUntil) {
|
|
17955
|
-
logger$
|
|
18114
|
+
logger$13.debug("CallRecoveryManager: keyframe request skipped — cooldown active");
|
|
17956
18115
|
return;
|
|
17957
18116
|
}
|
|
17958
18117
|
if (now - this._keyframeBurstStart > this._config.keyframeBurstWindowMs) {
|
|
@@ -17961,7 +18120,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17961
18120
|
}
|
|
17962
18121
|
if (this._keyframeBurstCount >= this._config.keyframeMaxBurst) {
|
|
17963
18122
|
this._keyframeCooldownUntil = now + this._config.keyframeCooldownMs;
|
|
17964
|
-
logger$
|
|
18123
|
+
logger$13.debug(`CallRecoveryManager: keyframe burst limit reached (${this._config.keyframeMaxBurst}), cooldown until ${this._keyframeCooldownUntil}`);
|
|
17965
18124
|
return;
|
|
17966
18125
|
}
|
|
17967
18126
|
this._keyframeBurstCount += 1;
|
|
@@ -17971,12 +18130,12 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17971
18130
|
reason,
|
|
17972
18131
|
timestamp: now
|
|
17973
18132
|
});
|
|
17974
|
-
logger$
|
|
18133
|
+
logger$13.debug(`CallRecoveryManager: keyframe requested (burst ${this._keyframeBurstCount}/${this._config.keyframeMaxBurst})`);
|
|
17975
18134
|
}
|
|
17976
18135
|
async executeIceRestart(relayOnly) {
|
|
17977
18136
|
this._attemptCount += 1;
|
|
17978
18137
|
const tier = relayOnly ? "Tier 3 (relay-only)" : "Tier 2 (standard)";
|
|
17979
|
-
logger$
|
|
18138
|
+
logger$13.info(`CallRecoveryManager: ${tier} ICE restart — attempt ${this._attemptCount}/${this._config.maxAttempts}`);
|
|
17980
18139
|
this.emitEvent({
|
|
17981
18140
|
action: "reinvite_started",
|
|
17982
18141
|
reason: `${tier} ICE restart`,
|
|
@@ -17993,7 +18152,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17993
18152
|
maxAttempts: this._config.maxAttempts,
|
|
17994
18153
|
timestamp: Date.now()
|
|
17995
18154
|
});
|
|
17996
|
-
logger$
|
|
18155
|
+
logger$13.info(`CallRecoveryManager: ${tier} ICE restart succeeded`);
|
|
17997
18156
|
this._attemptCount = 0;
|
|
17998
18157
|
return true;
|
|
17999
18158
|
}
|
|
@@ -18004,7 +18163,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
18004
18163
|
maxAttempts: this._config.maxAttempts,
|
|
18005
18164
|
timestamp: Date.now()
|
|
18006
18165
|
});
|
|
18007
|
-
logger$
|
|
18166
|
+
logger$13.warn(`CallRecoveryManager: ${tier} ICE restart failed`);
|
|
18008
18167
|
return false;
|
|
18009
18168
|
} catch {
|
|
18010
18169
|
this.emitEvent({
|
|
@@ -18014,7 +18173,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
18014
18173
|
maxAttempts: this._config.maxAttempts,
|
|
18015
18174
|
timestamp: Date.now()
|
|
18016
18175
|
});
|
|
18017
|
-
logger$
|
|
18176
|
+
logger$13.warn(`CallRecoveryManager: ${tier} ICE restart timed out`);
|
|
18018
18177
|
return false;
|
|
18019
18178
|
}
|
|
18020
18179
|
}
|
|
@@ -18035,7 +18194,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
18035
18194
|
transitionTo(state) {
|
|
18036
18195
|
const prev = this._recoveryState$.value;
|
|
18037
18196
|
if (prev !== state) {
|
|
18038
|
-
logger$
|
|
18197
|
+
logger$13.debug(`CallRecoveryManager: state ${prev} -> ${state}`);
|
|
18039
18198
|
this._recoveryState$.next(state);
|
|
18040
18199
|
}
|
|
18041
18200
|
}
|
|
@@ -18138,7 +18297,13 @@ function mosToQualityLevel(mos) {
|
|
|
18138
18297
|
//#endregion
|
|
18139
18298
|
//#region src/core/entities/Call.ts
|
|
18140
18299
|
var import_cjs$11 = require_cjs();
|
|
18141
|
-
const logger$
|
|
18300
|
+
const logger$12 = getLogger();
|
|
18301
|
+
/**
|
|
18302
|
+
* Verto method for setting member layout positions. Its gateway DTO requires a
|
|
18303
|
+
* `targets` array whose entries are `{ target, position }` (NOT bare targets),
|
|
18304
|
+
* so {@link WebRTCCall.buildMethodParams} special-cases it. See issue #19400.
|
|
18305
|
+
*/
|
|
18306
|
+
const POSITION_SET_METHOD = "call.member.position.set";
|
|
18142
18307
|
/**
|
|
18143
18308
|
* Ratio between the critical and warning RTT spike multipliers.
|
|
18144
18309
|
* Warning threshold = baseline * warningMultiplier (default 3x)
|
|
@@ -18156,7 +18321,7 @@ const fromDestinationParams = (destination) => {
|
|
|
18156
18321
|
});
|
|
18157
18322
|
return params;
|
|
18158
18323
|
} catch (error) {
|
|
18159
|
-
logger$
|
|
18324
|
+
logger$12.warn(`Failed to parse destination URI: ${destination}`, error);
|
|
18160
18325
|
return {};
|
|
18161
18326
|
}
|
|
18162
18327
|
};
|
|
@@ -18346,7 +18511,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18346
18511
|
* @throws {JSONRPCError} If the RPC call returns an error.
|
|
18347
18512
|
*/
|
|
18348
18513
|
async executeMethod(target, method, args) {
|
|
18349
|
-
const params = this.buildMethodParams(target, args);
|
|
18514
|
+
const params = this.buildMethodParams(target, args, method);
|
|
18350
18515
|
const request = buildRPCRequest({
|
|
18351
18516
|
method,
|
|
18352
18517
|
params
|
|
@@ -18356,16 +18521,20 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18356
18521
|
if (isJSONRPCErrorResponse(response)) throw new JSONRPCError(parseInt(response.result?.code ?? "0"), `Error response from method ${method}: ${response.result?.code} ${response.result?.message}`, void 0, void 0, request.id);
|
|
18357
18522
|
return response;
|
|
18358
18523
|
} catch (error) {
|
|
18359
|
-
logger$
|
|
18524
|
+
logger$12.error(`[Call] Error executing method ${method} with params`, params, error);
|
|
18360
18525
|
throw error;
|
|
18361
18526
|
}
|
|
18362
18527
|
}
|
|
18363
|
-
buildMethodParams(target, args) {
|
|
18528
|
+
buildMethodParams(target, args, method) {
|
|
18364
18529
|
const self = {
|
|
18365
18530
|
node_id: this.nodeId ?? "",
|
|
18366
18531
|
call_id: this.id,
|
|
18367
18532
|
member_id: this.vertoManager.selfId ?? ""
|
|
18368
18533
|
};
|
|
18534
|
+
if (method === POSITION_SET_METHOD) return {
|
|
18535
|
+
...args,
|
|
18536
|
+
self
|
|
18537
|
+
};
|
|
18369
18538
|
if (typeof target === "object") return {
|
|
18370
18539
|
...args,
|
|
18371
18540
|
self,
|
|
@@ -18559,9 +18728,9 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18559
18728
|
*/
|
|
18560
18729
|
initResilienceSubsystems() {
|
|
18561
18730
|
const pc = this.rtcPeerConnection;
|
|
18562
|
-
logger$
|
|
18731
|
+
logger$12.debug(`[Call] initResilienceSubsystems: pc=${pc ? "exists" : "undefined"}, connectionState=${pc?.connectionState}`);
|
|
18563
18732
|
if (!pc) {
|
|
18564
|
-
logger$
|
|
18733
|
+
logger$12.warn("[Call] No peer connection available, skipping resilience init");
|
|
18565
18734
|
return;
|
|
18566
18735
|
}
|
|
18567
18736
|
try {
|
|
@@ -18596,14 +18765,14 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18596
18765
|
disableVideo: () => {
|
|
18597
18766
|
try {
|
|
18598
18767
|
this.vertoManager.muteMainVideoInputDevice();
|
|
18599
|
-
logger$
|
|
18768
|
+
logger$12.debug("[Call] Recovery manager disabled video");
|
|
18600
18769
|
} catch {
|
|
18601
|
-
logger$
|
|
18770
|
+
logger$12.debug("[Call] Recovery manager failed to disable video");
|
|
18602
18771
|
}
|
|
18603
18772
|
},
|
|
18604
18773
|
enableVideo: () => {
|
|
18605
18774
|
this.vertoManager.unmuteMainVideoInputDevice().catch(() => {
|
|
18606
|
-
logger$
|
|
18775
|
+
logger$12.debug("[Call] Recovery manager failed to enable video");
|
|
18607
18776
|
});
|
|
18608
18777
|
},
|
|
18609
18778
|
isNegotiating: () => this.vertoManager.mainPeerConnection.isNegotiating,
|
|
@@ -18653,7 +18822,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18653
18822
|
this.subscribeTo(this._recoveryManager.recoveryEvent$, (event) => {
|
|
18654
18823
|
this._recoveryEvent$.next(event);
|
|
18655
18824
|
if (event.action === "max_attempts_reached") {
|
|
18656
|
-
logger$
|
|
18825
|
+
logger$12.warn("[Call] All recovery attempts exhausted, terminating call");
|
|
18657
18826
|
this.emitError({
|
|
18658
18827
|
kind: "network",
|
|
18659
18828
|
fatal: true,
|
|
@@ -18673,13 +18842,13 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18673
18842
|
else if (event.type === "online") this._recoveryManager?.handleWebSocketReconnect();
|
|
18674
18843
|
});
|
|
18675
18844
|
this.subscribeTo(this.clientSession.authenticated$.pipe((0, import_cjs$11.skip)(1), (0, import_cjs$11.filter)(Boolean)), () => {
|
|
18676
|
-
logger$
|
|
18845
|
+
logger$12.debug("[Call] WebSocket reconnected — notifying recovery manager");
|
|
18677
18846
|
this._recoveryManager?.handleWebSocketReconnect();
|
|
18678
18847
|
});
|
|
18679
18848
|
this._statsMonitor.start();
|
|
18680
|
-
logger$
|
|
18849
|
+
logger$12.debug("[Call] Resilience subsystems initialized for call", this.id);
|
|
18681
18850
|
} catch (error) {
|
|
18682
|
-
logger$
|
|
18851
|
+
logger$12.warn("[Call] Failed to initialize resilience subsystems:", error);
|
|
18683
18852
|
}
|
|
18684
18853
|
}
|
|
18685
18854
|
/**
|
|
@@ -18762,19 +18931,19 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18762
18931
|
}
|
|
18763
18932
|
isCallSessionEvent(event) {
|
|
18764
18933
|
try {
|
|
18765
|
-
logger$
|
|
18934
|
+
logger$12.debug("[Call] Checking if event is for this call session:", event);
|
|
18766
18935
|
const callId = getValueFrom(event, "params.params.callID") ?? getValueFrom(event, "params.call_id");
|
|
18767
18936
|
const roomSessionId = getValueFrom(event, "params.room_session_id");
|
|
18768
|
-
logger$
|
|
18937
|
+
logger$12.debug(`[Call] Extracted session identifiers callID: ${callId} and roomSessionID: ${roomSessionId} from event:`);
|
|
18769
18938
|
return callId === this.id || !!callId && this.callEventsManager.isCallIdValid(callId) || !!roomSessionId && this.callEventsManager.isRoomSessionIdValid(roomSessionId);
|
|
18770
18939
|
} catch (error) {
|
|
18771
|
-
logger$
|
|
18940
|
+
logger$12.error("[Call] Error checking if event is for this call session:", error);
|
|
18772
18941
|
return false;
|
|
18773
18942
|
}
|
|
18774
18943
|
}
|
|
18775
18944
|
get callSessionEvents$() {
|
|
18776
18945
|
return this.cachedObservable("callSessionEvents$", () => this.clientSession.signalingEvent$.pipe((0, import_cjs$11.filter)((event) => this.isCallSessionEvent(event)), (0, import_cjs$11.tap)((event) => {
|
|
18777
|
-
logger$
|
|
18946
|
+
logger$12.debug("[Call] Received call session event:", event);
|
|
18778
18947
|
}), (0, import_cjs$11.takeUntil)(this.destroyed$), (0, import_cjs$11.share)()));
|
|
18779
18948
|
}
|
|
18780
18949
|
/** Observable of call-updated events. */
|
|
@@ -18844,16 +19013,16 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18844
19013
|
this._customSubscriptions.set(eventType, filtered$);
|
|
18845
19014
|
}, (error) => {
|
|
18846
19015
|
this._customSubscriptions.delete(eventType);
|
|
18847
|
-
logger$
|
|
19016
|
+
logger$12.warn(`[Call] verto.subscribe for '${eventType}' failed, not caching:`, error);
|
|
18848
19017
|
});
|
|
18849
19018
|
this._customSubscriptions.set(eventType, filtered$);
|
|
18850
19019
|
return filtered$;
|
|
18851
19020
|
}
|
|
18852
19021
|
get webrtcMessages$() {
|
|
18853
|
-
return this.cachedObservable("webrtcMessages$", () => this.callSessionEvents$.pipe(filterAs(isWebrtcMessageMetadata, "params"), (0, import_cjs$11.tap)((event) => logger$
|
|
19022
|
+
return this.cachedObservable("webrtcMessages$", () => this.callSessionEvents$.pipe(filterAs(isWebrtcMessageMetadata, "params"), (0, import_cjs$11.tap)((event) => logger$12.debug("[Call] Event is a WebRTC message event:", event)), (0, import_cjs$11.takeUntil)(this.destroyed$), (0, import_cjs$11.share)()));
|
|
18854
19023
|
}
|
|
18855
19024
|
get callEvent$() {
|
|
18856
|
-
return this.cachedObservable("callEvent$", () => this.callSessionEvents$.pipe(filterAs(isSignalwireCallMetadata, "params"), (0, import_cjs$11.tap)((event) => logger$
|
|
19025
|
+
return this.cachedObservable("callEvent$", () => this.callSessionEvents$.pipe(filterAs(isSignalwireCallMetadata, "params"), (0, import_cjs$11.tap)((event) => logger$12.debug("[Call] Event is a call event:", event)), (0, import_cjs$11.takeUntil)(this.destroyed$), (0, import_cjs$11.share)()));
|
|
18857
19026
|
}
|
|
18858
19027
|
get layoutEvent$() {
|
|
18859
19028
|
return this.cachedObservable("layoutEvent$", () => this.callEvent$.pipe(filterAs(isLayoutChangedMetadata, "params")));
|
|
@@ -18931,10 +19100,21 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18931
19100
|
return this.deferEmission(this._answered$.asObservable());
|
|
18932
19101
|
}
|
|
18933
19102
|
/**
|
|
18934
|
-
* Sets the call layout and participant positions.
|
|
19103
|
+
* Sets the call layout and, optionally, individual participant positions.
|
|
19104
|
+
*
|
|
19105
|
+
* The gateway `call.layout.set` DTO has **no** `positions` member, so when
|
|
19106
|
+
* `positions` is provided this method issues a `call.member.position.set`
|
|
19107
|
+
* request per member (via {@link Participant.setPosition}, which keys each
|
|
19108
|
+
* position by that member's own call context) alongside `call.layout.set`
|
|
19109
|
+
* (issue #19400, Flag #6).
|
|
19110
|
+
*
|
|
19111
|
+
* **These operations are NOT atomic.** The layout is applied first, then each
|
|
19112
|
+
* member position sequentially, so members may briefly flash into their
|
|
19113
|
+
* default slots before being moved to the requested positions.
|
|
18935
19114
|
*
|
|
18936
19115
|
* @param layout - Layout name (must be one of {@link layouts}).
|
|
18937
|
-
* @param positions -
|
|
19116
|
+
* @param positions - Optional map of member IDs to {@link VideoPosition} values.
|
|
19117
|
+
* When omitted or empty, only the layout is changed.
|
|
18938
19118
|
* @throws {InvalidParams} If the layout is not in the available {@link layouts}.
|
|
18939
19119
|
*
|
|
18940
19120
|
* @example
|
|
@@ -18947,10 +19127,17 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18947
19127
|
async setLayout(layout, positions) {
|
|
18948
19128
|
if (!this.layouts.includes(layout)) throw new InvalidParams(`Layout ${layout} is not available in the current call layouts: ${this.layouts.join(", ")}`);
|
|
18949
19129
|
const selfId = await (0, import_cjs$11.firstValueFrom)(this.selfId$.pipe((0, import_cjs$11.filter)((id) => id !== null)));
|
|
18950
|
-
await this.executeMethod(selfId, "call.layout.set", {
|
|
18951
|
-
|
|
18952
|
-
|
|
18953
|
-
|
|
19130
|
+
await this.executeMethod(selfId, "call.layout.set", { layout });
|
|
19131
|
+
const positionEntries = Object.entries(positions ?? {});
|
|
19132
|
+
if (positionEntries.length === 0) return;
|
|
19133
|
+
for (const [memberId, position] of positionEntries) {
|
|
19134
|
+
const participant = this.participants.find((p) => p.id === memberId);
|
|
19135
|
+
if (!participant) {
|
|
19136
|
+
logger$12.warn(`[Call] setLayout: member ${memberId} not found in participants; skipping position ${position}`);
|
|
19137
|
+
continue;
|
|
19138
|
+
}
|
|
19139
|
+
await participant.setPosition(position);
|
|
19140
|
+
}
|
|
18954
19141
|
}
|
|
18955
19142
|
/**
|
|
18956
19143
|
* Transfers the call to another destination.
|
|
@@ -18982,7 +19169,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18982
19169
|
setLocalMicrophoneGain(value) {
|
|
18983
19170
|
const pipeline = this.vertoManager.ensureLocalAudioPipeline();
|
|
18984
19171
|
if (!pipeline) {
|
|
18985
|
-
logger$
|
|
19172
|
+
logger$12.warn("[Call] setLocalMicrophoneGain: audio pipeline unavailable");
|
|
18986
19173
|
return;
|
|
18987
19174
|
}
|
|
18988
19175
|
const percent = Math.max(0, Math.min(200, value));
|
|
@@ -19027,7 +19214,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
19027
19214
|
enablePushToTalk() {
|
|
19028
19215
|
const pipeline = this.vertoManager.ensureLocalAudioPipeline();
|
|
19029
19216
|
if (!pipeline) {
|
|
19030
|
-
logger$
|
|
19217
|
+
logger$12.warn("[Call] enablePushToTalk: audio pipeline unavailable");
|
|
19031
19218
|
return;
|
|
19032
19219
|
}
|
|
19033
19220
|
pipeline.setPTTActive(false);
|
|
@@ -19124,6 +19311,7 @@ function inferCallErrorKind(error) {
|
|
|
19124
19311
|
if (error instanceof RPCTimeoutError) return "timeout";
|
|
19125
19312
|
if (error instanceof JSONRPCError) return "signaling";
|
|
19126
19313
|
if (error instanceof MediaTrackError) return "media";
|
|
19314
|
+
if (error instanceof MediaAccessError) return "media";
|
|
19127
19315
|
if (error instanceof WebSocketConnectionError || error instanceof TransportConnectionError) return "network";
|
|
19128
19316
|
return "internal";
|
|
19129
19317
|
}
|
|
@@ -19140,6 +19328,7 @@ const RECOVERABLE_RPC_CODES = new Set([
|
|
|
19140
19328
|
function isFatalError(error) {
|
|
19141
19329
|
if (error instanceof VertoPongError) return false;
|
|
19142
19330
|
if (error instanceof MediaTrackError) return false;
|
|
19331
|
+
if (error instanceof MediaAccessError) return error.fatal;
|
|
19143
19332
|
if (error instanceof RPCTimeoutError) return false;
|
|
19144
19333
|
if (error instanceof JSONRPCError && RECOVERABLE_RPC_CODES.has(error.code)) return false;
|
|
19145
19334
|
return true;
|
|
@@ -19165,10 +19354,10 @@ var CallFactory = class {
|
|
|
19165
19354
|
return {
|
|
19166
19355
|
vertoManager: new WebRTCVertoManager(callInstance, this.attachManager, this.deviceController, this.webRTCApiProvider, {
|
|
19167
19356
|
nodeId: options.nodeId,
|
|
19168
|
-
onError: (error) => {
|
|
19357
|
+
onError: (error, options$1) => {
|
|
19169
19358
|
const callError = {
|
|
19170
19359
|
kind: inferCallErrorKind(error),
|
|
19171
|
-
fatal: isFatalError(error),
|
|
19360
|
+
fatal: options$1?.fatal ?? isFatalError(error),
|
|
19172
19361
|
error,
|
|
19173
19362
|
callId: callInstance.id
|
|
19174
19363
|
};
|
|
@@ -19190,7 +19379,7 @@ var CallFactory = class {
|
|
|
19190
19379
|
//#endregion
|
|
19191
19380
|
//#region src/behaviors/Collection.ts
|
|
19192
19381
|
var import_cjs$10 = require_cjs();
|
|
19193
|
-
const logger$
|
|
19382
|
+
const logger$11 = getLogger();
|
|
19194
19383
|
var Fetcher = class {
|
|
19195
19384
|
constructor(endpoint, params, http) {
|
|
19196
19385
|
this.endpoint = endpoint;
|
|
@@ -19214,7 +19403,7 @@ var Fetcher = class {
|
|
|
19214
19403
|
this.hasMore = !!this.nextUrl;
|
|
19215
19404
|
return result.data.filter(this.filter).map(this.mapper);
|
|
19216
19405
|
}
|
|
19217
|
-
logger$
|
|
19406
|
+
logger$11.error("Failed to fetch entity");
|
|
19218
19407
|
return [];
|
|
19219
19408
|
}
|
|
19220
19409
|
async id(v) {
|
|
@@ -19290,7 +19479,7 @@ var EntityCollection = class extends Destroyable {
|
|
|
19290
19479
|
this._hasMore$.next(this.fetchController.hasMore ?? false);
|
|
19291
19480
|
this._loading$.next(false);
|
|
19292
19481
|
} catch (error) {
|
|
19293
|
-
logger$
|
|
19482
|
+
logger$11.error(`Failed to fetch initial collection data`, error);
|
|
19294
19483
|
this._hasMore$.next(this.fetchController.hasMore ?? false);
|
|
19295
19484
|
this._loading$.next(false);
|
|
19296
19485
|
this.onError?.(new CollectionFetchError("fetchMore", error));
|
|
@@ -19304,7 +19493,7 @@ var EntityCollection = class extends Destroyable {
|
|
|
19304
19493
|
if (data) this.upsertData(data);
|
|
19305
19494
|
return data;
|
|
19306
19495
|
} catch (error) {
|
|
19307
|
-
logger$
|
|
19496
|
+
logger$11.error(`Failed to fetch data for (${String(key)}:${String(value)}) :`, error);
|
|
19308
19497
|
this._loading$.next(false);
|
|
19309
19498
|
this.onError?.(new CollectionFetchError(`tryFetch(${String(key)})`, error));
|
|
19310
19499
|
}
|
|
@@ -19553,13 +19742,13 @@ var Address = class extends Destroyable {
|
|
|
19553
19742
|
//#endregion
|
|
19554
19743
|
//#region src/core/utils.ts
|
|
19555
19744
|
var import_cjs$8 = require_cjs();
|
|
19556
|
-
const logger$
|
|
19745
|
+
const logger$10 = getLogger();
|
|
19557
19746
|
const isRPCConnectResult = (e) => {
|
|
19558
|
-
logger$
|
|
19747
|
+
logger$10.debug("isRPCConnectResult check:", e);
|
|
19559
19748
|
if (!e || typeof e !== "object") return false;
|
|
19560
19749
|
const result = e;
|
|
19561
19750
|
const is = typeof result.identity === "string" && typeof result.protocol === "string" && typeof result.authorization === "object" && typeof result.authorization.jti === "string" && typeof result.authorization.project_id === "string" && typeof result.authorization.fabric_subscriber === "object";
|
|
19562
|
-
logger$
|
|
19751
|
+
logger$10.debug("isRPCConnectResult check result:", is);
|
|
19563
19752
|
return is;
|
|
19564
19753
|
};
|
|
19565
19754
|
var PendingRPC = class PendingRPC {
|
|
@@ -19568,7 +19757,7 @@ var PendingRPC = class PendingRPC {
|
|
|
19568
19757
|
}
|
|
19569
19758
|
constructor(request, responses$, options) {
|
|
19570
19759
|
this.id = v4_default();
|
|
19571
|
-
logger$
|
|
19760
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}: method:${request.method}] Creating PendingRPC`);
|
|
19572
19761
|
this.request = request;
|
|
19573
19762
|
const timeoutMs = options?.timeoutMs ?? PendingRPC.defaultTimeoutMs;
|
|
19574
19763
|
const signal = options?.signal;
|
|
@@ -19594,22 +19783,22 @@ var PendingRPC = class PendingRPC {
|
|
|
19594
19783
|
isSettled = true;
|
|
19595
19784
|
if (response.error) {
|
|
19596
19785
|
const rpcError = new JSONRPCError(response.error.code, response.error.message, response.error.data, void 0, request.id);
|
|
19597
|
-
logger$
|
|
19786
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with RPC error:`, rpcError);
|
|
19598
19787
|
reject(rpcError);
|
|
19599
19788
|
} else {
|
|
19600
|
-
logger$
|
|
19789
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Resolving promise with response:`, response);
|
|
19601
19790
|
resolve(response);
|
|
19602
19791
|
}
|
|
19603
19792
|
subscription.unsubscribe();
|
|
19604
19793
|
},
|
|
19605
19794
|
error: (error) => {
|
|
19606
|
-
logger$
|
|
19795
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with error:`, error);
|
|
19607
19796
|
isSettled = true;
|
|
19608
19797
|
reject(error);
|
|
19609
19798
|
subscription.unsubscribe();
|
|
19610
19799
|
},
|
|
19611
19800
|
complete: () => {
|
|
19612
|
-
logger$
|
|
19801
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Observable completed`);
|
|
19613
19802
|
if (!isSettled) reject(new RPCTimeoutError(request.id, timeoutMs));
|
|
19614
19803
|
subscription.unsubscribe();
|
|
19615
19804
|
}
|
|
@@ -19630,7 +19819,18 @@ var PendingRPC = class PendingRPC {
|
|
|
19630
19819
|
//#endregion
|
|
19631
19820
|
//#region src/managers/ClientSessionManager.ts
|
|
19632
19821
|
var import_cjs$7 = require_cjs();
|
|
19633
|
-
const logger$
|
|
19822
|
+
const logger$9 = getLogger();
|
|
19823
|
+
/**
|
|
19824
|
+
* Decide whether an error emitted on `call.errors$` during dial should
|
|
19825
|
+
* abort the dial. A non-fatal MediaAccessError means the call degraded to
|
|
19826
|
+
* receive-only and still connects — everything else rejects `dial()` with
|
|
19827
|
+
* the real cause.
|
|
19828
|
+
*
|
|
19829
|
+
* Pure function — exported for unit testing.
|
|
19830
|
+
*/
|
|
19831
|
+
function shouldAbortDial(callError) {
|
|
19832
|
+
return callError.fatal || !(callError.error instanceof MediaAccessError);
|
|
19833
|
+
}
|
|
19634
19834
|
const getAddressSearchURI = (options) => {
|
|
19635
19835
|
const to = options.to?.split("?")[0];
|
|
19636
19836
|
const from$9 = options.from?.startsWith("subscriber://") ? options.from.replace("subscriber://", "") : options.from;
|
|
@@ -19728,7 +19928,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19728
19928
|
try {
|
|
19729
19929
|
return await this.transport.execute(request, options);
|
|
19730
19930
|
} catch (error) {
|
|
19731
|
-
logger$
|
|
19931
|
+
logger$9.debug("[Session] Execute Error", error);
|
|
19732
19932
|
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
19733
19933
|
throw error;
|
|
19734
19934
|
}
|
|
@@ -19742,13 +19942,13 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19742
19942
|
return true;
|
|
19743
19943
|
}
|
|
19744
19944
|
setupMessageHandlers() {
|
|
19745
|
-
logger$
|
|
19945
|
+
logger$9.debug("[Session] Setting up message handlers");
|
|
19746
19946
|
this.subscribeTo(this.authStateEvent$, async (authStateEvent) => {
|
|
19747
|
-
logger$
|
|
19947
|
+
logger$9.debug("[Session] Authorization state event received:", authStateEvent);
|
|
19748
19948
|
try {
|
|
19749
19949
|
await this.updateAuthorizationStateInStorage(authStateEvent.authorization_state);
|
|
19750
19950
|
} catch (error) {
|
|
19751
|
-
logger$
|
|
19951
|
+
logger$9.error("[Session] Failed to handle authorization state update:", error);
|
|
19752
19952
|
this._errors$.next(new AuthStateHandlerError(error));
|
|
19753
19953
|
}
|
|
19754
19954
|
});
|
|
@@ -19756,29 +19956,29 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19756
19956
|
if (this._authState$.value.kind === "authenticated") this._authState$.next({ kind: "unauthenticated" });
|
|
19757
19957
|
});
|
|
19758
19958
|
this.subscribeTo(this.transport.connectionStatus$.pipe((0, import_cjs$7.filter)((status) => status === "connected"), (0, import_cjs$7.exhaustMap)(() => {
|
|
19759
|
-
logger$
|
|
19959
|
+
logger$9.debug("[Session] Connection established, initiating authentication");
|
|
19760
19960
|
return (0, import_cjs$7.from)(this.authenticate()).pipe((0, import_cjs$7.catchError)((error) => {
|
|
19761
19961
|
this.handleAuthenticationError(error).catch((err) => {
|
|
19762
|
-
logger$
|
|
19962
|
+
logger$9.error("[Session] Error handling authentication failure:", err);
|
|
19763
19963
|
});
|
|
19764
19964
|
return import_cjs$7.EMPTY;
|
|
19765
19965
|
}));
|
|
19766
19966
|
})), void 0);
|
|
19767
19967
|
this.subscribeTo(this.vertoInvite$, async (invite) => {
|
|
19768
|
-
logger$
|
|
19968
|
+
logger$9.debug("[Session] Verto invite received:", invite);
|
|
19769
19969
|
try {
|
|
19770
19970
|
await this.createInboundCall(invite);
|
|
19771
19971
|
} catch (error) {
|
|
19772
|
-
logger$
|
|
19972
|
+
logger$9.error("[Session] Error handling Verto invite:", error);
|
|
19773
19973
|
this._errors$.next(new VertoInviteHandlerError(error));
|
|
19774
19974
|
}
|
|
19775
19975
|
});
|
|
19776
19976
|
this.subscribeTo(this.vertoAttach$, async (attach) => {
|
|
19777
|
-
logger$
|
|
19977
|
+
logger$9.debug("[Session] Verto attach received:", attach);
|
|
19778
19978
|
try {
|
|
19779
19979
|
await this.handleVertoAttach(attach);
|
|
19780
19980
|
} catch (error) {
|
|
19781
|
-
logger$
|
|
19981
|
+
logger$9.error("[Session] Error handling Verto attach:", error);
|
|
19782
19982
|
this._errors$.next(new VertoAttachHandlerError(error));
|
|
19783
19983
|
}
|
|
19784
19984
|
});
|
|
@@ -19788,36 +19988,36 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19788
19988
|
const storedState = await this.storage.getItem(this.authorizationStateKey);
|
|
19789
19989
|
this.authorizationState$.next(storedState ?? void 0);
|
|
19790
19990
|
} catch (error) {
|
|
19791
|
-
logger$
|
|
19991
|
+
logger$9.error("Failed to retrieve authorization state from storage:", error);
|
|
19792
19992
|
this.authorizationState$.next(void 0);
|
|
19793
19993
|
}
|
|
19794
19994
|
}
|
|
19795
19995
|
async updateAuthorizationStateInStorage(authorizationState) {
|
|
19796
19996
|
if (!authorizationState) {
|
|
19797
|
-
logger$
|
|
19997
|
+
logger$9.debug("[Session] Removing authorization state from storage");
|
|
19798
19998
|
try {
|
|
19799
19999
|
await this.storage.removeItem(this.authorizationStateKey);
|
|
19800
20000
|
this.authorizationState$.next(void 0);
|
|
19801
20001
|
} catch (error) {
|
|
19802
|
-
logger$
|
|
20002
|
+
logger$9.error("Failed to remove authorization state from storage:", error);
|
|
19803
20003
|
throw error;
|
|
19804
20004
|
}
|
|
19805
20005
|
return;
|
|
19806
20006
|
}
|
|
19807
20007
|
try {
|
|
19808
|
-
logger$
|
|
20008
|
+
logger$9.debug("[Session] Updating authorization state in storage");
|
|
19809
20009
|
await this.storage.setItem(this.authorizationStateKey, authorizationState);
|
|
19810
20010
|
this.authorizationState$.next(authorizationState);
|
|
19811
20011
|
} catch (error) {
|
|
19812
|
-
logger$
|
|
20012
|
+
logger$9.error("Failed to retrieve authorization state from storage:", error);
|
|
19813
20013
|
throw error;
|
|
19814
20014
|
}
|
|
19815
20015
|
}
|
|
19816
20016
|
get authStateEvent$() {
|
|
19817
20017
|
return this.cachedObservable("authStateEvent$", () => this.signalingEvent$.pipe((0, import_cjs$7.tap)((msg) => {
|
|
19818
|
-
logger$
|
|
20018
|
+
logger$9.debug("[Session] Received incoming message:", msg);
|
|
19819
20019
|
}), filterAs(isSignalwireAuthorizationStateMetadata, "params"), (0, import_cjs$7.tap)((event) => {
|
|
19820
|
-
logger$
|
|
20020
|
+
logger$9.debug("[Session] Authorization state event received:", event.authorization_state);
|
|
19821
20021
|
})));
|
|
19822
20022
|
}
|
|
19823
20023
|
get signalingEvent$() {
|
|
@@ -19855,42 +20055,72 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19855
20055
|
await (0, import_cjs$7.firstValueFrom)(this.authenticated$.pipe((0, import_cjs$7.takeUntil)(this.destroyed$), (0, import_cjs$7.filter)(Boolean), (0, import_cjs$7.take)(1), (0, import_cjs$7.timeout)({ first: 15e3 })));
|
|
19856
20056
|
}
|
|
19857
20057
|
async handleAuthenticationError(error) {
|
|
19858
|
-
logger$
|
|
20058
|
+
logger$9.error("Authentication error:", error);
|
|
19859
20059
|
const isRecoverableAuthError = error instanceof JSONRPCError && (error.code === RPC_ERROR_REQUESTER_VALIDATION_FAILED || error.code === RPC_ERROR_INVALID_PARAMS || error.code === RPC_ERROR_AUTHENTICATION_FAILED);
|
|
19860
20060
|
const hasStoredState = await (0, import_cjs$7.firstValueFrom)(this.authorizationState$.pipe((0, import_cjs$7.take)(1))) !== void 0;
|
|
19861
20061
|
if (isRecoverableAuthError && hasStoredState) {
|
|
19862
|
-
logger$
|
|
20062
|
+
logger$9.debug("[Session] Recoverable auth error — cleaning up stored state and reconnecting fresh");
|
|
19863
20063
|
try {
|
|
19864
20064
|
await this.cleanupStoredConnectionParams();
|
|
19865
20065
|
} catch (cleanupError) {
|
|
19866
|
-
logger$
|
|
20066
|
+
logger$9.error("Failed to cleanup stored connection params:", cleanupError);
|
|
19867
20067
|
} finally {
|
|
19868
20068
|
this.transport.reconnect();
|
|
19869
20069
|
}
|
|
19870
20070
|
} else this._errors$.next(error);
|
|
19871
20071
|
}
|
|
20072
|
+
/**
|
|
20073
|
+
* Clear the resume state (authorization_state + protocol) only.
|
|
20074
|
+
*
|
|
20075
|
+
* This is the stale-auth-state recovery helper used by handleAuthError:
|
|
20076
|
+
* the server rejected a reconnect, so the resume state is discarded and a
|
|
20077
|
+
* fresh connect follows. Attach records are deliberately preserved — the
|
|
20078
|
+
* session lives on through the reconnect and reattachCalls() needs the
|
|
20079
|
+
* stored call references afterwards. Do NOT add detachAll() here.
|
|
20080
|
+
*
|
|
20081
|
+
* For public teardown (disconnect/destroy), use {@link teardownSessionState}
|
|
20082
|
+
* instead, which clears the attach records as well.
|
|
20083
|
+
*/
|
|
19872
20084
|
async cleanupStoredConnectionParams() {
|
|
19873
20085
|
await this.transport.setProtocol(void 0);
|
|
19874
20086
|
await this.updateAuthorizationStateInStorage(void 0);
|
|
19875
20087
|
this._authorization$.next(void 0);
|
|
19876
20088
|
}
|
|
20089
|
+
/**
|
|
20090
|
+
* Public-teardown helper for disconnect()/destroy(). Clears the resume
|
|
20091
|
+
* state (authorization_state + protocol) AND the attach records as one
|
|
20092
|
+
* atomic unit.
|
|
20093
|
+
*
|
|
20094
|
+
* The two stores are coupled: the backend only honors attach records
|
|
20095
|
+
* within the session identified by the resume state, so ending the
|
|
20096
|
+
* session must clear both. Clearing one without the other strands records
|
|
20097
|
+
* no future session can honor (disconnect) or revives a session the
|
|
20098
|
+
* developer explicitly ended (destroy).
|
|
20099
|
+
*
|
|
20100
|
+
* Distinct from {@link cleanupStoredConnectionParams}, which keeps the
|
|
20101
|
+
* attach records for the stale-auth-state recovery path.
|
|
20102
|
+
*/
|
|
20103
|
+
async teardownSessionState() {
|
|
20104
|
+
await this.cleanupStoredConnectionParams();
|
|
20105
|
+
await this.attachManager.detachAll();
|
|
20106
|
+
}
|
|
19877
20107
|
async updateAuthState(authorization_state) {
|
|
19878
20108
|
try {
|
|
19879
20109
|
await this.storage.setItem(this.authorizationStateKey, authorization_state);
|
|
19880
20110
|
} catch (error) {
|
|
19881
|
-
logger$
|
|
20111
|
+
logger$9.error("Failed to update authorization state in storage:", error);
|
|
19882
20112
|
this._errors$.next(new AuthStateHandlerError(error));
|
|
19883
20113
|
}
|
|
19884
20114
|
}
|
|
19885
20115
|
async reauthenticate(token, dpopToken, options) {
|
|
19886
|
-
logger$
|
|
20116
|
+
logger$9.debug("[Session] Re-authenticating session");
|
|
19887
20117
|
try {
|
|
19888
20118
|
let resolvedDpopToken = dpopToken;
|
|
19889
20119
|
if (!resolvedDpopToken && this.dpopManager?.initialized) try {
|
|
19890
20120
|
resolvedDpopToken = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
|
|
19891
20121
|
} catch (error) {
|
|
19892
20122
|
if (this.clientBound) throw error;
|
|
19893
|
-
logger$
|
|
20123
|
+
logger$9.warn("[Session] Failed to create DPoP proof for reauthenticate:", error);
|
|
19894
20124
|
}
|
|
19895
20125
|
const request = RPCReauthenticate({
|
|
19896
20126
|
project: this._authorization$.value?.project_id ?? "",
|
|
@@ -19898,24 +20128,24 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19898
20128
|
...resolvedDpopToken ? { dpop_token: resolvedDpopToken } : {}
|
|
19899
20129
|
});
|
|
19900
20130
|
await (0, import_cjs$7.lastValueFrom)((0, import_cjs$7.from)(this.transport.execute(request)).pipe(throwOnRPCError(), (0, import_cjs$7.take)(1), (0, import_cjs$7.catchError)((err) => {
|
|
19901
|
-
logger$
|
|
20131
|
+
logger$9.error("[Session] Re-authentication RPC failed:", err);
|
|
19902
20132
|
throw err;
|
|
19903
20133
|
})));
|
|
19904
20134
|
if (options?.clientBound) this._wasClientBound = true;
|
|
19905
|
-
logger$
|
|
20135
|
+
logger$9.debug("[Session] Re-authentication successful, updating stored auth state");
|
|
19906
20136
|
} catch (error) {
|
|
19907
|
-
logger$
|
|
20137
|
+
logger$9.error("[Session] Re-authentication failed:", error);
|
|
19908
20138
|
this._errors$.next(new AuthStateHandlerError(error));
|
|
19909
20139
|
throw error;
|
|
19910
20140
|
}
|
|
19911
20141
|
}
|
|
19912
20142
|
async authenticate() {
|
|
19913
|
-
logger$
|
|
20143
|
+
logger$9.debug("[Session] Starting authentication process");
|
|
19914
20144
|
const persistedParams = await (0, import_cjs$7.firstValueFrom)((0, import_cjs$7.combineLatest)({
|
|
19915
20145
|
protocol: this.transport.protocol$,
|
|
19916
20146
|
authorization_state: this.authorizationState$
|
|
19917
20147
|
}).pipe((0, import_cjs$7.take)(1)));
|
|
19918
|
-
logger$
|
|
20148
|
+
logger$9.debug("[Session] Persisted params:\n", {
|
|
19919
20149
|
protocol: persistedParams.protocol,
|
|
19920
20150
|
authStateLength: persistedParams.authorization_state?.length
|
|
19921
20151
|
});
|
|
@@ -19923,16 +20153,16 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19923
20153
|
const storedToken = this.getCredential().token;
|
|
19924
20154
|
const isReconnect = hasReconnectState && storedToken;
|
|
19925
20155
|
let dpopToken;
|
|
19926
|
-
if (isReconnect) logger$
|
|
20156
|
+
if (isReconnect) logger$9.debug("[Session] Reconnecting with stored jwt_token + authorization_state");
|
|
19927
20157
|
else if (this.onBeforeReconnect && this.clientBound) {
|
|
19928
|
-
logger$
|
|
20158
|
+
logger$9.debug("[Session] Refreshing credentials before fresh connect");
|
|
19929
20159
|
await this.onBeforeReconnect();
|
|
19930
20160
|
}
|
|
19931
20161
|
if ((!isReconnect || this.clientBound) && this.dpopManager?.initialized) try {
|
|
19932
20162
|
dpopToken = await this.dpopManager.createRpcProof({ method: "signalwire.connect" });
|
|
19933
20163
|
} catch (error) {
|
|
19934
20164
|
if (this.clientBound) throw error;
|
|
19935
|
-
logger$
|
|
20165
|
+
logger$9.warn("[Session] Failed to create DPoP proof for connect, proceeding without:", error);
|
|
19936
20166
|
}
|
|
19937
20167
|
const rpcConnectRequest = RPCConnect({
|
|
19938
20168
|
authentication: isReconnect ? { jwt_token: storedToken } : this.authentication,
|
|
@@ -19949,12 +20179,12 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19949
20179
|
} : {}
|
|
19950
20180
|
});
|
|
19951
20181
|
const response = await (0, import_cjs$7.lastValueFrom)((0, import_cjs$7.from)(this.transport.execute(rpcConnectRequest)).pipe(throwOnRPCError(), (0, import_cjs$7.map)((res) => res.result), (0, import_cjs$7.filter)(isRPCConnectResult), (0, import_cjs$7.tap)(() => {
|
|
19952
|
-
logger$
|
|
20182
|
+
logger$9.debug("[Session] Response passed filter, processing authentication result");
|
|
19953
20183
|
}), (0, import_cjs$7.take)(1), (0, import_cjs$7.catchError)((err) => {
|
|
19954
|
-
logger$
|
|
20184
|
+
logger$9.error("[Session] Authentication RPC failed:", err);
|
|
19955
20185
|
throw err;
|
|
19956
20186
|
})));
|
|
19957
|
-
logger$
|
|
20187
|
+
logger$9.debug("[Session] Processing authentication result:", {
|
|
19958
20188
|
hasProtocol: !!response.protocol,
|
|
19959
20189
|
hasAuthorization: !!response.authorization,
|
|
19960
20190
|
hasIceServers: !!response.ice_servers
|
|
@@ -19963,12 +20193,12 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19963
20193
|
this._authorization$.next(response.authorization);
|
|
19964
20194
|
this._iceServers$.next(response.ice_servers ?? []);
|
|
19965
20195
|
this._authState$.next({ kind: "authenticated" });
|
|
19966
|
-
logger$
|
|
20196
|
+
logger$9.debug("[Session] Authentication completed successfully");
|
|
19967
20197
|
}
|
|
19968
20198
|
async disconnect() {
|
|
19969
20199
|
this.transport.disconnect();
|
|
19970
20200
|
this._authState$.next({ kind: "unauthenticated" });
|
|
19971
|
-
await this.
|
|
20201
|
+
await this.teardownSessionState();
|
|
19972
20202
|
}
|
|
19973
20203
|
async createInboundCall(invite) {
|
|
19974
20204
|
const callSession = await this.createCall({
|
|
@@ -19999,11 +20229,11 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19999
20229
|
async handleVertoAttach(attach) {
|
|
20000
20230
|
const { callID } = attach;
|
|
20001
20231
|
if (callID in this._calls$.value) {
|
|
20002
|
-
logger$
|
|
20232
|
+
logger$9.debug(`[Session] Verto attach for existing call ${callID}, deferring to per-call handler`);
|
|
20003
20233
|
return;
|
|
20004
20234
|
}
|
|
20005
20235
|
const storedOptions = await this.attachManager.consumePendingAttachment(callID);
|
|
20006
|
-
logger$
|
|
20236
|
+
logger$9.debug(`[Session] Creating reattached call for callID: ${callID}`);
|
|
20007
20237
|
const callSession = await this.createCall({
|
|
20008
20238
|
nodeId: attach.node_id,
|
|
20009
20239
|
callId: callID,
|
|
@@ -20027,14 +20257,14 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
20027
20257
|
to: destinationURI,
|
|
20028
20258
|
...options
|
|
20029
20259
|
});
|
|
20030
|
-
await (0, import_cjs$7.firstValueFrom)((0, import_cjs$7.race)(callSession.selfId$.pipe((0, import_cjs$7.filter)((id) => Boolean(id)), (0, import_cjs$7.take)(1), (0, import_cjs$7.timeout)(this.callCreateTimeout)), callSession.errors$.pipe((0, import_cjs$7.take)(1), (0, import_cjs$7.switchMap)((callError) => (0, import_cjs$7.throwError)(() => callError.error)))));
|
|
20260
|
+
await (0, import_cjs$7.firstValueFrom)((0, import_cjs$7.race)(callSession.selfId$.pipe((0, import_cjs$7.filter)((id) => Boolean(id)), (0, import_cjs$7.take)(1), (0, import_cjs$7.timeout)(this.callCreateTimeout)), callSession.errors$.pipe((0, import_cjs$7.filter)(shouldAbortDial), (0, import_cjs$7.take)(1), (0, import_cjs$7.switchMap)((callError) => (0, import_cjs$7.throwError)(() => callError.error)))));
|
|
20031
20261
|
this._calls$.next({
|
|
20032
20262
|
[`${callSession.id}`]: callSession,
|
|
20033
20263
|
...this._calls$.value
|
|
20034
20264
|
});
|
|
20035
20265
|
return callSession;
|
|
20036
20266
|
} catch (error) {
|
|
20037
|
-
logger$
|
|
20267
|
+
logger$9.error("[Session] Error creating outbound call:", error);
|
|
20038
20268
|
callSession?.destroy();
|
|
20039
20269
|
const callError = new CallCreateError(error instanceof import_cjs$7.TimeoutError ? "Call create timeout" : "Call creation failed", error, "outbound");
|
|
20040
20270
|
this._errors$.next(callError);
|
|
@@ -20052,7 +20282,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
20052
20282
|
address = this._directory.get(addressId);
|
|
20053
20283
|
if (!address) throw new DependencyError(`Address ID: ${addressId} not found`);
|
|
20054
20284
|
} catch {
|
|
20055
|
-
logger$
|
|
20285
|
+
logger$9.warn(`[Session] Directory lookup failed for ${addressURI}, proceeding with raw URI`);
|
|
20056
20286
|
}
|
|
20057
20287
|
const callSession = this.callFactory.createCall(address, { ...options });
|
|
20058
20288
|
this.subscribeTo(callSession.status$.pipe((0, import_cjs$7.filter)((status) => status === "destroyed"), (0, import_cjs$7.take)(1)), () => {
|
|
@@ -20061,7 +20291,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
20061
20291
|
});
|
|
20062
20292
|
return callSession;
|
|
20063
20293
|
} catch (error) {
|
|
20064
|
-
logger$
|
|
20294
|
+
logger$9.error("[Session] Error creating call session:", error);
|
|
20065
20295
|
throw new CallCreateError("Call create error", error, options.initOffer ? "inbound" : "outbound");
|
|
20066
20296
|
}
|
|
20067
20297
|
}
|
|
@@ -20110,7 +20340,7 @@ const isString = (obj) => typeof obj === "string";
|
|
|
20110
20340
|
//#endregion
|
|
20111
20341
|
//#region src/managers/ConversationsManager.ts
|
|
20112
20342
|
var import_cjs$6 = require_cjs();
|
|
20113
|
-
const logger$
|
|
20343
|
+
const logger$8 = getLogger();
|
|
20114
20344
|
var ConversationMessagesFetcher = class extends Fetcher {
|
|
20115
20345
|
constructor(groupId, http) {
|
|
20116
20346
|
super(`/api/fabric/conversations/${groupId}/messages`, "page_size=100", http);
|
|
@@ -20150,13 +20380,13 @@ var ConversationsManager = class {
|
|
|
20150
20380
|
}
|
|
20151
20381
|
throw new ConversationError("Join Failed - Unexpected response");
|
|
20152
20382
|
} catch (error) {
|
|
20153
|
-
logger$
|
|
20383
|
+
logger$8.error("[ConversationsManager] Failed to join conversation:", error);
|
|
20154
20384
|
throw error;
|
|
20155
20385
|
}
|
|
20156
20386
|
}
|
|
20157
20387
|
async getConversationMessageCollection(addressId) {
|
|
20158
20388
|
const groupId = this.groupIds.get(addressId) ?? await this.join(addressId);
|
|
20159
|
-
return Promise.resolve(new ConversationMessageCollection(groupId, this.clientSession.signalingEvent$.pipe(filterAs(isConversationMessageMetadata, "params"), (0, import_cjs$6.tap)((event) => logger$
|
|
20389
|
+
return Promise.resolve(new ConversationMessageCollection(groupId, this.clientSession.signalingEvent$.pipe(filterAs(isConversationMessageMetadata, "params"), (0, import_cjs$6.tap)((event) => logger$8.debug("[ConversationsManager ] Conversation Event:", event)), (0, import_cjs$6.map)((params) => ({ ...params }))), this.http, this.onError));
|
|
20160
20390
|
}
|
|
20161
20391
|
async sendText(text, destinationAddressId) {
|
|
20162
20392
|
const groupId = this.groupIds.get(destinationAddressId) ?? await this.join(destinationAddressId);
|
|
@@ -20173,7 +20403,7 @@ var ConversationsManager = class {
|
|
|
20173
20403
|
})).ok) return;
|
|
20174
20404
|
throw new ConversationError("Send Text Failed - Unexpected response");
|
|
20175
20405
|
} catch (error) {
|
|
20176
|
-
logger$
|
|
20406
|
+
logger$8.error("[ConversationsManager] Failed to send text message:", error);
|
|
20177
20407
|
throw error;
|
|
20178
20408
|
}
|
|
20179
20409
|
}
|
|
@@ -20182,7 +20412,7 @@ var ConversationsManager = class {
|
|
|
20182
20412
|
//#endregion
|
|
20183
20413
|
//#region src/managers/DeviceTokenManager.ts
|
|
20184
20414
|
var import_cjs$5 = require_cjs();
|
|
20185
|
-
const logger$
|
|
20415
|
+
const logger$7 = getLogger();
|
|
20186
20416
|
/**
|
|
20187
20417
|
* Resolves the token expiry timestamp (epoch seconds) using a 3-tier priority chain:
|
|
20188
20418
|
* 1. `data.expires_at` — server-provided absolute timestamp
|
|
@@ -20192,7 +20422,7 @@ const logger$6 = getLogger();
|
|
|
20192
20422
|
function resolveExpiresAt(data) {
|
|
20193
20423
|
if (data.expires_at) return data.expires_at;
|
|
20194
20424
|
if (data.expires_in) return Math.floor(Date.now() / 1e3) + data.expires_in;
|
|
20195
|
-
logger$
|
|
20425
|
+
logger$7.warn("[DeviceToken] Could not determine token expiry, using default");
|
|
20196
20426
|
return Math.floor(Date.now() / 1e3) + DEVICE_TOKEN_DEFAULT_EXPIRE_IN;
|
|
20197
20427
|
}
|
|
20198
20428
|
/**
|
|
@@ -20225,11 +20455,12 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20225
20455
|
this.getCredential = getCredential;
|
|
20226
20456
|
this._currentToken$ = this.createBehaviorSubject(null);
|
|
20227
20457
|
this._refreshInProgress = false;
|
|
20458
|
+
this._paused = false;
|
|
20228
20459
|
this._effectiveExpireIn = DEVICE_TOKEN_DEFAULT_EXPIRE_IN;
|
|
20229
20460
|
this.subscribeTo(this._currentToken$.pipe((0, import_cjs$5.filter)(Boolean), (0, import_cjs$5.switchMap)((tokenData) => {
|
|
20230
20461
|
const expiresAt = resolveExpiresAt(tokenData);
|
|
20231
20462
|
const refreshIn = Math.max(expiresAt * 1e3 - Date.now() - DEVICE_TOKEN_REFRESH_BUFFER_MS, 1e3);
|
|
20232
|
-
logger$
|
|
20463
|
+
logger$7.debug(`[DeviceToken] Scheduling Client Bound SAT refresh in ${refreshIn}ms`);
|
|
20233
20464
|
return (0, import_cjs$5.timer)(refreshIn);
|
|
20234
20465
|
})), () => {
|
|
20235
20466
|
this.executeRefresh();
|
|
@@ -20243,7 +20474,12 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20243
20474
|
* Activates the Client Bound SAT flow when the user's token has
|
|
20244
20475
|
* `sat:refresh` scope.
|
|
20245
20476
|
*
|
|
20246
|
-
*
|
|
20477
|
+
* Returns an {@link ActivationResult} indicating whether the manager
|
|
20478
|
+
* took ownership of refresh duties. The caller must use the `activated`
|
|
20479
|
+
* boolean to decide whether to keep its own refresh path armed — when
|
|
20480
|
+
* `activated` is `false`, the caller is responsible for refresh.
|
|
20481
|
+
*
|
|
20482
|
+
* Steps on success:
|
|
20247
20483
|
* 1. Check user's `sat_claims` for `sat:refresh` scope
|
|
20248
20484
|
* 2. Call `/api/fabric/subscriber/devices/token` with a DPoP proof
|
|
20249
20485
|
* 3. Reauthenticate the session with the Client Bound SAT + DPoP proof
|
|
@@ -20252,32 +20488,51 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20252
20488
|
async activate(user, session, updateCredential) {
|
|
20253
20489
|
const { satClaims } = user;
|
|
20254
20490
|
if (!satClaims?.scope?.includes(SAT_REFRESH_SCOPE)) {
|
|
20255
|
-
logger$
|
|
20256
|
-
return
|
|
20491
|
+
logger$7.debug("[DeviceToken] No sat:refresh scope, skipping Client Bound SAT activation");
|
|
20492
|
+
return {
|
|
20493
|
+
activated: false,
|
|
20494
|
+
reason: "no-scope"
|
|
20495
|
+
};
|
|
20257
20496
|
}
|
|
20258
20497
|
this._session = session;
|
|
20259
20498
|
this._updateCredential = updateCredential;
|
|
20260
20499
|
try {
|
|
20500
|
+
const cached = this._currentToken$.value;
|
|
20501
|
+
if (cached && this.isTokenFresh(cached)) {
|
|
20502
|
+
logger$7.debug("[DeviceToken] Reusing cached Client Bound SAT — skipping /devices/token");
|
|
20503
|
+
const rpcProof$1 = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
|
|
20504
|
+
await session.reauthenticate(cached.token, rpcProof$1, { clientBound: true });
|
|
20505
|
+
updateCredential({ token: cached.token });
|
|
20506
|
+
return { activated: true };
|
|
20507
|
+
}
|
|
20261
20508
|
const tokenData = await this.obtainToken();
|
|
20262
20509
|
if (!tokenData.expires_at && !tokenData.expires_in && satClaims.expires_at) tokenData.expires_at = satClaims.expires_at;
|
|
20263
20510
|
this._effectiveExpireIn = resolveExpireIn(tokenData);
|
|
20264
20511
|
const rpcProof = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
|
|
20265
20512
|
await session.reauthenticate(tokenData.token, rpcProof, { clientBound: true });
|
|
20266
20513
|
updateCredential({ token: tokenData.token });
|
|
20267
|
-
logger$
|
|
20514
|
+
logger$7.info("[DeviceToken] Client Bound SAT activated successfully");
|
|
20268
20515
|
this._currentToken$.next(tokenData);
|
|
20516
|
+
return { activated: true };
|
|
20269
20517
|
} catch (error) {
|
|
20270
|
-
logger$
|
|
20518
|
+
logger$7.error("[DeviceToken] Failed to activate Client Bound SAT:", error);
|
|
20271
20519
|
this.errorHandler(new DPoPInitError(error, "Failed to activate Client Bound SAT"));
|
|
20272
|
-
|
|
20273
|
-
|
|
20274
|
-
|
|
20275
|
-
|
|
20276
|
-
expires_at: expiresAt
|
|
20277
|
-
});
|
|
20520
|
+
return {
|
|
20521
|
+
activated: false,
|
|
20522
|
+
reason: "endpoint-failed"
|
|
20523
|
+
};
|
|
20278
20524
|
}
|
|
20279
20525
|
}
|
|
20280
20526
|
/**
|
|
20527
|
+
* Returns true when the cached token has enough headroom before expiry to
|
|
20528
|
+
* be safely reused on reactivation. The headroom matches the refresh
|
|
20529
|
+
* buffer, so a token within the refresh window is treated as stale (the
|
|
20530
|
+
* reactive pipeline is about to refresh it anyway).
|
|
20531
|
+
*/
|
|
20532
|
+
isTokenFresh(token) {
|
|
20533
|
+
return resolveExpiresAt(token) * 1e3 - Date.now() > DEVICE_TOKEN_REFRESH_BUFFER_MS;
|
|
20534
|
+
}
|
|
20535
|
+
/**
|
|
20281
20536
|
* Obtains a Client Bound SAT from `/api/fabric/subscriber/devices/token`.
|
|
20282
20537
|
* Returns the full {@link DeviceTokenResponse} including expiry metadata.
|
|
20283
20538
|
*/
|
|
@@ -20307,7 +20562,7 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20307
20562
|
* handled by the reactive pipeline).
|
|
20308
20563
|
*/
|
|
20309
20564
|
async refreshToken(session, currentToken, updateCredential) {
|
|
20310
|
-
logger$
|
|
20565
|
+
logger$7.debug("[DeviceToken] Refreshing Client Bound SAT");
|
|
20311
20566
|
const dpopProof = await this.dpopManager.createHttpProof({
|
|
20312
20567
|
method: "POST",
|
|
20313
20568
|
uri: DEVICE_REFRESH_ENDPOINT,
|
|
@@ -20329,7 +20584,7 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20329
20584
|
const rpcProof = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
|
|
20330
20585
|
await session.reauthenticate(data.token, rpcProof);
|
|
20331
20586
|
updateCredential({ token: data.token });
|
|
20332
|
-
logger$
|
|
20587
|
+
logger$7.info("[DeviceToken] Client Bound SAT refreshed successfully");
|
|
20333
20588
|
return data;
|
|
20334
20589
|
}
|
|
20335
20590
|
/**
|
|
@@ -20338,18 +20593,22 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20338
20593
|
* On all retries exhausted, emits to `errorHandler`.
|
|
20339
20594
|
*/
|
|
20340
20595
|
async executeRefresh() {
|
|
20596
|
+
if (this._paused) {
|
|
20597
|
+
logger$7.debug("[DeviceToken] Manager paused, skipping refresh");
|
|
20598
|
+
return;
|
|
20599
|
+
}
|
|
20341
20600
|
if (this._refreshInProgress) {
|
|
20342
|
-
logger$
|
|
20601
|
+
logger$7.debug("[DeviceToken] Refresh already in progress, skipping");
|
|
20343
20602
|
return;
|
|
20344
20603
|
}
|
|
20345
20604
|
const session = this._session;
|
|
20346
20605
|
const updateCredential = this._updateCredential;
|
|
20347
20606
|
if (!session || !updateCredential) {
|
|
20348
|
-
logger$
|
|
20607
|
+
logger$7.warn("[DeviceToken] Cannot refresh: session or updateCredential not set");
|
|
20349
20608
|
return;
|
|
20350
20609
|
}
|
|
20351
20610
|
if (!session.authenticated) {
|
|
20352
|
-
logger$
|
|
20611
|
+
logger$7.debug("[DeviceToken] Session not authenticated, deferring refresh");
|
|
20353
20612
|
return;
|
|
20354
20613
|
}
|
|
20355
20614
|
this._refreshInProgress = true;
|
|
@@ -20359,7 +20618,7 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20359
20618
|
const newTokenData = await this.retryRefresh(session, currentToken, updateCredential);
|
|
20360
20619
|
this._currentToken$.next(newTokenData);
|
|
20361
20620
|
} catch (error) {
|
|
20362
|
-
logger$
|
|
20621
|
+
logger$7.error("[DeviceToken] Automatic Client Bound SAT refresh failed:", error);
|
|
20363
20622
|
this.errorHandler(error instanceof TokenRefreshError ? error : new TokenRefreshError("Automatic token refresh failed", error));
|
|
20364
20623
|
} finally {
|
|
20365
20624
|
this._refreshInProgress = false;
|
|
@@ -20377,18 +20636,215 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20377
20636
|
lastError = error;
|
|
20378
20637
|
if (attempt < DEVICE_TOKEN_REFRESH_MAX_RETRIES - 1) {
|
|
20379
20638
|
const delay$1 = DEVICE_TOKEN_REFRESH_RETRY_BASE_MS * Math.pow(2, attempt);
|
|
20380
|
-
logger$
|
|
20639
|
+
logger$7.warn(`[DeviceToken] Refresh attempt ${attempt + 1} failed, retrying in ${delay$1}ms`);
|
|
20381
20640
|
await new Promise((resolve) => setTimeout(resolve, delay$1));
|
|
20382
20641
|
}
|
|
20383
20642
|
}
|
|
20384
20643
|
throw lastError instanceof Error ? lastError : new TokenRefreshError("All refresh retries exhausted", lastError);
|
|
20385
20644
|
}
|
|
20645
|
+
/**
|
|
20646
|
+
* Stops the reactive refresh pipeline from firing. Use when the underlying
|
|
20647
|
+
* session is being torn down (e.g., during {@link SignalWire.disconnect})
|
|
20648
|
+
* so a scheduled refresh cannot fire against a destroyed session.
|
|
20649
|
+
*
|
|
20650
|
+
* The manager's state (cached token, effective TTL, subscriptions) is
|
|
20651
|
+
* preserved — call {@link resume} to re-enable firing after reconnect.
|
|
20652
|
+
*/
|
|
20653
|
+
pause() {
|
|
20654
|
+
this._paused = true;
|
|
20655
|
+
}
|
|
20656
|
+
/** Re-enables the reactive refresh pipeline after a previous {@link pause}. */
|
|
20657
|
+
resume() {
|
|
20658
|
+
this._paused = false;
|
|
20659
|
+
}
|
|
20386
20660
|
/** Cleans up the manager, cancelling the reactive pipeline and all subscriptions. */
|
|
20387
20661
|
destroy() {
|
|
20388
20662
|
super.destroy();
|
|
20389
20663
|
}
|
|
20390
20664
|
};
|
|
20391
20665
|
|
|
20666
|
+
//#endregion
|
|
20667
|
+
//#region src/managers/CredentialRefreshCoordinator.ts
|
|
20668
|
+
const logger$6 = getLogger();
|
|
20669
|
+
const defaultDeviceTokenManagerFactory = (dpopManager, http, errorHandler, getCredential) => new DeviceTokenManager(dpopManager, http, errorHandler, getCredential);
|
|
20670
|
+
/**
|
|
20671
|
+
* Centralizes credential-refresh ownership across the two competing
|
|
20672
|
+
* mechanisms — developer-provided `CredentialProvider.refresh()` and the
|
|
20673
|
+
* Client Bound SAT path via {@link DeviceTokenManager}.
|
|
20674
|
+
*
|
|
20675
|
+
* Maintains the invariant: **at most one refresh mechanism is armed at a
|
|
20676
|
+
* time, and at least one is armed whenever the current credential has an
|
|
20677
|
+
* `expiry_at`**.
|
|
20678
|
+
*
|
|
20679
|
+
* Replaces the previous design where refresh state was distributed across
|
|
20680
|
+
* `SignalWire` (timer field, scheduler method, activation helper) and
|
|
20681
|
+
* `DeviceTokenManager` (reactive pipeline). Centralizing the invariant in
|
|
20682
|
+
* one component eliminates the bug class that produced issue #19074.
|
|
20683
|
+
*
|
|
20684
|
+
* Race-safety:
|
|
20685
|
+
* - `_activating` flag prevents overlapping `activate()` calls from racing.
|
|
20686
|
+
* - `_activationGeneration` lets late resolutions detect they've been
|
|
20687
|
+
* preempted by a newer activation (e.g., reconnect during in-flight
|
|
20688
|
+
* `obtainToken`).
|
|
20689
|
+
*/
|
|
20690
|
+
var CredentialRefreshCoordinator = class extends Destroyable {
|
|
20691
|
+
constructor(dpopManager, deps) {
|
|
20692
|
+
super();
|
|
20693
|
+
this.deps = deps;
|
|
20694
|
+
this._activating = false;
|
|
20695
|
+
this._activationGeneration = 0;
|
|
20696
|
+
if (dpopManager?.initialized) this._deviceTokenManager = (deps.deviceTokenManagerFactory ?? defaultDeviceTokenManagerFactory)(dpopManager, deps.http, (error) => deps.notifier.onError(error), () => deps.store.read());
|
|
20697
|
+
}
|
|
20698
|
+
/** True when the Client Bound SAT path is available (DPoP initialized). */
|
|
20699
|
+
get clientBoundSATAvailable() {
|
|
20700
|
+
return this._deviceTokenManager !== void 0;
|
|
20701
|
+
}
|
|
20702
|
+
/** True when the developer-provided refresh timer is currently armed. */
|
|
20703
|
+
get developerRefreshArmed() {
|
|
20704
|
+
return this._developerTimerId !== void 0;
|
|
20705
|
+
}
|
|
20706
|
+
/**
|
|
20707
|
+
* Arms the developer-provided refresh timer to fire shortly before
|
|
20708
|
+
* `expiresAt`. Replaces any previously scheduled developer refresh.
|
|
20709
|
+
*
|
|
20710
|
+
* Idempotent — multiple calls just reschedule. On retry exhaustion,
|
|
20711
|
+
* invokes `deps.onRefreshExhausted` so the orchestrator can disconnect.
|
|
20712
|
+
*/
|
|
20713
|
+
scheduleDeveloperRefresh(provider, expiresAt, attempt = 0) {
|
|
20714
|
+
if (this._developerTimerId !== void 0) clearTimeout(this._developerTimerId);
|
|
20715
|
+
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);
|
|
20716
|
+
this._developerTimerId = setTimeout(async () => {
|
|
20717
|
+
try {
|
|
20718
|
+
if (!provider.refresh) throw new InvalidCredentialsError("Credential provider does not support refresh");
|
|
20719
|
+
const newCredentials = await provider.refresh();
|
|
20720
|
+
this.deps.store.write(newCredentials);
|
|
20721
|
+
this.deps.store.persist(newCredentials);
|
|
20722
|
+
logger$6.info("[Coordinator] Credentials refreshed successfully.");
|
|
20723
|
+
if (newCredentials.expiry_at) this.scheduleDeveloperRefresh(provider, newCredentials.expiry_at, 0);
|
|
20724
|
+
} catch (error) {
|
|
20725
|
+
const nextAttempt = attempt + 1;
|
|
20726
|
+
logger$6.error(`[Coordinator] Credential refresh failed (attempt ${nextAttempt}/${CREDENTIAL_REFRESH_MAX_RETRIES}):`, error);
|
|
20727
|
+
this.deps.notifier.onError(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
20728
|
+
if (nextAttempt < CREDENTIAL_REFRESH_MAX_RETRIES) this.scheduleDeveloperRefresh(provider, expiresAt, nextAttempt);
|
|
20729
|
+
else {
|
|
20730
|
+
logger$6.error("[Coordinator] Credential refresh exhausted all retries. Disconnecting.");
|
|
20731
|
+
this.deps.notifier.onError(new TokenRefreshError("Credential refresh failed after max retries"));
|
|
20732
|
+
this.deps.notifier.onRefreshExhausted();
|
|
20733
|
+
}
|
|
20734
|
+
}
|
|
20735
|
+
}, refreshInterval);
|
|
20736
|
+
}
|
|
20737
|
+
/**
|
|
20738
|
+
* Cancels any scheduled developer-provided refresh. Idempotent.
|
|
20739
|
+
*
|
|
20740
|
+
* @internal Used by the coordinator's own activation flow. External
|
|
20741
|
+
* callers should use {@link suspend} for disconnect-time quiescence —
|
|
20742
|
+
* `suspend()` also pauses the internal Client Bound SAT pipeline.
|
|
20743
|
+
*/
|
|
20744
|
+
cancelDeveloperRefresh() {
|
|
20745
|
+
if (this._developerTimerId !== void 0) {
|
|
20746
|
+
clearTimeout(this._developerTimerId);
|
|
20747
|
+
this._developerTimerId = void 0;
|
|
20748
|
+
}
|
|
20749
|
+
}
|
|
20750
|
+
/**
|
|
20751
|
+
* Suspends both refresh paths — cancels the developer timer and pauses
|
|
20752
|
+
* the internal reactive pipeline. Use when the underlying session is
|
|
20753
|
+
* being torn down (e.g., {@link SignalWire.disconnect}). The next
|
|
20754
|
+
* {@link activate} call re-enables the internal pipeline.
|
|
20755
|
+
*
|
|
20756
|
+
* The internal manager's cached token survives — see
|
|
20757
|
+
* {@link DeviceTokenManager.pause} — so a subsequent reconnect can
|
|
20758
|
+
* skip the `/devices/token` exchange entirely.
|
|
20759
|
+
*/
|
|
20760
|
+
suspend() {
|
|
20761
|
+
this.cancelDeveloperRefresh();
|
|
20762
|
+
this._deviceTokenManager?.pause();
|
|
20763
|
+
}
|
|
20764
|
+
/**
|
|
20765
|
+
* Asks the Client Bound SAT path to take over refresh. If it accepts,
|
|
20766
|
+
* the developer-provided timer (if any) is cancelled. If it declines, the
|
|
20767
|
+
* developer timer remains armed and a `credential_refresh_fallback`
|
|
20768
|
+
* warning is emitted.
|
|
20769
|
+
*
|
|
20770
|
+
* **Idempotent** — re-entrant calls during an in-flight activation are
|
|
20771
|
+
* dropped. Use `_activationGeneration` to detect stale resolutions
|
|
20772
|
+
* (e.g., a reconnect-triggered activate() that races with an earlier one).
|
|
20773
|
+
*/
|
|
20774
|
+
async activate(user, session) {
|
|
20775
|
+
if (this._activating) {
|
|
20776
|
+
logger$6.debug("[Coordinator] activate() in flight; ignoring re-entrant call");
|
|
20777
|
+
return;
|
|
20778
|
+
}
|
|
20779
|
+
if (!this._deviceTokenManager) return;
|
|
20780
|
+
this._deviceTokenManager.resume();
|
|
20781
|
+
const generation = ++this._activationGeneration;
|
|
20782
|
+
this._activating = true;
|
|
20783
|
+
try {
|
|
20784
|
+
const result = await this.withActivationTimeout(this._deviceTokenManager.activate(user, session, (cred) => this.deps.store.merge(cred)));
|
|
20785
|
+
if (generation !== this._activationGeneration) {
|
|
20786
|
+
logger$6.debug("[Coordinator] activate() result discarded (preempted by newer activation)");
|
|
20787
|
+
return;
|
|
20788
|
+
}
|
|
20789
|
+
if (result.activated) {
|
|
20790
|
+
this.cancelDeveloperRefresh();
|
|
20791
|
+
logger$6.debug("[Coordinator] Developer refresh disabled — Client Bound SAT owns refresh");
|
|
20792
|
+
return;
|
|
20793
|
+
}
|
|
20794
|
+
logger$6.warn(`[SignalWire] [SW-REFRESH-FALLBACK] Client Bound SAT declined (reason=${result.reason}); using developer-provided refresh handler.`);
|
|
20795
|
+
this.deps.notifier.onWarning({
|
|
20796
|
+
code: "credential_refresh_fallback",
|
|
20797
|
+
source: "CredentialProvider",
|
|
20798
|
+
reason: result.reason,
|
|
20799
|
+
message: `Client Bound SAT activation declined (${result.reason}); using developer-provided refresh.`
|
|
20800
|
+
});
|
|
20801
|
+
} finally {
|
|
20802
|
+
this._activating = false;
|
|
20803
|
+
}
|
|
20804
|
+
}
|
|
20805
|
+
destroy() {
|
|
20806
|
+
this.cancelDeveloperRefresh();
|
|
20807
|
+
this._deviceTokenManager?.destroy();
|
|
20808
|
+
this._deviceTokenManager = void 0;
|
|
20809
|
+
super.destroy();
|
|
20810
|
+
}
|
|
20811
|
+
/**
|
|
20812
|
+
* Races the manager's `activate()` against a hard timeout. A wedged HTTP
|
|
20813
|
+
* layer (e.g., proxy issues) could otherwise hang the activation
|
|
20814
|
+
* indefinitely, leaving the session with no refresh mechanism while the
|
|
20815
|
+
* `_activating` guard blocks subsequent retries.
|
|
20816
|
+
*
|
|
20817
|
+
* On timeout, the manager is paused immediately. The inner `activate()`
|
|
20818
|
+
* may still complete in the background and emit to `_currentToken$`,
|
|
20819
|
+
* which would normally arm the reactive refresh pipeline; pausing
|
|
20820
|
+
* prevents that pipeline from firing while the developer refresh path
|
|
20821
|
+
* is the active mechanism — preserving the "at most one mechanism
|
|
20822
|
+
* armed" invariant. The next `activate()` call resumes the manager.
|
|
20823
|
+
*/
|
|
20824
|
+
async withActivationTimeout(inner) {
|
|
20825
|
+
return new Promise((resolve) => {
|
|
20826
|
+
const timer$4 = setTimeout(() => {
|
|
20827
|
+
this._deviceTokenManager?.pause();
|
|
20828
|
+
resolve({
|
|
20829
|
+
activated: false,
|
|
20830
|
+
reason: "activation-timeout"
|
|
20831
|
+
});
|
|
20832
|
+
}, CREDENTIAL_ACTIVATE_TIMEOUT_MS);
|
|
20833
|
+
inner.then((result) => {
|
|
20834
|
+
clearTimeout(timer$4);
|
|
20835
|
+
resolve(result);
|
|
20836
|
+
}, (error) => {
|
|
20837
|
+
clearTimeout(timer$4);
|
|
20838
|
+
this.deps.notifier.onError(error instanceof Error ? error : new Error(String(error)));
|
|
20839
|
+
resolve({
|
|
20840
|
+
activated: false,
|
|
20841
|
+
reason: "endpoint-failed"
|
|
20842
|
+
});
|
|
20843
|
+
});
|
|
20844
|
+
});
|
|
20845
|
+
}
|
|
20846
|
+
};
|
|
20847
|
+
|
|
20392
20848
|
//#endregion
|
|
20393
20849
|
//#region src/managers/DiagnosticsCollector.ts
|
|
20394
20850
|
const logger$5 = getLogger();
|
|
@@ -20909,6 +21365,10 @@ var TransportManager = class extends Destroyable {
|
|
|
20909
21365
|
if (!isSignalwireRequest(message)) return true;
|
|
20910
21366
|
const eventChannel = message.params.event_channel;
|
|
20911
21367
|
if (!eventChannel) return true;
|
|
21368
|
+
if (message.params.event_type.startsWith("conversation.")) {
|
|
21369
|
+
logger$2.debug(`[Transport] Received conversation event: ${message.params.event_type} (event_channel: ${eventChannel})`);
|
|
21370
|
+
return true;
|
|
21371
|
+
}
|
|
20912
21372
|
const currentProtocol = this._currentProtocol;
|
|
20913
21373
|
if (!currentProtocol) return true;
|
|
20914
21374
|
if (!eventChannel.includes(currentProtocol)) {
|
|
@@ -21102,6 +21562,7 @@ var SignalWire = class extends Destroyable {
|
|
|
21102
21562
|
this._isConnected$ = this.createBehaviorSubject(false);
|
|
21103
21563
|
this._isRegistered$ = this.createBehaviorSubject(false);
|
|
21104
21564
|
this._errors$ = this.createReplaySubject(1);
|
|
21565
|
+
this._warnings$ = this.createReplaySubject(10);
|
|
21105
21566
|
this._options = {};
|
|
21106
21567
|
this._deps = new DependencyContainer();
|
|
21107
21568
|
this._credentialProvider = credentialProvider;
|
|
@@ -21161,6 +21622,27 @@ var SignalWire = class extends Destroyable {
|
|
|
21161
21622
|
*/
|
|
21162
21623
|
async resolveCredentials() {
|
|
21163
21624
|
const fingerprint = await this.initDPoP();
|
|
21625
|
+
this._refreshCoordinator = new CredentialRefreshCoordinator(this._dpopManager, {
|
|
21626
|
+
http: this._deps.http,
|
|
21627
|
+
notifier: {
|
|
21628
|
+
onError: (error) => this._errors$.next(error),
|
|
21629
|
+
onWarning: (warning) => this._warnings$.next(warning),
|
|
21630
|
+
onRefreshExhausted: () => void this.disconnect()
|
|
21631
|
+
},
|
|
21632
|
+
store: {
|
|
21633
|
+
read: () => this._deps.credential,
|
|
21634
|
+
write: (credential) => {
|
|
21635
|
+
this._deps.credential = credential;
|
|
21636
|
+
},
|
|
21637
|
+
merge: (partial) => {
|
|
21638
|
+
this._deps.credential = {
|
|
21639
|
+
...this._deps.credential,
|
|
21640
|
+
...partial
|
|
21641
|
+
};
|
|
21642
|
+
},
|
|
21643
|
+
persist: (credential) => this.persistCredential(credential)
|
|
21644
|
+
}
|
|
21645
|
+
});
|
|
21164
21646
|
if (this._credentialProvider) return this.validateCredentials(this._credentialProvider, void 0, fingerprint);
|
|
21165
21647
|
for (const scope of this._deps.persistSession ? ["local", "session"] : ["session"]) try {
|
|
21166
21648
|
const cached = await this._deps.storage.getItem("sw:cached_credential", scope);
|
|
@@ -21190,7 +21672,16 @@ var SignalWire = class extends Destroyable {
|
|
|
21190
21672
|
logger$1.error("[SignalWire] Provided credentials have expired.");
|
|
21191
21673
|
throw new InvalidCredentialsError("Provided credentials have expired.");
|
|
21192
21674
|
}
|
|
21193
|
-
if (_credentials.expiry_at && credentialProvider?.refresh) this.
|
|
21675
|
+
if (_credentials.expiry_at && credentialProvider?.refresh) this._refreshCoordinator?.scheduleDeveloperRefresh(credentialProvider, _credentials.expiry_at);
|
|
21676
|
+
else if (_credentials.expiry_at && !credentialProvider?.refresh) {
|
|
21677
|
+
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.`);
|
|
21678
|
+
this._warnings$.next({
|
|
21679
|
+
code: "credential_no_refresh_handler",
|
|
21680
|
+
source: "CredentialProvider",
|
|
21681
|
+
message: "Credential has expiry_at but no refresh handler. Session will terminate at expiry unless the SAT carries 'sat:refresh' scope.",
|
|
21682
|
+
expiresAt: _credentials.expiry_at
|
|
21683
|
+
});
|
|
21684
|
+
}
|
|
21194
21685
|
this._deps.credential = _credentials;
|
|
21195
21686
|
this.persistCredential(_credentials);
|
|
21196
21687
|
if (this.isConnected && this._clientSession.authenticated && _credentials.token) try {
|
|
@@ -21201,35 +21692,6 @@ var SignalWire = class extends Destroyable {
|
|
|
21201
21692
|
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
21202
21693
|
}
|
|
21203
21694
|
}
|
|
21204
|
-
/**
|
|
21205
|
-
* Schedules credential refresh with exponential backoff retry on failure.
|
|
21206
|
-
* On success, resets attempt counter and schedules the next refresh.
|
|
21207
|
-
* After exhausting retries, emits TokenRefreshError and disconnects.
|
|
21208
|
-
*/
|
|
21209
|
-
scheduleCredentialRefresh(credentialProvider, expiresAt, attempt = 0) {
|
|
21210
|
-
if (this._refreshTimerId !== void 0) clearTimeout(this._refreshTimerId);
|
|
21211
|
-
const refreshInterval = attempt === 0 ? Math.max(expiresAt - Date.now() - CREDENTIAL_REFRESH_BUFFER_MS, 1e3) : Math.min(CREDENTIAL_REFRESH_RETRY_BASE_MS * Math.pow(2, attempt) * (.5 + Math.random() * .5), CREDENTIAL_REFRESH_MAX_DELAY_MS);
|
|
21212
|
-
this._refreshTimerId = setTimeout(async () => {
|
|
21213
|
-
try {
|
|
21214
|
-
if (!credentialProvider.refresh) throw new InvalidCredentialsError("Credential provider does not support refresh");
|
|
21215
|
-
const newCredentials = await credentialProvider.refresh();
|
|
21216
|
-
this._deps.credential = newCredentials;
|
|
21217
|
-
this.persistCredential(newCredentials);
|
|
21218
|
-
logger$1.info("[SignalWire] Credentials refreshed successfully.");
|
|
21219
|
-
if (newCredentials.expiry_at) this.scheduleCredentialRefresh(credentialProvider, newCredentials.expiry_at, 0);
|
|
21220
|
-
} catch (error) {
|
|
21221
|
-
const nextAttempt = attempt + 1;
|
|
21222
|
-
logger$1.error(`[SignalWire] Credential refresh failed (attempt ${nextAttempt}/${CREDENTIAL_REFRESH_MAX_RETRIES}):`, error);
|
|
21223
|
-
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
21224
|
-
if (nextAttempt < CREDENTIAL_REFRESH_MAX_RETRIES) this.scheduleCredentialRefresh(credentialProvider, expiresAt, nextAttempt);
|
|
21225
|
-
else {
|
|
21226
|
-
logger$1.error("[SignalWire] Credential refresh exhausted all retries. Disconnecting.");
|
|
21227
|
-
this._errors$.next(new TokenRefreshError("Credential refresh failed after max retries"));
|
|
21228
|
-
this.disconnect();
|
|
21229
|
-
}
|
|
21230
|
-
}
|
|
21231
|
-
}, refreshInterval);
|
|
21232
|
-
}
|
|
21233
21695
|
/** Persist credential to localStorage when persistSession is enabled. */
|
|
21234
21696
|
persistCredential(credential) {
|
|
21235
21697
|
if (!credential.token) return;
|
|
@@ -21323,6 +21785,7 @@ var SignalWire = class extends Destroyable {
|
|
|
21323
21785
|
logger$1.debug("[SignalWire] Credential expired, refreshing before reconnect");
|
|
21324
21786
|
const newCredentials = await this._credentialProvider.authenticate(fingerprint ? { fingerprint } : void 0);
|
|
21325
21787
|
this._deps.credential = newCredentials;
|
|
21788
|
+
if (newCredentials.expiry_at && this._credentialProvider.refresh) this._refreshCoordinator?.scheduleDeveloperRefresh(this._credentialProvider, newCredentials.expiry_at);
|
|
21326
21789
|
logger$1.debug("[SignalWire] Credential refreshed successfully for reconnect");
|
|
21327
21790
|
} catch (error) {
|
|
21328
21791
|
logger$1.error("[SignalWire] Failed to refresh credentials for reconnect:", error);
|
|
@@ -21334,33 +21797,12 @@ var SignalWire = class extends Destroyable {
|
|
|
21334
21797
|
this._errors$.next(error);
|
|
21335
21798
|
});
|
|
21336
21799
|
await this._clientSession.connect();
|
|
21337
|
-
|
|
21338
|
-
if (this._refreshTimerId) {
|
|
21339
|
-
clearTimeout(this._refreshTimerId);
|
|
21340
|
-
this._refreshTimerId = void 0;
|
|
21341
|
-
logger$1.debug("[SignalWire] Developer refresh disabled — Client Bound SAT activation starting");
|
|
21342
|
-
}
|
|
21343
|
-
this._deviceTokenManager = new DeviceTokenManager(this._dpopManager, this._deps.http, (error) => this._errors$.next(error), () => this._deps.credential);
|
|
21344
|
-
await this._deviceTokenManager.activate(this._deps.user, this._clientSession, (cred) => {
|
|
21345
|
-
this._deps.credential = {
|
|
21346
|
-
...this._deps.credential,
|
|
21347
|
-
...cred
|
|
21348
|
-
};
|
|
21349
|
-
});
|
|
21350
|
-
}
|
|
21800
|
+
await this._refreshCoordinator?.activate(this._deps.user, this._clientSession);
|
|
21351
21801
|
this.subscribeTo(this._clientSession.authenticated$.pipe((0, import_cjs$1.skip)(1), (0, import_cjs$1.filter)(Boolean)), async () => {
|
|
21352
21802
|
try {
|
|
21353
|
-
|
|
21354
|
-
await this._deviceTokenManager.activate(this._deps.user, this._clientSession, (cred) => {
|
|
21355
|
-
this._deps.credential = {
|
|
21356
|
-
...this._deps.credential,
|
|
21357
|
-
...cred
|
|
21358
|
-
};
|
|
21359
|
-
});
|
|
21360
|
-
logger$1.debug("[SignalWire] Client Bound SAT re-activated after reconnect");
|
|
21361
|
-
}
|
|
21803
|
+
await this._refreshCoordinator?.activate(this._deps.user, this._clientSession);
|
|
21362
21804
|
} catch (error) {
|
|
21363
|
-
logger$1.error("[SignalWire]
|
|
21805
|
+
logger$1.error("[SignalWire] Refresh re-arm after reconnect failed (non-fatal):", error);
|
|
21364
21806
|
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
21365
21807
|
}
|
|
21366
21808
|
try {
|
|
@@ -21446,6 +21888,19 @@ var SignalWire = class extends Destroyable {
|
|
|
21446
21888
|
get errors$() {
|
|
21447
21889
|
return this.deferEmission(this._errors$.asObservable());
|
|
21448
21890
|
}
|
|
21891
|
+
/**
|
|
21892
|
+
* Observable stream of non-fatal SDK warnings.
|
|
21893
|
+
*
|
|
21894
|
+
* Subscribe to detect SDK behaviors that affect session liveness or developer-facing
|
|
21895
|
+
* contracts but do not warrant disconnection — e.g., a fallback from Client Bound SAT
|
|
21896
|
+
* refresh to the developer-provided `refresh()` because the SAT lacks `sat:refresh`
|
|
21897
|
+
* scope. Discriminated by `code`.
|
|
21898
|
+
*
|
|
21899
|
+
* Independent from {@link errors$}: existing error consumers are not notified.
|
|
21900
|
+
*/
|
|
21901
|
+
get warnings$() {
|
|
21902
|
+
return this.deferEmission(this._warnings$.asObservable());
|
|
21903
|
+
}
|
|
21449
21904
|
/** Platform WebRTC capabilities detected at construction time. */
|
|
21450
21905
|
get platformCapabilities() {
|
|
21451
21906
|
this._platformCapabilities ??= detectPlatformCapabilities(this._options.webRTCApiProvider);
|
|
@@ -21514,7 +21969,7 @@ var SignalWire = class extends Destroyable {
|
|
|
21514
21969
|
logger$1.warn("[SignalWire] Failed to initialize VisibilityController:", error);
|
|
21515
21970
|
}
|
|
21516
21971
|
try {
|
|
21517
|
-
this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "
|
|
21972
|
+
this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "4.0.0-rc.1" });
|
|
21518
21973
|
} catch (error) {
|
|
21519
21974
|
logger$1.warn("[SignalWire] Failed to initialize DiagnosticsCollector:", error);
|
|
21520
21975
|
}
|
|
@@ -21522,14 +21977,19 @@ var SignalWire = class extends Destroyable {
|
|
|
21522
21977
|
/**
|
|
21523
21978
|
* Disconnects the WebSocket and tears down the current session.
|
|
21524
21979
|
*
|
|
21980
|
+
* Ends the session identified by the protocol and clears its persisted
|
|
21981
|
+
* resume state (`authorization_state` + protocol) and attach records
|
|
21982
|
+
* together — a later {@link connect} with the same credentials starts a
|
|
21983
|
+
* fresh session and cannot reattach to the ended session's calls.
|
|
21984
|
+
* Credentials and device preferences are preserved. To temporarily stop
|
|
21985
|
+
* receiving inbound calls while keeping the session alive, use
|
|
21986
|
+
* `unregister()` instead.
|
|
21987
|
+
*
|
|
21525
21988
|
* The client can be reconnected by calling {@link connect} again,
|
|
21526
21989
|
* which creates a fresh transport and session.
|
|
21527
21990
|
*/
|
|
21528
21991
|
async disconnect() {
|
|
21529
|
-
|
|
21530
|
-
clearTimeout(this._refreshTimerId);
|
|
21531
|
-
this._refreshTimerId = void 0;
|
|
21532
|
-
}
|
|
21992
|
+
this._refreshCoordinator?.suspend();
|
|
21533
21993
|
this._diagnosticsCollector?.record("connection", "disconnected");
|
|
21534
21994
|
await this.teardownTransportAndSession();
|
|
21535
21995
|
this._isConnected$.next(false);
|
|
@@ -21916,15 +22376,20 @@ var SignalWire = class extends Destroyable {
|
|
|
21916
22376
|
prefs.preferredVideoInput = null;
|
|
21917
22377
|
await this._deviceController.clearDeviceState();
|
|
21918
22378
|
}
|
|
21919
|
-
/**
|
|
22379
|
+
/**
|
|
22380
|
+
* Destroys the client, clearing timers and releasing all resources.
|
|
22381
|
+
*
|
|
22382
|
+
* Intentionally destroying the client ends its session: the resume state
|
|
22383
|
+
* (`authorization_state` + protocol) and the attach records are both
|
|
22384
|
+
* cleared. Credentials and device preferences are preserved — use
|
|
22385
|
+
* {@link resetToDefaults} for a full wipe. To temporarily stop receiving
|
|
22386
|
+
* inbound calls while keeping the session alive, use `unregister()`.
|
|
22387
|
+
*/
|
|
21920
22388
|
destroy() {
|
|
21921
|
-
|
|
21922
|
-
|
|
21923
|
-
this._refreshTimerId = void 0;
|
|
21924
|
-
}
|
|
21925
|
-
this._deviceTokenManager?.destroy();
|
|
22389
|
+
this._refreshCoordinator?.destroy();
|
|
22390
|
+
this._refreshCoordinator = void 0;
|
|
21926
22391
|
this._dpopManager?.destroy();
|
|
21927
|
-
|
|
22392
|
+
this._clientSession.teardownSessionState();
|
|
21928
22393
|
this._transport.destroy();
|
|
21929
22394
|
this._clientSession.destroy();
|
|
21930
22395
|
try {
|
|
@@ -22039,7 +22504,7 @@ var StaticCredentialProvider = class {
|
|
|
22039
22504
|
/**
|
|
22040
22505
|
* Library version from package.json, injected at build time.
|
|
22041
22506
|
*/
|
|
22042
|
-
const version = "
|
|
22507
|
+
const version = "4.0.0-rc.1";
|
|
22043
22508
|
/**
|
|
22044
22509
|
* Flag indicating the library has been loaded and is ready to use.
|
|
22045
22510
|
* For UMD builds: `window.SignalWire.ready`
|
|
@@ -22061,7 +22526,7 @@ const ready = true;
|
|
|
22061
22526
|
*/
|
|
22062
22527
|
const emitReadyEvent = () => {
|
|
22063
22528
|
if (typeof window !== "undefined") {
|
|
22064
|
-
const event = new CustomEvent("signalwire:js:ready", { detail: { version: "
|
|
22529
|
+
const event = new CustomEvent("signalwire:js:ready", { detail: { version: "4.0.0-rc.1" } });
|
|
22065
22530
|
window.dispatchEvent(event);
|
|
22066
22531
|
}
|
|
22067
22532
|
};
|
|
@@ -22085,5 +22550,5 @@ emitReadyEvent();
|
|
|
22085
22550
|
if (typeof process === "undefined") globalThis.process = { env: { NODE_ENV: "production" } };
|
|
22086
22551
|
|
|
22087
22552
|
//#endregion
|
|
22088
|
-
export { Address, CallCreateError, ClientPreferences, CollectionFetchError, DPoPInitError, DeviceTokenError, EmbedTokenCredentialProvider, InvalidCredentialsError, MediaTrackError, MessageParseError, OverconstrainedFallbackError, Participant, PreflightError, RecoveryError, SelfCapabilities, SelfParticipant, SignalWire, StaticCredentialProvider, TokenRefreshError, UnexpectedError, User, VertoPongError, WebRTCCall, embeddableCall, getLogger, isSelfParticipant, ready, setDebugOptions, setLogLevel, setLogger, version };
|
|
22553
|
+
export { Address, CallCreateError, ClientPreferences, CollectionFetchError, DPoPInitError, DeviceTokenError, EmbedTokenCredentialProvider, InvalidCredentialsError, MediaAccessError, MediaTrackError, MessageParseError, OverconstrainedFallbackError, Participant, PreflightError, RecoveryError, SelfCapabilities, SelfParticipant, SignalWire, StaticCredentialProvider, TokenRefreshError, UnexpectedError, User, VertoPongError, WebRTCCall, embeddableCall, getLogger, isSelfParticipant, ready, setDebugOptions, setLogLevel, setLogger, version };
|
|
22089
22554
|
//# sourceMappingURL=browser.mjs.map
|