@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.umd.js
CHANGED
|
@@ -9021,6 +9021,143 @@ var Destroyable = class {
|
|
|
9021
9021
|
}
|
|
9022
9022
|
};
|
|
9023
9023
|
|
|
9024
|
+
//#endregion
|
|
9025
|
+
//#region src/core/constants.ts
|
|
9026
|
+
const INVITE_VERSION = 1e3;
|
|
9027
|
+
const DEFAULT_ICE_CANDIDATE_TIMEOUT_MS = 600;
|
|
9028
|
+
const DEFAULT_ICE_GATHERING_TIMEOUT_MS = 6e3;
|
|
9029
|
+
const DEFAULT_RECONNECT_CALLS_TIMEOUT_MS = 300 * 1e3;
|
|
9030
|
+
const DEFAULT_CONNECTION_TIMEOUT_MS = 1e4;
|
|
9031
|
+
const DEFAULT_RECONNECT_DELAY_MIN_MS = 100;
|
|
9032
|
+
const DEFAULT_RECONNECT_DELAY_MAX_MS = 3e3;
|
|
9033
|
+
const DEFAULT_DEVICE_DEBOUNCE_TIME_MS = 1500;
|
|
9034
|
+
const DEFAULT_DEVICE_POLLING_INTERVAL_MS = 0;
|
|
9035
|
+
const PREFERENCES_STORAGE_KEY = "sw:preferences";
|
|
9036
|
+
/** Scope value that enables automatic token refresh. */
|
|
9037
|
+
const SAT_REFRESH_SCOPE = "sat:refresh";
|
|
9038
|
+
/** API endpoints for device token operations. */
|
|
9039
|
+
const DEVICE_TOKEN_ENDPOINT = "/api/fabric/subscriber/devices/token";
|
|
9040
|
+
const DEVICE_REFRESH_ENDPOINT = "/api/fabric/subscriber/devices/refresh";
|
|
9041
|
+
/** Default device token TTL in seconds (15 minutes). */
|
|
9042
|
+
const DEVICE_TOKEN_DEFAULT_EXPIRE_IN = 900;
|
|
9043
|
+
/** Buffer time in milliseconds before expiry to trigger refresh. */
|
|
9044
|
+
const DEVICE_TOKEN_REFRESH_BUFFER_MS = 3e4;
|
|
9045
|
+
/** Maximum retry attempts for device token refresh on transient failure. */
|
|
9046
|
+
const DEVICE_TOKEN_REFRESH_MAX_RETRIES = 3;
|
|
9047
|
+
/** Base delay in milliseconds for exponential backoff on refresh retry. */
|
|
9048
|
+
const DEVICE_TOKEN_REFRESH_RETRY_BASE_MS = 1e3;
|
|
9049
|
+
/** Maximum retry attempts for developer credential refresh on transient failure. */
|
|
9050
|
+
const CREDENTIAL_REFRESH_MAX_RETRIES = 5;
|
|
9051
|
+
/** Base delay in milliseconds for exponential backoff on credential refresh retry. */
|
|
9052
|
+
const CREDENTIAL_REFRESH_RETRY_BASE_MS = 1e3;
|
|
9053
|
+
/** Maximum delay in milliseconds for credential refresh backoff. */
|
|
9054
|
+
const CREDENTIAL_REFRESH_MAX_DELAY_MS = 3e4;
|
|
9055
|
+
/** Buffer in milliseconds before token expiry to trigger refresh. */
|
|
9056
|
+
const CREDENTIAL_REFRESH_BUFFER_MS = 5e3;
|
|
9057
|
+
/**
|
|
9058
|
+
* Maximum time the coordinator will wait for `DeviceTokenManager.activate()`
|
|
9059
|
+
* to resolve before treating the activation as failed and falling back to
|
|
9060
|
+
* the developer-provided refresh path. Prevents a wedged HTTP layer from
|
|
9061
|
+
* leaving the session with no active refresh mechanism.
|
|
9062
|
+
*/
|
|
9063
|
+
const CREDENTIAL_ACTIVATE_TIMEOUT_MS = 3e4;
|
|
9064
|
+
/** JSON-RPC error code for requester validation failure (corrupted auth state). */
|
|
9065
|
+
const RPC_ERROR_REQUESTER_VALIDATION_FAILED = -32003;
|
|
9066
|
+
/** JSON-RPC error code for invalid params (e.g., missing authentication block). */
|
|
9067
|
+
const RPC_ERROR_INVALID_PARAMS = -32602;
|
|
9068
|
+
/** JSON-RPC error code for authentication failure (invalid token, missing DPoP, etc.). */
|
|
9069
|
+
const RPC_ERROR_AUTHENTICATION_FAILED = -32002;
|
|
9070
|
+
/** Error names browsers use for a media permission denial (user or policy). */
|
|
9071
|
+
const MEDIA_ACCESS_DENIAL_NAMES = [
|
|
9072
|
+
"NotAllowedError",
|
|
9073
|
+
"SecurityError",
|
|
9074
|
+
"PermissionDeniedError"
|
|
9075
|
+
];
|
|
9076
|
+
/** Default polling interval for RTCPeerConnection.getStats() in milliseconds. */
|
|
9077
|
+
const DEFAULT_STATS_POLLING_INTERVAL_MS = 1e3;
|
|
9078
|
+
/** Number of initial samples used to build a baseline for spike detection. */
|
|
9079
|
+
const DEFAULT_STATS_BASELINE_SAMPLES = 10;
|
|
9080
|
+
/** Duration in ms with no inbound audio packets before emitting a critical issue. */
|
|
9081
|
+
const DEFAULT_STATS_NO_PACKET_THRESHOLD_MS = 2e3;
|
|
9082
|
+
/** Multiplier applied to baseline RTT to detect a warning-level RTT spike. */
|
|
9083
|
+
const DEFAULT_STATS_RTT_SPIKE_MULTIPLIER = 3;
|
|
9084
|
+
/** Packet loss fraction (0-1) above which a warning is emitted. */
|
|
9085
|
+
const DEFAULT_STATS_PACKET_LOSS_THRESHOLD = .05;
|
|
9086
|
+
/** Multiplier applied to baseline jitter to detect a jitter spike. */
|
|
9087
|
+
const DEFAULT_STATS_JITTER_SPIKE_MULTIPLIER = 4;
|
|
9088
|
+
/** Number of seconds of metrics history to retain. */
|
|
9089
|
+
const DEFAULT_STATS_HISTORY_SIZE = 30;
|
|
9090
|
+
/** Maximum keyframe requests allowed within a single burst window. */
|
|
9091
|
+
const DEFAULT_KEYFRAME_MAX_BURST$1 = 3;
|
|
9092
|
+
/** Duration of the keyframe burst window in milliseconds. */
|
|
9093
|
+
const DEFAULT_KEYFRAME_BURST_WINDOW_MS$1 = 3e3;
|
|
9094
|
+
/** Cooldown period in ms after burst limit is reached before allowing more keyframes. */
|
|
9095
|
+
const DEFAULT_KEYFRAME_COOLDOWN_MS$1 = 1e4;
|
|
9096
|
+
/** Minimum time between re-INVITE attempts in milliseconds. */
|
|
9097
|
+
const DEFAULT_REINVITE_DEBOUNCE_TIME_MS = 1e4;
|
|
9098
|
+
/** Maximum number of re-INVITE attempts per call. */
|
|
9099
|
+
const DEFAULT_REINVITE_MAX_ATTEMPTS = 3;
|
|
9100
|
+
/** Timeout for a single re-INVITE attempt in milliseconds. */
|
|
9101
|
+
const DEFAULT_REINVITE_TIMEOUT_MS = 5e3;
|
|
9102
|
+
/** Debounce window in ms to collapse multiple detection signals into one trigger. */
|
|
9103
|
+
const DEFAULT_RECOVERY_DEBOUNCE_TIME_MS = 2e3;
|
|
9104
|
+
/** Cooldown period in ms between recovery attempts. */
|
|
9105
|
+
const DEFAULT_RECOVERY_COOLDOWN_MS = 1e4;
|
|
9106
|
+
/** Grace period in ms before treating ICE 'disconnected' as a failure. */
|
|
9107
|
+
const DEFAULT_ICE_DISCONNECTED_GRACE_PERIOD_MS = 3e3;
|
|
9108
|
+
/** Timeout for a single ICE restart attempt in milliseconds. */
|
|
9109
|
+
const DEFAULT_ICE_RESTART_TIMEOUT_MS$1 = 5e3;
|
|
9110
|
+
/** Maximum recovery attempts before emitting 'max_attempts_reached'. */
|
|
9111
|
+
const DEFAULT_MAX_RECOVERY_ATTEMPTS = 3;
|
|
9112
|
+
/** Upper bound in ms for waiting on iceGatheringState === 'complete' after an ICE restart. */
|
|
9113
|
+
const ICE_GATHERING_COMPLETE_TIMEOUT_MS = 1e4;
|
|
9114
|
+
/** Upper bound in ms for waiting on RTCPeerConnection.connectionState === 'connected' after a recovery ICE restart. */
|
|
9115
|
+
const PEER_CONNECTION_RECOVERY_WAIT_MS = 5e3;
|
|
9116
|
+
/** Polling interval in ms while waiting for RTCPeerConnection.connectionState to transition. */
|
|
9117
|
+
const PEER_CONNECTION_RECOVERY_POLL_MS = 100;
|
|
9118
|
+
/** Polling interval for LocalAudioPipeline.level$ (ms). ~30fps is smooth for meters. */
|
|
9119
|
+
const AUDIO_LEVEL_POLL_INTERVAL_MS = 33;
|
|
9120
|
+
/** RMS level threshold (0..1) above which the local participant is considered speaking. */
|
|
9121
|
+
const VAD_THRESHOLD = .03;
|
|
9122
|
+
/** Hold window in ms below the threshold before speaking$ flips back to false. */
|
|
9123
|
+
const VAD_HOLD_MS = 250;
|
|
9124
|
+
/** Whether to persist device selections to storage by default. */
|
|
9125
|
+
const DEFAULT_PERSIST_DEVICE_SELECTION = true;
|
|
9126
|
+
/** Whether to auto-apply device changes to active calls by default. */
|
|
9127
|
+
const DEFAULT_SYNC_DEVICES_TO_ACTIVE_CALLS = true;
|
|
9128
|
+
/** Storage keys for persisted device selections. */
|
|
9129
|
+
const DEVICE_STORAGE_KEY_AUDIO_INPUT = "sw:device:audioinput";
|
|
9130
|
+
const DEVICE_STORAGE_KEY_AUDIO_OUTPUT = "sw:device:audiooutput";
|
|
9131
|
+
const DEVICE_STORAGE_KEY_VIDEO_INPUT = "sw:device:videoinput";
|
|
9132
|
+
/** Whether to auto-mute video when the tab becomes hidden. */
|
|
9133
|
+
const DEFAULT_AUTO_MUTE_VIDEO_ON_HIDDEN = false;
|
|
9134
|
+
/** Whether to re-enumerate devices when the page becomes visible. */
|
|
9135
|
+
const DEFAULT_REFRESH_DEVICES_ON_VISIBLE = true;
|
|
9136
|
+
/** Whether to check peer connection health when the page becomes visible. */
|
|
9137
|
+
const DEFAULT_CHECK_CONNECTION_ON_VISIBLE = true;
|
|
9138
|
+
/** Whether automatic video degradation on low bandwidth is enabled. */
|
|
9139
|
+
const DEFAULT_ENABLE_AUTO_DEGRADATION = true;
|
|
9140
|
+
/** Bitrate in kbps below which video is automatically disabled. */
|
|
9141
|
+
const DEFAULT_DEGRADATION_BITRATE_THRESHOLD_KBPS = 150;
|
|
9142
|
+
/** Bitrate in kbps above which video is automatically re-enabled (hysteresis). */
|
|
9143
|
+
const DEFAULT_DEGRADATION_RECOVERY_THRESHOLD_KBPS = 300;
|
|
9144
|
+
/** Whether relay-only escalation is enabled as a last-resort recovery tier. */
|
|
9145
|
+
const DEFAULT_ENABLE_RELAY_FALLBACK = true;
|
|
9146
|
+
/** Whether to listen for browser online/offline/connection events. */
|
|
9147
|
+
const DEFAULT_ENABLE_NETWORK_CHANGE_DETECTION = true;
|
|
9148
|
+
/** Whether to intercept server-sent media-timeout hangups and attempt recovery. */
|
|
9149
|
+
const DEFAULT_ENABLE_SERVER_HANGUP_INTERCEPTION = true;
|
|
9150
|
+
/** Default video track constraints applied when video is enabled without explicit constraints. */
|
|
9151
|
+
const DEFAULT_VIDEO_CONSTRAINTS = {
|
|
9152
|
+
width: { ideal: 1280 },
|
|
9153
|
+
height: { ideal: 720 },
|
|
9154
|
+
aspectRatio: 16 / 9
|
|
9155
|
+
};
|
|
9156
|
+
/** Whether stereo Opus is enabled by default. */
|
|
9157
|
+
const DEFAULT_STEREO_AUDIO = false;
|
|
9158
|
+
/** Max average bitrate for stereo Opus in bits per second. */
|
|
9159
|
+
const DEFAULT_STEREO_MAX_AVERAGE_BITRATE = 51e4;
|
|
9160
|
+
|
|
9024
9161
|
//#endregion
|
|
9025
9162
|
//#region src/core/errors.ts
|
|
9026
9163
|
var UnexpectedError = class extends Error {
|
|
@@ -9230,6 +9367,33 @@ var MediaTrackError = class extends Error {
|
|
|
9230
9367
|
this.name = "MediaTrackError";
|
|
9231
9368
|
}
|
|
9232
9369
|
};
|
|
9370
|
+
/** True when a `getUserMedia`/`getDisplayMedia` rejection is a permission denial. */
|
|
9371
|
+
function isMediaAccessDenial(originalError) {
|
|
9372
|
+
return originalError instanceof Error && MEDIA_ACCESS_DENIAL_NAMES.includes(originalError.name);
|
|
9373
|
+
}
|
|
9374
|
+
/**
|
|
9375
|
+
* Failure to acquire local media (camera, microphone, or screen capture)
|
|
9376
|
+
* via `getUserMedia`/`getDisplayMedia`.
|
|
9377
|
+
*
|
|
9378
|
+
* Non-fatal by default: screenshare and additional-device failures never
|
|
9379
|
+
* end the call, and main-connection failures degrade to receive-only when
|
|
9380
|
+
* possible. The wrapping site sets `fatal` to `true` only when the call
|
|
9381
|
+
* cannot continue (receive-only fallback disabled or no receive intent).
|
|
9382
|
+
*/
|
|
9383
|
+
var MediaAccessError = class extends Error {
|
|
9384
|
+
constructor(operation, media, originalError, fatal = false) {
|
|
9385
|
+
super(`Media access ${isMediaAccessDenial(originalError) ? "denied" : "failed"} for ${operation} (${media})`, { cause: originalError instanceof Error ? originalError : void 0 });
|
|
9386
|
+
this.operation = operation;
|
|
9387
|
+
this.media = media;
|
|
9388
|
+
this.originalError = originalError;
|
|
9389
|
+
this.fatal = fatal;
|
|
9390
|
+
this.name = "MediaAccessError";
|
|
9391
|
+
}
|
|
9392
|
+
/** True when the underlying failure is a permission denial (user or policy). */
|
|
9393
|
+
get denied() {
|
|
9394
|
+
return isMediaAccessDenial(this.originalError);
|
|
9395
|
+
}
|
|
9396
|
+
};
|
|
9233
9397
|
var DPoPInitError = class extends Error {
|
|
9234
9398
|
constructor(originalError, message = "Failed to initialize DPoP key pair") {
|
|
9235
9399
|
super(message, { cause: originalError instanceof Error ? originalError : void 0 });
|
|
@@ -9470,9 +9634,9 @@ var require_loglevel = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
9470
9634
|
defaultLogger$1 = new Logger();
|
|
9471
9635
|
defaultLogger$1.getLogger = function getLogger$1(name) {
|
|
9472
9636
|
if (typeof name !== "symbol" && typeof name !== "string" || name === "") throw new TypeError("You must supply a name when creating a logger.");
|
|
9473
|
-
var logger$
|
|
9474
|
-
if (!logger$
|
|
9475
|
-
return logger$
|
|
9637
|
+
var logger$33 = _loggersByName[name];
|
|
9638
|
+
if (!logger$33) logger$33 = _loggersByName[name] = new Logger(name, defaultLogger$1.methodFactory);
|
|
9639
|
+
return logger$33;
|
|
9476
9640
|
};
|
|
9477
9641
|
var _log = typeof window !== undefinedType ? window.log : void 0;
|
|
9478
9642
|
defaultLogger$1.noConflict = function() {
|
|
@@ -9508,8 +9672,8 @@ const defaultLoggerLevel = defaultLogger.levels.WARN;
|
|
|
9508
9672
|
defaultLogger.setLevel(defaultLoggerLevel);
|
|
9509
9673
|
let userLogger = null;
|
|
9510
9674
|
/** Replace the built-in logger with a custom implementation. Pass `null` to restore defaults. */
|
|
9511
|
-
const setLogger = (logger$
|
|
9512
|
-
userLogger = logger$
|
|
9675
|
+
const setLogger = (logger$33) => {
|
|
9676
|
+
userLogger = logger$33;
|
|
9513
9677
|
};
|
|
9514
9678
|
let debugOptions = {};
|
|
9515
9679
|
/** Configure debug options (e.g., `{ logWsTraffic: true }`). */
|
|
@@ -9553,8 +9717,8 @@ const wsTraffic = (options) => {
|
|
|
9553
9717
|
loggerInstance.debug(`${options.type.toUpperCase()}: \n`, msg, "\n");
|
|
9554
9718
|
};
|
|
9555
9719
|
const getLogger = () => {
|
|
9556
|
-
const logger$
|
|
9557
|
-
return new Proxy(logger$
|
|
9720
|
+
const logger$33 = getLoggerInstance();
|
|
9721
|
+
return new Proxy(logger$33, { get(_target, prop, _receiver) {
|
|
9558
9722
|
if (prop === "wsTraffic") return wsTraffic;
|
|
9559
9723
|
const instance = getLoggerInstance();
|
|
9560
9724
|
const value = Reflect.get(instance, prop);
|
|
@@ -9606,7 +9770,7 @@ const asyncRetry = async ({ asyncCallable, maxRetries: retries = DEFAULT_MAX_RET
|
|
|
9606
9770
|
|
|
9607
9771
|
//#endregion
|
|
9608
9772
|
//#region src/controllers/HTTPRequestController.ts
|
|
9609
|
-
const logger$
|
|
9773
|
+
const logger$32 = getLogger();
|
|
9610
9774
|
const GET_PARAMS = {
|
|
9611
9775
|
method: "GET",
|
|
9612
9776
|
headers: { Accept: "application/json" }
|
|
@@ -9670,7 +9834,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9670
9834
|
this._responses$.next(response);
|
|
9671
9835
|
return response;
|
|
9672
9836
|
} catch (error) {
|
|
9673
|
-
logger$
|
|
9837
|
+
logger$32.error("[HTTPRequestController] Request error:", error);
|
|
9674
9838
|
this._status$.next("error");
|
|
9675
9839
|
const err = error instanceof Error ? error : new Error("HTTP request failed", { cause: error });
|
|
9676
9840
|
this._errors$.next(err);
|
|
@@ -9697,7 +9861,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9697
9861
|
const url = this.buildURL(request.url);
|
|
9698
9862
|
const headers = this.buildHeaders(request.headers);
|
|
9699
9863
|
const timeout$5 = request.timeout ?? this.requestTimeout;
|
|
9700
|
-
logger$
|
|
9864
|
+
logger$32.debug("[HTTPRequestController] Executing request:", {
|
|
9701
9865
|
method: request.method,
|
|
9702
9866
|
url,
|
|
9703
9867
|
headers: Object.keys(headers).reduce((acc, key) => {
|
|
@@ -9717,7 +9881,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9717
9881
|
});
|
|
9718
9882
|
clearTimeout(timeoutId);
|
|
9719
9883
|
const httpResponse = await this.convertResponse(response);
|
|
9720
|
-
logger$
|
|
9884
|
+
logger$32.debug("[HTTPRequestController] Response received:", {
|
|
9721
9885
|
status: response.status,
|
|
9722
9886
|
statusText: response.statusText,
|
|
9723
9887
|
headers: [...response.headers.entries()],
|
|
@@ -9727,7 +9891,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9727
9891
|
} catch (error) {
|
|
9728
9892
|
clearTimeout(timeoutId);
|
|
9729
9893
|
if (error instanceof Error && error.name === "AbortError") throw new RequestTimeoutError(`Request timeout after ${timeout$5}ms`, { cause: error });
|
|
9730
|
-
logger$
|
|
9894
|
+
logger$32.error("[HTTPRequestController] Request failed:", error);
|
|
9731
9895
|
throw error;
|
|
9732
9896
|
}
|
|
9733
9897
|
}
|
|
@@ -9741,8 +9905,8 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9741
9905
|
const credential = this.getCredential();
|
|
9742
9906
|
if (credential.token) {
|
|
9743
9907
|
headers.Authorization = `Bearer ${credential.token}`;
|
|
9744
|
-
logger$
|
|
9745
|
-
} else logger$
|
|
9908
|
+
logger$32.debug("[HTTPRequestController] Using Bearer token auth, token length:", credential.token.length);
|
|
9909
|
+
} else logger$32.warn("[HTTPRequestController] No credentials available for authentication");
|
|
9746
9910
|
return headers;
|
|
9747
9911
|
}
|
|
9748
9912
|
/**
|
|
@@ -9879,130 +10043,6 @@ var DeviceHistoryManager = class {
|
|
|
9879
10043
|
}
|
|
9880
10044
|
};
|
|
9881
10045
|
|
|
9882
|
-
//#endregion
|
|
9883
|
-
//#region src/core/constants.ts
|
|
9884
|
-
const INVITE_VERSION = 1e3;
|
|
9885
|
-
const DEFAULT_ICE_CANDIDATE_TIMEOUT_MS = 600;
|
|
9886
|
-
const DEFAULT_ICE_GATHERING_TIMEOUT_MS = 6e3;
|
|
9887
|
-
const DEFAULT_RECONNECT_CALLS_TIMEOUT_MS = 300 * 1e3;
|
|
9888
|
-
const DEFAULT_CONNECTION_TIMEOUT_MS = 1e4;
|
|
9889
|
-
const DEFAULT_RECONNECT_DELAY_MIN_MS = 100;
|
|
9890
|
-
const DEFAULT_RECONNECT_DELAY_MAX_MS = 3e3;
|
|
9891
|
-
const DEFAULT_DEVICE_DEBOUNCE_TIME_MS = 1500;
|
|
9892
|
-
const DEFAULT_DEVICE_POLLING_INTERVAL_MS = 0;
|
|
9893
|
-
const PREFERENCES_STORAGE_KEY = "sw:preferences";
|
|
9894
|
-
/** Scope value that enables automatic token refresh. */
|
|
9895
|
-
const SAT_REFRESH_SCOPE = "sat:refresh";
|
|
9896
|
-
/** API endpoints for device token operations. */
|
|
9897
|
-
const DEVICE_TOKEN_ENDPOINT = "/api/fabric/subscriber/devices/token";
|
|
9898
|
-
const DEVICE_REFRESH_ENDPOINT = "/api/fabric/subscriber/devices/refresh";
|
|
9899
|
-
/** Default device token TTL in seconds (15 minutes). */
|
|
9900
|
-
const DEVICE_TOKEN_DEFAULT_EXPIRE_IN = 900;
|
|
9901
|
-
/** Buffer time in milliseconds before expiry to trigger refresh. */
|
|
9902
|
-
const DEVICE_TOKEN_REFRESH_BUFFER_MS = 3e4;
|
|
9903
|
-
/** Maximum retry attempts for device token refresh on transient failure. */
|
|
9904
|
-
const DEVICE_TOKEN_REFRESH_MAX_RETRIES = 3;
|
|
9905
|
-
/** Base delay in milliseconds for exponential backoff on refresh retry. */
|
|
9906
|
-
const DEVICE_TOKEN_REFRESH_RETRY_BASE_MS = 1e3;
|
|
9907
|
-
/** Maximum retry attempts for developer credential refresh on transient failure. */
|
|
9908
|
-
const CREDENTIAL_REFRESH_MAX_RETRIES = 5;
|
|
9909
|
-
/** Base delay in milliseconds for exponential backoff on credential refresh retry. */
|
|
9910
|
-
const CREDENTIAL_REFRESH_RETRY_BASE_MS = 1e3;
|
|
9911
|
-
/** Maximum delay in milliseconds for credential refresh backoff. */
|
|
9912
|
-
const CREDENTIAL_REFRESH_MAX_DELAY_MS = 3e4;
|
|
9913
|
-
/** Buffer in milliseconds before token expiry to trigger refresh. */
|
|
9914
|
-
const CREDENTIAL_REFRESH_BUFFER_MS = 5e3;
|
|
9915
|
-
/** JSON-RPC error code for requester validation failure (corrupted auth state). */
|
|
9916
|
-
const RPC_ERROR_REQUESTER_VALIDATION_FAILED = -32003;
|
|
9917
|
-
/** JSON-RPC error code for invalid params (e.g., missing authentication block). */
|
|
9918
|
-
const RPC_ERROR_INVALID_PARAMS = -32602;
|
|
9919
|
-
/** JSON-RPC error code for authentication failure (invalid token, missing DPoP, etc.). */
|
|
9920
|
-
const RPC_ERROR_AUTHENTICATION_FAILED = -32002;
|
|
9921
|
-
/** Default polling interval for RTCPeerConnection.getStats() in milliseconds. */
|
|
9922
|
-
const DEFAULT_STATS_POLLING_INTERVAL_MS = 1e3;
|
|
9923
|
-
/** Number of initial samples used to build a baseline for spike detection. */
|
|
9924
|
-
const DEFAULT_STATS_BASELINE_SAMPLES = 10;
|
|
9925
|
-
/** Duration in ms with no inbound audio packets before emitting a critical issue. */
|
|
9926
|
-
const DEFAULT_STATS_NO_PACKET_THRESHOLD_MS = 2e3;
|
|
9927
|
-
/** Multiplier applied to baseline RTT to detect a warning-level RTT spike. */
|
|
9928
|
-
const DEFAULT_STATS_RTT_SPIKE_MULTIPLIER = 3;
|
|
9929
|
-
/** Packet loss fraction (0-1) above which a warning is emitted. */
|
|
9930
|
-
const DEFAULT_STATS_PACKET_LOSS_THRESHOLD = .05;
|
|
9931
|
-
/** Multiplier applied to baseline jitter to detect a jitter spike. */
|
|
9932
|
-
const DEFAULT_STATS_JITTER_SPIKE_MULTIPLIER = 4;
|
|
9933
|
-
/** Number of seconds of metrics history to retain. */
|
|
9934
|
-
const DEFAULT_STATS_HISTORY_SIZE = 30;
|
|
9935
|
-
/** Maximum keyframe requests allowed within a single burst window. */
|
|
9936
|
-
const DEFAULT_KEYFRAME_MAX_BURST$1 = 3;
|
|
9937
|
-
/** Duration of the keyframe burst window in milliseconds. */
|
|
9938
|
-
const DEFAULT_KEYFRAME_BURST_WINDOW_MS$1 = 3e3;
|
|
9939
|
-
/** Cooldown period in ms after burst limit is reached before allowing more keyframes. */
|
|
9940
|
-
const DEFAULT_KEYFRAME_COOLDOWN_MS$1 = 1e4;
|
|
9941
|
-
/** Minimum time between re-INVITE attempts in milliseconds. */
|
|
9942
|
-
const DEFAULT_REINVITE_DEBOUNCE_TIME_MS = 1e4;
|
|
9943
|
-
/** Maximum number of re-INVITE attempts per call. */
|
|
9944
|
-
const DEFAULT_REINVITE_MAX_ATTEMPTS = 3;
|
|
9945
|
-
/** Timeout for a single re-INVITE attempt in milliseconds. */
|
|
9946
|
-
const DEFAULT_REINVITE_TIMEOUT_MS = 5e3;
|
|
9947
|
-
/** Debounce window in ms to collapse multiple detection signals into one trigger. */
|
|
9948
|
-
const DEFAULT_RECOVERY_DEBOUNCE_TIME_MS = 2e3;
|
|
9949
|
-
/** Cooldown period in ms between recovery attempts. */
|
|
9950
|
-
const DEFAULT_RECOVERY_COOLDOWN_MS = 1e4;
|
|
9951
|
-
/** Grace period in ms before treating ICE 'disconnected' as a failure. */
|
|
9952
|
-
const DEFAULT_ICE_DISCONNECTED_GRACE_PERIOD_MS = 3e3;
|
|
9953
|
-
/** Timeout for a single ICE restart attempt in milliseconds. */
|
|
9954
|
-
const DEFAULT_ICE_RESTART_TIMEOUT_MS$1 = 5e3;
|
|
9955
|
-
/** Maximum recovery attempts before emitting 'max_attempts_reached'. */
|
|
9956
|
-
const DEFAULT_MAX_RECOVERY_ATTEMPTS = 3;
|
|
9957
|
-
/** Upper bound in ms for waiting on iceGatheringState === 'complete' after an ICE restart. */
|
|
9958
|
-
const ICE_GATHERING_COMPLETE_TIMEOUT_MS = 1e4;
|
|
9959
|
-
/** Upper bound in ms for waiting on RTCPeerConnection.connectionState === 'connected' after a recovery ICE restart. */
|
|
9960
|
-
const PEER_CONNECTION_RECOVERY_WAIT_MS = 5e3;
|
|
9961
|
-
/** Polling interval in ms while waiting for RTCPeerConnection.connectionState to transition. */
|
|
9962
|
-
const PEER_CONNECTION_RECOVERY_POLL_MS = 100;
|
|
9963
|
-
/** Polling interval for LocalAudioPipeline.level$ (ms). ~30fps is smooth for meters. */
|
|
9964
|
-
const AUDIO_LEVEL_POLL_INTERVAL_MS = 33;
|
|
9965
|
-
/** RMS level threshold (0..1) above which the local participant is considered speaking. */
|
|
9966
|
-
const VAD_THRESHOLD = .03;
|
|
9967
|
-
/** Hold window in ms below the threshold before speaking$ flips back to false. */
|
|
9968
|
-
const VAD_HOLD_MS = 250;
|
|
9969
|
-
/** Whether to persist device selections to storage by default. */
|
|
9970
|
-
const DEFAULT_PERSIST_DEVICE_SELECTION = true;
|
|
9971
|
-
/** Whether to auto-apply device changes to active calls by default. */
|
|
9972
|
-
const DEFAULT_SYNC_DEVICES_TO_ACTIVE_CALLS = true;
|
|
9973
|
-
/** Storage keys for persisted device selections. */
|
|
9974
|
-
const DEVICE_STORAGE_KEY_AUDIO_INPUT = "sw:device:audioinput";
|
|
9975
|
-
const DEVICE_STORAGE_KEY_AUDIO_OUTPUT = "sw:device:audiooutput";
|
|
9976
|
-
const DEVICE_STORAGE_KEY_VIDEO_INPUT = "sw:device:videoinput";
|
|
9977
|
-
/** Whether to auto-mute video when the tab becomes hidden. */
|
|
9978
|
-
const DEFAULT_AUTO_MUTE_VIDEO_ON_HIDDEN = false;
|
|
9979
|
-
/** Whether to re-enumerate devices when the page becomes visible. */
|
|
9980
|
-
const DEFAULT_REFRESH_DEVICES_ON_VISIBLE = true;
|
|
9981
|
-
/** Whether to check peer connection health when the page becomes visible. */
|
|
9982
|
-
const DEFAULT_CHECK_CONNECTION_ON_VISIBLE = true;
|
|
9983
|
-
/** Whether automatic video degradation on low bandwidth is enabled. */
|
|
9984
|
-
const DEFAULT_ENABLE_AUTO_DEGRADATION = true;
|
|
9985
|
-
/** Bitrate in kbps below which video is automatically disabled. */
|
|
9986
|
-
const DEFAULT_DEGRADATION_BITRATE_THRESHOLD_KBPS = 150;
|
|
9987
|
-
/** Bitrate in kbps above which video is automatically re-enabled (hysteresis). */
|
|
9988
|
-
const DEFAULT_DEGRADATION_RECOVERY_THRESHOLD_KBPS = 300;
|
|
9989
|
-
/** Whether relay-only escalation is enabled as a last-resort recovery tier. */
|
|
9990
|
-
const DEFAULT_ENABLE_RELAY_FALLBACK = true;
|
|
9991
|
-
/** Whether to listen for browser online/offline/connection events. */
|
|
9992
|
-
const DEFAULT_ENABLE_NETWORK_CHANGE_DETECTION = true;
|
|
9993
|
-
/** Whether to intercept server-sent media-timeout hangups and attempt recovery. */
|
|
9994
|
-
const DEFAULT_ENABLE_SERVER_HANGUP_INTERCEPTION = true;
|
|
9995
|
-
/** Default video track constraints applied when video is enabled without explicit constraints. */
|
|
9996
|
-
const DEFAULT_VIDEO_CONSTRAINTS = {
|
|
9997
|
-
width: { ideal: 1280 },
|
|
9998
|
-
height: { ideal: 720 },
|
|
9999
|
-
aspectRatio: 16 / 9
|
|
10000
|
-
};
|
|
10001
|
-
/** Whether stereo Opus is enabled by default. */
|
|
10002
|
-
const DEFAULT_STEREO_AUDIO = false;
|
|
10003
|
-
/** Max average bitrate for stereo Opus in bits per second. */
|
|
10004
|
-
const DEFAULT_STEREO_MAX_AVERAGE_BITRATE = 51e4;
|
|
10005
|
-
|
|
10006
10046
|
//#endregion
|
|
10007
10047
|
//#region src/utils/time.ts
|
|
10008
10048
|
function fromSecToMs(seconds) {
|
|
@@ -10014,7 +10054,7 @@ function fromMsToSec(milliseconds) {
|
|
|
10014
10054
|
|
|
10015
10055
|
//#endregion
|
|
10016
10056
|
//#region src/containers/PreferencesContainer.ts
|
|
10017
|
-
const logger$
|
|
10057
|
+
const logger$31 = getLogger();
|
|
10018
10058
|
var PreferencesContainer = class PreferencesContainer {
|
|
10019
10059
|
static get instance() {
|
|
10020
10060
|
this._instance ??= new PreferencesContainer();
|
|
@@ -10676,7 +10716,7 @@ var ClientPreferences = class {
|
|
|
10676
10716
|
if (!this._storage) return;
|
|
10677
10717
|
const data = collectStoredPreferences();
|
|
10678
10718
|
this._storage.setItem(PREFERENCES_STORAGE_KEY, data, "local").catch((error) => {
|
|
10679
|
-
logger$
|
|
10719
|
+
logger$31.error(`[ClientPreferences] Failed to save preferences: ${String(error)}`);
|
|
10680
10720
|
});
|
|
10681
10721
|
}
|
|
10682
10722
|
/** Loads preferences from storage and applies them to the container. */
|
|
@@ -10685,7 +10725,7 @@ var ClientPreferences = class {
|
|
|
10685
10725
|
this._storage.getItem(PREFERENCES_STORAGE_KEY, "local").then((stored) => {
|
|
10686
10726
|
if (stored) applyStoredPreferences(stored);
|
|
10687
10727
|
}).catch((error) => {
|
|
10688
|
-
logger$
|
|
10728
|
+
logger$31.error(`[ClientPreferences] Failed to load preferences: ${String(error)}`);
|
|
10689
10729
|
});
|
|
10690
10730
|
}
|
|
10691
10731
|
};
|
|
@@ -10707,7 +10747,7 @@ function toError(value) {
|
|
|
10707
10747
|
//#endregion
|
|
10708
10748
|
//#region src/controllers/NavigatorDeviceController.ts
|
|
10709
10749
|
var import_cjs$29 = require_cjs();
|
|
10710
|
-
const logger$
|
|
10750
|
+
const logger$30 = getLogger();
|
|
10711
10751
|
/** Maps a device kind to its storage key. */
|
|
10712
10752
|
const DEVICE_STORAGE_KEYS = {
|
|
10713
10753
|
audioinput: DEVICE_STORAGE_KEY_AUDIO_INPUT,
|
|
@@ -10729,7 +10769,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10729
10769
|
super();
|
|
10730
10770
|
this.webRTCApiProvider = webRTCApiProvider;
|
|
10731
10771
|
this.deviceChangeHandler = () => {
|
|
10732
|
-
logger$
|
|
10772
|
+
logger$30.debug("[DeviceController] Device change detected");
|
|
10733
10773
|
this.enumerateDevices();
|
|
10734
10774
|
};
|
|
10735
10775
|
this._devicesState$ = this.createBehaviorSubject(initialDevicesState);
|
|
@@ -10794,13 +10834,13 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10794
10834
|
return this.cachedObservable("videoInputDevices$", () => this._devicesState$.pipe((0, import_cjs$29.map)((state) => state.videoinput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$)));
|
|
10795
10835
|
}
|
|
10796
10836
|
get selectedAudioInputDevice$() {
|
|
10797
|
-
return this.cachedObservable("selectedAudioInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.audioinput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$
|
|
10837
|
+
return this.cachedObservable("selectedAudioInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.audioinput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$30.debug("[DeviceController] Selected audio input device changed:", info))));
|
|
10798
10838
|
}
|
|
10799
10839
|
get selectedAudioOutputDevice$() {
|
|
10800
|
-
return this.cachedObservable("selectedAudioOutputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.audiooutput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$
|
|
10840
|
+
return this.cachedObservable("selectedAudioOutputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.audiooutput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$30.debug("[DeviceController] Selected audio output device changed:", info))));
|
|
10801
10841
|
}
|
|
10802
10842
|
get selectedVideoInputDevice$() {
|
|
10803
|
-
return this.cachedObservable("selectedVideoInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.videoinput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$
|
|
10843
|
+
return this.cachedObservable("selectedVideoInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.videoinput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$30.debug("[DeviceController] Selected video input device changed:", info))));
|
|
10804
10844
|
}
|
|
10805
10845
|
get selectedAudioInputDevice() {
|
|
10806
10846
|
if (this._audioInputDisabled$.value) return null;
|
|
@@ -10875,7 +10915,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10875
10915
|
if (device) this.persistDeviceSelection("audioinput", device);
|
|
10876
10916
|
}
|
|
10877
10917
|
selectVideoInputDevice(device) {
|
|
10878
|
-
logger$
|
|
10918
|
+
logger$30.debug("[DeviceController] Setting selected video input device:", device);
|
|
10879
10919
|
if (this._videoInputDisabled$.value && device) this._videoInputDisabled$.next(false);
|
|
10880
10920
|
const previous = this._selectedDevicesState$.value.videoinput;
|
|
10881
10921
|
if (previous && previous.deviceId !== device?.deviceId) this._deviceHistory.push("videoinput", previous);
|
|
@@ -10932,7 +10972,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10932
10972
|
}
|
|
10933
10973
|
const fromHistory = this._deviceHistory.findInHistory(kind, devices);
|
|
10934
10974
|
if (fromHistory) {
|
|
10935
|
-
logger$
|
|
10975
|
+
logger$30.debug(`[DeviceController] Device disappeared, falling back to history: ${fromHistory.label}`);
|
|
10936
10976
|
this.emitDeviceRecovered(kind, selected, fromHistory, "device_disconnected");
|
|
10937
10977
|
return fromHistory;
|
|
10938
10978
|
}
|
|
@@ -10985,7 +11025,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10985
11025
|
try {
|
|
10986
11026
|
await this._storageManager.setItem(DEVICE_STORAGE_KEYS[kind], stored, "local");
|
|
10987
11027
|
} catch (error) {
|
|
10988
|
-
logger$
|
|
11028
|
+
logger$30.error(`[DeviceController] Failed to persist device selection for ${kind}:`, error);
|
|
10989
11029
|
}
|
|
10990
11030
|
}
|
|
10991
11031
|
async loadPersistedDevices() {
|
|
@@ -11001,7 +11041,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
11001
11041
|
[kind]: stored
|
|
11002
11042
|
};
|
|
11003
11043
|
} catch (error) {
|
|
11004
|
-
logger$
|
|
11044
|
+
logger$30.error(`[DeviceController] Failed to load persisted device for ${kind}:`, error);
|
|
11005
11045
|
}
|
|
11006
11046
|
}
|
|
11007
11047
|
/** Clears device history, persisted selections, and re-enumerates devices. */
|
|
@@ -11019,7 +11059,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
11019
11059
|
this.disableDeviceMonitoring();
|
|
11020
11060
|
this.webRTCApiProvider.mediaDevices.addEventListener("devicechange", this.deviceChangeHandler);
|
|
11021
11061
|
if (PreferencesContainer.instance.devicePollingInterval > 0) this._devicesPoolingSubscription = (0, import_cjs$29.interval)(PreferencesContainer.instance.devicePollingInterval).subscribe(() => {
|
|
11022
|
-
logger$
|
|
11062
|
+
logger$30.debug("[DeviceController] Polling devices due to interval");
|
|
11023
11063
|
this.enumerateDevices();
|
|
11024
11064
|
});
|
|
11025
11065
|
this.enumerateDevices();
|
|
@@ -11045,13 +11085,13 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
11045
11085
|
videoinput: []
|
|
11046
11086
|
});
|
|
11047
11087
|
this._devicesState$.next(devicesByKind);
|
|
11048
|
-
logger$
|
|
11088
|
+
logger$30.debug("[DeviceController] Devices enumerated:", {
|
|
11049
11089
|
audioInputs: devicesByKind.audioinput.length,
|
|
11050
11090
|
audioOutputs: devicesByKind.audiooutput.length,
|
|
11051
11091
|
videoInputs: devicesByKind.videoinput.length
|
|
11052
11092
|
});
|
|
11053
11093
|
} catch (error) {
|
|
11054
|
-
logger$
|
|
11094
|
+
logger$30.error("[DeviceController] Failed to enumerate devices:", error);
|
|
11055
11095
|
this._errors$.next(toError(error));
|
|
11056
11096
|
}
|
|
11057
11097
|
}
|
|
@@ -11067,7 +11107,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
11067
11107
|
stream.getTracks().forEach((t) => t.stop());
|
|
11068
11108
|
return capabilities;
|
|
11069
11109
|
} catch (error) {
|
|
11070
|
-
logger$
|
|
11110
|
+
logger$30.error("[DeviceController] Failed to get device capabilities:", error);
|
|
11071
11111
|
this._errors$.next(toError(error));
|
|
11072
11112
|
throw error;
|
|
11073
11113
|
}
|
|
@@ -11318,7 +11358,7 @@ var DependencyContainer = class {
|
|
|
11318
11358
|
|
|
11319
11359
|
//#endregion
|
|
11320
11360
|
//#region src/controllers/CryptoController.ts
|
|
11321
|
-
const logger$
|
|
11361
|
+
const logger$29 = getLogger();
|
|
11322
11362
|
const DPOP_DB_NAME = "sw-dpop";
|
|
11323
11363
|
const DPOP_DB_VERSION = 1;
|
|
11324
11364
|
const DPOP_STORE_NAME = "keys";
|
|
@@ -11377,7 +11417,7 @@ async function loadKeyPairFromDB() {
|
|
|
11377
11417
|
tx.oncomplete = () => db.close();
|
|
11378
11418
|
});
|
|
11379
11419
|
} catch (error) {
|
|
11380
|
-
logger$
|
|
11420
|
+
logger$29.warn("[DPoP] Failed to load key pair from IndexedDB:", error);
|
|
11381
11421
|
return null;
|
|
11382
11422
|
}
|
|
11383
11423
|
}
|
|
@@ -11397,7 +11437,7 @@ async function saveKeyPairToDB(keyPair) {
|
|
|
11397
11437
|
};
|
|
11398
11438
|
});
|
|
11399
11439
|
} catch (error) {
|
|
11400
|
-
logger$
|
|
11440
|
+
logger$29.warn("[DPoP] Failed to save key pair to IndexedDB:", error);
|
|
11401
11441
|
}
|
|
11402
11442
|
}
|
|
11403
11443
|
async function deleteKeyPairFromDB() {
|
|
@@ -11416,7 +11456,7 @@ async function deleteKeyPairFromDB() {
|
|
|
11416
11456
|
};
|
|
11417
11457
|
});
|
|
11418
11458
|
} catch (error) {
|
|
11419
|
-
logger$
|
|
11459
|
+
logger$29.warn("[DPoP] Failed to delete key pair from IndexedDB:", error);
|
|
11420
11460
|
}
|
|
11421
11461
|
}
|
|
11422
11462
|
/**
|
|
@@ -11476,13 +11516,13 @@ var CryptoController = class {
|
|
|
11476
11516
|
this._publicJwk = await crypto.subtle.exportKey("jwk", stored.publicKey);
|
|
11477
11517
|
this._fingerprint = await computeJwkThumbprint(this._publicJwk);
|
|
11478
11518
|
this._initialized = true;
|
|
11479
|
-
logger$
|
|
11519
|
+
logger$29.debug("[DPoP] Key pair restored from IndexedDB, fingerprint:", this._fingerprint);
|
|
11480
11520
|
return this._fingerprint;
|
|
11481
11521
|
} catch (error) {
|
|
11482
|
-
logger$
|
|
11522
|
+
logger$29.warn("[DPoP] Stored key pair unusable, generating new one:", error);
|
|
11483
11523
|
await deleteKeyPairFromDB();
|
|
11484
11524
|
}
|
|
11485
|
-
logger$
|
|
11525
|
+
logger$29.debug("[DPoP] Generating RSA key pair");
|
|
11486
11526
|
this._keyPair = await crypto.subtle.generateKey({
|
|
11487
11527
|
name: "RSASSA-PKCS1-v1_5",
|
|
11488
11528
|
modulusLength: 2048,
|
|
@@ -11497,7 +11537,7 @@ var CryptoController = class {
|
|
|
11497
11537
|
this._fingerprint = await computeJwkThumbprint(this._publicJwk);
|
|
11498
11538
|
this._initialized = true;
|
|
11499
11539
|
await saveKeyPairToDB(this._keyPair);
|
|
11500
|
-
logger$
|
|
11540
|
+
logger$29.debug("[DPoP] Key pair generated and persisted, fingerprint:", this._fingerprint);
|
|
11501
11541
|
return this._fingerprint;
|
|
11502
11542
|
}
|
|
11503
11543
|
/**
|
|
@@ -11563,7 +11603,7 @@ var CryptoController = class {
|
|
|
11563
11603
|
this._fingerprint = null;
|
|
11564
11604
|
this._initialized = false;
|
|
11565
11605
|
deleteKeyPairFromDB();
|
|
11566
|
-
logger$
|
|
11606
|
+
logger$29.debug("[DPoP] Controller destroyed");
|
|
11567
11607
|
}
|
|
11568
11608
|
get publicJwk() {
|
|
11569
11609
|
if (!this._publicJwk) throw new DPoPInitError("CryptoController not initialized. Call init() first.");
|
|
@@ -11587,7 +11627,7 @@ var CryptoController = class {
|
|
|
11587
11627
|
//#endregion
|
|
11588
11628
|
//#region src/controllers/NetworkMonitor.ts
|
|
11589
11629
|
var import_cjs$28 = require_cjs();
|
|
11590
|
-
const logger$
|
|
11630
|
+
const logger$28 = getLogger();
|
|
11591
11631
|
/**
|
|
11592
11632
|
* Safely check whether we are running in a browser environment
|
|
11593
11633
|
* with `window` and the relevant event targets.
|
|
@@ -11644,7 +11684,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11644
11684
|
}
|
|
11645
11685
|
attachListeners() {
|
|
11646
11686
|
if (!hasBrowserNetworkEvents()) {
|
|
11647
|
-
logger$
|
|
11687
|
+
logger$28.debug("NetworkMonitor: no browser environment detected, skipping event listeners");
|
|
11648
11688
|
return;
|
|
11649
11689
|
}
|
|
11650
11690
|
window.addEventListener("online", this._onOnline);
|
|
@@ -11652,7 +11692,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11652
11692
|
const connection = getNetworkConnection();
|
|
11653
11693
|
if (connection) connection.addEventListener("change", this._onConnectionChange);
|
|
11654
11694
|
this._listenersAttached = true;
|
|
11655
|
-
logger$
|
|
11695
|
+
logger$28.debug("NetworkMonitor: event listeners attached");
|
|
11656
11696
|
}
|
|
11657
11697
|
removeListeners() {
|
|
11658
11698
|
if (!this._listenersAttached) return;
|
|
@@ -11663,10 +11703,10 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11663
11703
|
if (connection) connection.removeEventListener("change", this._onConnectionChange);
|
|
11664
11704
|
}
|
|
11665
11705
|
this._listenersAttached = false;
|
|
11666
|
-
logger$
|
|
11706
|
+
logger$28.debug("NetworkMonitor: event listeners removed");
|
|
11667
11707
|
}
|
|
11668
11708
|
handleOnline() {
|
|
11669
|
-
logger$
|
|
11709
|
+
logger$28.info("NetworkMonitor: browser went online");
|
|
11670
11710
|
this._isOnline$.next(true);
|
|
11671
11711
|
this._networkChange$.next({
|
|
11672
11712
|
type: "online",
|
|
@@ -11675,7 +11715,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11675
11715
|
});
|
|
11676
11716
|
}
|
|
11677
11717
|
handleOffline() {
|
|
11678
|
-
logger$
|
|
11718
|
+
logger$28.info("NetworkMonitor: browser went offline");
|
|
11679
11719
|
this._isOnline$.next(false);
|
|
11680
11720
|
this._networkChange$.next({
|
|
11681
11721
|
type: "offline",
|
|
@@ -11684,7 +11724,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11684
11724
|
}
|
|
11685
11725
|
handleConnectionChange() {
|
|
11686
11726
|
const networkType = getNetworkType();
|
|
11687
|
-
logger$
|
|
11727
|
+
logger$28.info(`NetworkMonitor: connection changed — effectiveType=${networkType ?? "unknown"}`);
|
|
11688
11728
|
this._networkChange$.next({
|
|
11689
11729
|
type: "connection_change",
|
|
11690
11730
|
timestamp: Date.now(),
|
|
@@ -11800,7 +11840,7 @@ function getNavigatorMediaDevices() {
|
|
|
11800
11840
|
//#endregion
|
|
11801
11841
|
//#region src/controllers/PreflightRunner.ts
|
|
11802
11842
|
var import_cjs$27 = require_cjs();
|
|
11803
|
-
const logger$
|
|
11843
|
+
const logger$27 = getLogger();
|
|
11804
11844
|
const DEFAULT_MEDIA_TEST_DURATION_S = 10;
|
|
11805
11845
|
const ICE_GATHERING_TIMEOUT_MS = 1e4;
|
|
11806
11846
|
const SIGNALING_RTT_TIMEOUT_MS = 5e3;
|
|
@@ -11849,7 +11889,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11849
11889
|
if (!this._options.skipMediaTest) try {
|
|
11850
11890
|
bandwidth = await this.testMediaBandwidth(destination);
|
|
11851
11891
|
} catch (error) {
|
|
11852
|
-
logger$
|
|
11892
|
+
logger$27.warn("[PreflightRunner] Media bandwidth test failed:", error);
|
|
11853
11893
|
warnings.push("Media bandwidth test failed");
|
|
11854
11894
|
}
|
|
11855
11895
|
return {
|
|
@@ -11861,7 +11901,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11861
11901
|
warnings
|
|
11862
11902
|
};
|
|
11863
11903
|
} catch (error) {
|
|
11864
|
-
logger$
|
|
11904
|
+
logger$27.error("[PreflightRunner] Preflight test failed:", error);
|
|
11865
11905
|
throw new PreflightError("preflight", error instanceof Error ? error : new Error(String(error)));
|
|
11866
11906
|
} finally {
|
|
11867
11907
|
this.destroy();
|
|
@@ -11892,7 +11932,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11892
11932
|
if (track.kind === "video" && track.readyState === "live") videoWorking = true;
|
|
11893
11933
|
}
|
|
11894
11934
|
} catch (error) {
|
|
11895
|
-
logger$
|
|
11935
|
+
logger$27.warn("[PreflightRunner] Device test failed:", error);
|
|
11896
11936
|
} finally {
|
|
11897
11937
|
if (audioStream) audioStream.getTracks().forEach((t) => t.stop());
|
|
11898
11938
|
}
|
|
@@ -11950,7 +11990,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11950
11990
|
rttMs
|
|
11951
11991
|
};
|
|
11952
11992
|
} catch (error) {
|
|
11953
|
-
logger$
|
|
11993
|
+
logger$27.warn("[PreflightRunner] ICE connectivity test failed:", error);
|
|
11954
11994
|
return {
|
|
11955
11995
|
type: "failed",
|
|
11956
11996
|
turnReachable: false,
|
|
@@ -11998,7 +12038,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11998
12038
|
//#endregion
|
|
11999
12039
|
//#region src/controllers/VisibilityController.ts
|
|
12000
12040
|
var import_cjs$26 = require_cjs();
|
|
12001
|
-
const logger$
|
|
12041
|
+
const logger$26 = getLogger();
|
|
12002
12042
|
/**
|
|
12003
12043
|
* Checks whether the document visibility API is available.
|
|
12004
12044
|
*/
|
|
@@ -12035,8 +12075,8 @@ var VisibilityController = class extends Destroyable {
|
|
|
12035
12075
|
this._boundHandler = this._handleVisibilityChange.bind(this);
|
|
12036
12076
|
if (this._hasVisibilityApi) {
|
|
12037
12077
|
document.addEventListener("visibilitychange", this._boundHandler);
|
|
12038
|
-
logger$
|
|
12039
|
-
} else logger$
|
|
12078
|
+
logger$26.debug("VisibilityController: listening for visibilitychange events");
|
|
12079
|
+
} else logger$26.debug("VisibilityController: document visibility API not available, defaulting to visible");
|
|
12040
12080
|
}
|
|
12041
12081
|
/**
|
|
12042
12082
|
* Observable of the current visibility state.
|
|
@@ -12061,7 +12101,7 @@ var VisibilityController = class extends Destroyable {
|
|
|
12061
12101
|
destroy() {
|
|
12062
12102
|
if (this._hasVisibilityApi) {
|
|
12063
12103
|
document.removeEventListener("visibilitychange", this._boundHandler);
|
|
12064
|
-
logger$
|
|
12104
|
+
logger$26.debug("VisibilityController: removed visibilitychange listener");
|
|
12065
12105
|
}
|
|
12066
12106
|
super.destroy();
|
|
12067
12107
|
}
|
|
@@ -12079,7 +12119,7 @@ var VisibilityController = class extends Destroyable {
|
|
|
12079
12119
|
timestamp: Date.now()
|
|
12080
12120
|
};
|
|
12081
12121
|
this._visibilityChange$.next(changeEvent);
|
|
12082
|
-
logger$
|
|
12122
|
+
logger$26.debug("VisibilityController: visibility changed", {
|
|
12083
12123
|
from: previousState,
|
|
12084
12124
|
to: newState
|
|
12085
12125
|
});
|
|
@@ -12320,7 +12360,7 @@ const RPCEventAckResponse = (id) => makeRPCResponse({
|
|
|
12320
12360
|
|
|
12321
12361
|
//#endregion
|
|
12322
12362
|
//#region src/managers/AttachManager.ts
|
|
12323
|
-
const logger$
|
|
12363
|
+
const logger$25 = getLogger();
|
|
12324
12364
|
var AttachManager = class {
|
|
12325
12365
|
constructor(storage, deviceController, reconnectCallsTimeout, attachKey) {
|
|
12326
12366
|
this.storage = storage;
|
|
@@ -12341,7 +12381,7 @@ var AttachManager = class {
|
|
|
12341
12381
|
try {
|
|
12342
12382
|
return await this.storage.getItem(this.attachKey) ?? {};
|
|
12343
12383
|
} catch (error) {
|
|
12344
|
-
logger$
|
|
12384
|
+
logger$25.warn("[AttachManager] Failed to retrieve attached calls from storage", error);
|
|
12345
12385
|
return {};
|
|
12346
12386
|
}
|
|
12347
12387
|
}
|
|
@@ -12349,7 +12389,7 @@ var AttachManager = class {
|
|
|
12349
12389
|
try {
|
|
12350
12390
|
await this.storage.setItem(this.attachKey, attached);
|
|
12351
12391
|
} catch (error) {
|
|
12352
|
-
logger$
|
|
12392
|
+
logger$25.warn("[AttachManager] Failed to write attached calls to storage", error);
|
|
12353
12393
|
}
|
|
12354
12394
|
}
|
|
12355
12395
|
/**
|
|
@@ -12368,7 +12408,7 @@ var AttachManager = class {
|
|
|
12368
12408
|
}
|
|
12369
12409
|
async attach(call) {
|
|
12370
12410
|
if (!call.to) {
|
|
12371
|
-
logger$
|
|
12411
|
+
logger$25.warn("[AttachManager] Skip attach for calls with no destination");
|
|
12372
12412
|
return;
|
|
12373
12413
|
}
|
|
12374
12414
|
const destination = call.to;
|
|
@@ -12421,15 +12461,15 @@ var AttachManager = class {
|
|
|
12421
12461
|
callId,
|
|
12422
12462
|
...options
|
|
12423
12463
|
});
|
|
12424
|
-
logger$
|
|
12464
|
+
logger$25.info(`[AttachManager] Reattached call ${callId} (attempt ${attempt})`);
|
|
12425
12465
|
succeeded = true;
|
|
12426
12466
|
break;
|
|
12427
12467
|
} catch (error) {
|
|
12428
|
-
logger$
|
|
12468
|
+
logger$25.warn(`[AttachManager] Reattach attempt ${attempt}/3 failed for call ${callId}:`, error);
|
|
12429
12469
|
if (attempt < 3) await new Promise((r) => setTimeout(r, (attempt + 1) * 1e3));
|
|
12430
12470
|
}
|
|
12431
12471
|
if (!succeeded) {
|
|
12432
|
-
logger$
|
|
12472
|
+
logger$25.warn(`[AttachManager] Reattach failed after 3 attempts for call ${callId}, removing reference`);
|
|
12433
12473
|
await this.detach({
|
|
12434
12474
|
id: callId,
|
|
12435
12475
|
mediaDirections: attachment.mediaDirections
|
|
@@ -13366,7 +13406,9 @@ const DEFAULT_MEMBER_CAPABILITIES = {
|
|
|
13366
13406
|
position: false,
|
|
13367
13407
|
meta: false,
|
|
13368
13408
|
remove: false,
|
|
13369
|
-
audioFlags: false
|
|
13409
|
+
audioFlags: false,
|
|
13410
|
+
denoise: false,
|
|
13411
|
+
lowbitrate: false
|
|
13370
13412
|
};
|
|
13371
13413
|
/**
|
|
13372
13414
|
* Default call capabilities with no permissions
|
|
@@ -13439,7 +13481,9 @@ function computeMemberCapabilities(flags, memberType) {
|
|
|
13439
13481
|
position: hasBooleanCapability(flags, memberType, null, "position"),
|
|
13440
13482
|
meta: hasBooleanCapability(flags, memberType, null, "meta"),
|
|
13441
13483
|
remove: hasBooleanCapability(flags, memberType, null, "remove"),
|
|
13442
|
-
audioFlags: hasBooleanCapability(flags, memberType, null, "audioflags")
|
|
13484
|
+
audioFlags: hasBooleanCapability(flags, memberType, null, "audioflags"),
|
|
13485
|
+
denoise: hasBooleanCapability(flags, memberType, null, "denoise"),
|
|
13486
|
+
lowbitrate: hasBooleanCapability(flags, memberType, null, "lowbitrate")
|
|
13443
13487
|
};
|
|
13444
13488
|
}
|
|
13445
13489
|
/**
|
|
@@ -13596,7 +13640,7 @@ function toggleHandraiseMethod(is) {
|
|
|
13596
13640
|
|
|
13597
13641
|
//#endregion
|
|
13598
13642
|
//#region src/core/entities/Participant.ts
|
|
13599
|
-
const logger$
|
|
13643
|
+
const logger$24 = getLogger();
|
|
13600
13644
|
const initialState = {};
|
|
13601
13645
|
/**
|
|
13602
13646
|
* Represents a participant in a call.
|
|
@@ -13822,6 +13866,10 @@ var Participant = class extends Destroyable {
|
|
|
13822
13866
|
get nodeId() {
|
|
13823
13867
|
return this._state$.value.node_id;
|
|
13824
13868
|
}
|
|
13869
|
+
/** Call ID for this participant's leg, or `undefined` if not available. */
|
|
13870
|
+
get callId() {
|
|
13871
|
+
return this._state$.value.call_id;
|
|
13872
|
+
}
|
|
13825
13873
|
/** @internal */
|
|
13826
13874
|
get value() {
|
|
13827
13875
|
return this._state$.value;
|
|
@@ -13883,8 +13931,9 @@ var Participant = class extends Destroyable {
|
|
|
13883
13931
|
noise_suppression: !this.noiseSuppression
|
|
13884
13932
|
});
|
|
13885
13933
|
}
|
|
13934
|
+
/** Toggles low-bitrate mode for this participant's media. */
|
|
13886
13935
|
async toggleLowbitrate() {
|
|
13887
|
-
|
|
13936
|
+
await this.executeMethod(this.id, "call.lowbitrate.set", { lowbitrate: !this.lowbitrate });
|
|
13888
13937
|
}
|
|
13889
13938
|
/**
|
|
13890
13939
|
* Adjusts the **conference-only** microphone energy gate / sensitivity level
|
|
@@ -13931,10 +13980,27 @@ var Participant = class extends Destroyable {
|
|
|
13931
13980
|
}
|
|
13932
13981
|
/**
|
|
13933
13982
|
* Sets the participant's position in the video layout.
|
|
13983
|
+
*
|
|
13984
|
+
* Requires the `member.position` capability. The gateway keys positions by the
|
|
13985
|
+
* **target member's own** `call_id`/`node_id` (see issue #19400 and the legacy
|
|
13986
|
+
* `setPositions` implementation), so this sends the participant's own call
|
|
13987
|
+
* context — matching {@link Participant.remove}. A resolved promise does not
|
|
13988
|
+
* guarantee a visible change: the backend silently returns `200` (no-op) for
|
|
13989
|
+
* non-conference targets.
|
|
13990
|
+
*
|
|
13934
13991
|
* @param value - The {@link VideoPosition} to assign (e.g. `'auto'`, `'reserved-0'`).
|
|
13935
13992
|
*/
|
|
13936
13993
|
async setPosition(value) {
|
|
13937
|
-
|
|
13994
|
+
const state = this._state$.value;
|
|
13995
|
+
const target = {
|
|
13996
|
+
member_id: this.id,
|
|
13997
|
+
call_id: state.call_id ?? "",
|
|
13998
|
+
node_id: state.node_id ?? ""
|
|
13999
|
+
};
|
|
14000
|
+
await this.executeMethod(target, "call.member.position.set", { targets: [{
|
|
14001
|
+
target,
|
|
14002
|
+
position: value
|
|
14003
|
+
}] });
|
|
13938
14004
|
}
|
|
13939
14005
|
/** Removes this participant from the call. */
|
|
13940
14006
|
async remove() {
|
|
@@ -14024,12 +14090,21 @@ var SelfParticipant = class extends Participant {
|
|
|
14024
14090
|
noise_suppression: true
|
|
14025
14091
|
});
|
|
14026
14092
|
}
|
|
14027
|
-
/**
|
|
14093
|
+
/**
|
|
14094
|
+
* Starts sharing the local screen.
|
|
14095
|
+
*
|
|
14096
|
+
* The call is unaffected when acquisition fails.
|
|
14097
|
+
*
|
|
14098
|
+
* @throws The raw `getDisplayMedia` error. A dismissed picker or a
|
|
14099
|
+
* permission denial rejects with a `NotAllowedError` `DOMException` —
|
|
14100
|
+
* inspect `error.name` to tell benign cancels apart from real failures.
|
|
14101
|
+
*/
|
|
14028
14102
|
async startScreenShare() {
|
|
14029
14103
|
try {
|
|
14030
14104
|
await this.vertoManager.addScreenMedia();
|
|
14031
14105
|
} catch (error) {
|
|
14032
|
-
logger$
|
|
14106
|
+
logger$24.error("[Participant.startScreenShare] Screen share error:", error);
|
|
14107
|
+
throw error;
|
|
14033
14108
|
}
|
|
14034
14109
|
}
|
|
14035
14110
|
/** Observable of the current screen share status. */
|
|
@@ -14044,12 +14119,20 @@ var SelfParticipant = class extends Participant {
|
|
|
14044
14119
|
async stopScreenShare() {
|
|
14045
14120
|
return this.vertoManager.removeScreenMedia();
|
|
14046
14121
|
}
|
|
14047
|
-
/**
|
|
14122
|
+
/**
|
|
14123
|
+
* Adds an additional media input device to the call.
|
|
14124
|
+
*
|
|
14125
|
+
* The call is unaffected when acquisition fails.
|
|
14126
|
+
*
|
|
14127
|
+
* @throws The raw `getUserMedia` error (e.g. `NotAllowedError` on
|
|
14128
|
+
* permission denial) — inspect `error.name` to decide how to react.
|
|
14129
|
+
*/
|
|
14048
14130
|
async addAdditionalDevice(options) {
|
|
14049
14131
|
try {
|
|
14050
14132
|
await this.vertoManager.addInputDevice(options);
|
|
14051
14133
|
} catch (error) {
|
|
14052
|
-
logger$
|
|
14134
|
+
logger$24.error("[Participant.addAdditionalDevice] Additional device error:", error);
|
|
14135
|
+
throw error;
|
|
14053
14136
|
}
|
|
14054
14137
|
}
|
|
14055
14138
|
/** Removes an additional media input device by ID. */
|
|
@@ -14111,7 +14194,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14111
14194
|
*/
|
|
14112
14195
|
exitStudioModeIfActive() {
|
|
14113
14196
|
if (this._studioAudio$.value) {
|
|
14114
|
-
logger$
|
|
14197
|
+
logger$24.debug("[SelfParticipant] Exiting studio audio mode due to individual flag toggle");
|
|
14115
14198
|
this._studioAudio$.next(false);
|
|
14116
14199
|
}
|
|
14117
14200
|
}
|
|
@@ -14135,7 +14218,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14135
14218
|
try {
|
|
14136
14219
|
await super.mute();
|
|
14137
14220
|
} catch (error) {
|
|
14138
|
-
logger$
|
|
14221
|
+
logger$24.warn("[Participant.toggleAudioInput] Server Error while muting audio input, proceeding with local toggle anyway", error);
|
|
14139
14222
|
} finally {
|
|
14140
14223
|
this.vertoManager.muteMainAudioInputDevice();
|
|
14141
14224
|
}
|
|
@@ -14145,7 +14228,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14145
14228
|
try {
|
|
14146
14229
|
await super.unmute();
|
|
14147
14230
|
} catch (error) {
|
|
14148
|
-
logger$
|
|
14231
|
+
logger$24.warn("[Participant.toggleAudioInput] Server Error while unmuting audio input, proceeding with local toggle anyway", error);
|
|
14149
14232
|
} finally {
|
|
14150
14233
|
await this.vertoManager.unmuteMainAudioInputDevice();
|
|
14151
14234
|
}
|
|
@@ -14155,7 +14238,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14155
14238
|
try {
|
|
14156
14239
|
await super.muteVideo();
|
|
14157
14240
|
} catch (error) {
|
|
14158
|
-
logger$
|
|
14241
|
+
logger$24.warn("[Participant.toggleVideoInput] Server Error while muting video input, proceeding with local toggle anyway", error);
|
|
14159
14242
|
} finally {
|
|
14160
14243
|
this.vertoManager.muteMainVideoInputDevice();
|
|
14161
14244
|
}
|
|
@@ -14165,7 +14248,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14165
14248
|
try {
|
|
14166
14249
|
await super.unmuteVideo();
|
|
14167
14250
|
} catch (error) {
|
|
14168
|
-
logger$
|
|
14251
|
+
logger$24.warn("[Participant.toggleVideoInput] Server Error while unmuting video input, proceeding with local toggle anyway", error);
|
|
14169
14252
|
} finally {
|
|
14170
14253
|
await this.vertoManager.unmuteMainVideoInputDevice();
|
|
14171
14254
|
}
|
|
@@ -14370,7 +14453,7 @@ function filterAs(predicate, resultPath) {
|
|
|
14370
14453
|
//#endregion
|
|
14371
14454
|
//#region src/operators/throwOnRPCError.ts
|
|
14372
14455
|
var import_cjs$21 = require_cjs();
|
|
14373
|
-
const logger$
|
|
14456
|
+
const logger$23 = getLogger();
|
|
14374
14457
|
/**
|
|
14375
14458
|
* RxJS operator that throws a {@link JSONRPCError} when the RPC response contains an error.
|
|
14376
14459
|
* Passes successful responses through unchanged.
|
|
@@ -14378,14 +14461,14 @@ const logger$22 = getLogger();
|
|
|
14378
14461
|
function throwOnRPCError() {
|
|
14379
14462
|
return (0, import_cjs$21.map)((response) => {
|
|
14380
14463
|
if (response.error) {
|
|
14381
|
-
logger$
|
|
14464
|
+
logger$23.error("[throwOnRPCError] RPC error response:", {
|
|
14382
14465
|
code: response.error.code,
|
|
14383
14466
|
message: response.error.message,
|
|
14384
14467
|
data: response.error.data
|
|
14385
14468
|
});
|
|
14386
14469
|
throw new JSONRPCError(response.error.code, response.error.message, response.error.data);
|
|
14387
14470
|
}
|
|
14388
|
-
logger$
|
|
14471
|
+
logger$23.debug("[throwOnRPCError] RPC successful response:", response);
|
|
14389
14472
|
return response;
|
|
14390
14473
|
});
|
|
14391
14474
|
}
|
|
@@ -14393,7 +14476,7 @@ function throwOnRPCError() {
|
|
|
14393
14476
|
//#endregion
|
|
14394
14477
|
//#region src/managers/CallEventsManager.ts
|
|
14395
14478
|
var import_cjs$20 = require_cjs();
|
|
14396
|
-
const logger$
|
|
14479
|
+
const logger$22 = getLogger();
|
|
14397
14480
|
const initialSessionState = {};
|
|
14398
14481
|
/** @internal */
|
|
14399
14482
|
var CallEventsManager = class extends Destroyable {
|
|
@@ -14497,7 +14580,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14497
14580
|
}
|
|
14498
14581
|
initSubscriptions() {
|
|
14499
14582
|
this.subscribeTo(this.callJoinedEvent$, (callJoinedEvent) => {
|
|
14500
|
-
logger$
|
|
14583
|
+
logger$22.debug("[CallEventsManager] Handling call.joined event for call/session IDs:", {
|
|
14501
14584
|
callId: callJoinedEvent.call_id,
|
|
14502
14585
|
roomSessionId: callJoinedEvent.room_session_id
|
|
14503
14586
|
});
|
|
@@ -14524,19 +14607,19 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14524
14607
|
if (this._self$.value?.capabilities.setLayout) this.updateLayouts();
|
|
14525
14608
|
});
|
|
14526
14609
|
this.subscribeTo(this.memberUpdates$, (member) => {
|
|
14527
|
-
logger$
|
|
14610
|
+
logger$22.debug("[CallEventsManager] Handling member update event for member ID:", member);
|
|
14528
14611
|
this.upsertParticipant(member);
|
|
14529
14612
|
});
|
|
14530
14613
|
this.subscribeTo(this.webRtcCallSession.memberLeft$, (memberLeftEvent) => {
|
|
14531
|
-
logger$
|
|
14614
|
+
logger$22.debug("[CallEventsManager] Handling member.left event for member ID:", memberLeftEvent.member.member_id);
|
|
14532
14615
|
const participants = { ...this._participants$.value };
|
|
14533
14616
|
if (memberLeftEvent.member.member_id in participants) {
|
|
14534
14617
|
delete participants[memberLeftEvent.member.member_id];
|
|
14535
14618
|
this._participants$.next(participants);
|
|
14536
|
-
} else logger$
|
|
14619
|
+
} else logger$22.warn(`[CallEventsManager] Received member.left event for unknown member ID: ${memberLeftEvent.member.member_id}`);
|
|
14537
14620
|
});
|
|
14538
14621
|
this.subscribeTo(this.webRtcCallSession.callUpdated$, (callUpdatedEvent) => {
|
|
14539
|
-
logger$
|
|
14622
|
+
logger$22.debug("[CallEventsManager] Handling call.updated event:", callUpdatedEvent);
|
|
14540
14623
|
const roomSession = callUpdatedEvent.room_session;
|
|
14541
14624
|
this._sessionState$.next({
|
|
14542
14625
|
...this._sessionState$.value,
|
|
@@ -14551,7 +14634,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14551
14634
|
});
|
|
14552
14635
|
});
|
|
14553
14636
|
this.subscribeTo(this.layoutChangedEvent$, (layoutChangedEvent) => {
|
|
14554
|
-
logger$
|
|
14637
|
+
logger$22.debug("[CallEventsManager] Handling layout.changed event:", layoutChangedEvent);
|
|
14555
14638
|
this._sessionState$.next({
|
|
14556
14639
|
...this._sessionState$.value,
|
|
14557
14640
|
layout_name: layoutChangedEvent.id,
|
|
@@ -14561,10 +14644,10 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14561
14644
|
});
|
|
14562
14645
|
}
|
|
14563
14646
|
updateParticipantPositions(layoutChangedEvent) {
|
|
14564
|
-
if (Object.keys(this._participants$.value).length > 0 && !layoutChangedEvent.layers.some((layer) => !!layer.member_id)) logger$
|
|
14647
|
+
if (Object.keys(this._participants$.value).length > 0 && !layoutChangedEvent.layers.some((layer) => !!layer.member_id)) logger$22.warn("[CallEventsManager] No layers with member_id found in layout.changed event. Nothing to update.");
|
|
14565
14648
|
layoutChangedEvent.layers.filter((layer) => !!layer.member_id).filter((layer) => {
|
|
14566
14649
|
if (!(layer.member_id in this._participants$.value)) {
|
|
14567
|
-
logger$
|
|
14650
|
+
logger$22.warn(`[CallEventsManager] Skipping layout layer for unknown member_id: ${layer.member_id}`);
|
|
14568
14651
|
return false;
|
|
14569
14652
|
}
|
|
14570
14653
|
return true;
|
|
@@ -14587,7 +14670,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14587
14670
|
layouts: response.result.layouts
|
|
14588
14671
|
});
|
|
14589
14672
|
}).catch((error) => {
|
|
14590
|
-
logger$
|
|
14673
|
+
logger$22.error("[CallEventsManager] Error fetching layouts:", error);
|
|
14591
14674
|
});
|
|
14592
14675
|
}
|
|
14593
14676
|
updateParticipants(members) {
|
|
@@ -14603,7 +14686,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14603
14686
|
}
|
|
14604
14687
|
const participant = this._participants$.value[member.member_id];
|
|
14605
14688
|
const oldValue = participant.value;
|
|
14606
|
-
logger$
|
|
14689
|
+
logger$22.debug("[CallEventsManager] Updating participant:", member.member_id, {
|
|
14607
14690
|
oldValue,
|
|
14608
14691
|
newValue: member
|
|
14609
14692
|
});
|
|
@@ -14616,17 +14699,17 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14616
14699
|
}
|
|
14617
14700
|
get callJoinedEvent$() {
|
|
14618
14701
|
return this.cachedObservable("callJoinedEvent$", () => this.webRtcCallSession.callEvent$.pipe((0, import_cjs$20.filter)(isCallJoinedPayload), (0, import_cjs$20.tap)((event) => {
|
|
14619
|
-
logger$
|
|
14702
|
+
logger$22.debug("[CallEventsManager] Call joined event:", event);
|
|
14620
14703
|
})));
|
|
14621
14704
|
}
|
|
14622
14705
|
get layoutChangedEvent$() {
|
|
14623
14706
|
return this.cachedObservable("layoutChangedEvent$", () => this.webRtcCallSession.callEvent$.pipe(filterAs(isLayoutChangedPayload, "layout"), (0, import_cjs$20.tap)((event) => {
|
|
14624
|
-
logger$
|
|
14707
|
+
logger$22.debug("[CallEventsManager] Layout changed event:", event);
|
|
14625
14708
|
})));
|
|
14626
14709
|
}
|
|
14627
14710
|
get memberUpdates$() {
|
|
14628
14711
|
return this.cachedObservable("memberUpdates$", () => (0, import_cjs$20.merge)(this.webRtcCallSession.memberJoined$, this.webRtcCallSession.memberUpdated$, this.webRtcCallSession.memberTalking$).pipe((0, import_cjs$20.map)((event) => event.member), (0, import_cjs$20.tap)((event) => {
|
|
14629
|
-
logger$
|
|
14712
|
+
logger$22.debug("[CallEventsManager] Member update event:", event);
|
|
14630
14713
|
})));
|
|
14631
14714
|
}
|
|
14632
14715
|
destroy() {
|
|
@@ -14883,7 +14966,7 @@ function appendStereoParams(fmtpLine, maxBitrate) {
|
|
|
14883
14966
|
//#endregion
|
|
14884
14967
|
//#region src/controllers/ICEGatheringController.ts
|
|
14885
14968
|
var import_cjs$19 = require_cjs();
|
|
14886
|
-
const logger$
|
|
14969
|
+
const logger$21 = getLogger();
|
|
14887
14970
|
var ICEGatheringController = class extends Destroyable {
|
|
14888
14971
|
constructor(peerConnection, peerConnectionControllerNegotiating$, options = {}) {
|
|
14889
14972
|
super();
|
|
@@ -14891,23 +14974,23 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14891
14974
|
this.peerConnectionControllerNegotiating$ = peerConnectionControllerNegotiating$;
|
|
14892
14975
|
this.onicegatheringstatechangeHandler = () => {
|
|
14893
14976
|
const { iceGatheringState } = this.peerConnection;
|
|
14894
|
-
logger$
|
|
14977
|
+
logger$21.debug(`[ICEGatheringController] ICE gathering state changed to: ${iceGatheringState}`);
|
|
14895
14978
|
if (iceGatheringState === "gathering") this._iceCandidatesState.next({
|
|
14896
14979
|
state: "gathering",
|
|
14897
14980
|
validSDP: false
|
|
14898
14981
|
});
|
|
14899
14982
|
};
|
|
14900
14983
|
this.onicecandidateHandler = (event) => {
|
|
14901
|
-
logger$
|
|
14984
|
+
logger$21.debug("[ICEGatheringController] ICE candidate event received:", event.candidate);
|
|
14902
14985
|
this.removeTimer("iceCandidateTimer");
|
|
14903
14986
|
if (event.candidate) this.iceCandidateTimer = setTimeout(() => {
|
|
14904
14987
|
if (this.peerConnection.iceGatheringState !== "complete") {
|
|
14905
|
-
logger$
|
|
14988
|
+
logger$21.warn("[ICEGatheringController] ICE candidate timeout, using current SDP");
|
|
14906
14989
|
this.handleICECandidateTimeout();
|
|
14907
14990
|
}
|
|
14908
14991
|
}, this.iceCandidateTimeout);
|
|
14909
14992
|
else {
|
|
14910
|
-
logger$
|
|
14993
|
+
logger$21.debug("[ICEGatheringController] ICE gathering completed: null candidate received");
|
|
14911
14994
|
this.removeTimer("iceGatheringTimer");
|
|
14912
14995
|
this.handleICEGatheringComplete();
|
|
14913
14996
|
}
|
|
@@ -14925,7 +15008,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14925
15008
|
this.setupEventListeners();
|
|
14926
15009
|
this.iceGatheringTimer = setTimeout(() => {
|
|
14927
15010
|
if (this.peerConnection.iceGatheringState !== "complete") {
|
|
14928
|
-
logger$
|
|
15011
|
+
logger$21.warn("[ICEGatheringController] ICE gathering timeout, using current SDP");
|
|
14929
15012
|
this.handleICEGatheringTimeout();
|
|
14930
15013
|
}
|
|
14931
15014
|
}, this.iceGatheringTimeout);
|
|
@@ -14952,9 +15035,9 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14952
15035
|
this.relayOnly = value;
|
|
14953
15036
|
}
|
|
14954
15037
|
handleICEGatheringComplete() {
|
|
14955
|
-
logger$
|
|
14956
|
-
logger$
|
|
14957
|
-
logger$
|
|
15038
|
+
logger$21.debug("[ICEGatheringController] Handling ICE gathering complete");
|
|
15039
|
+
logger$21.debug(`[ICEGatheringController] Checking ICE gathering state: ${this.peerConnection.iceGatheringState}`);
|
|
15040
|
+
logger$21.debug("[ICEGatheringController] ICE gathering complete");
|
|
14958
15041
|
this._iceCandidatesState.next({
|
|
14959
15042
|
state: "complete",
|
|
14960
15043
|
validSDP: this.hasValidLocalDescriptionSDP
|
|
@@ -14970,21 +15053,21 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14970
15053
|
this.removeTimer("iceGatheringTimer");
|
|
14971
15054
|
const validSDP = this.hasValidLocalDescriptionSDP;
|
|
14972
15055
|
if (validSDP) {
|
|
14973
|
-
logger$
|
|
15056
|
+
logger$21.debug("[ICEGatheringController] Local SDP is valid");
|
|
14974
15057
|
this._iceCandidatesState.next({
|
|
14975
15058
|
state: "timeout",
|
|
14976
15059
|
validSDP
|
|
14977
15060
|
});
|
|
14978
15061
|
this.stopGathering();
|
|
14979
|
-
} else logger$
|
|
15062
|
+
} else logger$21.debug("### ICE gathering timeout\n", this.peerConnection.localDescription?.sdp);
|
|
14980
15063
|
}
|
|
14981
15064
|
handleICECandidateTimeout() {
|
|
14982
15065
|
if (this.iceCandidateTimer) this.removeTimer("iceCandidateTimer");
|
|
14983
|
-
logger$
|
|
15066
|
+
logger$21.warn("[ICEGatheringController] ICE candidate timeout");
|
|
14984
15067
|
const validSDP = this.hasValidLocalDescriptionSDP;
|
|
14985
15068
|
if (!validSDP && !this.relayOnly) this.restartICEGatheringWithRelayOnly();
|
|
14986
15069
|
else {
|
|
14987
|
-
logger$
|
|
15070
|
+
logger$21.debug("[ICEGatheringController] Using current SDP due to ICE candidate timeout");
|
|
14988
15071
|
this._iceCandidatesState.next({
|
|
14989
15072
|
state: "timeout",
|
|
14990
15073
|
validSDP
|
|
@@ -14993,7 +15076,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14993
15076
|
}
|
|
14994
15077
|
}
|
|
14995
15078
|
restartICEGatheringWithRelayOnly() {
|
|
14996
|
-
logger$
|
|
15079
|
+
logger$21.debug("[ICEGatheringController] Restarting ICE gathering with relay-only candidates");
|
|
14997
15080
|
this.relayOnly = true;
|
|
14998
15081
|
this.peerConnection.setConfiguration({
|
|
14999
15082
|
...this.peerConnection.getConfiguration(),
|
|
@@ -15008,7 +15091,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
15008
15091
|
}
|
|
15009
15092
|
}
|
|
15010
15093
|
clearAllTimers() {
|
|
15011
|
-
logger$
|
|
15094
|
+
logger$21.debug("[ICEGatheringController] Clearing all timers");
|
|
15012
15095
|
this.removeTimer("iceGatheringTimer");
|
|
15013
15096
|
this.removeTimer("iceCandidateTimer");
|
|
15014
15097
|
}
|
|
@@ -15017,7 +15100,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
15017
15100
|
this.peerConnection.removeEventListener("icecandidate", this.onicecandidateHandler);
|
|
15018
15101
|
}
|
|
15019
15102
|
destroy() {
|
|
15020
|
-
logger$
|
|
15103
|
+
logger$21.debug("[ICEGatheringController] Destroying ICEGatheringController");
|
|
15021
15104
|
this.clearAllTimers();
|
|
15022
15105
|
this.removeEventListeners();
|
|
15023
15106
|
super.destroy();
|
|
@@ -15027,7 +15110,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
15027
15110
|
//#endregion
|
|
15028
15111
|
//#region src/controllers/LocalAudioPipeline.ts
|
|
15029
15112
|
var import_cjs$18 = require_cjs();
|
|
15030
|
-
const logger$
|
|
15113
|
+
const logger$20 = getLogger();
|
|
15031
15114
|
/**
|
|
15032
15115
|
* Web Audio pipeline for the local microphone stream.
|
|
15033
15116
|
*
|
|
@@ -15139,7 +15222,7 @@ var LocalAudioPipeline = class extends Destroyable {
|
|
|
15139
15222
|
try {
|
|
15140
15223
|
this._inputSource.disconnect();
|
|
15141
15224
|
} catch (error) {
|
|
15142
|
-
logger$
|
|
15225
|
+
logger$20.debug("[LocalAudioPipeline] input disconnect warning:", error);
|
|
15143
15226
|
}
|
|
15144
15227
|
this._inputSource = null;
|
|
15145
15228
|
}
|
|
@@ -15149,7 +15232,7 @@ var LocalAudioPipeline = class extends Destroyable {
|
|
|
15149
15232
|
this._inputSource = this._audioContext.createMediaStreamSource(this._inputStream);
|
|
15150
15233
|
this._inputSource.connect(this._gainNode);
|
|
15151
15234
|
if (this._audioContext.state === "suspended") this._audioContext.resume().catch((error) => {
|
|
15152
|
-
logger$
|
|
15235
|
+
logger$20.warn("[LocalAudioPipeline] AudioContext resume failed:", error);
|
|
15153
15236
|
});
|
|
15154
15237
|
}
|
|
15155
15238
|
destroy() {
|
|
@@ -15164,7 +15247,7 @@ var LocalAudioPipeline = class extends Destroyable {
|
|
|
15164
15247
|
this._analyser.disconnect();
|
|
15165
15248
|
} catch {}
|
|
15166
15249
|
this._audioContext.close().catch((error) => {
|
|
15167
|
-
logger$
|
|
15250
|
+
logger$20.debug("[LocalAudioPipeline] audio context close warning:", error);
|
|
15168
15251
|
});
|
|
15169
15252
|
super.destroy();
|
|
15170
15253
|
}
|
|
@@ -15191,7 +15274,7 @@ var LocalAudioPipeline = class extends Destroyable {
|
|
|
15191
15274
|
//#endregion
|
|
15192
15275
|
//#region src/controllers/LocalStreamController.ts
|
|
15193
15276
|
var import_cjs$17 = require_cjs();
|
|
15194
|
-
const logger$
|
|
15277
|
+
const logger$19 = getLogger();
|
|
15195
15278
|
var LocalStreamController = class extends Destroyable {
|
|
15196
15279
|
constructor(options) {
|
|
15197
15280
|
super();
|
|
@@ -15229,26 +15312,26 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15229
15312
|
* Build the local media stream based on the provided options.
|
|
15230
15313
|
*/
|
|
15231
15314
|
async buildLocalStream() {
|
|
15232
|
-
logger$
|
|
15315
|
+
logger$19.debug("[LocalStreamController] Building local media stream.");
|
|
15233
15316
|
let stream;
|
|
15234
15317
|
if (this.options.inputAudioStream ?? this.options.inputVideoStream) {
|
|
15235
15318
|
const tracks = [...this.options.inputAudioStream?.getTracks() ?? [], ...this.options.inputVideoStream?.getTracks() ?? []];
|
|
15236
15319
|
stream = new MediaStream(tracks);
|
|
15237
15320
|
} else if (this.options.propose === "screenshare") {
|
|
15238
|
-
logger$
|
|
15321
|
+
logger$19.debug("[LocalStreamController] Requesting display media for screen sharing with audio:", Boolean(this.options.inputAudioDeviceConstraints));
|
|
15239
15322
|
stream = await this.options.getDisplayMedia({
|
|
15240
15323
|
video: true,
|
|
15241
15324
|
audio: Boolean(this.options.inputAudioDeviceConstraints)
|
|
15242
15325
|
});
|
|
15243
|
-
logger$
|
|
15326
|
+
logger$19.debug("[LocalStreamController] Screen share media obtained:", stream);
|
|
15244
15327
|
} else {
|
|
15245
15328
|
const constraints = {
|
|
15246
15329
|
audio: this.options.inputAudioDeviceConstraints,
|
|
15247
15330
|
video: this.options.inputVideoDeviceConstraints
|
|
15248
15331
|
};
|
|
15249
|
-
logger$
|
|
15332
|
+
logger$19.debug("[LocalStreamController] Requesting user media with constraints:", constraints);
|
|
15250
15333
|
stream = await this.options.getUserMedia(constraints);
|
|
15251
|
-
logger$
|
|
15334
|
+
logger$19.debug("[LocalStreamController] User media obtained:", stream);
|
|
15252
15335
|
}
|
|
15253
15336
|
this._localStream$.next(stream);
|
|
15254
15337
|
this._localAudioTracks$.next(stream.getAudioTracks());
|
|
@@ -15267,7 +15350,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15267
15350
|
this._localStream$.next(localStream);
|
|
15268
15351
|
if (track.kind === "video") this._localVideoTracks$.next(localStream.getVideoTracks());
|
|
15269
15352
|
else this._localAudioTracks$.next(localStream.getAudioTracks());
|
|
15270
|
-
logger$
|
|
15353
|
+
logger$19.debug(`[LocalStreamController] ${track.kind} track added:`, track.id);
|
|
15271
15354
|
return localStream;
|
|
15272
15355
|
}
|
|
15273
15356
|
/**
|
|
@@ -15279,7 +15362,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15279
15362
|
const stream = this._localStream$.value;
|
|
15280
15363
|
const track = stream?.getTracks().find((t) => t.id === trackId);
|
|
15281
15364
|
if (!track) {
|
|
15282
|
-
logger$
|
|
15365
|
+
logger$19.debug(`[LocalStreamController] track not found: ${trackId}`);
|
|
15283
15366
|
return;
|
|
15284
15367
|
}
|
|
15285
15368
|
track.removeEventListener("ended", this.mediaTrackEndedHandler);
|
|
@@ -15288,7 +15371,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15288
15371
|
this._localStream$.next(stream);
|
|
15289
15372
|
if (track.kind === "video") this._localVideoTracks$.next(stream?.getVideoTracks() ?? []);
|
|
15290
15373
|
else this._localAudioTracks$.next(stream?.getAudioTracks() ?? []);
|
|
15291
|
-
logger$
|
|
15374
|
+
logger$19.debug(`[LocalStreamController] ${track.kind} track removed:`, trackId);
|
|
15292
15375
|
return track;
|
|
15293
15376
|
}
|
|
15294
15377
|
/**
|
|
@@ -15323,7 +15406,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15323
15406
|
*/
|
|
15324
15407
|
stopAllTracks() {
|
|
15325
15408
|
this._localStream$.value?.getTracks().forEach((track) => {
|
|
15326
|
-
logger$
|
|
15409
|
+
logger$19.debug(`[LocalStreamController] Stopping local track: ${track.kind}`);
|
|
15327
15410
|
track.removeEventListener("ended", this.mediaTrackEndedHandler);
|
|
15328
15411
|
track.stop();
|
|
15329
15412
|
});
|
|
@@ -15339,7 +15422,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15339
15422
|
|
|
15340
15423
|
//#endregion
|
|
15341
15424
|
//#region src/controllers/TransceiverController.ts
|
|
15342
|
-
const logger$
|
|
15425
|
+
const logger$18 = getLogger();
|
|
15343
15426
|
const getDirection = (send, recv) => {
|
|
15344
15427
|
if (send && recv) return "sendrecv";
|
|
15345
15428
|
else if (send && !recv) return "sendonly";
|
|
@@ -15447,7 +15530,7 @@ var TransceiverController = class extends Destroyable {
|
|
|
15447
15530
|
sendEncodings: isAudio ? void 0 : this.sendEncodings,
|
|
15448
15531
|
streams: direction === "recvonly" ? void 0 : [localStream]
|
|
15449
15532
|
};
|
|
15450
|
-
logger$
|
|
15533
|
+
logger$18.debug(`[TransceiverController] Setting up transceiver sender for local ${track.kind} track:`, {
|
|
15451
15534
|
transceiver,
|
|
15452
15535
|
transceiverParams
|
|
15453
15536
|
});
|
|
@@ -15455,11 +15538,11 @@ var TransceiverController = class extends Destroyable {
|
|
|
15455
15538
|
await transceiver.sender.replaceTrack(track);
|
|
15456
15539
|
transceiver.direction = transceiverParams.direction;
|
|
15457
15540
|
if (transceiverParams.streams?.some((stream) => Boolean(stream))) {
|
|
15458
|
-
logger$
|
|
15541
|
+
logger$18.debug(`[TransceiverController] Setting streams for transceiver sender for local ${track.kind} track:`, transceiverParams.streams);
|
|
15459
15542
|
transceiver.sender.setStreams(...transceiverParams.streams);
|
|
15460
15543
|
}
|
|
15461
15544
|
} else {
|
|
15462
|
-
logger$
|
|
15545
|
+
logger$18.debug(`[TransceiverController] Adding new transceiver for local ${track.kind} track:`, track.id);
|
|
15463
15546
|
this.peerConnection.addTransceiver(track, transceiverParams);
|
|
15464
15547
|
}
|
|
15465
15548
|
}
|
|
@@ -15473,13 +15556,13 @@ var TransceiverController = class extends Destroyable {
|
|
|
15473
15556
|
if (options.updateTransceiverDirection) transceiver.direction = "inactive";
|
|
15474
15557
|
}
|
|
15475
15558
|
} catch (error) {
|
|
15476
|
-
logger$
|
|
15559
|
+
logger$18.error("[TransceiverController] stopTrackSender error", kind, error);
|
|
15477
15560
|
this.options.onError?.(new MediaTrackError("stopTrackSender", kind, error));
|
|
15478
15561
|
}
|
|
15479
15562
|
}
|
|
15480
15563
|
async restoreTrackSender(kind) {
|
|
15481
15564
|
try {
|
|
15482
|
-
logger$
|
|
15565
|
+
logger$18.debug("[TransceiverController] restoreTrackSender called", kind);
|
|
15483
15566
|
const constraints = {};
|
|
15484
15567
|
const transceivers = this.transceiverByKind(kind);
|
|
15485
15568
|
for (const transceiver of transceivers) {
|
|
@@ -15489,23 +15572,23 @@ var TransceiverController = class extends Destroyable {
|
|
|
15489
15572
|
if (trackKind === "audio" || trackKind === "video") constraints[trackKind] = this.getConstraintsFor(trackKind);
|
|
15490
15573
|
}
|
|
15491
15574
|
}
|
|
15492
|
-
logger$
|
|
15575
|
+
logger$18.debug("[TransceiverController] restoreTrackSender constraints:", constraints);
|
|
15493
15576
|
if (Object.keys(constraints).length === 0) {
|
|
15494
|
-
logger$
|
|
15577
|
+
logger$18.warn("[TransceiverController] restoreTrackSender: no tracks need restoration", kind);
|
|
15495
15578
|
return;
|
|
15496
15579
|
}
|
|
15497
15580
|
const newTracks = (await this.options.getUserMedia(constraints)).getTracks();
|
|
15498
|
-
logger$
|
|
15581
|
+
logger$18.debug("[TransceiverController] restoreTrackSender new tracks:", newTracks);
|
|
15499
15582
|
for (const newTrack of newTracks) {
|
|
15500
15583
|
this.options.localStreamController.addTrack(newTrack);
|
|
15501
15584
|
const trackKind = newTrack.kind;
|
|
15502
15585
|
const transceiverOfKind = this.transceiverByKind(trackKind)[0];
|
|
15503
15586
|
transceiverOfKind.direction = trackKind === "audio" ? this.audioDirection : this.videoDirection;
|
|
15504
|
-
logger$
|
|
15587
|
+
logger$18.debug("[TransceiverController] restoreTrackSender setting direction for", trackKind, transceiverOfKind.direction);
|
|
15505
15588
|
await transceiverOfKind.sender.replaceTrack(newTrack);
|
|
15506
15589
|
}
|
|
15507
15590
|
} catch (error) {
|
|
15508
|
-
logger$
|
|
15591
|
+
logger$18.error("[TransceiverController] restoreTrackSender error", kind, error);
|
|
15509
15592
|
this.options.onError?.(new MediaTrackError("restoreTrackSender", kind, error));
|
|
15510
15593
|
}
|
|
15511
15594
|
}
|
|
@@ -15546,14 +15629,14 @@ var TransceiverController = class extends Destroyable {
|
|
|
15546
15629
|
};
|
|
15547
15630
|
try {
|
|
15548
15631
|
await track.applyConstraints(constraintsToApply);
|
|
15549
|
-
logger$
|
|
15550
|
-
logger$
|
|
15632
|
+
logger$18.debug(`[TransceiverController] Updated ${kind} sender constraints:`, constraintsToApply);
|
|
15633
|
+
logger$18.debug(`[TransceiverController] Updated ${kind} sender constraints:`, track.getConstraints());
|
|
15551
15634
|
} catch (error) {
|
|
15552
|
-
logger$
|
|
15635
|
+
logger$18.warn(`[TransceiverController] applyConstraints failed for ${kind} track ${track.id}, attempting track replacement fallback:`, error);
|
|
15553
15636
|
try {
|
|
15554
15637
|
await this.replaceTrackFallback(sender, track, kind, constraintsToApply);
|
|
15555
15638
|
} catch (fallbackError) {
|
|
15556
|
-
logger$
|
|
15639
|
+
logger$18.warn(`[TransceiverController] Track replacement fallback also failed for ${kind} track:`, fallbackError);
|
|
15557
15640
|
this.options.onError?.(new MediaTrackError("updateSendersConstraints", kind, fallbackError));
|
|
15558
15641
|
}
|
|
15559
15642
|
}
|
|
@@ -15581,7 +15664,7 @@ var TransceiverController = class extends Destroyable {
|
|
|
15581
15664
|
if (!newTrack) throw new MediaTrackError("replaceTrackFallback", kind, /* @__PURE__ */ new Error("getUserMedia returned no track of the requested kind"));
|
|
15582
15665
|
await sender.replaceTrack(newTrack);
|
|
15583
15666
|
this.options.localStreamController.addTrack(newTrack);
|
|
15584
|
-
logger$
|
|
15667
|
+
logger$18.debug(`[TransceiverController] Track replacement fallback succeeded for ${kind}. New track: ${newTrack.id}`);
|
|
15585
15668
|
}
|
|
15586
15669
|
getMediaDirections() {
|
|
15587
15670
|
if (this.peerConnection.connectionState === "connected") return this.peerConnection.getTransceivers().reduce((acc, transceiver) => {
|
|
@@ -15612,7 +15695,7 @@ var TransceiverController = class extends Destroyable {
|
|
|
15612
15695
|
//#endregion
|
|
15613
15696
|
//#region src/controllers/RTCPeerConnectionController.ts
|
|
15614
15697
|
var import_cjs$16 = require_cjs();
|
|
15615
|
-
const logger$
|
|
15698
|
+
const logger$17 = getLogger();
|
|
15616
15699
|
var RTCPeerConnectionController = class extends Destroyable {
|
|
15617
15700
|
constructor(options = {}, remoteSessionDescription, deviceController) {
|
|
15618
15701
|
super();
|
|
@@ -15628,43 +15711,43 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15628
15711
|
this.oniceconnectionstatechangeHandler = () => {
|
|
15629
15712
|
if (this.peerConnection) {
|
|
15630
15713
|
const { iceConnectionState } = this.peerConnection;
|
|
15631
|
-
logger$
|
|
15714
|
+
logger$17.debug(`[RTCPeerConnectionController] ICE connection state changed to: ${iceConnectionState}`);
|
|
15632
15715
|
this._iceConnectionState$.next(this.peerConnection.iceConnectionState);
|
|
15633
15716
|
}
|
|
15634
15717
|
};
|
|
15635
15718
|
this.onconnectionstatechangeHandler = () => {
|
|
15636
15719
|
if (this.peerConnection) {
|
|
15637
15720
|
const { connectionState } = this.peerConnection;
|
|
15638
|
-
logger$
|
|
15721
|
+
logger$17.debug(`[RTCPeerConnectionController] Connection state changed to: ${connectionState}`);
|
|
15639
15722
|
if (connectionState === "connected") this.removeConnectionTimer();
|
|
15640
15723
|
this._connectionState$.next(this.peerConnection.connectionState);
|
|
15641
15724
|
}
|
|
15642
15725
|
};
|
|
15643
15726
|
this.onsignalingstatechangeHandler = () => {
|
|
15644
|
-
logger$
|
|
15727
|
+
logger$17.debug(`[RTCPeerConnectionController] Signaling state changed to: ${this.peerConnection?.signalingState}`);
|
|
15645
15728
|
};
|
|
15646
15729
|
this.onicegatheringstatechangeHandler = () => {
|
|
15647
15730
|
if (this.peerConnection) this._iceGatheringState$.next(this.peerConnection.iceGatheringState);
|
|
15648
15731
|
};
|
|
15649
15732
|
this.onnegotiationneededHandler = (event) => {
|
|
15650
|
-
logger$
|
|
15733
|
+
logger$17.debug("[RTCPeerConnectionController] Negotiation needed event received.", event);
|
|
15651
15734
|
this.negotiationNeeded$.next();
|
|
15652
15735
|
};
|
|
15653
15736
|
this.updateSelectedInputDevice = async (kind, deviceInfo) => {
|
|
15654
15737
|
try {
|
|
15655
15738
|
const { localStream } = this;
|
|
15656
15739
|
if (!localStream) {
|
|
15657
|
-
logger$
|
|
15740
|
+
logger$17.warn("[RTCPeerConnectionController] No local stream available to update input device.");
|
|
15658
15741
|
return;
|
|
15659
15742
|
}
|
|
15660
|
-
logger$
|
|
15743
|
+
logger$17.debug(`[RTCPeerConnectionController] Updating selected ${kind} input device:`, localStream.getTracks());
|
|
15661
15744
|
const track = localStream.getTracks().find((track$1) => track$1.kind === kind);
|
|
15662
15745
|
if (track) {
|
|
15663
15746
|
this.transceiverController?.stopTrackSender(kind);
|
|
15664
15747
|
this.localStreamController.removeTrack(track.id);
|
|
15665
|
-
logger$
|
|
15748
|
+
logger$17.debug(`[RTCPeerConnectionController] Stopped existing ${kind} track: ${track.id}`, localStream.getTracks());
|
|
15666
15749
|
if (!deviceInfo) {
|
|
15667
|
-
logger$
|
|
15750
|
+
logger$17.debug(`[RTCPeerConnectionController] ${kind} input device selected: none`);
|
|
15668
15751
|
return;
|
|
15669
15752
|
}
|
|
15670
15753
|
const streamTrack = (await this.getUserMedia({ [kind]: {
|
|
@@ -15672,16 +15755,16 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15672
15755
|
...this.deviceController.deviceInfoToConstraints(deviceInfo)
|
|
15673
15756
|
} })).getTracks().find((t) => t.kind === kind);
|
|
15674
15757
|
if (streamTrack) {
|
|
15675
|
-
logger$
|
|
15758
|
+
logger$17.debug(`[RTCPeerConnectionController] Adding new ${kind} track: ${streamTrack.id}`);
|
|
15676
15759
|
this.localStreamController.addTrack(streamTrack);
|
|
15677
15760
|
await this.transceiverController?.replaceSenderTrack(kind, streamTrack);
|
|
15678
|
-
logger$
|
|
15761
|
+
logger$17.debug(`[RTCPeerConnectionController] Added new ${kind} track: ${streamTrack.id}`, this.localStream?.getTracks());
|
|
15679
15762
|
}
|
|
15680
15763
|
}
|
|
15681
|
-
logger$
|
|
15764
|
+
logger$17.debug(`[RTCPeerConnectionController] ${kind} input device selected:`, deviceInfo?.label);
|
|
15682
15765
|
} catch (error) {
|
|
15683
|
-
logger$
|
|
15684
|
-
this._errors$.next(
|
|
15766
|
+
logger$17.error(`[RTCPeerConnectionController] Failed to select ${kind} input device:`, error);
|
|
15767
|
+
this._errors$.next(new MediaTrackError("updateSelectedInputDevice", kind, error));
|
|
15685
15768
|
throw error;
|
|
15686
15769
|
}
|
|
15687
15770
|
};
|
|
@@ -15903,7 +15986,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15903
15986
|
case "main":
|
|
15904
15987
|
default: return {
|
|
15905
15988
|
...options,
|
|
15906
|
-
offerToReceiveAudio: true,
|
|
15989
|
+
offerToReceiveAudio: this.options.receiveAudio ?? true,
|
|
15907
15990
|
offerToReceiveVideo: this.options.receiveVideo ?? Boolean(this.inputVideoDeviceConstraints)
|
|
15908
15991
|
};
|
|
15909
15992
|
}
|
|
@@ -15930,15 +16013,15 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15930
16013
|
this.setupPeerConnection();
|
|
15931
16014
|
this.subscribeTo(this.negotiationNeeded$.pipe((0, import_cjs$16.auditTime)(0), (0, import_cjs$16.exhaustMap)(async () => this.startNegotiation())), {
|
|
15932
16015
|
next: () => {
|
|
15933
|
-
logger$
|
|
16016
|
+
logger$17.debug("[RTCPeerConnectionController] Start Negotiation completed successfully");
|
|
15934
16017
|
},
|
|
15935
16018
|
error: (error) => {
|
|
15936
|
-
logger$
|
|
16019
|
+
logger$17.error("[RTCPeerConnectionController] Start Negotiation error:", error);
|
|
15937
16020
|
this._errors$.next(toError(error));
|
|
15938
16021
|
}
|
|
15939
16022
|
});
|
|
15940
16023
|
this.subscribeTo((0, import_cjs$16.merge)(this.deviceController.selectedAudioInputDevice$.pipe((0, import_cjs$16.map)((deviceInfo) => ["audio", deviceInfo])), this.deviceController.selectedVideoInputDevice$.pipe((0, import_cjs$16.map)((deviceInfo) => ["video", deviceInfo]))).pipe((0, import_cjs$16.skipWhile)(() => !this.localStreamController.localStream)), async ([kind, deviceInfo]) => {
|
|
15941
|
-
logger$
|
|
16024
|
+
logger$17.debug(`[RTCPeerConnectionController] Selected input device changed for:`, {
|
|
15942
16025
|
kind,
|
|
15943
16026
|
deviceInfo
|
|
15944
16027
|
});
|
|
@@ -15955,7 +16038,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15955
16038
|
this._initialized$.next(true);
|
|
15956
16039
|
}
|
|
15957
16040
|
} catch (error) {
|
|
15958
|
-
logger$
|
|
16041
|
+
logger$17.error("[RTCPeerConnectionController] Initialization error:", error);
|
|
15959
16042
|
this._errors$.next(toError(error));
|
|
15960
16043
|
this.destroy();
|
|
15961
16044
|
}
|
|
@@ -15987,22 +16070,22 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15987
16070
|
}
|
|
15988
16071
|
async startNegotiation() {
|
|
15989
16072
|
if (this.isNegotiating) {
|
|
15990
|
-
logger$
|
|
16073
|
+
logger$17.debug("[RTCPeerConnectionController] Negotiation already in progress, skipping.");
|
|
15991
16074
|
return;
|
|
15992
16075
|
}
|
|
15993
16076
|
this.setupEventListeners();
|
|
15994
16077
|
if (this.type === "answer") {
|
|
15995
|
-
logger$
|
|
16078
|
+
logger$17.debug("[RTCPeerConnectionController] This is an answer type still, skipping offer creation.");
|
|
15996
16079
|
return;
|
|
15997
16080
|
}
|
|
15998
16081
|
this._isNegotiating$.next(true);
|
|
15999
|
-
logger$
|
|
16082
|
+
logger$17.debug("[RTCPeerConnectionController] Starting negotiation.");
|
|
16000
16083
|
try {
|
|
16001
16084
|
const { offerOptions } = this;
|
|
16002
|
-
logger$
|
|
16085
|
+
logger$17.debug("[RTCPeerConnectionController] Creating offer with options:", offerOptions);
|
|
16003
16086
|
await this.createOffer(offerOptions);
|
|
16004
16087
|
} catch (error) {
|
|
16005
|
-
logger$
|
|
16088
|
+
logger$17.error("[RTCPeerConnectionController] Error during negotiation:", error);
|
|
16006
16089
|
this._errors$.next(toError(error));
|
|
16007
16090
|
}
|
|
16008
16091
|
}
|
|
@@ -16018,14 +16101,14 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16018
16101
|
let readyToConnect = status !== "failed";
|
|
16019
16102
|
try {
|
|
16020
16103
|
if (status === "received" && sdp) {
|
|
16021
|
-
logger$
|
|
16104
|
+
logger$17.debug("[RTCPeerConnectionController] Received answer SDP:", sdp);
|
|
16022
16105
|
await this._setRemoteDescription({
|
|
16023
16106
|
type: "answer",
|
|
16024
16107
|
sdp
|
|
16025
16108
|
});
|
|
16026
16109
|
}
|
|
16027
16110
|
} catch (error) {
|
|
16028
|
-
logger$
|
|
16111
|
+
logger$17.error("[RTCPeerConnectionController] Error updating answer status:", error);
|
|
16029
16112
|
this._errors$.next(toError(error));
|
|
16030
16113
|
readyToConnect = false;
|
|
16031
16114
|
} finally {
|
|
@@ -16044,7 +16127,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16044
16127
|
await this.handleOfferReceived();
|
|
16045
16128
|
break;
|
|
16046
16129
|
case "failed":
|
|
16047
|
-
logger$
|
|
16130
|
+
logger$17.error("[RTCPeerConnectionController] Offer failed to be processed by remote.");
|
|
16048
16131
|
break;
|
|
16049
16132
|
case "sent":
|
|
16050
16133
|
default:
|
|
@@ -16057,13 +16140,14 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16057
16140
|
*/
|
|
16058
16141
|
async acceptInbound(mediaOverrides) {
|
|
16059
16142
|
if (mediaOverrides) {
|
|
16060
|
-
const { audio, video, receiveAudio, receiveVideo } = mediaOverrides;
|
|
16143
|
+
const { audio, video, receiveAudio, receiveVideo, fallbackToReceiveOnly } = mediaOverrides;
|
|
16061
16144
|
this.options = {
|
|
16062
16145
|
...this.options,
|
|
16063
16146
|
...audio !== void 0 ? { audio } : {},
|
|
16064
16147
|
...video !== void 0 ? { video } : {},
|
|
16065
16148
|
...receiveAudio !== void 0 ? { receiveAudio } : {},
|
|
16066
|
-
...receiveVideo !== void 0 ? { receiveVideo } : {}
|
|
16149
|
+
...receiveVideo !== void 0 ? { receiveVideo } : {},
|
|
16150
|
+
...fallbackToReceiveOnly !== void 0 ? { fallbackToReceiveOnly } : {}
|
|
16067
16151
|
};
|
|
16068
16152
|
this.transceiverController?.updateOptions({
|
|
16069
16153
|
receiveAudio: this.receiveAudio,
|
|
@@ -16076,7 +16160,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16076
16160
|
}
|
|
16077
16161
|
await this.setupLocalTracks();
|
|
16078
16162
|
const { answerOptions } = this;
|
|
16079
|
-
logger$
|
|
16163
|
+
logger$17.debug("[RTCPeerConnectionController] Creating inbound answer with options:", answerOptions);
|
|
16080
16164
|
await this.createAnswer(answerOptions);
|
|
16081
16165
|
}
|
|
16082
16166
|
async handleOfferReceived() {
|
|
@@ -16084,7 +16168,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16084
16168
|
this._isNegotiating$.next(true);
|
|
16085
16169
|
await this._setRemoteDescription(this.sdpInit);
|
|
16086
16170
|
const { answerOptions } = this;
|
|
16087
|
-
logger$
|
|
16171
|
+
logger$17.debug("[RTCPeerConnectionController] Creating answer with options:", answerOptions);
|
|
16088
16172
|
await this.createAnswer(answerOptions);
|
|
16089
16173
|
}
|
|
16090
16174
|
readyToConnect() {
|
|
@@ -16092,7 +16176,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16092
16176
|
this.connectionTimer = setTimeout(() => {
|
|
16093
16177
|
this.removeConnectionTimer();
|
|
16094
16178
|
if (this.peerConnection?.connectionState !== "connected") {
|
|
16095
|
-
logger$
|
|
16179
|
+
logger$17.debug("[RTCPeerConnectionController] Connection timeout, restarting ICE gathering with relay only.");
|
|
16096
16180
|
this.iceGatheringController.restartICEGatheringWithRelayOnly();
|
|
16097
16181
|
}
|
|
16098
16182
|
}, this.connectionTimeout);
|
|
@@ -16114,14 +16198,14 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16114
16198
|
const stereo = this.options.stereo ?? PreferencesContainer.instance.stereoAudio;
|
|
16115
16199
|
if (preferredAudioCodecs.length > 0 || preferredVideoCodecs.length > 0) {
|
|
16116
16200
|
result = setCodecPreferences(result, preferredAudioCodecs, preferredVideoCodecs);
|
|
16117
|
-
logger$
|
|
16201
|
+
logger$17.debug("[RTCPeerConnectionController] Applied codec preferences to SDP", {
|
|
16118
16202
|
preferredAudioCodecs,
|
|
16119
16203
|
preferredVideoCodecs
|
|
16120
16204
|
});
|
|
16121
16205
|
}
|
|
16122
16206
|
if (stereo) {
|
|
16123
16207
|
result = enableStereoOpus(result);
|
|
16124
|
-
logger$
|
|
16208
|
+
logger$17.debug("[RTCPeerConnectionController] Applied stereo Opus to SDP");
|
|
16125
16209
|
}
|
|
16126
16210
|
return Promise.resolve(result);
|
|
16127
16211
|
}
|
|
@@ -16177,25 +16261,25 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16177
16261
|
...this.peerConnection.getConfiguration(),
|
|
16178
16262
|
iceTransportPolicy: "relay"
|
|
16179
16263
|
});
|
|
16180
|
-
logger$
|
|
16264
|
+
logger$17.debug("[RTCPeerConnectionController] ICE transport policy set to relay-only");
|
|
16181
16265
|
} catch (error) {
|
|
16182
|
-
logger$
|
|
16266
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to set relay-only policy:", error);
|
|
16183
16267
|
}
|
|
16184
16268
|
this.setupEventListeners();
|
|
16185
16269
|
this._isNegotiating$.next(true);
|
|
16186
|
-
logger$
|
|
16270
|
+
logger$17.debug(`[RTCPeerConnectionController] Triggering ICE restart${relayOnly ? " (relay-only)" : ""}.`);
|
|
16187
16271
|
try {
|
|
16188
16272
|
const offer = await this.peerConnection.createOffer({ iceRestart: true });
|
|
16189
16273
|
await this.setLocalDescription(offer);
|
|
16190
16274
|
} catch (error) {
|
|
16191
|
-
logger$
|
|
16275
|
+
logger$17.error("[RTCPeerConnectionController] ICE restart offer failed:", error);
|
|
16192
16276
|
this._errors$.next(toError(error));
|
|
16193
16277
|
this.negotiationEnded();
|
|
16194
16278
|
if (policyChanged) this.restoreIceTransportPolicy();
|
|
16195
16279
|
throw error;
|
|
16196
16280
|
}
|
|
16197
16281
|
if (policyChanged) (0, import_cjs$16.firstValueFrom)((0, import_cjs$16.race)(this._iceGatheringState$.pipe((0, import_cjs$16.filter)((state) => state === "complete"), (0, import_cjs$16.take)(1)), (0, import_cjs$16.timer)(ICE_GATHERING_COMPLETE_TIMEOUT_MS).pipe((0, import_cjs$16.map)(() => "timeout")))).then(() => this.restoreIceTransportPolicy()).catch((error) => {
|
|
16198
|
-
logger$
|
|
16282
|
+
logger$17.warn("[RTCPeerConnectionController] Error waiting for ICE gathering to complete:", error);
|
|
16199
16283
|
this.restoreIceTransportPolicy();
|
|
16200
16284
|
});
|
|
16201
16285
|
}
|
|
@@ -16205,9 +16289,9 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16205
16289
|
...this.peerConnection.getConfiguration(),
|
|
16206
16290
|
iceTransportPolicy: this.options.relayOnly ? "relay" : "all"
|
|
16207
16291
|
});
|
|
16208
|
-
logger$
|
|
16292
|
+
logger$17.debug("[RTCPeerConnectionController] ICE transport policy restored");
|
|
16209
16293
|
} catch (error) {
|
|
16210
|
-
logger$
|
|
16294
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to restore ICE transport policy:", error);
|
|
16211
16295
|
}
|
|
16212
16296
|
}
|
|
16213
16297
|
/**
|
|
@@ -16219,13 +16303,25 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16219
16303
|
await this.setupRemoteTracks();
|
|
16220
16304
|
}
|
|
16221
16305
|
async setupLocalTracks() {
|
|
16222
|
-
logger$
|
|
16223
|
-
|
|
16306
|
+
logger$17.debug("[RTCPeerConnectionController] Setting up local tracks/transceivers.");
|
|
16307
|
+
if (this.hasNoLocalMediaToSend()) {
|
|
16308
|
+
if (!this.receiveAudio && !this.receiveVideo) throw new InvalidParams("Call requests no media: enable audio/video or receiveAudio/receiveVideo");
|
|
16309
|
+
logger$17.debug("[RTCPeerConnectionController] No local media requested; negotiating receive-only.");
|
|
16310
|
+
this.setupReceiveOnlyTransceivers();
|
|
16311
|
+
return;
|
|
16312
|
+
}
|
|
16313
|
+
let localStream;
|
|
16314
|
+
try {
|
|
16315
|
+
localStream = this.localStream ?? await this.localStreamController.buildLocalStream();
|
|
16316
|
+
} catch (error) {
|
|
16317
|
+
this.handleLocalMediaFailure(error);
|
|
16318
|
+
return;
|
|
16319
|
+
}
|
|
16224
16320
|
if (this.transceiverController?.useAddStream ?? false) {
|
|
16225
|
-
logger$
|
|
16321
|
+
logger$17.warn("[RTCPeerConnectionController] Using deprecated addStream API to add local stream.");
|
|
16226
16322
|
this.peerConnection?.addStream(localStream);
|
|
16227
16323
|
if (!this.isNegotiating) {
|
|
16228
|
-
logger$
|
|
16324
|
+
logger$17.debug("[RTCPeerConnectionController] Forcing negotiationneeded after local tracks setup.");
|
|
16229
16325
|
this.negotiationNeeded$.next();
|
|
16230
16326
|
}
|
|
16231
16327
|
return;
|
|
@@ -16241,12 +16337,54 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16241
16337
|
const transceivers = (kind === "audio" ? this.transceiverController?.audioTransceivers : this.transceiverController?.videoTransceivers) ?? [];
|
|
16242
16338
|
await this.transceiverController?.setupTransceiverSender(track, localStream, transceivers[index]);
|
|
16243
16339
|
} else {
|
|
16244
|
-
logger$
|
|
16340
|
+
logger$17.debug(`[RTCPeerConnectionController] Using addTrack for local ${kind} track:`, track.id);
|
|
16245
16341
|
this.peerConnection?.addTrack(track, localStream);
|
|
16246
16342
|
}
|
|
16247
16343
|
}
|
|
16248
16344
|
}
|
|
16249
16345
|
}
|
|
16346
|
+
/** True for a main connection with no local media to send. */
|
|
16347
|
+
hasNoLocalMediaToSend() {
|
|
16348
|
+
const hasInputStreams = Boolean(this.options.inputAudioStream ?? this.options.inputVideoStream);
|
|
16349
|
+
return this.propose === "main" && !this.localStream && !hasInputStreams && !this.inputAudioDeviceConstraints && !this.inputVideoDeviceConstraints;
|
|
16350
|
+
}
|
|
16351
|
+
/** The media kinds this connection wants to send: 'audiovideo' | 'video' | 'audio'. */
|
|
16352
|
+
get requestedMediaKinds() {
|
|
16353
|
+
const wantsAudio = Boolean(this.inputAudioDeviceConstraints);
|
|
16354
|
+
const wantsVideo = Boolean(this.inputVideoDeviceConstraints);
|
|
16355
|
+
if (wantsAudio && wantsVideo) return "audiovideo";
|
|
16356
|
+
return wantsVideo ? "video" : "audio";
|
|
16357
|
+
}
|
|
16358
|
+
/**
|
|
16359
|
+
* Handle a local media acquisition failure with a typed, semantically
|
|
16360
|
+
* accurate MediaAccessError created at the acquisition site:
|
|
16361
|
+
* - Auxiliary connections (screenshare / additional-device) throw a
|
|
16362
|
+
* non-fatal error — VertoManager surfaces it and the call is unaffected.
|
|
16363
|
+
* - The main connection degrades to receive-only when allowed (default),
|
|
16364
|
+
* otherwise fails with a fatal error.
|
|
16365
|
+
*/
|
|
16366
|
+
handleLocalMediaFailure(error) {
|
|
16367
|
+
if (this.propose === "screenshare") throw new MediaAccessError("startScreenShare", "screen", error, false);
|
|
16368
|
+
if (this.propose === "additional-device") throw new MediaAccessError("addInputDevice", this.requestedMediaKinds, error, false);
|
|
16369
|
+
const canReceive = this.receiveAudio || this.receiveVideo;
|
|
16370
|
+
if (!((this.options.fallbackToReceiveOnly ?? true) && canReceive)) throw new MediaAccessError("acquireLocalMedia", this.requestedMediaKinds, error, true);
|
|
16371
|
+
logger$17.warn("[RTCPeerConnectionController] Local media unavailable; continuing receive-only:", error);
|
|
16372
|
+
this._errors$.next(new MediaAccessError("acquireLocalMedia", this.requestedMediaKinds, error, false));
|
|
16373
|
+
this.setupReceiveOnlyTransceivers();
|
|
16374
|
+
}
|
|
16375
|
+
/**
|
|
16376
|
+
* Negotiate receive-only m-lines when there are no local tracks to send.
|
|
16377
|
+
* Only offer-type connections add transceivers — answer-type connections
|
|
16378
|
+
* reuse the transceivers created from the remote offer.
|
|
16379
|
+
*/
|
|
16380
|
+
setupReceiveOnlyTransceivers() {
|
|
16381
|
+
if (this.type !== "offer") return;
|
|
16382
|
+
if (this.transceiverController?.useAddTransceivers ?? false) {
|
|
16383
|
+
this.peerConnection?.addTransceiver("audio", { direction: this.receiveAudio ? "recvonly" : "inactive" });
|
|
16384
|
+
this.peerConnection?.addTransceiver("video", { direction: this.receiveVideo ? "recvonly" : "inactive" });
|
|
16385
|
+
}
|
|
16386
|
+
if (!this.isNegotiating) this.negotiationNeeded$.next();
|
|
16387
|
+
}
|
|
16250
16388
|
async getUserMedia(constraints) {
|
|
16251
16389
|
return (this.options.webRTCApiProvider?.mediaDevices ?? navigator.mediaDevices).getUserMedia(constraints);
|
|
16252
16390
|
}
|
|
@@ -16258,7 +16396,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16258
16396
|
async setupRemoteTracks() {
|
|
16259
16397
|
if (!this.peerConnection) throw new DependencyError("RTCPeerConnection is not initialized");
|
|
16260
16398
|
this.peerConnection.ontrack = (event) => {
|
|
16261
|
-
logger$
|
|
16399
|
+
logger$17.debug("[RTCPeerConnectionController] Remote track received:", event.track.kind);
|
|
16262
16400
|
if (event.streams[0]) this._remoteStream$.next(event.streams[0]);
|
|
16263
16401
|
else {
|
|
16264
16402
|
const existingTracks = this._remoteStream$.value?.getTracks() ?? [];
|
|
@@ -16282,8 +16420,8 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16282
16420
|
try {
|
|
16283
16421
|
stream = await this.getUserMedia({ audio: constraints });
|
|
16284
16422
|
} catch (error) {
|
|
16285
|
-
logger$
|
|
16286
|
-
this._errors$.next(
|
|
16423
|
+
logger$17.error("[RTCPeerConnectionController] Failed to re-acquire mic for pipeline restore:", error);
|
|
16424
|
+
this._errors$.next(new MediaTrackError("restoreAudioPipelineInput", "audio", error));
|
|
16287
16425
|
return;
|
|
16288
16426
|
}
|
|
16289
16427
|
const newTrack = stream.getAudioTracks().at(0);
|
|
@@ -16304,7 +16442,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16304
16442
|
try {
|
|
16305
16443
|
this._localAudioPipeline = new LocalAudioPipeline();
|
|
16306
16444
|
} catch (error) {
|
|
16307
|
-
logger$
|
|
16445
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to create LocalAudioPipeline:", error);
|
|
16308
16446
|
return null;
|
|
16309
16447
|
}
|
|
16310
16448
|
this.subscribeTo(this.localStreamController.localAudioTracks$, () => {
|
|
@@ -16326,7 +16464,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16326
16464
|
try {
|
|
16327
16465
|
await sender.replaceTrack(this._localAudioPipeline.outputTrack);
|
|
16328
16466
|
} catch (error) {
|
|
16329
|
-
logger$
|
|
16467
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to route audio sender through pipeline:", error);
|
|
16330
16468
|
}
|
|
16331
16469
|
}
|
|
16332
16470
|
/**
|
|
@@ -16342,10 +16480,10 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16342
16480
|
try {
|
|
16343
16481
|
const localStream = this.localStreamController.addTrack(track);
|
|
16344
16482
|
this.peerConnection.addTrack(track, localStream);
|
|
16345
|
-
logger$
|
|
16483
|
+
logger$17.debug(`[RTCPeerConnectionController] ${track.kind} track added:`, track.id);
|
|
16346
16484
|
} catch (error) {
|
|
16347
|
-
logger$
|
|
16348
|
-
this._errors$.next(
|
|
16485
|
+
logger$17.error(`[RTCPeerConnectionController] Failed to add ${track.kind} track:`, error);
|
|
16486
|
+
this._errors$.next(new MediaTrackError("addLocalTrack", track.kind, error));
|
|
16349
16487
|
throw error;
|
|
16350
16488
|
}
|
|
16351
16489
|
}
|
|
@@ -16361,16 +16499,16 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16361
16499
|
}
|
|
16362
16500
|
const sender = this.peerConnection.getSenders().find((sender$1) => sender$1.track?.id === trackId);
|
|
16363
16501
|
if (!sender) {
|
|
16364
|
-
logger$
|
|
16502
|
+
logger$17.debug(`[RTCPeerConnectionController] track not found: ${trackId}`);
|
|
16365
16503
|
return;
|
|
16366
16504
|
}
|
|
16367
16505
|
try {
|
|
16368
16506
|
this.peerConnection.removeTrack(sender);
|
|
16369
16507
|
this.localStreamController.removeTrack(trackId);
|
|
16370
|
-
logger$
|
|
16508
|
+
logger$17.debug(`[RTCPeerConnectionController] ${sender.track?.kind} track removed:`, trackId);
|
|
16371
16509
|
} catch (error) {
|
|
16372
|
-
logger$
|
|
16373
|
-
this._errors$.next(
|
|
16510
|
+
logger$17.error(`[RTCPeerConnectionController] Failed to remove ${sender.track?.kind} track:`, error);
|
|
16511
|
+
this._errors$.next(new MediaTrackError("removeLocalTrack", sender.track?.kind ?? "unknown", error));
|
|
16374
16512
|
throw error;
|
|
16375
16513
|
}
|
|
16376
16514
|
}
|
|
@@ -16396,7 +16534,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16396
16534
|
async replaceAudioTrackWithConstraints(constraints) {
|
|
16397
16535
|
const senders = this.peerConnection?.getSenders().filter((s) => s.track?.kind === "audio" && s.track.readyState === "live");
|
|
16398
16536
|
if (!senders || senders.length === 0) {
|
|
16399
|
-
logger$
|
|
16537
|
+
logger$17.warn("[RTCPeerConnectionController] No live audio sender to replace");
|
|
16400
16538
|
return;
|
|
16401
16539
|
}
|
|
16402
16540
|
for (const sender of senders) {
|
|
@@ -16414,7 +16552,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16414
16552
|
const newTrack = (await this.getUserMedia({ audio: mergedConstraints })).getAudioTracks()[0];
|
|
16415
16553
|
await sender.replaceTrack(newTrack);
|
|
16416
16554
|
this.localStreamController.addTrack(newTrack);
|
|
16417
|
-
logger$
|
|
16555
|
+
logger$17.debug(`[RTCPeerConnectionController] Audio track replaced for server-pushed params. New track: ${newTrack.id}`);
|
|
16418
16556
|
}
|
|
16419
16557
|
}
|
|
16420
16558
|
/**
|
|
@@ -16422,7 +16560,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16422
16560
|
* Completes all observables to prevent memory leaks.
|
|
16423
16561
|
*/
|
|
16424
16562
|
destroy() {
|
|
16425
|
-
logger$
|
|
16563
|
+
logger$17.debug(`[RTCPeerConnectionController] Destroying RTCPeerConnectionController. ${this.propose}`);
|
|
16426
16564
|
this.removeConnectionTimer();
|
|
16427
16565
|
this._iceGatheringController?.destroy();
|
|
16428
16566
|
this._localAudioPipeline?.destroy();
|
|
@@ -16448,7 +16586,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16448
16586
|
}
|
|
16449
16587
|
stopRemoteTracks() {
|
|
16450
16588
|
this._remoteStream$.value?.getTracks().forEach((track) => {
|
|
16451
|
-
logger$
|
|
16589
|
+
logger$17.debug(`[RTCPeerConnectionController] Stopping remote track: ${track.kind}`);
|
|
16452
16590
|
track.stop();
|
|
16453
16591
|
});
|
|
16454
16592
|
}
|
|
@@ -16465,7 +16603,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16465
16603
|
...params,
|
|
16466
16604
|
sdp: finalRemote
|
|
16467
16605
|
};
|
|
16468
|
-
logger$
|
|
16606
|
+
logger$17.debug("[RTCPeerConnectionController] Setting remote description:", answer);
|
|
16469
16607
|
return this.peerConnection.setRemoteDescription(answer);
|
|
16470
16608
|
}
|
|
16471
16609
|
};
|
|
@@ -16480,7 +16618,11 @@ function isVertoInviteMessage(value) {
|
|
|
16480
16618
|
const msg = value;
|
|
16481
16619
|
return msg.method === "verto.invite" && isObject(msg.params) && hasProperty(msg.params, "sdp") && hasProperty(msg.params, "callID");
|
|
16482
16620
|
}
|
|
16483
|
-
|
|
16621
|
+
/**
|
|
16622
|
+
* Guards inbound `verto.bye` frames (server → client). Outbound bye messages are
|
|
16623
|
+
* built locally and never type-guarded, so only the inbound shape is asserted.
|
|
16624
|
+
*/
|
|
16625
|
+
function isVertoByeInboundMessage(value) {
|
|
16484
16626
|
if (!isVertoMethodMessage(value)) return false;
|
|
16485
16627
|
return value.method === "verto.bye";
|
|
16486
16628
|
}
|
|
@@ -16500,11 +16642,19 @@ function isVertoMediaParamsInnerParams(value) {
|
|
|
16500
16642
|
function isVertoPingInnerParams(value) {
|
|
16501
16643
|
return isObject(value) && hasProperty(value, "jsonrpc") && value.jsonrpc === "2.0" && hasProperty(value, "method") && value.method === "verto.ping";
|
|
16502
16644
|
}
|
|
16645
|
+
/**
|
|
16646
|
+
* Guards the params of an inbound `verto.bye` frame (top-level `callID`). Use
|
|
16647
|
+
* this to recognise the bye payload extracted by `vertoBye$`, e.g. when racing
|
|
16648
|
+
* it against a boolean answer/reject signal.
|
|
16649
|
+
*/
|
|
16650
|
+
function isVertoByeInboundParamsGuard(value) {
|
|
16651
|
+
return isObject(value) && hasProperty(value, "callID") && typeof value.callID === "string";
|
|
16652
|
+
}
|
|
16503
16653
|
|
|
16504
16654
|
//#endregion
|
|
16505
16655
|
//#region src/managers/VertoManager.ts
|
|
16506
16656
|
var import_cjs$15 = require_cjs();
|
|
16507
|
-
const logger$
|
|
16657
|
+
const logger$16 = getLogger();
|
|
16508
16658
|
/**
|
|
16509
16659
|
* Decide what value goes on the `node_id` field of a `webrtc.verto` envelope.
|
|
16510
16660
|
*
|
|
@@ -16560,7 +16710,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16560
16710
|
try {
|
|
16561
16711
|
await this.executeVerto(vertoModifyMessage);
|
|
16562
16712
|
} catch (error) {
|
|
16563
|
-
logger$
|
|
16713
|
+
logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto hold:", error);
|
|
16564
16714
|
throw error;
|
|
16565
16715
|
}
|
|
16566
16716
|
}
|
|
@@ -16573,7 +16723,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16573
16723
|
try {
|
|
16574
16724
|
await this.executeVerto(vertoModifyMessage);
|
|
16575
16725
|
} catch (error) {
|
|
16576
|
-
logger$
|
|
16726
|
+
logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto unhold:", error);
|
|
16577
16727
|
throw error;
|
|
16578
16728
|
}
|
|
16579
16729
|
}
|
|
@@ -16626,7 +16776,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16626
16776
|
if (event.member_id) this.setSelfIdIfNull(event.member_id);
|
|
16627
16777
|
});
|
|
16628
16778
|
this.subscribeTo(this.vertoMedia$, (event) => {
|
|
16629
|
-
logger$
|
|
16779
|
+
logger$16.debug("[WebRTCManager] Received Verto media event (early media SDP):", event);
|
|
16630
16780
|
const { sdp, callID } = event;
|
|
16631
16781
|
this.emitMainSignalingStatus(callID, "ringing");
|
|
16632
16782
|
this._rtcPeerConnectionsMap.get(callID)?.updateAnswerStatus({
|
|
@@ -16635,7 +16785,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16635
16785
|
});
|
|
16636
16786
|
});
|
|
16637
16787
|
this.subscribeTo(this.vertoAnswer$, (event) => {
|
|
16638
|
-
logger$
|
|
16788
|
+
logger$16.debug("[WebRTCManager] Received Verto answer event:", event);
|
|
16639
16789
|
const { sdp, callID } = event;
|
|
16640
16790
|
this.emitMainSignalingStatus(callID, "connecting");
|
|
16641
16791
|
this._rtcPeerConnectionsMap.get(callID)?.updateAnswerStatus({
|
|
@@ -16644,7 +16794,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16644
16794
|
});
|
|
16645
16795
|
});
|
|
16646
16796
|
this.subscribeTo(this.vertoMediaParams$, (event) => {
|
|
16647
|
-
logger$
|
|
16797
|
+
logger$16.debug("[WebRTCManager] Received Verto mediaParams event:", event);
|
|
16648
16798
|
const { mediaParams, callID } = event;
|
|
16649
16799
|
const rtcPeerConnController = this._rtcPeerConnectionsMap.get(callID);
|
|
16650
16800
|
const { audio, video } = mediaParams;
|
|
@@ -16658,7 +16808,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16658
16808
|
timestamp: Date.now()
|
|
16659
16809
|
});
|
|
16660
16810
|
} catch (error) {
|
|
16661
|
-
logger$
|
|
16811
|
+
logger$16.warn("[WebRTCManager] Error applying server-pushed media params:", error);
|
|
16662
16812
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
16663
16813
|
}
|
|
16664
16814
|
})();
|
|
@@ -16680,13 +16830,13 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16680
16830
|
*/
|
|
16681
16831
|
setNodeIdIfNull(nodeId) {
|
|
16682
16832
|
if (!this._nodeId$.value && nodeId) {
|
|
16683
|
-
logger$
|
|
16833
|
+
logger$16.debug(`[WebRTCManager] Early node_id set: ${nodeId}`);
|
|
16684
16834
|
this._nodeId$.next(nodeId);
|
|
16685
16835
|
}
|
|
16686
16836
|
}
|
|
16687
16837
|
setSelfIdIfNull(selfId) {
|
|
16688
16838
|
if (!this._selfId$.value && selfId) {
|
|
16689
|
-
logger$
|
|
16839
|
+
logger$16.debug(`[WebRTCManager] Early selfId set: ${selfId}`);
|
|
16690
16840
|
this._selfId$.next(selfId);
|
|
16691
16841
|
}
|
|
16692
16842
|
}
|
|
@@ -16695,7 +16845,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16695
16845
|
const vertoPongMessage = VertoPong({ ...vertoPing });
|
|
16696
16846
|
await this.executeVerto(vertoPongMessage);
|
|
16697
16847
|
} catch (error) {
|
|
16698
|
-
logger$
|
|
16848
|
+
logger$16.warn("[WebRTCManager] Call might disconnect, error sending Verto pong:", error);
|
|
16699
16849
|
this.onError?.(new VertoPongError(error));
|
|
16700
16850
|
}
|
|
16701
16851
|
}
|
|
@@ -16705,7 +16855,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16705
16855
|
if (audio) await this.mainPeerConnection.updateSendersConstraints("audio", audio);
|
|
16706
16856
|
if (video) await this.mainPeerConnection.updateSendersConstraints("video", video);
|
|
16707
16857
|
} catch (error) {
|
|
16708
|
-
logger$
|
|
16858
|
+
logger$16.warn("[WebRTCManager] Error updating media constraints:", error);
|
|
16709
16859
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
16710
16860
|
throw error;
|
|
16711
16861
|
}
|
|
@@ -16735,20 +16885,20 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16735
16885
|
try {
|
|
16736
16886
|
const pc = this.mainPeerConnection.peerConnection;
|
|
16737
16887
|
if (!pc) {
|
|
16738
|
-
logger$
|
|
16888
|
+
logger$16.warn("[WebRTCManager] No peer connection for keyframe request");
|
|
16739
16889
|
return;
|
|
16740
16890
|
}
|
|
16741
16891
|
const videoReceiver = pc.getReceivers().find((r) => r.track.kind === "video");
|
|
16742
16892
|
if (!videoReceiver) {
|
|
16743
|
-
logger$
|
|
16893
|
+
logger$16.warn("[WebRTCManager] No video receiver for keyframe request");
|
|
16744
16894
|
return;
|
|
16745
16895
|
}
|
|
16746
16896
|
if (typeof videoReceiver.requestKeyFrame === "function") {
|
|
16747
16897
|
videoReceiver.requestKeyFrame();
|
|
16748
|
-
logger$
|
|
16749
|
-
} else logger$
|
|
16898
|
+
logger$16.debug("[WebRTCManager] Keyframe requested via RTCRtpReceiver.requestKeyFrame()");
|
|
16899
|
+
} else logger$16.debug("[WebRTCManager] requestKeyFrame() not supported, skipping");
|
|
16750
16900
|
} catch (error) {
|
|
16751
|
-
logger$
|
|
16901
|
+
logger$16.warn("[WebRTCManager] Keyframe request failed (non-fatal):", error);
|
|
16752
16902
|
}
|
|
16753
16903
|
}
|
|
16754
16904
|
/**
|
|
@@ -16766,13 +16916,13 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16766
16916
|
try {
|
|
16767
16917
|
const controller = this.mainPeerConnection;
|
|
16768
16918
|
if (!controller.peerConnection) {
|
|
16769
|
-
logger$
|
|
16919
|
+
logger$16.warn("[WebRTCManager] No peer connection for ICE restart");
|
|
16770
16920
|
return;
|
|
16771
16921
|
}
|
|
16772
16922
|
await controller.triggerIceRestart(relayOnly);
|
|
16773
|
-
logger$
|
|
16923
|
+
logger$16.info(`[WebRTCManager] ICE restart initiated${relayOnly ? " (relay-only)" : ""}`);
|
|
16774
16924
|
} catch (error) {
|
|
16775
|
-
logger$
|
|
16925
|
+
logger$16.error("[WebRTCManager] ICE restart failed:", error);
|
|
16776
16926
|
throw error;
|
|
16777
16927
|
}
|
|
16778
16928
|
}
|
|
@@ -16790,13 +16940,13 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16790
16940
|
const entries = Array.from(this._rtcPeerConnectionsMap.entries());
|
|
16791
16941
|
for (const [id, controller] of entries) try {
|
|
16792
16942
|
if (!controller.peerConnection) {
|
|
16793
|
-
logger$
|
|
16943
|
+
logger$16.debug(`[WebRTCManager] No peer connection for leg ${id}, skipping ICE restart`);
|
|
16794
16944
|
continue;
|
|
16795
16945
|
}
|
|
16796
16946
|
await controller.triggerIceRestart(relayOnly);
|
|
16797
|
-
logger$
|
|
16947
|
+
logger$16.info(`[WebRTCManager] ICE restart initiated for leg ${id}${relayOnly ? " (relay-only)" : ""}`);
|
|
16798
16948
|
} catch (error) {
|
|
16799
|
-
logger$
|
|
16949
|
+
logger$16.warn(`[WebRTCManager] ICE restart failed for leg ${id}:`, error);
|
|
16800
16950
|
}
|
|
16801
16951
|
}
|
|
16802
16952
|
/**
|
|
@@ -16808,7 +16958,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16808
16958
|
requestKeyframeAll() {
|
|
16809
16959
|
for (const [id, controller] of this._rtcPeerConnectionsMap) {
|
|
16810
16960
|
if (controller.isScreenShare) {
|
|
16811
|
-
logger$
|
|
16961
|
+
logger$16.debug(`[WebRTCManager] Skipping keyframe for send-only screen share leg ${id}`);
|
|
16812
16962
|
continue;
|
|
16813
16963
|
}
|
|
16814
16964
|
try {
|
|
@@ -16818,10 +16968,10 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16818
16968
|
if (!videoReceiver) continue;
|
|
16819
16969
|
if (typeof videoReceiver.requestKeyFrame === "function") {
|
|
16820
16970
|
videoReceiver.requestKeyFrame();
|
|
16821
|
-
logger$
|
|
16971
|
+
logger$16.debug(`[WebRTCManager] Keyframe requested for leg ${id}`);
|
|
16822
16972
|
}
|
|
16823
16973
|
} catch (error) {
|
|
16824
|
-
logger$
|
|
16974
|
+
logger$16.warn(`[WebRTCManager] Keyframe request failed for leg ${id} (non-fatal):`, error);
|
|
16825
16975
|
}
|
|
16826
16976
|
}
|
|
16827
16977
|
}
|
|
@@ -16838,7 +16988,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16838
16988
|
return this.cachedObservable("vertoMediaParams$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoMediaParamsInnerParams, "params"), (0, import_cjs$15.takeUntil)(this.destroyed$)));
|
|
16839
16989
|
}
|
|
16840
16990
|
get vertoBye$() {
|
|
16841
|
-
return this.cachedObservable("vertoBye$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(
|
|
16991
|
+
return this.cachedObservable("vertoBye$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoByeInboundMessage, "params"), (0, import_cjs$15.takeUntil)(this.destroyed$)));
|
|
16842
16992
|
}
|
|
16843
16993
|
get vertoAttach$() {
|
|
16844
16994
|
return this.cachedObservable("vertoAttach$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoAttachMessage, "params"), (0, import_cjs$15.takeUntil)(this.destroyed$)));
|
|
@@ -16882,7 +17032,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16882
17032
|
default:
|
|
16883
17033
|
}
|
|
16884
17034
|
} catch (error) {
|
|
16885
|
-
logger$
|
|
17035
|
+
logger$16.error(`[WebRTCManager] Error sending Verto ${vertoMethod}:`, error);
|
|
16886
17036
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
16887
17037
|
if (vertoMethod === "verto.modify") this.onModifyFailed?.();
|
|
16888
17038
|
}
|
|
@@ -16897,7 +17047,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16897
17047
|
sdp
|
|
16898
17048
|
});
|
|
16899
17049
|
} catch (error) {
|
|
16900
|
-
logger$
|
|
17050
|
+
logger$16.warn("[WebRTCManager] Error processing modify response:", error);
|
|
16901
17051
|
const modifyError = error instanceof Error ? error : new Error(String(error), { cause: error });
|
|
16902
17052
|
this.onError?.(modifyError);
|
|
16903
17053
|
}
|
|
@@ -16907,7 +17057,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16907
17057
|
const rtcPeerConnController = this._rtcPeerConnectionsMap.get(callId);
|
|
16908
17058
|
if (!rtcPeerConnController) {
|
|
16909
17059
|
const signalingError = new DependencyError(`Cannot emit signaling status, RTCPeerConnectionController not found for callID: ${callId}`);
|
|
16910
|
-
logger$
|
|
17060
|
+
logger$16.error("[WebRTCManager] Failed to emit signaling status:", {
|
|
16911
17061
|
callId,
|
|
16912
17062
|
status,
|
|
16913
17063
|
signalingError
|
|
@@ -16923,7 +17073,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16923
17073
|
this._nodeId$.next(getValueFrom(response, "result.node_id") ?? null);
|
|
16924
17074
|
const memberId = getValueFrom(response, "result.result.result.memberID") ?? null;
|
|
16925
17075
|
const callId = getValueFrom(response, "result.result.result.callID") ?? null;
|
|
16926
|
-
logger$
|
|
17076
|
+
logger$16.debug("[WebRTCManager] Verto invite response:", {
|
|
16927
17077
|
callId,
|
|
16928
17078
|
memberId,
|
|
16929
17079
|
response
|
|
@@ -16933,14 +17083,14 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16933
17083
|
if (callId) {
|
|
16934
17084
|
this.webRtcCallSession.addCallId(callId);
|
|
16935
17085
|
this.attachManager.attach(this.buildAttachableCall(callId));
|
|
16936
|
-
} else logger$
|
|
17086
|
+
} else logger$16.warn("[WebRTCManager] Cannot attach call, missing callId:", {
|
|
16937
17087
|
nodeId: this.nodeId,
|
|
16938
17088
|
callId
|
|
16939
17089
|
});
|
|
16940
|
-
logger$
|
|
16941
|
-
logger$
|
|
17090
|
+
logger$16.info("[WebRTCManager] Verto invite successful");
|
|
17091
|
+
logger$16.debug(`[WebRTCManager] nodeid: ${this._nodeId$.value}, selfId: ${this._selfId$.value}`);
|
|
16942
17092
|
} else {
|
|
16943
|
-
logger$
|
|
17093
|
+
logger$16.error("[WebRTCManager] Verto invite failed:", response);
|
|
16944
17094
|
const inviteError = response.error ? new JSONRPCError(response.error.code, response.error.message, response.error.data) : /* @__PURE__ */ new Error("Verto invite failed: unexpected response");
|
|
16945
17095
|
this.onError?.(inviteError);
|
|
16946
17096
|
}
|
|
@@ -16967,6 +17117,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16967
17117
|
inputVideoStream: options.inputVideoStream,
|
|
16968
17118
|
receiveAudio: options.receiveAudio,
|
|
16969
17119
|
receiveVideo: options.receiveVideo,
|
|
17120
|
+
fallbackToReceiveOnly: options.fallbackToReceiveOnly,
|
|
16970
17121
|
webRTCApiProvider: this.webRTCApiProvider,
|
|
16971
17122
|
preferredVideoCodecs: options.preferredVideoCodecs,
|
|
16972
17123
|
preferredAudioCodecs: options.preferredAudioCodecs,
|
|
@@ -16985,17 +17136,17 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16985
17136
|
if (options.initOffer) this.handleInboundAnswer(rtcPeerConnController);
|
|
16986
17137
|
}
|
|
16987
17138
|
async handleInboundAnswer(rtcPeerConnController) {
|
|
16988
|
-
logger$
|
|
17139
|
+
logger$16.debug("[WebRTCManager] Waiting for inbound call to be accepted or rejected");
|
|
16989
17140
|
const vertoByeOrAccepted = await (0, import_cjs$15.firstValueFrom)((0, import_cjs$15.race)(this.vertoBye$, this.webRtcCallSession.answered$).pipe((0, import_cjs$15.takeUntil)(this.destroyed$))).catch(() => null);
|
|
16990
17141
|
if (vertoByeOrAccepted === null) {
|
|
16991
|
-
logger$
|
|
17142
|
+
logger$16.debug("[WebRTCManager] Inbound answer handler aborted (destroyed).");
|
|
16992
17143
|
return;
|
|
16993
17144
|
}
|
|
16994
|
-
if (
|
|
16995
|
-
logger$
|
|
17145
|
+
if (isVertoByeInboundParamsGuard(vertoByeOrAccepted)) {
|
|
17146
|
+
logger$16.info("[WebRTCManager] Inbound call ended by remote before answer.");
|
|
16996
17147
|
this.callSession?.destroy();
|
|
16997
17148
|
} else if (!vertoByeOrAccepted) {
|
|
16998
|
-
logger$
|
|
17149
|
+
logger$16.info("[WebRTCManager] Inbound call rejected by user.");
|
|
16999
17150
|
try {
|
|
17000
17151
|
await this.bye("USER_BUSY");
|
|
17001
17152
|
} finally {
|
|
@@ -17003,19 +17154,19 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17003
17154
|
this.callSession?.destroy();
|
|
17004
17155
|
}
|
|
17005
17156
|
} else {
|
|
17006
|
-
logger$
|
|
17157
|
+
logger$16.debug("[WebRTCManager] Inbound call accepted, creating SDP answer");
|
|
17007
17158
|
const answerOptions = this.webRtcCallSession.answerMediaOptions;
|
|
17008
17159
|
try {
|
|
17009
17160
|
await rtcPeerConnController.acceptInbound(answerOptions);
|
|
17010
17161
|
} catch (error) {
|
|
17011
|
-
logger$
|
|
17162
|
+
logger$16.error("[WebRTCManager] Error creating inbound answer:", error);
|
|
17012
17163
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
17013
17164
|
}
|
|
17014
17165
|
}
|
|
17015
17166
|
}
|
|
17016
17167
|
setupVertoAttachHandler() {
|
|
17017
17168
|
this.subscribeTo(this.vertoAttach$, async (vertoAttach) => {
|
|
17018
|
-
logger$
|
|
17169
|
+
logger$16.debug("[WebRTCManager] Received Verto attach event for existing call:", vertoAttach);
|
|
17019
17170
|
const { callID } = vertoAttach;
|
|
17020
17171
|
await this.attachManager.attach({
|
|
17021
17172
|
nodeId: this.nodeId ?? void 0,
|
|
@@ -17087,17 +17238,17 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17087
17238
|
};
|
|
17088
17239
|
}
|
|
17089
17240
|
async sendLocalDescriptionOnceAccepted(vertoMessageRequest, rtcPeerConnectionController) {
|
|
17090
|
-
logger$
|
|
17241
|
+
logger$16.debug("[WebRTCManager] Waiting for call to be accepted or ended before sending answer");
|
|
17091
17242
|
const vertoByeOrAccepted = await (0, import_cjs$15.firstValueFrom)((0, import_cjs$15.race)(this.vertoBye$, this.webRtcCallSession.answered$).pipe((0, import_cjs$15.takeUntil)(this.destroyed$))).catch(() => null);
|
|
17092
17243
|
if (vertoByeOrAccepted === null) {
|
|
17093
|
-
logger$
|
|
17244
|
+
logger$16.debug("[WebRTCManager] Destroyed while waiting for call acceptance");
|
|
17094
17245
|
return;
|
|
17095
17246
|
}
|
|
17096
|
-
if (
|
|
17097
|
-
logger$
|
|
17247
|
+
if (isVertoByeInboundParamsGuard(vertoByeOrAccepted)) {
|
|
17248
|
+
logger$16.info("[WebRTCManager] Call ended before answer was sent.");
|
|
17098
17249
|
this.callSession?.destroy();
|
|
17099
17250
|
} else if (!vertoByeOrAccepted) {
|
|
17100
|
-
logger$
|
|
17251
|
+
logger$16.info("[WebRTCManager] Call was not accepted, sending verto.bye.");
|
|
17101
17252
|
try {
|
|
17102
17253
|
await this.bye("USER_BUSY");
|
|
17103
17254
|
} finally {
|
|
@@ -17105,14 +17256,14 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17105
17256
|
this.callSession?.destroy();
|
|
17106
17257
|
}
|
|
17107
17258
|
} else {
|
|
17108
|
-
logger$
|
|
17259
|
+
logger$16.debug("[WebRTCManager] Call accepted, sending answer");
|
|
17109
17260
|
try {
|
|
17110
17261
|
this.emitMainSignalingStatus(rtcPeerConnectionController.id, "connecting");
|
|
17111
17262
|
await this.sendLocalDescription(vertoMessageRequest, rtcPeerConnectionController);
|
|
17112
17263
|
await rtcPeerConnectionController.updateAnswerStatus({ status: "sent" });
|
|
17113
17264
|
await this.attachManager.attach(this.buildAttachableCall());
|
|
17114
17265
|
} catch (error) {
|
|
17115
|
-
logger$
|
|
17266
|
+
logger$16.error("[WebRTCManager] Error sending Verto answer:", error);
|
|
17116
17267
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
17117
17268
|
await rtcPeerConnectionController.updateAnswerStatus({ status: "failed" });
|
|
17118
17269
|
}
|
|
@@ -17193,9 +17344,11 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17193
17344
|
await this.initAdditionalPeerConnection("screenshare", options);
|
|
17194
17345
|
}
|
|
17195
17346
|
async initAdditionalPeerConnection(propose, options) {
|
|
17347
|
+
const isScreenShare = propose === "screenshare";
|
|
17348
|
+
let firstPeerConnectionError;
|
|
17196
17349
|
let rtcPeerConnController = null;
|
|
17197
17350
|
try {
|
|
17198
|
-
this._screenShareStatus$.next("starting");
|
|
17351
|
+
if (isScreenShare) this._screenShareStatus$.next("starting");
|
|
17199
17352
|
rtcPeerConnController = new RTCPeerConnectionController({
|
|
17200
17353
|
...options,
|
|
17201
17354
|
...this.RTCPeerConnectionConfig,
|
|
@@ -17203,21 +17356,27 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17203
17356
|
webRTCApiProvider: this.webRTCApiProvider
|
|
17204
17357
|
}, void 0, this.deviceController);
|
|
17205
17358
|
this.setupLocalDescriptionHandler(rtcPeerConnController);
|
|
17206
|
-
if (
|
|
17359
|
+
if (isScreenShare) this._screenShareId = rtcPeerConnController.id;
|
|
17207
17360
|
this._rtcPeerConnectionsMap.set(rtcPeerConnController.id, rtcPeerConnController);
|
|
17208
17361
|
this._rtcPeerConnections$.next(Array.from(this._rtcPeerConnectionsMap.values()));
|
|
17209
17362
|
this.subscribeTo(rtcPeerConnController.errors$, (error) => {
|
|
17210
|
-
|
|
17363
|
+
firstPeerConnectionError ??= error;
|
|
17364
|
+
this.onError?.(error, { fatal: false });
|
|
17211
17365
|
});
|
|
17212
17366
|
await (0, import_cjs$15.firstValueFrom)(rtcPeerConnController.connectionState$.pipe((0, import_cjs$15.filter)((state) => state === "connected"), (0, import_cjs$15.take)(1), (0, import_cjs$15.timeout)(this._screenShareTimeoutMs), (0, import_cjs$15.takeUntil)(this.destroyed$)));
|
|
17213
|
-
this._screenShareStatus$.next("started");
|
|
17214
|
-
logger$
|
|
17367
|
+
if (isScreenShare) this._screenShareStatus$.next("started");
|
|
17368
|
+
logger$16.info(`[WebRTCManager] Additional peer connection connected (${propose}).`);
|
|
17215
17369
|
return rtcPeerConnController.id;
|
|
17216
17370
|
} catch (error) {
|
|
17217
|
-
logger$
|
|
17218
|
-
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
17371
|
+
logger$16.warn("[WebRTCManager] Error initializing additional peer connection:", error);
|
|
17219
17372
|
if (rtcPeerConnController) rtcPeerConnController.destroy();
|
|
17220
|
-
this._screenShareStatus$.next("none");
|
|
17373
|
+
if (isScreenShare) this._screenShareStatus$.next("none");
|
|
17374
|
+
if (firstPeerConnectionError) throw firstPeerConnectionError instanceof MediaAccessError && firstPeerConnectionError.originalError instanceof Error ? firstPeerConnectionError.originalError : firstPeerConnectionError;
|
|
17375
|
+
if (error instanceof import_cjs$15.EmptyError) {
|
|
17376
|
+
logger$16.debug("[WebRTCManager] Additional peer connection aborted before connecting.");
|
|
17377
|
+
return;
|
|
17378
|
+
}
|
|
17379
|
+
throw error instanceof Error ? error : new Error(String(error), { cause: error });
|
|
17221
17380
|
}
|
|
17222
17381
|
}
|
|
17223
17382
|
async removeInputDevices(id) {
|
|
@@ -17233,9 +17392,9 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17233
17392
|
if (removeTrack) return this.mainPeerConnection.stopTrackSender(removeTrack, { updateTransceiverDirection: true });
|
|
17234
17393
|
}
|
|
17235
17394
|
async removeScreenMedia() {
|
|
17236
|
-
if (!["starting", "started"].includes(this._screenShareStatus$.value)) logger$
|
|
17395
|
+
if (!["starting", "started"].includes(this._screenShareStatus$.value)) logger$16.warn("[WebRTCManager] No active screen share to stop.");
|
|
17237
17396
|
if (!this._screenShareId) {
|
|
17238
|
-
logger$
|
|
17397
|
+
logger$16.debug("[WebRTCManager] No screen share peer connection found.");
|
|
17239
17398
|
return;
|
|
17240
17399
|
}
|
|
17241
17400
|
this._screenShareStatus$.next("stopping");
|
|
@@ -17264,7 +17423,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17264
17423
|
dialogParams: this.dialogParams(rtcPeerConnController)
|
|
17265
17424
|
}));
|
|
17266
17425
|
} catch (error) {
|
|
17267
|
-
logger$
|
|
17426
|
+
logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto bye:", error);
|
|
17268
17427
|
throw error;
|
|
17269
17428
|
}
|
|
17270
17429
|
}
|
|
@@ -17282,7 +17441,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17282
17441
|
try {
|
|
17283
17442
|
await this.executeVerto(vertoInfoMessage);
|
|
17284
17443
|
} catch (error) {
|
|
17285
|
-
logger$
|
|
17444
|
+
logger$16.warn("[WebRTCManager] Error sending DTMF digits:", error);
|
|
17286
17445
|
throw error;
|
|
17287
17446
|
}
|
|
17288
17447
|
}
|
|
@@ -17293,10 +17452,10 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17293
17452
|
action: "transfer"
|
|
17294
17453
|
});
|
|
17295
17454
|
try {
|
|
17296
|
-
logger$
|
|
17455
|
+
logger$16.debug("[WebRTCManager] Transferring call with options:", options);
|
|
17297
17456
|
await this.executeVerto(message);
|
|
17298
17457
|
} catch (error) {
|
|
17299
|
-
logger$
|
|
17458
|
+
logger$16.error("[WebRTCManager] Error transferring call:", error);
|
|
17300
17459
|
throw error;
|
|
17301
17460
|
}
|
|
17302
17461
|
}
|
|
@@ -17313,7 +17472,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17313
17472
|
//#endregion
|
|
17314
17473
|
//#region src/controllers/RemoteAudioMeter.ts
|
|
17315
17474
|
var import_cjs$14 = require_cjs();
|
|
17316
|
-
const logger$
|
|
17475
|
+
const logger$15 = getLogger();
|
|
17317
17476
|
/**
|
|
17318
17477
|
* Read-only audio level meter for a remote MediaStream. Attaches an
|
|
17319
17478
|
* AnalyserNode to a MediaStreamAudioSourceNode so it observes the stream
|
|
@@ -17348,7 +17507,7 @@ var RemoteAudioMeter = class extends Destroyable {
|
|
|
17348
17507
|
try {
|
|
17349
17508
|
this._source.disconnect();
|
|
17350
17509
|
} catch (error) {
|
|
17351
|
-
logger$
|
|
17510
|
+
logger$15.debug("[RemoteAudioMeter] source disconnect warning:", error);
|
|
17352
17511
|
}
|
|
17353
17512
|
this._source = null;
|
|
17354
17513
|
this._stream = null;
|
|
@@ -17365,7 +17524,7 @@ var RemoteAudioMeter = class extends Destroyable {
|
|
|
17365
17524
|
this._source = null;
|
|
17366
17525
|
}
|
|
17367
17526
|
this._audioContext.close().catch((error) => {
|
|
17368
|
-
logger$
|
|
17527
|
+
logger$15.debug("[RemoteAudioMeter] audio context close warning:", error);
|
|
17369
17528
|
});
|
|
17370
17529
|
super.destroy();
|
|
17371
17530
|
}
|
|
@@ -17384,7 +17543,7 @@ var RemoteAudioMeter = class extends Destroyable {
|
|
|
17384
17543
|
//#endregion
|
|
17385
17544
|
//#region src/controllers/RTCStatsMonitor.ts
|
|
17386
17545
|
var import_cjs$13 = require_cjs();
|
|
17387
|
-
const logger$
|
|
17546
|
+
const logger$14 = getLogger();
|
|
17388
17547
|
const DEFAULT_POLLING_INTERVAL_MS = 1e3;
|
|
17389
17548
|
const DEFAULT_BASELINE_SAMPLES = 10;
|
|
17390
17549
|
const DEFAULT_NO_AUDIO_PACKET_THRESHOLD_MS = 2e3;
|
|
@@ -17474,9 +17633,9 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
17474
17633
|
const now = Date.now();
|
|
17475
17634
|
this.lastAudioPacketChangeTime = now;
|
|
17476
17635
|
this.lastVideoPacketChangeTime = now;
|
|
17477
|
-
logger$
|
|
17636
|
+
logger$14.debug("[RTCStatsMonitor] Starting stats monitoring");
|
|
17478
17637
|
this.subscribeTo((0, import_cjs$13.interval)(this.pollingIntervalMs).pipe((0, import_cjs$13.filter)(() => this.running), (0, import_cjs$13.switchMap)(() => (0, import_cjs$13.from)(this.peerConnection.getStats()).pipe((0, import_cjs$13.catchError)((err) => {
|
|
17479
|
-
logger$
|
|
17638
|
+
logger$14.warn("[RTCStatsMonitor] Failed to get stats:", err);
|
|
17480
17639
|
return import_cjs$13.EMPTY;
|
|
17481
17640
|
}))), (0, import_cjs$13.filter)(() => this.running), (0, import_cjs$13.map)((report) => this.extractSample(report))), (sample$1) => this._sample$.next(sample$1));
|
|
17482
17641
|
this.subscribeTo(this._sample$.pipe((0, import_cjs$13.take)(this.baselineSampleCount), (0, import_cjs$13.toArray)(), (0, import_cjs$13.map)((samples) => ({
|
|
@@ -17484,7 +17643,7 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
17484
17643
|
jitter: samples.reduce((s, b) => s + b.audioJitter, 0) / samples.length,
|
|
17485
17644
|
ready: true
|
|
17486
17645
|
}))), (baseline) => {
|
|
17487
|
-
logger$
|
|
17646
|
+
logger$14.debug(`[RTCStatsMonitor] Baseline established: rtt=${baseline.rtt.toFixed(1)}ms, jitter=${baseline.jitter.toFixed(1)}ms`);
|
|
17488
17647
|
this._baseline$.next(baseline);
|
|
17489
17648
|
});
|
|
17490
17649
|
this.subscribeTo(this._sample$.pipe((0, import_cjs$13.scan)((acc, sample$1) => ({
|
|
@@ -17521,10 +17680,10 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
17521
17680
|
stop() {
|
|
17522
17681
|
if (!this.running) return;
|
|
17523
17682
|
this.running = false;
|
|
17524
|
-
logger$
|
|
17683
|
+
logger$14.debug("[RTCStatsMonitor] Stopping stats monitoring");
|
|
17525
17684
|
}
|
|
17526
17685
|
destroy() {
|
|
17527
|
-
logger$
|
|
17686
|
+
logger$14.debug("[RTCStatsMonitor] Destroying RTCStatsMonitor");
|
|
17528
17687
|
this.stop();
|
|
17529
17688
|
super.destroy();
|
|
17530
17689
|
}
|
|
@@ -17653,7 +17812,7 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
17653
17812
|
//#endregion
|
|
17654
17813
|
//#region src/managers/CallRecoveryManager.ts
|
|
17655
17814
|
var import_cjs$12 = require_cjs();
|
|
17656
|
-
const logger$
|
|
17815
|
+
const logger$13 = getLogger();
|
|
17657
17816
|
const DEFAULT_DEBOUNCE_TIME_MS = 2e3;
|
|
17658
17817
|
const DEFAULT_COOLDOWN_MS = 1e4;
|
|
17659
17818
|
const DEFAULT_ICE_GRACE_PERIOD_MS = 3e3;
|
|
@@ -17750,10 +17909,10 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17750
17909
|
*/
|
|
17751
17910
|
async requestIceRestart() {
|
|
17752
17911
|
if (this._recoveryState$.value === "recovering") {
|
|
17753
|
-
logger$
|
|
17912
|
+
logger$13.info("CallRecoveryManager: manual ICE restart skipped — recovery already in progress");
|
|
17754
17913
|
return;
|
|
17755
17914
|
}
|
|
17756
|
-
logger$
|
|
17915
|
+
logger$13.info("CallRecoveryManager: manual ICE restart requested");
|
|
17757
17916
|
this.transitionTo("recovering");
|
|
17758
17917
|
await this.executeIceRestart(false);
|
|
17759
17918
|
this.startCooldown();
|
|
@@ -17769,7 +17928,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17769
17928
|
* WebSocket reconnect or call state recovers to 'connected'.
|
|
17770
17929
|
*/
|
|
17771
17930
|
reset() {
|
|
17772
|
-
logger$
|
|
17931
|
+
logger$13.info("CallRecoveryManager: resetting counters");
|
|
17773
17932
|
this._attemptCount = 0;
|
|
17774
17933
|
this._keyframeBurstCount = 0;
|
|
17775
17934
|
this._keyframeBurstStart = 0;
|
|
@@ -17784,7 +17943,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17784
17943
|
*/
|
|
17785
17944
|
notifyModifyFailed() {
|
|
17786
17945
|
if (this._recoveryState$.value === "cooldown" || this._recoveryState$.value === "idle") {
|
|
17787
|
-
logger$
|
|
17946
|
+
logger$13.info("CallRecoveryManager: verto.modify failed — re-entering recovery");
|
|
17788
17947
|
this._cooldownUntil = 0;
|
|
17789
17948
|
this.transitionTo("idle");
|
|
17790
17949
|
this.pushTrigger({
|
|
@@ -17808,7 +17967,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17808
17967
|
reason: `bandwidth ${bitrateKbps}kbps below threshold ${this._config.degradationBitrateThreshold}kbps`,
|
|
17809
17968
|
timestamp: Date.now()
|
|
17810
17969
|
});
|
|
17811
|
-
logger$
|
|
17970
|
+
logger$13.warn(`CallRecoveryManager: disabling video — bandwidth ${bitrateKbps}kbps < ${this._config.degradationBitrateThreshold}kbps`);
|
|
17812
17971
|
} else if (wasConstrained && bitrateKbps >= this._config.degradationRecoveryThreshold) {
|
|
17813
17972
|
this._bandwidthConstrained$.next(false);
|
|
17814
17973
|
this._callbacks.enableVideo();
|
|
@@ -17817,7 +17976,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17817
17976
|
reason: `bandwidth ${bitrateKbps}kbps recovered above ${this._config.degradationRecoveryThreshold}kbps`,
|
|
17818
17977
|
timestamp: Date.now()
|
|
17819
17978
|
});
|
|
17820
|
-
logger$
|
|
17979
|
+
logger$13.info(`CallRecoveryManager: restoring video — bandwidth ${bitrateKbps}kbps >= ${this._config.degradationRecoveryThreshold}kbps`);
|
|
17821
17980
|
}
|
|
17822
17981
|
}
|
|
17823
17982
|
/**
|
|
@@ -17836,14 +17995,14 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17836
17995
|
handleWebSocketReconnect() {
|
|
17837
17996
|
const pcState = this._callbacks.getPeerConnectionState();
|
|
17838
17997
|
if (pcState === "connected" || pcState === "completed") {
|
|
17839
|
-
logger$
|
|
17998
|
+
logger$13.info("CallRecoveryManager: signal-only reconnect — peer connection still alive");
|
|
17840
17999
|
this.emitEvent({
|
|
17841
18000
|
action: "signal_reconnect",
|
|
17842
18001
|
reason: "WebSocket reconnected, peer connection still connected",
|
|
17843
18002
|
timestamp: Date.now()
|
|
17844
18003
|
});
|
|
17845
18004
|
} else {
|
|
17846
|
-
logger$
|
|
18005
|
+
logger$13.info("CallRecoveryManager: full reconnect — peer connection also down");
|
|
17847
18006
|
this.emitEvent({
|
|
17848
18007
|
action: "full_reconnect",
|
|
17849
18008
|
reason: "WebSocket reconnected, peer connection not connected — ICE restart needed",
|
|
@@ -17867,7 +18026,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17867
18026
|
}), (0, import_cjs$12.debounceTime)(this._config.debounceTimeMs), (0, import_cjs$12.withLatestFrom)(this._inputs.signalingReady$), (0, import_cjs$12.filter)(([, signalingReady]) => this.passGateChecks(signalingReady)), (0, import_cjs$12.map)(([trigger]) => trigger), (0, import_cjs$12.exhaustMap)((trigger) => this.executeTieredRecovery(trigger)), (0, import_cjs$12.takeUntil)((0, import_cjs$12.merge)(this._destroyed$, this._pipelineStop$))), {
|
|
17868
18027
|
next: () => {},
|
|
17869
18028
|
error: (err) => {
|
|
17870
|
-
logger$
|
|
18029
|
+
logger$13.error("CallRecoveryManager: pipeline error", err);
|
|
17871
18030
|
this.transitionTo("idle");
|
|
17872
18031
|
}
|
|
17873
18032
|
});
|
|
@@ -17894,27 +18053,27 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17894
18053
|
reason: `no packet loss for ${this._config.packetLossRecoveryDelaySec}s — restoring video`,
|
|
17895
18054
|
timestamp: Date.now()
|
|
17896
18055
|
});
|
|
17897
|
-
logger$
|
|
18056
|
+
logger$13.info(`CallRecoveryManager: restoring video — no packet loss for ${this._config.packetLossRecoveryDelaySec}s`);
|
|
17898
18057
|
});
|
|
17899
18058
|
}
|
|
17900
18059
|
passGateChecks(signalingReady) {
|
|
17901
18060
|
if (this._callbacks.isNegotiating()) {
|
|
17902
|
-
logger$
|
|
18061
|
+
logger$13.debug("CallRecoveryManager: gate blocked — negotiation in progress");
|
|
17903
18062
|
this.transitionTo("idle");
|
|
17904
18063
|
return false;
|
|
17905
18064
|
}
|
|
17906
18065
|
if (!signalingReady) {
|
|
17907
|
-
logger$
|
|
18066
|
+
logger$13.debug("CallRecoveryManager: gate blocked — signaling not ready");
|
|
17908
18067
|
this.transitionTo("idle");
|
|
17909
18068
|
return false;
|
|
17910
18069
|
}
|
|
17911
18070
|
if (!this._callbacks.isCallConnected()) {
|
|
17912
|
-
logger$
|
|
18071
|
+
logger$13.debug("CallRecoveryManager: gate blocked — call not connected");
|
|
17913
18072
|
this.transitionTo("idle");
|
|
17914
18073
|
return false;
|
|
17915
18074
|
}
|
|
17916
18075
|
if (this.isCooldownActive()) {
|
|
17917
|
-
logger$
|
|
18076
|
+
logger$13.debug("CallRecoveryManager: gate blocked — cooldown active");
|
|
17918
18077
|
this.transitionTo("cooldown");
|
|
17919
18078
|
return false;
|
|
17920
18079
|
}
|
|
@@ -17925,9 +18084,9 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17925
18084
|
}
|
|
17926
18085
|
executeTieredRecovery(trigger) {
|
|
17927
18086
|
this.transitionTo("recovering");
|
|
17928
|
-
logger$
|
|
18087
|
+
logger$13.info(`CallRecoveryManager: starting tiered recovery — source=${trigger.source} detail=${trigger.detail}`);
|
|
17929
18088
|
return (0, import_cjs$12.from)(this.runTiers(trigger)).pipe((0, import_cjs$12.tap)(() => this.startCooldown()), (0, import_cjs$12.catchError)((err) => {
|
|
17930
|
-
logger$
|
|
18089
|
+
logger$13.error("CallRecoveryManager: tiered recovery failed", err);
|
|
17931
18090
|
this.startCooldown();
|
|
17932
18091
|
return import_cjs$12.EMPTY;
|
|
17933
18092
|
}));
|
|
@@ -17935,7 +18094,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17935
18094
|
async runTiers(trigger) {
|
|
17936
18095
|
this.executeKeyframe(trigger.detail);
|
|
17937
18096
|
if (trigger.issueType && DEGRADATION_ONLY_ISSUES.has(trigger.issueType)) {
|
|
17938
|
-
logger$
|
|
18097
|
+
logger$13.debug(`CallRecoveryManager: degradation-only issue (${trigger.issueType}) — Tier 1 only, skipping ICE restart`);
|
|
17939
18098
|
return;
|
|
17940
18099
|
}
|
|
17941
18100
|
if (this._attemptCount < this._config.maxAttempts) {
|
|
@@ -17952,13 +18111,13 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17952
18111
|
maxAttempts: this._config.maxAttempts,
|
|
17953
18112
|
timestamp: Date.now()
|
|
17954
18113
|
});
|
|
17955
|
-
logger$
|
|
18114
|
+
logger$13.warn("CallRecoveryManager: max recovery attempts reached");
|
|
17956
18115
|
}
|
|
17957
18116
|
}
|
|
17958
18117
|
executeKeyframe(reason) {
|
|
17959
18118
|
const now = Date.now();
|
|
17960
18119
|
if (now < this._keyframeCooldownUntil) {
|
|
17961
|
-
logger$
|
|
18120
|
+
logger$13.debug("CallRecoveryManager: keyframe request skipped — cooldown active");
|
|
17962
18121
|
return;
|
|
17963
18122
|
}
|
|
17964
18123
|
if (now - this._keyframeBurstStart > this._config.keyframeBurstWindowMs) {
|
|
@@ -17967,7 +18126,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17967
18126
|
}
|
|
17968
18127
|
if (this._keyframeBurstCount >= this._config.keyframeMaxBurst) {
|
|
17969
18128
|
this._keyframeCooldownUntil = now + this._config.keyframeCooldownMs;
|
|
17970
|
-
logger$
|
|
18129
|
+
logger$13.debug(`CallRecoveryManager: keyframe burst limit reached (${this._config.keyframeMaxBurst}), cooldown until ${this._keyframeCooldownUntil}`);
|
|
17971
18130
|
return;
|
|
17972
18131
|
}
|
|
17973
18132
|
this._keyframeBurstCount += 1;
|
|
@@ -17977,12 +18136,12 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17977
18136
|
reason,
|
|
17978
18137
|
timestamp: now
|
|
17979
18138
|
});
|
|
17980
|
-
logger$
|
|
18139
|
+
logger$13.debug(`CallRecoveryManager: keyframe requested (burst ${this._keyframeBurstCount}/${this._config.keyframeMaxBurst})`);
|
|
17981
18140
|
}
|
|
17982
18141
|
async executeIceRestart(relayOnly) {
|
|
17983
18142
|
this._attemptCount += 1;
|
|
17984
18143
|
const tier = relayOnly ? "Tier 3 (relay-only)" : "Tier 2 (standard)";
|
|
17985
|
-
logger$
|
|
18144
|
+
logger$13.info(`CallRecoveryManager: ${tier} ICE restart — attempt ${this._attemptCount}/${this._config.maxAttempts}`);
|
|
17986
18145
|
this.emitEvent({
|
|
17987
18146
|
action: "reinvite_started",
|
|
17988
18147
|
reason: `${tier} ICE restart`,
|
|
@@ -17999,7 +18158,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17999
18158
|
maxAttempts: this._config.maxAttempts,
|
|
18000
18159
|
timestamp: Date.now()
|
|
18001
18160
|
});
|
|
18002
|
-
logger$
|
|
18161
|
+
logger$13.info(`CallRecoveryManager: ${tier} ICE restart succeeded`);
|
|
18003
18162
|
this._attemptCount = 0;
|
|
18004
18163
|
return true;
|
|
18005
18164
|
}
|
|
@@ -18010,7 +18169,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
18010
18169
|
maxAttempts: this._config.maxAttempts,
|
|
18011
18170
|
timestamp: Date.now()
|
|
18012
18171
|
});
|
|
18013
|
-
logger$
|
|
18172
|
+
logger$13.warn(`CallRecoveryManager: ${tier} ICE restart failed`);
|
|
18014
18173
|
return false;
|
|
18015
18174
|
} catch {
|
|
18016
18175
|
this.emitEvent({
|
|
@@ -18020,7 +18179,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
18020
18179
|
maxAttempts: this._config.maxAttempts,
|
|
18021
18180
|
timestamp: Date.now()
|
|
18022
18181
|
});
|
|
18023
|
-
logger$
|
|
18182
|
+
logger$13.warn(`CallRecoveryManager: ${tier} ICE restart timed out`);
|
|
18024
18183
|
return false;
|
|
18025
18184
|
}
|
|
18026
18185
|
}
|
|
@@ -18041,7 +18200,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
18041
18200
|
transitionTo(state) {
|
|
18042
18201
|
const prev = this._recoveryState$.value;
|
|
18043
18202
|
if (prev !== state) {
|
|
18044
|
-
logger$
|
|
18203
|
+
logger$13.debug(`CallRecoveryManager: state ${prev} -> ${state}`);
|
|
18045
18204
|
this._recoveryState$.next(state);
|
|
18046
18205
|
}
|
|
18047
18206
|
}
|
|
@@ -18144,7 +18303,13 @@ function mosToQualityLevel(mos) {
|
|
|
18144
18303
|
//#endregion
|
|
18145
18304
|
//#region src/core/entities/Call.ts
|
|
18146
18305
|
var import_cjs$11 = require_cjs();
|
|
18147
|
-
const logger$
|
|
18306
|
+
const logger$12 = getLogger();
|
|
18307
|
+
/**
|
|
18308
|
+
* Verto method for setting member layout positions. Its gateway DTO requires a
|
|
18309
|
+
* `targets` array whose entries are `{ target, position }` (NOT bare targets),
|
|
18310
|
+
* so {@link WebRTCCall.buildMethodParams} special-cases it. See issue #19400.
|
|
18311
|
+
*/
|
|
18312
|
+
const POSITION_SET_METHOD = "call.member.position.set";
|
|
18148
18313
|
/**
|
|
18149
18314
|
* Ratio between the critical and warning RTT spike multipliers.
|
|
18150
18315
|
* Warning threshold = baseline * warningMultiplier (default 3x)
|
|
@@ -18162,7 +18327,7 @@ const fromDestinationParams = (destination) => {
|
|
|
18162
18327
|
});
|
|
18163
18328
|
return params;
|
|
18164
18329
|
} catch (error) {
|
|
18165
|
-
logger$
|
|
18330
|
+
logger$12.warn(`Failed to parse destination URI: ${destination}`, error);
|
|
18166
18331
|
return {};
|
|
18167
18332
|
}
|
|
18168
18333
|
};
|
|
@@ -18352,7 +18517,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18352
18517
|
* @throws {JSONRPCError} If the RPC call returns an error.
|
|
18353
18518
|
*/
|
|
18354
18519
|
async executeMethod(target, method, args) {
|
|
18355
|
-
const params = this.buildMethodParams(target, args);
|
|
18520
|
+
const params = this.buildMethodParams(target, args, method);
|
|
18356
18521
|
const request = buildRPCRequest({
|
|
18357
18522
|
method,
|
|
18358
18523
|
params
|
|
@@ -18362,16 +18527,20 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18362
18527
|
if (isJSONRPCErrorResponse(response)) throw new JSONRPCError(parseInt(response.result?.code ?? "0"), `Error response from method ${method}: ${response.result?.code} ${response.result?.message}`, void 0, void 0, request.id);
|
|
18363
18528
|
return response;
|
|
18364
18529
|
} catch (error) {
|
|
18365
|
-
logger$
|
|
18530
|
+
logger$12.error(`[Call] Error executing method ${method} with params`, params, error);
|
|
18366
18531
|
throw error;
|
|
18367
18532
|
}
|
|
18368
18533
|
}
|
|
18369
|
-
buildMethodParams(target, args) {
|
|
18534
|
+
buildMethodParams(target, args, method) {
|
|
18370
18535
|
const self = {
|
|
18371
18536
|
node_id: this.nodeId ?? "",
|
|
18372
18537
|
call_id: this.id,
|
|
18373
18538
|
member_id: this.vertoManager.selfId ?? ""
|
|
18374
18539
|
};
|
|
18540
|
+
if (method === POSITION_SET_METHOD) return {
|
|
18541
|
+
...args,
|
|
18542
|
+
self
|
|
18543
|
+
};
|
|
18375
18544
|
if (typeof target === "object") return {
|
|
18376
18545
|
...args,
|
|
18377
18546
|
self,
|
|
@@ -18565,9 +18734,9 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18565
18734
|
*/
|
|
18566
18735
|
initResilienceSubsystems() {
|
|
18567
18736
|
const pc = this.rtcPeerConnection;
|
|
18568
|
-
logger$
|
|
18737
|
+
logger$12.debug(`[Call] initResilienceSubsystems: pc=${pc ? "exists" : "undefined"}, connectionState=${pc?.connectionState}`);
|
|
18569
18738
|
if (!pc) {
|
|
18570
|
-
logger$
|
|
18739
|
+
logger$12.warn("[Call] No peer connection available, skipping resilience init");
|
|
18571
18740
|
return;
|
|
18572
18741
|
}
|
|
18573
18742
|
try {
|
|
@@ -18602,14 +18771,14 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18602
18771
|
disableVideo: () => {
|
|
18603
18772
|
try {
|
|
18604
18773
|
this.vertoManager.muteMainVideoInputDevice();
|
|
18605
|
-
logger$
|
|
18774
|
+
logger$12.debug("[Call] Recovery manager disabled video");
|
|
18606
18775
|
} catch {
|
|
18607
|
-
logger$
|
|
18776
|
+
logger$12.debug("[Call] Recovery manager failed to disable video");
|
|
18608
18777
|
}
|
|
18609
18778
|
},
|
|
18610
18779
|
enableVideo: () => {
|
|
18611
18780
|
this.vertoManager.unmuteMainVideoInputDevice().catch(() => {
|
|
18612
|
-
logger$
|
|
18781
|
+
logger$12.debug("[Call] Recovery manager failed to enable video");
|
|
18613
18782
|
});
|
|
18614
18783
|
},
|
|
18615
18784
|
isNegotiating: () => this.vertoManager.mainPeerConnection.isNegotiating,
|
|
@@ -18659,7 +18828,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18659
18828
|
this.subscribeTo(this._recoveryManager.recoveryEvent$, (event) => {
|
|
18660
18829
|
this._recoveryEvent$.next(event);
|
|
18661
18830
|
if (event.action === "max_attempts_reached") {
|
|
18662
|
-
logger$
|
|
18831
|
+
logger$12.warn("[Call] All recovery attempts exhausted, terminating call");
|
|
18663
18832
|
this.emitError({
|
|
18664
18833
|
kind: "network",
|
|
18665
18834
|
fatal: true,
|
|
@@ -18679,13 +18848,13 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18679
18848
|
else if (event.type === "online") this._recoveryManager?.handleWebSocketReconnect();
|
|
18680
18849
|
});
|
|
18681
18850
|
this.subscribeTo(this.clientSession.authenticated$.pipe((0, import_cjs$11.skip)(1), (0, import_cjs$11.filter)(Boolean)), () => {
|
|
18682
|
-
logger$
|
|
18851
|
+
logger$12.debug("[Call] WebSocket reconnected — notifying recovery manager");
|
|
18683
18852
|
this._recoveryManager?.handleWebSocketReconnect();
|
|
18684
18853
|
});
|
|
18685
18854
|
this._statsMonitor.start();
|
|
18686
|
-
logger$
|
|
18855
|
+
logger$12.debug("[Call] Resilience subsystems initialized for call", this.id);
|
|
18687
18856
|
} catch (error) {
|
|
18688
|
-
logger$
|
|
18857
|
+
logger$12.warn("[Call] Failed to initialize resilience subsystems:", error);
|
|
18689
18858
|
}
|
|
18690
18859
|
}
|
|
18691
18860
|
/**
|
|
@@ -18768,19 +18937,19 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18768
18937
|
}
|
|
18769
18938
|
isCallSessionEvent(event) {
|
|
18770
18939
|
try {
|
|
18771
|
-
logger$
|
|
18940
|
+
logger$12.debug("[Call] Checking if event is for this call session:", event);
|
|
18772
18941
|
const callId = getValueFrom(event, "params.params.callID") ?? getValueFrom(event, "params.call_id");
|
|
18773
18942
|
const roomSessionId = getValueFrom(event, "params.room_session_id");
|
|
18774
|
-
logger$
|
|
18943
|
+
logger$12.debug(`[Call] Extracted session identifiers callID: ${callId} and roomSessionID: ${roomSessionId} from event:`);
|
|
18775
18944
|
return callId === this.id || !!callId && this.callEventsManager.isCallIdValid(callId) || !!roomSessionId && this.callEventsManager.isRoomSessionIdValid(roomSessionId);
|
|
18776
18945
|
} catch (error) {
|
|
18777
|
-
logger$
|
|
18946
|
+
logger$12.error("[Call] Error checking if event is for this call session:", error);
|
|
18778
18947
|
return false;
|
|
18779
18948
|
}
|
|
18780
18949
|
}
|
|
18781
18950
|
get callSessionEvents$() {
|
|
18782
18951
|
return this.cachedObservable("callSessionEvents$", () => this.clientSession.signalingEvent$.pipe((0, import_cjs$11.filter)((event) => this.isCallSessionEvent(event)), (0, import_cjs$11.tap)((event) => {
|
|
18783
|
-
logger$
|
|
18952
|
+
logger$12.debug("[Call] Received call session event:", event);
|
|
18784
18953
|
}), (0, import_cjs$11.takeUntil)(this.destroyed$), (0, import_cjs$11.share)()));
|
|
18785
18954
|
}
|
|
18786
18955
|
/** Observable of call-updated events. */
|
|
@@ -18850,16 +19019,16 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18850
19019
|
this._customSubscriptions.set(eventType, filtered$);
|
|
18851
19020
|
}, (error) => {
|
|
18852
19021
|
this._customSubscriptions.delete(eventType);
|
|
18853
|
-
logger$
|
|
19022
|
+
logger$12.warn(`[Call] verto.subscribe for '${eventType}' failed, not caching:`, error);
|
|
18854
19023
|
});
|
|
18855
19024
|
this._customSubscriptions.set(eventType, filtered$);
|
|
18856
19025
|
return filtered$;
|
|
18857
19026
|
}
|
|
18858
19027
|
get webrtcMessages$() {
|
|
18859
|
-
return this.cachedObservable("webrtcMessages$", () => this.callSessionEvents$.pipe(filterAs(isWebrtcMessageMetadata, "params"), (0, import_cjs$11.tap)((event) => logger$
|
|
19028
|
+
return this.cachedObservable("webrtcMessages$", () => this.callSessionEvents$.pipe(filterAs(isWebrtcMessageMetadata, "params"), (0, import_cjs$11.tap)((event) => logger$12.debug("[Call] Event is a WebRTC message event:", event)), (0, import_cjs$11.takeUntil)(this.destroyed$), (0, import_cjs$11.share)()));
|
|
18860
19029
|
}
|
|
18861
19030
|
get callEvent$() {
|
|
18862
|
-
return this.cachedObservable("callEvent$", () => this.callSessionEvents$.pipe(filterAs(isSignalwireCallMetadata, "params"), (0, import_cjs$11.tap)((event) => logger$
|
|
19031
|
+
return this.cachedObservable("callEvent$", () => this.callSessionEvents$.pipe(filterAs(isSignalwireCallMetadata, "params"), (0, import_cjs$11.tap)((event) => logger$12.debug("[Call] Event is a call event:", event)), (0, import_cjs$11.takeUntil)(this.destroyed$), (0, import_cjs$11.share)()));
|
|
18863
19032
|
}
|
|
18864
19033
|
get layoutEvent$() {
|
|
18865
19034
|
return this.cachedObservable("layoutEvent$", () => this.callEvent$.pipe(filterAs(isLayoutChangedMetadata, "params")));
|
|
@@ -18937,10 +19106,21 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18937
19106
|
return this.deferEmission(this._answered$.asObservable());
|
|
18938
19107
|
}
|
|
18939
19108
|
/**
|
|
18940
|
-
* Sets the call layout and participant positions.
|
|
19109
|
+
* Sets the call layout and, optionally, individual participant positions.
|
|
19110
|
+
*
|
|
19111
|
+
* The gateway `call.layout.set` DTO has **no** `positions` member, so when
|
|
19112
|
+
* `positions` is provided this method issues a `call.member.position.set`
|
|
19113
|
+
* request per member (via {@link Participant.setPosition}, which keys each
|
|
19114
|
+
* position by that member's own call context) alongside `call.layout.set`
|
|
19115
|
+
* (issue #19400, Flag #6).
|
|
19116
|
+
*
|
|
19117
|
+
* **These operations are NOT atomic.** The layout is applied first, then each
|
|
19118
|
+
* member position sequentially, so members may briefly flash into their
|
|
19119
|
+
* default slots before being moved to the requested positions.
|
|
18941
19120
|
*
|
|
18942
19121
|
* @param layout - Layout name (must be one of {@link layouts}).
|
|
18943
|
-
* @param positions -
|
|
19122
|
+
* @param positions - Optional map of member IDs to {@link VideoPosition} values.
|
|
19123
|
+
* When omitted or empty, only the layout is changed.
|
|
18944
19124
|
* @throws {InvalidParams} If the layout is not in the available {@link layouts}.
|
|
18945
19125
|
*
|
|
18946
19126
|
* @example
|
|
@@ -18953,10 +19133,17 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18953
19133
|
async setLayout(layout, positions) {
|
|
18954
19134
|
if (!this.layouts.includes(layout)) throw new InvalidParams(`Layout ${layout} is not available in the current call layouts: ${this.layouts.join(", ")}`);
|
|
18955
19135
|
const selfId = await (0, import_cjs$11.firstValueFrom)(this.selfId$.pipe((0, import_cjs$11.filter)((id) => id !== null)));
|
|
18956
|
-
await this.executeMethod(selfId, "call.layout.set", {
|
|
18957
|
-
|
|
18958
|
-
|
|
18959
|
-
|
|
19136
|
+
await this.executeMethod(selfId, "call.layout.set", { layout });
|
|
19137
|
+
const positionEntries = Object.entries(positions ?? {});
|
|
19138
|
+
if (positionEntries.length === 0) return;
|
|
19139
|
+
for (const [memberId, position] of positionEntries) {
|
|
19140
|
+
const participant = this.participants.find((p) => p.id === memberId);
|
|
19141
|
+
if (!participant) {
|
|
19142
|
+
logger$12.warn(`[Call] setLayout: member ${memberId} not found in participants; skipping position ${position}`);
|
|
19143
|
+
continue;
|
|
19144
|
+
}
|
|
19145
|
+
await participant.setPosition(position);
|
|
19146
|
+
}
|
|
18960
19147
|
}
|
|
18961
19148
|
/**
|
|
18962
19149
|
* Transfers the call to another destination.
|
|
@@ -18988,7 +19175,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18988
19175
|
setLocalMicrophoneGain(value) {
|
|
18989
19176
|
const pipeline = this.vertoManager.ensureLocalAudioPipeline();
|
|
18990
19177
|
if (!pipeline) {
|
|
18991
|
-
logger$
|
|
19178
|
+
logger$12.warn("[Call] setLocalMicrophoneGain: audio pipeline unavailable");
|
|
18992
19179
|
return;
|
|
18993
19180
|
}
|
|
18994
19181
|
const percent = Math.max(0, Math.min(200, value));
|
|
@@ -19033,7 +19220,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
19033
19220
|
enablePushToTalk() {
|
|
19034
19221
|
const pipeline = this.vertoManager.ensureLocalAudioPipeline();
|
|
19035
19222
|
if (!pipeline) {
|
|
19036
|
-
logger$
|
|
19223
|
+
logger$12.warn("[Call] enablePushToTalk: audio pipeline unavailable");
|
|
19037
19224
|
return;
|
|
19038
19225
|
}
|
|
19039
19226
|
pipeline.setPTTActive(false);
|
|
@@ -19130,6 +19317,7 @@ function inferCallErrorKind(error) {
|
|
|
19130
19317
|
if (error instanceof RPCTimeoutError) return "timeout";
|
|
19131
19318
|
if (error instanceof JSONRPCError) return "signaling";
|
|
19132
19319
|
if (error instanceof MediaTrackError) return "media";
|
|
19320
|
+
if (error instanceof MediaAccessError) return "media";
|
|
19133
19321
|
if (error instanceof WebSocketConnectionError || error instanceof TransportConnectionError) return "network";
|
|
19134
19322
|
return "internal";
|
|
19135
19323
|
}
|
|
@@ -19146,6 +19334,7 @@ const RECOVERABLE_RPC_CODES = new Set([
|
|
|
19146
19334
|
function isFatalError(error) {
|
|
19147
19335
|
if (error instanceof VertoPongError) return false;
|
|
19148
19336
|
if (error instanceof MediaTrackError) return false;
|
|
19337
|
+
if (error instanceof MediaAccessError) return error.fatal;
|
|
19149
19338
|
if (error instanceof RPCTimeoutError) return false;
|
|
19150
19339
|
if (error instanceof JSONRPCError && RECOVERABLE_RPC_CODES.has(error.code)) return false;
|
|
19151
19340
|
return true;
|
|
@@ -19171,10 +19360,10 @@ var CallFactory = class {
|
|
|
19171
19360
|
return {
|
|
19172
19361
|
vertoManager: new WebRTCVertoManager(callInstance, this.attachManager, this.deviceController, this.webRTCApiProvider, {
|
|
19173
19362
|
nodeId: options.nodeId,
|
|
19174
|
-
onError: (error) => {
|
|
19363
|
+
onError: (error, options$1) => {
|
|
19175
19364
|
const callError = {
|
|
19176
19365
|
kind: inferCallErrorKind(error),
|
|
19177
|
-
fatal: isFatalError(error),
|
|
19366
|
+
fatal: options$1?.fatal ?? isFatalError(error),
|
|
19178
19367
|
error,
|
|
19179
19368
|
callId: callInstance.id
|
|
19180
19369
|
};
|
|
@@ -19196,7 +19385,7 @@ var CallFactory = class {
|
|
|
19196
19385
|
//#endregion
|
|
19197
19386
|
//#region src/behaviors/Collection.ts
|
|
19198
19387
|
var import_cjs$10 = require_cjs();
|
|
19199
|
-
const logger$
|
|
19388
|
+
const logger$11 = getLogger();
|
|
19200
19389
|
var Fetcher = class {
|
|
19201
19390
|
constructor(endpoint, params, http) {
|
|
19202
19391
|
this.endpoint = endpoint;
|
|
@@ -19220,7 +19409,7 @@ var Fetcher = class {
|
|
|
19220
19409
|
this.hasMore = !!this.nextUrl;
|
|
19221
19410
|
return result.data.filter(this.filter).map(this.mapper);
|
|
19222
19411
|
}
|
|
19223
|
-
logger$
|
|
19412
|
+
logger$11.error("Failed to fetch entity");
|
|
19224
19413
|
return [];
|
|
19225
19414
|
}
|
|
19226
19415
|
async id(v) {
|
|
@@ -19296,7 +19485,7 @@ var EntityCollection = class extends Destroyable {
|
|
|
19296
19485
|
this._hasMore$.next(this.fetchController.hasMore ?? false);
|
|
19297
19486
|
this._loading$.next(false);
|
|
19298
19487
|
} catch (error) {
|
|
19299
|
-
logger$
|
|
19488
|
+
logger$11.error(`Failed to fetch initial collection data`, error);
|
|
19300
19489
|
this._hasMore$.next(this.fetchController.hasMore ?? false);
|
|
19301
19490
|
this._loading$.next(false);
|
|
19302
19491
|
this.onError?.(new CollectionFetchError("fetchMore", error));
|
|
@@ -19310,7 +19499,7 @@ var EntityCollection = class extends Destroyable {
|
|
|
19310
19499
|
if (data) this.upsertData(data);
|
|
19311
19500
|
return data;
|
|
19312
19501
|
} catch (error) {
|
|
19313
|
-
logger$
|
|
19502
|
+
logger$11.error(`Failed to fetch data for (${String(key)}:${String(value)}) :`, error);
|
|
19314
19503
|
this._loading$.next(false);
|
|
19315
19504
|
this.onError?.(new CollectionFetchError(`tryFetch(${String(key)})`, error));
|
|
19316
19505
|
}
|
|
@@ -19559,13 +19748,13 @@ var Address = class extends Destroyable {
|
|
|
19559
19748
|
//#endregion
|
|
19560
19749
|
//#region src/core/utils.ts
|
|
19561
19750
|
var import_cjs$8 = require_cjs();
|
|
19562
|
-
const logger$
|
|
19751
|
+
const logger$10 = getLogger();
|
|
19563
19752
|
const isRPCConnectResult = (e) => {
|
|
19564
|
-
logger$
|
|
19753
|
+
logger$10.debug("isRPCConnectResult check:", e);
|
|
19565
19754
|
if (!e || typeof e !== "object") return false;
|
|
19566
19755
|
const result = e;
|
|
19567
19756
|
const is = typeof result.identity === "string" && typeof result.protocol === "string" && typeof result.authorization === "object" && typeof result.authorization.jti === "string" && typeof result.authorization.project_id === "string" && typeof result.authorization.fabric_subscriber === "object";
|
|
19568
|
-
logger$
|
|
19757
|
+
logger$10.debug("isRPCConnectResult check result:", is);
|
|
19569
19758
|
return is;
|
|
19570
19759
|
};
|
|
19571
19760
|
var PendingRPC = class PendingRPC {
|
|
@@ -19574,7 +19763,7 @@ var PendingRPC = class PendingRPC {
|
|
|
19574
19763
|
}
|
|
19575
19764
|
constructor(request, responses$, options) {
|
|
19576
19765
|
this.id = v4_default();
|
|
19577
|
-
logger$
|
|
19766
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}: method:${request.method}] Creating PendingRPC`);
|
|
19578
19767
|
this.request = request;
|
|
19579
19768
|
const timeoutMs = options?.timeoutMs ?? PendingRPC.defaultTimeoutMs;
|
|
19580
19769
|
const signal = options?.signal;
|
|
@@ -19600,22 +19789,22 @@ var PendingRPC = class PendingRPC {
|
|
|
19600
19789
|
isSettled = true;
|
|
19601
19790
|
if (response.error) {
|
|
19602
19791
|
const rpcError = new JSONRPCError(response.error.code, response.error.message, response.error.data, void 0, request.id);
|
|
19603
|
-
logger$
|
|
19792
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with RPC error:`, rpcError);
|
|
19604
19793
|
reject(rpcError);
|
|
19605
19794
|
} else {
|
|
19606
|
-
logger$
|
|
19795
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Resolving promise with response:`, response);
|
|
19607
19796
|
resolve(response);
|
|
19608
19797
|
}
|
|
19609
19798
|
subscription.unsubscribe();
|
|
19610
19799
|
},
|
|
19611
19800
|
error: (error) => {
|
|
19612
|
-
logger$
|
|
19801
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with error:`, error);
|
|
19613
19802
|
isSettled = true;
|
|
19614
19803
|
reject(error);
|
|
19615
19804
|
subscription.unsubscribe();
|
|
19616
19805
|
},
|
|
19617
19806
|
complete: () => {
|
|
19618
|
-
logger$
|
|
19807
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Observable completed`);
|
|
19619
19808
|
if (!isSettled) reject(new RPCTimeoutError(request.id, timeoutMs));
|
|
19620
19809
|
subscription.unsubscribe();
|
|
19621
19810
|
}
|
|
@@ -19636,7 +19825,18 @@ var PendingRPC = class PendingRPC {
|
|
|
19636
19825
|
//#endregion
|
|
19637
19826
|
//#region src/managers/ClientSessionManager.ts
|
|
19638
19827
|
var import_cjs$7 = require_cjs();
|
|
19639
|
-
const logger$
|
|
19828
|
+
const logger$9 = getLogger();
|
|
19829
|
+
/**
|
|
19830
|
+
* Decide whether an error emitted on `call.errors$` during dial should
|
|
19831
|
+
* abort the dial. A non-fatal MediaAccessError means the call degraded to
|
|
19832
|
+
* receive-only and still connects — everything else rejects `dial()` with
|
|
19833
|
+
* the real cause.
|
|
19834
|
+
*
|
|
19835
|
+
* Pure function — exported for unit testing.
|
|
19836
|
+
*/
|
|
19837
|
+
function shouldAbortDial(callError) {
|
|
19838
|
+
return callError.fatal || !(callError.error instanceof MediaAccessError);
|
|
19839
|
+
}
|
|
19640
19840
|
const getAddressSearchURI = (options) => {
|
|
19641
19841
|
const to = options.to?.split("?")[0];
|
|
19642
19842
|
const from$9 = options.from?.startsWith("subscriber://") ? options.from.replace("subscriber://", "") : options.from;
|
|
@@ -19734,7 +19934,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19734
19934
|
try {
|
|
19735
19935
|
return await this.transport.execute(request, options);
|
|
19736
19936
|
} catch (error) {
|
|
19737
|
-
logger$
|
|
19937
|
+
logger$9.debug("[Session] Execute Error", error);
|
|
19738
19938
|
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
19739
19939
|
throw error;
|
|
19740
19940
|
}
|
|
@@ -19748,13 +19948,13 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19748
19948
|
return true;
|
|
19749
19949
|
}
|
|
19750
19950
|
setupMessageHandlers() {
|
|
19751
|
-
logger$
|
|
19951
|
+
logger$9.debug("[Session] Setting up message handlers");
|
|
19752
19952
|
this.subscribeTo(this.authStateEvent$, async (authStateEvent) => {
|
|
19753
|
-
logger$
|
|
19953
|
+
logger$9.debug("[Session] Authorization state event received:", authStateEvent);
|
|
19754
19954
|
try {
|
|
19755
19955
|
await this.updateAuthorizationStateInStorage(authStateEvent.authorization_state);
|
|
19756
19956
|
} catch (error) {
|
|
19757
|
-
logger$
|
|
19957
|
+
logger$9.error("[Session] Failed to handle authorization state update:", error);
|
|
19758
19958
|
this._errors$.next(new AuthStateHandlerError(error));
|
|
19759
19959
|
}
|
|
19760
19960
|
});
|
|
@@ -19762,29 +19962,29 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19762
19962
|
if (this._authState$.value.kind === "authenticated") this._authState$.next({ kind: "unauthenticated" });
|
|
19763
19963
|
});
|
|
19764
19964
|
this.subscribeTo(this.transport.connectionStatus$.pipe((0, import_cjs$7.filter)((status) => status === "connected"), (0, import_cjs$7.exhaustMap)(() => {
|
|
19765
|
-
logger$
|
|
19965
|
+
logger$9.debug("[Session] Connection established, initiating authentication");
|
|
19766
19966
|
return (0, import_cjs$7.from)(this.authenticate()).pipe((0, import_cjs$7.catchError)((error) => {
|
|
19767
19967
|
this.handleAuthenticationError(error).catch((err) => {
|
|
19768
|
-
logger$
|
|
19968
|
+
logger$9.error("[Session] Error handling authentication failure:", err);
|
|
19769
19969
|
});
|
|
19770
19970
|
return import_cjs$7.EMPTY;
|
|
19771
19971
|
}));
|
|
19772
19972
|
})), void 0);
|
|
19773
19973
|
this.subscribeTo(this.vertoInvite$, async (invite) => {
|
|
19774
|
-
logger$
|
|
19974
|
+
logger$9.debug("[Session] Verto invite received:", invite);
|
|
19775
19975
|
try {
|
|
19776
19976
|
await this.createInboundCall(invite);
|
|
19777
19977
|
} catch (error) {
|
|
19778
|
-
logger$
|
|
19978
|
+
logger$9.error("[Session] Error handling Verto invite:", error);
|
|
19779
19979
|
this._errors$.next(new VertoInviteHandlerError(error));
|
|
19780
19980
|
}
|
|
19781
19981
|
});
|
|
19782
19982
|
this.subscribeTo(this.vertoAttach$, async (attach) => {
|
|
19783
|
-
logger$
|
|
19983
|
+
logger$9.debug("[Session] Verto attach received:", attach);
|
|
19784
19984
|
try {
|
|
19785
19985
|
await this.handleVertoAttach(attach);
|
|
19786
19986
|
} catch (error) {
|
|
19787
|
-
logger$
|
|
19987
|
+
logger$9.error("[Session] Error handling Verto attach:", error);
|
|
19788
19988
|
this._errors$.next(new VertoAttachHandlerError(error));
|
|
19789
19989
|
}
|
|
19790
19990
|
});
|
|
@@ -19794,36 +19994,36 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19794
19994
|
const storedState = await this.storage.getItem(this.authorizationStateKey);
|
|
19795
19995
|
this.authorizationState$.next(storedState ?? void 0);
|
|
19796
19996
|
} catch (error) {
|
|
19797
|
-
logger$
|
|
19997
|
+
logger$9.error("Failed to retrieve authorization state from storage:", error);
|
|
19798
19998
|
this.authorizationState$.next(void 0);
|
|
19799
19999
|
}
|
|
19800
20000
|
}
|
|
19801
20001
|
async updateAuthorizationStateInStorage(authorizationState) {
|
|
19802
20002
|
if (!authorizationState) {
|
|
19803
|
-
logger$
|
|
20003
|
+
logger$9.debug("[Session] Removing authorization state from storage");
|
|
19804
20004
|
try {
|
|
19805
20005
|
await this.storage.removeItem(this.authorizationStateKey);
|
|
19806
20006
|
this.authorizationState$.next(void 0);
|
|
19807
20007
|
} catch (error) {
|
|
19808
|
-
logger$
|
|
20008
|
+
logger$9.error("Failed to remove authorization state from storage:", error);
|
|
19809
20009
|
throw error;
|
|
19810
20010
|
}
|
|
19811
20011
|
return;
|
|
19812
20012
|
}
|
|
19813
20013
|
try {
|
|
19814
|
-
logger$
|
|
20014
|
+
logger$9.debug("[Session] Updating authorization state in storage");
|
|
19815
20015
|
await this.storage.setItem(this.authorizationStateKey, authorizationState);
|
|
19816
20016
|
this.authorizationState$.next(authorizationState);
|
|
19817
20017
|
} catch (error) {
|
|
19818
|
-
logger$
|
|
20018
|
+
logger$9.error("Failed to retrieve authorization state from storage:", error);
|
|
19819
20019
|
throw error;
|
|
19820
20020
|
}
|
|
19821
20021
|
}
|
|
19822
20022
|
get authStateEvent$() {
|
|
19823
20023
|
return this.cachedObservable("authStateEvent$", () => this.signalingEvent$.pipe((0, import_cjs$7.tap)((msg) => {
|
|
19824
|
-
logger$
|
|
20024
|
+
logger$9.debug("[Session] Received incoming message:", msg);
|
|
19825
20025
|
}), filterAs(isSignalwireAuthorizationStateMetadata, "params"), (0, import_cjs$7.tap)((event) => {
|
|
19826
|
-
logger$
|
|
20026
|
+
logger$9.debug("[Session] Authorization state event received:", event.authorization_state);
|
|
19827
20027
|
})));
|
|
19828
20028
|
}
|
|
19829
20029
|
get signalingEvent$() {
|
|
@@ -19861,42 +20061,72 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19861
20061
|
await (0, import_cjs$7.firstValueFrom)(this.authenticated$.pipe((0, import_cjs$7.takeUntil)(this.destroyed$), (0, import_cjs$7.filter)(Boolean), (0, import_cjs$7.take)(1), (0, import_cjs$7.timeout)({ first: 15e3 })));
|
|
19862
20062
|
}
|
|
19863
20063
|
async handleAuthenticationError(error) {
|
|
19864
|
-
logger$
|
|
20064
|
+
logger$9.error("Authentication error:", error);
|
|
19865
20065
|
const isRecoverableAuthError = error instanceof JSONRPCError && (error.code === RPC_ERROR_REQUESTER_VALIDATION_FAILED || error.code === RPC_ERROR_INVALID_PARAMS || error.code === RPC_ERROR_AUTHENTICATION_FAILED);
|
|
19866
20066
|
const hasStoredState = await (0, import_cjs$7.firstValueFrom)(this.authorizationState$.pipe((0, import_cjs$7.take)(1))) !== void 0;
|
|
19867
20067
|
if (isRecoverableAuthError && hasStoredState) {
|
|
19868
|
-
logger$
|
|
20068
|
+
logger$9.debug("[Session] Recoverable auth error — cleaning up stored state and reconnecting fresh");
|
|
19869
20069
|
try {
|
|
19870
20070
|
await this.cleanupStoredConnectionParams();
|
|
19871
20071
|
} catch (cleanupError) {
|
|
19872
|
-
logger$
|
|
20072
|
+
logger$9.error("Failed to cleanup stored connection params:", cleanupError);
|
|
19873
20073
|
} finally {
|
|
19874
20074
|
this.transport.reconnect();
|
|
19875
20075
|
}
|
|
19876
20076
|
} else this._errors$.next(error);
|
|
19877
20077
|
}
|
|
20078
|
+
/**
|
|
20079
|
+
* Clear the resume state (authorization_state + protocol) only.
|
|
20080
|
+
*
|
|
20081
|
+
* This is the stale-auth-state recovery helper used by handleAuthError:
|
|
20082
|
+
* the server rejected a reconnect, so the resume state is discarded and a
|
|
20083
|
+
* fresh connect follows. Attach records are deliberately preserved — the
|
|
20084
|
+
* session lives on through the reconnect and reattachCalls() needs the
|
|
20085
|
+
* stored call references afterwards. Do NOT add detachAll() here.
|
|
20086
|
+
*
|
|
20087
|
+
* For public teardown (disconnect/destroy), use {@link teardownSessionState}
|
|
20088
|
+
* instead, which clears the attach records as well.
|
|
20089
|
+
*/
|
|
19878
20090
|
async cleanupStoredConnectionParams() {
|
|
19879
20091
|
await this.transport.setProtocol(void 0);
|
|
19880
20092
|
await this.updateAuthorizationStateInStorage(void 0);
|
|
19881
20093
|
this._authorization$.next(void 0);
|
|
19882
20094
|
}
|
|
20095
|
+
/**
|
|
20096
|
+
* Public-teardown helper for disconnect()/destroy(). Clears the resume
|
|
20097
|
+
* state (authorization_state + protocol) AND the attach records as one
|
|
20098
|
+
* atomic unit.
|
|
20099
|
+
*
|
|
20100
|
+
* The two stores are coupled: the backend only honors attach records
|
|
20101
|
+
* within the session identified by the resume state, so ending the
|
|
20102
|
+
* session must clear both. Clearing one without the other strands records
|
|
20103
|
+
* no future session can honor (disconnect) or revives a session the
|
|
20104
|
+
* developer explicitly ended (destroy).
|
|
20105
|
+
*
|
|
20106
|
+
* Distinct from {@link cleanupStoredConnectionParams}, which keeps the
|
|
20107
|
+
* attach records for the stale-auth-state recovery path.
|
|
20108
|
+
*/
|
|
20109
|
+
async teardownSessionState() {
|
|
20110
|
+
await this.cleanupStoredConnectionParams();
|
|
20111
|
+
await this.attachManager.detachAll();
|
|
20112
|
+
}
|
|
19883
20113
|
async updateAuthState(authorization_state) {
|
|
19884
20114
|
try {
|
|
19885
20115
|
await this.storage.setItem(this.authorizationStateKey, authorization_state);
|
|
19886
20116
|
} catch (error) {
|
|
19887
|
-
logger$
|
|
20117
|
+
logger$9.error("Failed to update authorization state in storage:", error);
|
|
19888
20118
|
this._errors$.next(new AuthStateHandlerError(error));
|
|
19889
20119
|
}
|
|
19890
20120
|
}
|
|
19891
20121
|
async reauthenticate(token, dpopToken, options) {
|
|
19892
|
-
logger$
|
|
20122
|
+
logger$9.debug("[Session] Re-authenticating session");
|
|
19893
20123
|
try {
|
|
19894
20124
|
let resolvedDpopToken = dpopToken;
|
|
19895
20125
|
if (!resolvedDpopToken && this.dpopManager?.initialized) try {
|
|
19896
20126
|
resolvedDpopToken = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
|
|
19897
20127
|
} catch (error) {
|
|
19898
20128
|
if (this.clientBound) throw error;
|
|
19899
|
-
logger$
|
|
20129
|
+
logger$9.warn("[Session] Failed to create DPoP proof for reauthenticate:", error);
|
|
19900
20130
|
}
|
|
19901
20131
|
const request = RPCReauthenticate({
|
|
19902
20132
|
project: this._authorization$.value?.project_id ?? "",
|
|
@@ -19904,24 +20134,24 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19904
20134
|
...resolvedDpopToken ? { dpop_token: resolvedDpopToken } : {}
|
|
19905
20135
|
});
|
|
19906
20136
|
await (0, import_cjs$7.lastValueFrom)((0, import_cjs$7.from)(this.transport.execute(request)).pipe(throwOnRPCError(), (0, import_cjs$7.take)(1), (0, import_cjs$7.catchError)((err) => {
|
|
19907
|
-
logger$
|
|
20137
|
+
logger$9.error("[Session] Re-authentication RPC failed:", err);
|
|
19908
20138
|
throw err;
|
|
19909
20139
|
})));
|
|
19910
20140
|
if (options?.clientBound) this._wasClientBound = true;
|
|
19911
|
-
logger$
|
|
20141
|
+
logger$9.debug("[Session] Re-authentication successful, updating stored auth state");
|
|
19912
20142
|
} catch (error) {
|
|
19913
|
-
logger$
|
|
20143
|
+
logger$9.error("[Session] Re-authentication failed:", error);
|
|
19914
20144
|
this._errors$.next(new AuthStateHandlerError(error));
|
|
19915
20145
|
throw error;
|
|
19916
20146
|
}
|
|
19917
20147
|
}
|
|
19918
20148
|
async authenticate() {
|
|
19919
|
-
logger$
|
|
20149
|
+
logger$9.debug("[Session] Starting authentication process");
|
|
19920
20150
|
const persistedParams = await (0, import_cjs$7.firstValueFrom)((0, import_cjs$7.combineLatest)({
|
|
19921
20151
|
protocol: this.transport.protocol$,
|
|
19922
20152
|
authorization_state: this.authorizationState$
|
|
19923
20153
|
}).pipe((0, import_cjs$7.take)(1)));
|
|
19924
|
-
logger$
|
|
20154
|
+
logger$9.debug("[Session] Persisted params:\n", {
|
|
19925
20155
|
protocol: persistedParams.protocol,
|
|
19926
20156
|
authStateLength: persistedParams.authorization_state?.length
|
|
19927
20157
|
});
|
|
@@ -19929,16 +20159,16 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19929
20159
|
const storedToken = this.getCredential().token;
|
|
19930
20160
|
const isReconnect = hasReconnectState && storedToken;
|
|
19931
20161
|
let dpopToken;
|
|
19932
|
-
if (isReconnect) logger$
|
|
20162
|
+
if (isReconnect) logger$9.debug("[Session] Reconnecting with stored jwt_token + authorization_state");
|
|
19933
20163
|
else if (this.onBeforeReconnect && this.clientBound) {
|
|
19934
|
-
logger$
|
|
20164
|
+
logger$9.debug("[Session] Refreshing credentials before fresh connect");
|
|
19935
20165
|
await this.onBeforeReconnect();
|
|
19936
20166
|
}
|
|
19937
20167
|
if ((!isReconnect || this.clientBound) && this.dpopManager?.initialized) try {
|
|
19938
20168
|
dpopToken = await this.dpopManager.createRpcProof({ method: "signalwire.connect" });
|
|
19939
20169
|
} catch (error) {
|
|
19940
20170
|
if (this.clientBound) throw error;
|
|
19941
|
-
logger$
|
|
20171
|
+
logger$9.warn("[Session] Failed to create DPoP proof for connect, proceeding without:", error);
|
|
19942
20172
|
}
|
|
19943
20173
|
const rpcConnectRequest = RPCConnect({
|
|
19944
20174
|
authentication: isReconnect ? { jwt_token: storedToken } : this.authentication,
|
|
@@ -19955,12 +20185,12 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19955
20185
|
} : {}
|
|
19956
20186
|
});
|
|
19957
20187
|
const response = await (0, import_cjs$7.lastValueFrom)((0, import_cjs$7.from)(this.transport.execute(rpcConnectRequest)).pipe(throwOnRPCError(), (0, import_cjs$7.map)((res) => res.result), (0, import_cjs$7.filter)(isRPCConnectResult), (0, import_cjs$7.tap)(() => {
|
|
19958
|
-
logger$
|
|
20188
|
+
logger$9.debug("[Session] Response passed filter, processing authentication result");
|
|
19959
20189
|
}), (0, import_cjs$7.take)(1), (0, import_cjs$7.catchError)((err) => {
|
|
19960
|
-
logger$
|
|
20190
|
+
logger$9.error("[Session] Authentication RPC failed:", err);
|
|
19961
20191
|
throw err;
|
|
19962
20192
|
})));
|
|
19963
|
-
logger$
|
|
20193
|
+
logger$9.debug("[Session] Processing authentication result:", {
|
|
19964
20194
|
hasProtocol: !!response.protocol,
|
|
19965
20195
|
hasAuthorization: !!response.authorization,
|
|
19966
20196
|
hasIceServers: !!response.ice_servers
|
|
@@ -19969,12 +20199,12 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19969
20199
|
this._authorization$.next(response.authorization);
|
|
19970
20200
|
this._iceServers$.next(response.ice_servers ?? []);
|
|
19971
20201
|
this._authState$.next({ kind: "authenticated" });
|
|
19972
|
-
logger$
|
|
20202
|
+
logger$9.debug("[Session] Authentication completed successfully");
|
|
19973
20203
|
}
|
|
19974
20204
|
async disconnect() {
|
|
19975
20205
|
this.transport.disconnect();
|
|
19976
20206
|
this._authState$.next({ kind: "unauthenticated" });
|
|
19977
|
-
await this.
|
|
20207
|
+
await this.teardownSessionState();
|
|
19978
20208
|
}
|
|
19979
20209
|
async createInboundCall(invite) {
|
|
19980
20210
|
const callSession = await this.createCall({
|
|
@@ -20005,11 +20235,11 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
20005
20235
|
async handleVertoAttach(attach) {
|
|
20006
20236
|
const { callID } = attach;
|
|
20007
20237
|
if (callID in this._calls$.value) {
|
|
20008
|
-
logger$
|
|
20238
|
+
logger$9.debug(`[Session] Verto attach for existing call ${callID}, deferring to per-call handler`);
|
|
20009
20239
|
return;
|
|
20010
20240
|
}
|
|
20011
20241
|
const storedOptions = await this.attachManager.consumePendingAttachment(callID);
|
|
20012
|
-
logger$
|
|
20242
|
+
logger$9.debug(`[Session] Creating reattached call for callID: ${callID}`);
|
|
20013
20243
|
const callSession = await this.createCall({
|
|
20014
20244
|
nodeId: attach.node_id,
|
|
20015
20245
|
callId: callID,
|
|
@@ -20033,14 +20263,14 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
20033
20263
|
to: destinationURI,
|
|
20034
20264
|
...options
|
|
20035
20265
|
});
|
|
20036
|
-
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)))));
|
|
20266
|
+
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)))));
|
|
20037
20267
|
this._calls$.next({
|
|
20038
20268
|
[`${callSession.id}`]: callSession,
|
|
20039
20269
|
...this._calls$.value
|
|
20040
20270
|
});
|
|
20041
20271
|
return callSession;
|
|
20042
20272
|
} catch (error) {
|
|
20043
|
-
logger$
|
|
20273
|
+
logger$9.error("[Session] Error creating outbound call:", error);
|
|
20044
20274
|
callSession?.destroy();
|
|
20045
20275
|
const callError = new CallCreateError(error instanceof import_cjs$7.TimeoutError ? "Call create timeout" : "Call creation failed", error, "outbound");
|
|
20046
20276
|
this._errors$.next(callError);
|
|
@@ -20058,7 +20288,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
20058
20288
|
address = this._directory.get(addressId);
|
|
20059
20289
|
if (!address) throw new DependencyError(`Address ID: ${addressId} not found`);
|
|
20060
20290
|
} catch {
|
|
20061
|
-
logger$
|
|
20291
|
+
logger$9.warn(`[Session] Directory lookup failed for ${addressURI}, proceeding with raw URI`);
|
|
20062
20292
|
}
|
|
20063
20293
|
const callSession = this.callFactory.createCall(address, { ...options });
|
|
20064
20294
|
this.subscribeTo(callSession.status$.pipe((0, import_cjs$7.filter)((status) => status === "destroyed"), (0, import_cjs$7.take)(1)), () => {
|
|
@@ -20067,7 +20297,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
20067
20297
|
});
|
|
20068
20298
|
return callSession;
|
|
20069
20299
|
} catch (error) {
|
|
20070
|
-
logger$
|
|
20300
|
+
logger$9.error("[Session] Error creating call session:", error);
|
|
20071
20301
|
throw new CallCreateError("Call create error", error, options.initOffer ? "inbound" : "outbound");
|
|
20072
20302
|
}
|
|
20073
20303
|
}
|
|
@@ -20116,7 +20346,7 @@ const isString = (obj) => typeof obj === "string";
|
|
|
20116
20346
|
//#endregion
|
|
20117
20347
|
//#region src/managers/ConversationsManager.ts
|
|
20118
20348
|
var import_cjs$6 = require_cjs();
|
|
20119
|
-
const logger$
|
|
20349
|
+
const logger$8 = getLogger();
|
|
20120
20350
|
var ConversationMessagesFetcher = class extends Fetcher {
|
|
20121
20351
|
constructor(groupId, http) {
|
|
20122
20352
|
super(`/api/fabric/conversations/${groupId}/messages`, "page_size=100", http);
|
|
@@ -20156,13 +20386,13 @@ var ConversationsManager = class {
|
|
|
20156
20386
|
}
|
|
20157
20387
|
throw new ConversationError("Join Failed - Unexpected response");
|
|
20158
20388
|
} catch (error) {
|
|
20159
|
-
logger$
|
|
20389
|
+
logger$8.error("[ConversationsManager] Failed to join conversation:", error);
|
|
20160
20390
|
throw error;
|
|
20161
20391
|
}
|
|
20162
20392
|
}
|
|
20163
20393
|
async getConversationMessageCollection(addressId) {
|
|
20164
20394
|
const groupId = this.groupIds.get(addressId) ?? await this.join(addressId);
|
|
20165
|
-
return Promise.resolve(new ConversationMessageCollection(groupId, this.clientSession.signalingEvent$.pipe(filterAs(isConversationMessageMetadata, "params"), (0, import_cjs$6.tap)((event) => logger$
|
|
20395
|
+
return Promise.resolve(new ConversationMessageCollection(groupId, this.clientSession.signalingEvent$.pipe(filterAs(isConversationMessageMetadata, "params"), (0, import_cjs$6.tap)((event) => logger$8.debug("[ConversationsManager ] Conversation Event:", event)), (0, import_cjs$6.map)((params) => ({ ...params }))), this.http, this.onError));
|
|
20166
20396
|
}
|
|
20167
20397
|
async sendText(text, destinationAddressId) {
|
|
20168
20398
|
const groupId = this.groupIds.get(destinationAddressId) ?? await this.join(destinationAddressId);
|
|
@@ -20179,7 +20409,7 @@ var ConversationsManager = class {
|
|
|
20179
20409
|
})).ok) return;
|
|
20180
20410
|
throw new ConversationError("Send Text Failed - Unexpected response");
|
|
20181
20411
|
} catch (error) {
|
|
20182
|
-
logger$
|
|
20412
|
+
logger$8.error("[ConversationsManager] Failed to send text message:", error);
|
|
20183
20413
|
throw error;
|
|
20184
20414
|
}
|
|
20185
20415
|
}
|
|
@@ -20188,7 +20418,7 @@ var ConversationsManager = class {
|
|
|
20188
20418
|
//#endregion
|
|
20189
20419
|
//#region src/managers/DeviceTokenManager.ts
|
|
20190
20420
|
var import_cjs$5 = require_cjs();
|
|
20191
|
-
const logger$
|
|
20421
|
+
const logger$7 = getLogger();
|
|
20192
20422
|
/**
|
|
20193
20423
|
* Resolves the token expiry timestamp (epoch seconds) using a 3-tier priority chain:
|
|
20194
20424
|
* 1. `data.expires_at` — server-provided absolute timestamp
|
|
@@ -20198,7 +20428,7 @@ const logger$6 = getLogger();
|
|
|
20198
20428
|
function resolveExpiresAt(data) {
|
|
20199
20429
|
if (data.expires_at) return data.expires_at;
|
|
20200
20430
|
if (data.expires_in) return Math.floor(Date.now() / 1e3) + data.expires_in;
|
|
20201
|
-
logger$
|
|
20431
|
+
logger$7.warn("[DeviceToken] Could not determine token expiry, using default");
|
|
20202
20432
|
return Math.floor(Date.now() / 1e3) + DEVICE_TOKEN_DEFAULT_EXPIRE_IN;
|
|
20203
20433
|
}
|
|
20204
20434
|
/**
|
|
@@ -20231,11 +20461,12 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20231
20461
|
this.getCredential = getCredential;
|
|
20232
20462
|
this._currentToken$ = this.createBehaviorSubject(null);
|
|
20233
20463
|
this._refreshInProgress = false;
|
|
20464
|
+
this._paused = false;
|
|
20234
20465
|
this._effectiveExpireIn = DEVICE_TOKEN_DEFAULT_EXPIRE_IN;
|
|
20235
20466
|
this.subscribeTo(this._currentToken$.pipe((0, import_cjs$5.filter)(Boolean), (0, import_cjs$5.switchMap)((tokenData) => {
|
|
20236
20467
|
const expiresAt = resolveExpiresAt(tokenData);
|
|
20237
20468
|
const refreshIn = Math.max(expiresAt * 1e3 - Date.now() - DEVICE_TOKEN_REFRESH_BUFFER_MS, 1e3);
|
|
20238
|
-
logger$
|
|
20469
|
+
logger$7.debug(`[DeviceToken] Scheduling Client Bound SAT refresh in ${refreshIn}ms`);
|
|
20239
20470
|
return (0, import_cjs$5.timer)(refreshIn);
|
|
20240
20471
|
})), () => {
|
|
20241
20472
|
this.executeRefresh();
|
|
@@ -20249,7 +20480,12 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20249
20480
|
* Activates the Client Bound SAT flow when the user's token has
|
|
20250
20481
|
* `sat:refresh` scope.
|
|
20251
20482
|
*
|
|
20252
|
-
*
|
|
20483
|
+
* Returns an {@link ActivationResult} indicating whether the manager
|
|
20484
|
+
* took ownership of refresh duties. The caller must use the `activated`
|
|
20485
|
+
* boolean to decide whether to keep its own refresh path armed — when
|
|
20486
|
+
* `activated` is `false`, the caller is responsible for refresh.
|
|
20487
|
+
*
|
|
20488
|
+
* Steps on success:
|
|
20253
20489
|
* 1. Check user's `sat_claims` for `sat:refresh` scope
|
|
20254
20490
|
* 2. Call `/api/fabric/subscriber/devices/token` with a DPoP proof
|
|
20255
20491
|
* 3. Reauthenticate the session with the Client Bound SAT + DPoP proof
|
|
@@ -20258,32 +20494,51 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20258
20494
|
async activate(user, session, updateCredential) {
|
|
20259
20495
|
const { satClaims } = user;
|
|
20260
20496
|
if (!satClaims?.scope?.includes(SAT_REFRESH_SCOPE)) {
|
|
20261
|
-
logger$
|
|
20262
|
-
return
|
|
20497
|
+
logger$7.debug("[DeviceToken] No sat:refresh scope, skipping Client Bound SAT activation");
|
|
20498
|
+
return {
|
|
20499
|
+
activated: false,
|
|
20500
|
+
reason: "no-scope"
|
|
20501
|
+
};
|
|
20263
20502
|
}
|
|
20264
20503
|
this._session = session;
|
|
20265
20504
|
this._updateCredential = updateCredential;
|
|
20266
20505
|
try {
|
|
20506
|
+
const cached = this._currentToken$.value;
|
|
20507
|
+
if (cached && this.isTokenFresh(cached)) {
|
|
20508
|
+
logger$7.debug("[DeviceToken] Reusing cached Client Bound SAT — skipping /devices/token");
|
|
20509
|
+
const rpcProof$1 = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
|
|
20510
|
+
await session.reauthenticate(cached.token, rpcProof$1, { clientBound: true });
|
|
20511
|
+
updateCredential({ token: cached.token });
|
|
20512
|
+
return { activated: true };
|
|
20513
|
+
}
|
|
20267
20514
|
const tokenData = await this.obtainToken();
|
|
20268
20515
|
if (!tokenData.expires_at && !tokenData.expires_in && satClaims.expires_at) tokenData.expires_at = satClaims.expires_at;
|
|
20269
20516
|
this._effectiveExpireIn = resolveExpireIn(tokenData);
|
|
20270
20517
|
const rpcProof = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
|
|
20271
20518
|
await session.reauthenticate(tokenData.token, rpcProof, { clientBound: true });
|
|
20272
20519
|
updateCredential({ token: tokenData.token });
|
|
20273
|
-
logger$
|
|
20520
|
+
logger$7.info("[DeviceToken] Client Bound SAT activated successfully");
|
|
20274
20521
|
this._currentToken$.next(tokenData);
|
|
20522
|
+
return { activated: true };
|
|
20275
20523
|
} catch (error) {
|
|
20276
|
-
logger$
|
|
20524
|
+
logger$7.error("[DeviceToken] Failed to activate Client Bound SAT:", error);
|
|
20277
20525
|
this.errorHandler(new DPoPInitError(error, "Failed to activate Client Bound SAT"));
|
|
20278
|
-
|
|
20279
|
-
|
|
20280
|
-
|
|
20281
|
-
|
|
20282
|
-
expires_at: expiresAt
|
|
20283
|
-
});
|
|
20526
|
+
return {
|
|
20527
|
+
activated: false,
|
|
20528
|
+
reason: "endpoint-failed"
|
|
20529
|
+
};
|
|
20284
20530
|
}
|
|
20285
20531
|
}
|
|
20286
20532
|
/**
|
|
20533
|
+
* Returns true when the cached token has enough headroom before expiry to
|
|
20534
|
+
* be safely reused on reactivation. The headroom matches the refresh
|
|
20535
|
+
* buffer, so a token within the refresh window is treated as stale (the
|
|
20536
|
+
* reactive pipeline is about to refresh it anyway).
|
|
20537
|
+
*/
|
|
20538
|
+
isTokenFresh(token) {
|
|
20539
|
+
return resolveExpiresAt(token) * 1e3 - Date.now() > DEVICE_TOKEN_REFRESH_BUFFER_MS;
|
|
20540
|
+
}
|
|
20541
|
+
/**
|
|
20287
20542
|
* Obtains a Client Bound SAT from `/api/fabric/subscriber/devices/token`.
|
|
20288
20543
|
* Returns the full {@link DeviceTokenResponse} including expiry metadata.
|
|
20289
20544
|
*/
|
|
@@ -20313,7 +20568,7 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20313
20568
|
* handled by the reactive pipeline).
|
|
20314
20569
|
*/
|
|
20315
20570
|
async refreshToken(session, currentToken, updateCredential) {
|
|
20316
|
-
logger$
|
|
20571
|
+
logger$7.debug("[DeviceToken] Refreshing Client Bound SAT");
|
|
20317
20572
|
const dpopProof = await this.dpopManager.createHttpProof({
|
|
20318
20573
|
method: "POST",
|
|
20319
20574
|
uri: DEVICE_REFRESH_ENDPOINT,
|
|
@@ -20335,7 +20590,7 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20335
20590
|
const rpcProof = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
|
|
20336
20591
|
await session.reauthenticate(data.token, rpcProof);
|
|
20337
20592
|
updateCredential({ token: data.token });
|
|
20338
|
-
logger$
|
|
20593
|
+
logger$7.info("[DeviceToken] Client Bound SAT refreshed successfully");
|
|
20339
20594
|
return data;
|
|
20340
20595
|
}
|
|
20341
20596
|
/**
|
|
@@ -20344,18 +20599,22 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20344
20599
|
* On all retries exhausted, emits to `errorHandler`.
|
|
20345
20600
|
*/
|
|
20346
20601
|
async executeRefresh() {
|
|
20602
|
+
if (this._paused) {
|
|
20603
|
+
logger$7.debug("[DeviceToken] Manager paused, skipping refresh");
|
|
20604
|
+
return;
|
|
20605
|
+
}
|
|
20347
20606
|
if (this._refreshInProgress) {
|
|
20348
|
-
logger$
|
|
20607
|
+
logger$7.debug("[DeviceToken] Refresh already in progress, skipping");
|
|
20349
20608
|
return;
|
|
20350
20609
|
}
|
|
20351
20610
|
const session = this._session;
|
|
20352
20611
|
const updateCredential = this._updateCredential;
|
|
20353
20612
|
if (!session || !updateCredential) {
|
|
20354
|
-
logger$
|
|
20613
|
+
logger$7.warn("[DeviceToken] Cannot refresh: session or updateCredential not set");
|
|
20355
20614
|
return;
|
|
20356
20615
|
}
|
|
20357
20616
|
if (!session.authenticated) {
|
|
20358
|
-
logger$
|
|
20617
|
+
logger$7.debug("[DeviceToken] Session not authenticated, deferring refresh");
|
|
20359
20618
|
return;
|
|
20360
20619
|
}
|
|
20361
20620
|
this._refreshInProgress = true;
|
|
@@ -20365,7 +20624,7 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20365
20624
|
const newTokenData = await this.retryRefresh(session, currentToken, updateCredential);
|
|
20366
20625
|
this._currentToken$.next(newTokenData);
|
|
20367
20626
|
} catch (error) {
|
|
20368
|
-
logger$
|
|
20627
|
+
logger$7.error("[DeviceToken] Automatic Client Bound SAT refresh failed:", error);
|
|
20369
20628
|
this.errorHandler(error instanceof TokenRefreshError ? error : new TokenRefreshError("Automatic token refresh failed", error));
|
|
20370
20629
|
} finally {
|
|
20371
20630
|
this._refreshInProgress = false;
|
|
@@ -20383,18 +20642,215 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20383
20642
|
lastError = error;
|
|
20384
20643
|
if (attempt < DEVICE_TOKEN_REFRESH_MAX_RETRIES - 1) {
|
|
20385
20644
|
const delay$1 = DEVICE_TOKEN_REFRESH_RETRY_BASE_MS * Math.pow(2, attempt);
|
|
20386
|
-
logger$
|
|
20645
|
+
logger$7.warn(`[DeviceToken] Refresh attempt ${attempt + 1} failed, retrying in ${delay$1}ms`);
|
|
20387
20646
|
await new Promise((resolve) => setTimeout(resolve, delay$1));
|
|
20388
20647
|
}
|
|
20389
20648
|
}
|
|
20390
20649
|
throw lastError instanceof Error ? lastError : new TokenRefreshError("All refresh retries exhausted", lastError);
|
|
20391
20650
|
}
|
|
20651
|
+
/**
|
|
20652
|
+
* Stops the reactive refresh pipeline from firing. Use when the underlying
|
|
20653
|
+
* session is being torn down (e.g., during {@link SignalWire.disconnect})
|
|
20654
|
+
* so a scheduled refresh cannot fire against a destroyed session.
|
|
20655
|
+
*
|
|
20656
|
+
* The manager's state (cached token, effective TTL, subscriptions) is
|
|
20657
|
+
* preserved — call {@link resume} to re-enable firing after reconnect.
|
|
20658
|
+
*/
|
|
20659
|
+
pause() {
|
|
20660
|
+
this._paused = true;
|
|
20661
|
+
}
|
|
20662
|
+
/** Re-enables the reactive refresh pipeline after a previous {@link pause}. */
|
|
20663
|
+
resume() {
|
|
20664
|
+
this._paused = false;
|
|
20665
|
+
}
|
|
20392
20666
|
/** Cleans up the manager, cancelling the reactive pipeline and all subscriptions. */
|
|
20393
20667
|
destroy() {
|
|
20394
20668
|
super.destroy();
|
|
20395
20669
|
}
|
|
20396
20670
|
};
|
|
20397
20671
|
|
|
20672
|
+
//#endregion
|
|
20673
|
+
//#region src/managers/CredentialRefreshCoordinator.ts
|
|
20674
|
+
const logger$6 = getLogger();
|
|
20675
|
+
const defaultDeviceTokenManagerFactory = (dpopManager, http, errorHandler, getCredential) => new DeviceTokenManager(dpopManager, http, errorHandler, getCredential);
|
|
20676
|
+
/**
|
|
20677
|
+
* Centralizes credential-refresh ownership across the two competing
|
|
20678
|
+
* mechanisms — developer-provided `CredentialProvider.refresh()` and the
|
|
20679
|
+
* Client Bound SAT path via {@link DeviceTokenManager}.
|
|
20680
|
+
*
|
|
20681
|
+
* Maintains the invariant: **at most one refresh mechanism is armed at a
|
|
20682
|
+
* time, and at least one is armed whenever the current credential has an
|
|
20683
|
+
* `expiry_at`**.
|
|
20684
|
+
*
|
|
20685
|
+
* Replaces the previous design where refresh state was distributed across
|
|
20686
|
+
* `SignalWire` (timer field, scheduler method, activation helper) and
|
|
20687
|
+
* `DeviceTokenManager` (reactive pipeline). Centralizing the invariant in
|
|
20688
|
+
* one component eliminates the bug class that produced issue #19074.
|
|
20689
|
+
*
|
|
20690
|
+
* Race-safety:
|
|
20691
|
+
* - `_activating` flag prevents overlapping `activate()` calls from racing.
|
|
20692
|
+
* - `_activationGeneration` lets late resolutions detect they've been
|
|
20693
|
+
* preempted by a newer activation (e.g., reconnect during in-flight
|
|
20694
|
+
* `obtainToken`).
|
|
20695
|
+
*/
|
|
20696
|
+
var CredentialRefreshCoordinator = class extends Destroyable {
|
|
20697
|
+
constructor(dpopManager, deps) {
|
|
20698
|
+
super();
|
|
20699
|
+
this.deps = deps;
|
|
20700
|
+
this._activating = false;
|
|
20701
|
+
this._activationGeneration = 0;
|
|
20702
|
+
if (dpopManager?.initialized) this._deviceTokenManager = (deps.deviceTokenManagerFactory ?? defaultDeviceTokenManagerFactory)(dpopManager, deps.http, (error) => deps.notifier.onError(error), () => deps.store.read());
|
|
20703
|
+
}
|
|
20704
|
+
/** True when the Client Bound SAT path is available (DPoP initialized). */
|
|
20705
|
+
get clientBoundSATAvailable() {
|
|
20706
|
+
return this._deviceTokenManager !== void 0;
|
|
20707
|
+
}
|
|
20708
|
+
/** True when the developer-provided refresh timer is currently armed. */
|
|
20709
|
+
get developerRefreshArmed() {
|
|
20710
|
+
return this._developerTimerId !== void 0;
|
|
20711
|
+
}
|
|
20712
|
+
/**
|
|
20713
|
+
* Arms the developer-provided refresh timer to fire shortly before
|
|
20714
|
+
* `expiresAt`. Replaces any previously scheduled developer refresh.
|
|
20715
|
+
*
|
|
20716
|
+
* Idempotent — multiple calls just reschedule. On retry exhaustion,
|
|
20717
|
+
* invokes `deps.onRefreshExhausted` so the orchestrator can disconnect.
|
|
20718
|
+
*/
|
|
20719
|
+
scheduleDeveloperRefresh(provider, expiresAt, attempt = 0) {
|
|
20720
|
+
if (this._developerTimerId !== void 0) clearTimeout(this._developerTimerId);
|
|
20721
|
+
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);
|
|
20722
|
+
this._developerTimerId = setTimeout(async () => {
|
|
20723
|
+
try {
|
|
20724
|
+
if (!provider.refresh) throw new InvalidCredentialsError("Credential provider does not support refresh");
|
|
20725
|
+
const newCredentials = await provider.refresh();
|
|
20726
|
+
this.deps.store.write(newCredentials);
|
|
20727
|
+
this.deps.store.persist(newCredentials);
|
|
20728
|
+
logger$6.info("[Coordinator] Credentials refreshed successfully.");
|
|
20729
|
+
if (newCredentials.expiry_at) this.scheduleDeveloperRefresh(provider, newCredentials.expiry_at, 0);
|
|
20730
|
+
} catch (error) {
|
|
20731
|
+
const nextAttempt = attempt + 1;
|
|
20732
|
+
logger$6.error(`[Coordinator] Credential refresh failed (attempt ${nextAttempt}/${CREDENTIAL_REFRESH_MAX_RETRIES}):`, error);
|
|
20733
|
+
this.deps.notifier.onError(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
20734
|
+
if (nextAttempt < CREDENTIAL_REFRESH_MAX_RETRIES) this.scheduleDeveloperRefresh(provider, expiresAt, nextAttempt);
|
|
20735
|
+
else {
|
|
20736
|
+
logger$6.error("[Coordinator] Credential refresh exhausted all retries. Disconnecting.");
|
|
20737
|
+
this.deps.notifier.onError(new TokenRefreshError("Credential refresh failed after max retries"));
|
|
20738
|
+
this.deps.notifier.onRefreshExhausted();
|
|
20739
|
+
}
|
|
20740
|
+
}
|
|
20741
|
+
}, refreshInterval);
|
|
20742
|
+
}
|
|
20743
|
+
/**
|
|
20744
|
+
* Cancels any scheduled developer-provided refresh. Idempotent.
|
|
20745
|
+
*
|
|
20746
|
+
* @internal Used by the coordinator's own activation flow. External
|
|
20747
|
+
* callers should use {@link suspend} for disconnect-time quiescence —
|
|
20748
|
+
* `suspend()` also pauses the internal Client Bound SAT pipeline.
|
|
20749
|
+
*/
|
|
20750
|
+
cancelDeveloperRefresh() {
|
|
20751
|
+
if (this._developerTimerId !== void 0) {
|
|
20752
|
+
clearTimeout(this._developerTimerId);
|
|
20753
|
+
this._developerTimerId = void 0;
|
|
20754
|
+
}
|
|
20755
|
+
}
|
|
20756
|
+
/**
|
|
20757
|
+
* Suspends both refresh paths — cancels the developer timer and pauses
|
|
20758
|
+
* the internal reactive pipeline. Use when the underlying session is
|
|
20759
|
+
* being torn down (e.g., {@link SignalWire.disconnect}). The next
|
|
20760
|
+
* {@link activate} call re-enables the internal pipeline.
|
|
20761
|
+
*
|
|
20762
|
+
* The internal manager's cached token survives — see
|
|
20763
|
+
* {@link DeviceTokenManager.pause} — so a subsequent reconnect can
|
|
20764
|
+
* skip the `/devices/token` exchange entirely.
|
|
20765
|
+
*/
|
|
20766
|
+
suspend() {
|
|
20767
|
+
this.cancelDeveloperRefresh();
|
|
20768
|
+
this._deviceTokenManager?.pause();
|
|
20769
|
+
}
|
|
20770
|
+
/**
|
|
20771
|
+
* Asks the Client Bound SAT path to take over refresh. If it accepts,
|
|
20772
|
+
* the developer-provided timer (if any) is cancelled. If it declines, the
|
|
20773
|
+
* developer timer remains armed and a `credential_refresh_fallback`
|
|
20774
|
+
* warning is emitted.
|
|
20775
|
+
*
|
|
20776
|
+
* **Idempotent** — re-entrant calls during an in-flight activation are
|
|
20777
|
+
* dropped. Use `_activationGeneration` to detect stale resolutions
|
|
20778
|
+
* (e.g., a reconnect-triggered activate() that races with an earlier one).
|
|
20779
|
+
*/
|
|
20780
|
+
async activate(user, session) {
|
|
20781
|
+
if (this._activating) {
|
|
20782
|
+
logger$6.debug("[Coordinator] activate() in flight; ignoring re-entrant call");
|
|
20783
|
+
return;
|
|
20784
|
+
}
|
|
20785
|
+
if (!this._deviceTokenManager) return;
|
|
20786
|
+
this._deviceTokenManager.resume();
|
|
20787
|
+
const generation = ++this._activationGeneration;
|
|
20788
|
+
this._activating = true;
|
|
20789
|
+
try {
|
|
20790
|
+
const result = await this.withActivationTimeout(this._deviceTokenManager.activate(user, session, (cred) => this.deps.store.merge(cred)));
|
|
20791
|
+
if (generation !== this._activationGeneration) {
|
|
20792
|
+
logger$6.debug("[Coordinator] activate() result discarded (preempted by newer activation)");
|
|
20793
|
+
return;
|
|
20794
|
+
}
|
|
20795
|
+
if (result.activated) {
|
|
20796
|
+
this.cancelDeveloperRefresh();
|
|
20797
|
+
logger$6.debug("[Coordinator] Developer refresh disabled — Client Bound SAT owns refresh");
|
|
20798
|
+
return;
|
|
20799
|
+
}
|
|
20800
|
+
logger$6.warn(`[SignalWire] [SW-REFRESH-FALLBACK] Client Bound SAT declined (reason=${result.reason}); using developer-provided refresh handler.`);
|
|
20801
|
+
this.deps.notifier.onWarning({
|
|
20802
|
+
code: "credential_refresh_fallback",
|
|
20803
|
+
source: "CredentialProvider",
|
|
20804
|
+
reason: result.reason,
|
|
20805
|
+
message: `Client Bound SAT activation declined (${result.reason}); using developer-provided refresh.`
|
|
20806
|
+
});
|
|
20807
|
+
} finally {
|
|
20808
|
+
this._activating = false;
|
|
20809
|
+
}
|
|
20810
|
+
}
|
|
20811
|
+
destroy() {
|
|
20812
|
+
this.cancelDeveloperRefresh();
|
|
20813
|
+
this._deviceTokenManager?.destroy();
|
|
20814
|
+
this._deviceTokenManager = void 0;
|
|
20815
|
+
super.destroy();
|
|
20816
|
+
}
|
|
20817
|
+
/**
|
|
20818
|
+
* Races the manager's `activate()` against a hard timeout. A wedged HTTP
|
|
20819
|
+
* layer (e.g., proxy issues) could otherwise hang the activation
|
|
20820
|
+
* indefinitely, leaving the session with no refresh mechanism while the
|
|
20821
|
+
* `_activating` guard blocks subsequent retries.
|
|
20822
|
+
*
|
|
20823
|
+
* On timeout, the manager is paused immediately. The inner `activate()`
|
|
20824
|
+
* may still complete in the background and emit to `_currentToken$`,
|
|
20825
|
+
* which would normally arm the reactive refresh pipeline; pausing
|
|
20826
|
+
* prevents that pipeline from firing while the developer refresh path
|
|
20827
|
+
* is the active mechanism — preserving the "at most one mechanism
|
|
20828
|
+
* armed" invariant. The next `activate()` call resumes the manager.
|
|
20829
|
+
*/
|
|
20830
|
+
async withActivationTimeout(inner) {
|
|
20831
|
+
return new Promise((resolve) => {
|
|
20832
|
+
const timer$4 = setTimeout(() => {
|
|
20833
|
+
this._deviceTokenManager?.pause();
|
|
20834
|
+
resolve({
|
|
20835
|
+
activated: false,
|
|
20836
|
+
reason: "activation-timeout"
|
|
20837
|
+
});
|
|
20838
|
+
}, CREDENTIAL_ACTIVATE_TIMEOUT_MS);
|
|
20839
|
+
inner.then((result) => {
|
|
20840
|
+
clearTimeout(timer$4);
|
|
20841
|
+
resolve(result);
|
|
20842
|
+
}, (error) => {
|
|
20843
|
+
clearTimeout(timer$4);
|
|
20844
|
+
this.deps.notifier.onError(error instanceof Error ? error : new Error(String(error)));
|
|
20845
|
+
resolve({
|
|
20846
|
+
activated: false,
|
|
20847
|
+
reason: "endpoint-failed"
|
|
20848
|
+
});
|
|
20849
|
+
});
|
|
20850
|
+
});
|
|
20851
|
+
}
|
|
20852
|
+
};
|
|
20853
|
+
|
|
20398
20854
|
//#endregion
|
|
20399
20855
|
//#region src/managers/DiagnosticsCollector.ts
|
|
20400
20856
|
const logger$5 = getLogger();
|
|
@@ -20915,6 +21371,10 @@ var TransportManager = class extends Destroyable {
|
|
|
20915
21371
|
if (!isSignalwireRequest(message)) return true;
|
|
20916
21372
|
const eventChannel = message.params.event_channel;
|
|
20917
21373
|
if (!eventChannel) return true;
|
|
21374
|
+
if (message.params.event_type.startsWith("conversation.")) {
|
|
21375
|
+
logger$2.debug(`[Transport] Received conversation event: ${message.params.event_type} (event_channel: ${eventChannel})`);
|
|
21376
|
+
return true;
|
|
21377
|
+
}
|
|
20918
21378
|
const currentProtocol = this._currentProtocol;
|
|
20919
21379
|
if (!currentProtocol) return true;
|
|
20920
21380
|
if (!eventChannel.includes(currentProtocol)) {
|
|
@@ -21108,6 +21568,7 @@ var SignalWire = class extends Destroyable {
|
|
|
21108
21568
|
this._isConnected$ = this.createBehaviorSubject(false);
|
|
21109
21569
|
this._isRegistered$ = this.createBehaviorSubject(false);
|
|
21110
21570
|
this._errors$ = this.createReplaySubject(1);
|
|
21571
|
+
this._warnings$ = this.createReplaySubject(10);
|
|
21111
21572
|
this._options = {};
|
|
21112
21573
|
this._deps = new DependencyContainer();
|
|
21113
21574
|
this._credentialProvider = credentialProvider;
|
|
@@ -21167,6 +21628,27 @@ var SignalWire = class extends Destroyable {
|
|
|
21167
21628
|
*/
|
|
21168
21629
|
async resolveCredentials() {
|
|
21169
21630
|
const fingerprint = await this.initDPoP();
|
|
21631
|
+
this._refreshCoordinator = new CredentialRefreshCoordinator(this._dpopManager, {
|
|
21632
|
+
http: this._deps.http,
|
|
21633
|
+
notifier: {
|
|
21634
|
+
onError: (error) => this._errors$.next(error),
|
|
21635
|
+
onWarning: (warning) => this._warnings$.next(warning),
|
|
21636
|
+
onRefreshExhausted: () => void this.disconnect()
|
|
21637
|
+
},
|
|
21638
|
+
store: {
|
|
21639
|
+
read: () => this._deps.credential,
|
|
21640
|
+
write: (credential) => {
|
|
21641
|
+
this._deps.credential = credential;
|
|
21642
|
+
},
|
|
21643
|
+
merge: (partial) => {
|
|
21644
|
+
this._deps.credential = {
|
|
21645
|
+
...this._deps.credential,
|
|
21646
|
+
...partial
|
|
21647
|
+
};
|
|
21648
|
+
},
|
|
21649
|
+
persist: (credential) => this.persistCredential(credential)
|
|
21650
|
+
}
|
|
21651
|
+
});
|
|
21170
21652
|
if (this._credentialProvider) return this.validateCredentials(this._credentialProvider, void 0, fingerprint);
|
|
21171
21653
|
for (const scope of this._deps.persistSession ? ["local", "session"] : ["session"]) try {
|
|
21172
21654
|
const cached = await this._deps.storage.getItem("sw:cached_credential", scope);
|
|
@@ -21196,7 +21678,16 @@ var SignalWire = class extends Destroyable {
|
|
|
21196
21678
|
logger$1.error("[SignalWire] Provided credentials have expired.");
|
|
21197
21679
|
throw new InvalidCredentialsError("Provided credentials have expired.");
|
|
21198
21680
|
}
|
|
21199
|
-
if (_credentials.expiry_at && credentialProvider?.refresh) this.
|
|
21681
|
+
if (_credentials.expiry_at && credentialProvider?.refresh) this._refreshCoordinator?.scheduleDeveloperRefresh(credentialProvider, _credentials.expiry_at);
|
|
21682
|
+
else if (_credentials.expiry_at && !credentialProvider?.refresh) {
|
|
21683
|
+
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.`);
|
|
21684
|
+
this._warnings$.next({
|
|
21685
|
+
code: "credential_no_refresh_handler",
|
|
21686
|
+
source: "CredentialProvider",
|
|
21687
|
+
message: "Credential has expiry_at but no refresh handler. Session will terminate at expiry unless the SAT carries 'sat:refresh' scope.",
|
|
21688
|
+
expiresAt: _credentials.expiry_at
|
|
21689
|
+
});
|
|
21690
|
+
}
|
|
21200
21691
|
this._deps.credential = _credentials;
|
|
21201
21692
|
this.persistCredential(_credentials);
|
|
21202
21693
|
if (this.isConnected && this._clientSession.authenticated && _credentials.token) try {
|
|
@@ -21207,35 +21698,6 @@ var SignalWire = class extends Destroyable {
|
|
|
21207
21698
|
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
21208
21699
|
}
|
|
21209
21700
|
}
|
|
21210
|
-
/**
|
|
21211
|
-
* Schedules credential refresh with exponential backoff retry on failure.
|
|
21212
|
-
* On success, resets attempt counter and schedules the next refresh.
|
|
21213
|
-
* After exhausting retries, emits TokenRefreshError and disconnects.
|
|
21214
|
-
*/
|
|
21215
|
-
scheduleCredentialRefresh(credentialProvider, expiresAt, attempt = 0) {
|
|
21216
|
-
if (this._refreshTimerId !== void 0) clearTimeout(this._refreshTimerId);
|
|
21217
|
-
const refreshInterval = attempt === 0 ? Math.max(expiresAt - Date.now() - CREDENTIAL_REFRESH_BUFFER_MS, 1e3) : Math.min(CREDENTIAL_REFRESH_RETRY_BASE_MS * Math.pow(2, attempt) * (.5 + Math.random() * .5), CREDENTIAL_REFRESH_MAX_DELAY_MS);
|
|
21218
|
-
this._refreshTimerId = setTimeout(async () => {
|
|
21219
|
-
try {
|
|
21220
|
-
if (!credentialProvider.refresh) throw new InvalidCredentialsError("Credential provider does not support refresh");
|
|
21221
|
-
const newCredentials = await credentialProvider.refresh();
|
|
21222
|
-
this._deps.credential = newCredentials;
|
|
21223
|
-
this.persistCredential(newCredentials);
|
|
21224
|
-
logger$1.info("[SignalWire] Credentials refreshed successfully.");
|
|
21225
|
-
if (newCredentials.expiry_at) this.scheduleCredentialRefresh(credentialProvider, newCredentials.expiry_at, 0);
|
|
21226
|
-
} catch (error) {
|
|
21227
|
-
const nextAttempt = attempt + 1;
|
|
21228
|
-
logger$1.error(`[SignalWire] Credential refresh failed (attempt ${nextAttempt}/${CREDENTIAL_REFRESH_MAX_RETRIES}):`, error);
|
|
21229
|
-
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
21230
|
-
if (nextAttempt < CREDENTIAL_REFRESH_MAX_RETRIES) this.scheduleCredentialRefresh(credentialProvider, expiresAt, nextAttempt);
|
|
21231
|
-
else {
|
|
21232
|
-
logger$1.error("[SignalWire] Credential refresh exhausted all retries. Disconnecting.");
|
|
21233
|
-
this._errors$.next(new TokenRefreshError("Credential refresh failed after max retries"));
|
|
21234
|
-
this.disconnect();
|
|
21235
|
-
}
|
|
21236
|
-
}
|
|
21237
|
-
}, refreshInterval);
|
|
21238
|
-
}
|
|
21239
21701
|
/** Persist credential to localStorage when persistSession is enabled. */
|
|
21240
21702
|
persistCredential(credential) {
|
|
21241
21703
|
if (!credential.token) return;
|
|
@@ -21329,6 +21791,7 @@ var SignalWire = class extends Destroyable {
|
|
|
21329
21791
|
logger$1.debug("[SignalWire] Credential expired, refreshing before reconnect");
|
|
21330
21792
|
const newCredentials = await this._credentialProvider.authenticate(fingerprint ? { fingerprint } : void 0);
|
|
21331
21793
|
this._deps.credential = newCredentials;
|
|
21794
|
+
if (newCredentials.expiry_at && this._credentialProvider.refresh) this._refreshCoordinator?.scheduleDeveloperRefresh(this._credentialProvider, newCredentials.expiry_at);
|
|
21332
21795
|
logger$1.debug("[SignalWire] Credential refreshed successfully for reconnect");
|
|
21333
21796
|
} catch (error) {
|
|
21334
21797
|
logger$1.error("[SignalWire] Failed to refresh credentials for reconnect:", error);
|
|
@@ -21340,33 +21803,12 @@ var SignalWire = class extends Destroyable {
|
|
|
21340
21803
|
this._errors$.next(error);
|
|
21341
21804
|
});
|
|
21342
21805
|
await this._clientSession.connect();
|
|
21343
|
-
|
|
21344
|
-
if (this._refreshTimerId) {
|
|
21345
|
-
clearTimeout(this._refreshTimerId);
|
|
21346
|
-
this._refreshTimerId = void 0;
|
|
21347
|
-
logger$1.debug("[SignalWire] Developer refresh disabled — Client Bound SAT activation starting");
|
|
21348
|
-
}
|
|
21349
|
-
this._deviceTokenManager = new DeviceTokenManager(this._dpopManager, this._deps.http, (error) => this._errors$.next(error), () => this._deps.credential);
|
|
21350
|
-
await this._deviceTokenManager.activate(this._deps.user, this._clientSession, (cred) => {
|
|
21351
|
-
this._deps.credential = {
|
|
21352
|
-
...this._deps.credential,
|
|
21353
|
-
...cred
|
|
21354
|
-
};
|
|
21355
|
-
});
|
|
21356
|
-
}
|
|
21806
|
+
await this._refreshCoordinator?.activate(this._deps.user, this._clientSession);
|
|
21357
21807
|
this.subscribeTo(this._clientSession.authenticated$.pipe((0, import_cjs$1.skip)(1), (0, import_cjs$1.filter)(Boolean)), async () => {
|
|
21358
21808
|
try {
|
|
21359
|
-
|
|
21360
|
-
await this._deviceTokenManager.activate(this._deps.user, this._clientSession, (cred) => {
|
|
21361
|
-
this._deps.credential = {
|
|
21362
|
-
...this._deps.credential,
|
|
21363
|
-
...cred
|
|
21364
|
-
};
|
|
21365
|
-
});
|
|
21366
|
-
logger$1.debug("[SignalWire] Client Bound SAT re-activated after reconnect");
|
|
21367
|
-
}
|
|
21809
|
+
await this._refreshCoordinator?.activate(this._deps.user, this._clientSession);
|
|
21368
21810
|
} catch (error) {
|
|
21369
|
-
logger$1.error("[SignalWire]
|
|
21811
|
+
logger$1.error("[SignalWire] Refresh re-arm after reconnect failed (non-fatal):", error);
|
|
21370
21812
|
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
21371
21813
|
}
|
|
21372
21814
|
try {
|
|
@@ -21452,6 +21894,19 @@ var SignalWire = class extends Destroyable {
|
|
|
21452
21894
|
get errors$() {
|
|
21453
21895
|
return this.deferEmission(this._errors$.asObservable());
|
|
21454
21896
|
}
|
|
21897
|
+
/**
|
|
21898
|
+
* Observable stream of non-fatal SDK warnings.
|
|
21899
|
+
*
|
|
21900
|
+
* Subscribe to detect SDK behaviors that affect session liveness or developer-facing
|
|
21901
|
+
* contracts but do not warrant disconnection — e.g., a fallback from Client Bound SAT
|
|
21902
|
+
* refresh to the developer-provided `refresh()` because the SAT lacks `sat:refresh`
|
|
21903
|
+
* scope. Discriminated by `code`.
|
|
21904
|
+
*
|
|
21905
|
+
* Independent from {@link errors$}: existing error consumers are not notified.
|
|
21906
|
+
*/
|
|
21907
|
+
get warnings$() {
|
|
21908
|
+
return this.deferEmission(this._warnings$.asObservable());
|
|
21909
|
+
}
|
|
21455
21910
|
/** Platform WebRTC capabilities detected at construction time. */
|
|
21456
21911
|
get platformCapabilities() {
|
|
21457
21912
|
this._platformCapabilities ??= detectPlatformCapabilities(this._options.webRTCApiProvider);
|
|
@@ -21520,7 +21975,7 @@ var SignalWire = class extends Destroyable {
|
|
|
21520
21975
|
logger$1.warn("[SignalWire] Failed to initialize VisibilityController:", error);
|
|
21521
21976
|
}
|
|
21522
21977
|
try {
|
|
21523
|
-
this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "
|
|
21978
|
+
this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "4.0.0-rc.1" });
|
|
21524
21979
|
} catch (error) {
|
|
21525
21980
|
logger$1.warn("[SignalWire] Failed to initialize DiagnosticsCollector:", error);
|
|
21526
21981
|
}
|
|
@@ -21528,14 +21983,19 @@ var SignalWire = class extends Destroyable {
|
|
|
21528
21983
|
/**
|
|
21529
21984
|
* Disconnects the WebSocket and tears down the current session.
|
|
21530
21985
|
*
|
|
21986
|
+
* Ends the session identified by the protocol and clears its persisted
|
|
21987
|
+
* resume state (`authorization_state` + protocol) and attach records
|
|
21988
|
+
* together — a later {@link connect} with the same credentials starts a
|
|
21989
|
+
* fresh session and cannot reattach to the ended session's calls.
|
|
21990
|
+
* Credentials and device preferences are preserved. To temporarily stop
|
|
21991
|
+
* receiving inbound calls while keeping the session alive, use
|
|
21992
|
+
* `unregister()` instead.
|
|
21993
|
+
*
|
|
21531
21994
|
* The client can be reconnected by calling {@link connect} again,
|
|
21532
21995
|
* which creates a fresh transport and session.
|
|
21533
21996
|
*/
|
|
21534
21997
|
async disconnect() {
|
|
21535
|
-
|
|
21536
|
-
clearTimeout(this._refreshTimerId);
|
|
21537
|
-
this._refreshTimerId = void 0;
|
|
21538
|
-
}
|
|
21998
|
+
this._refreshCoordinator?.suspend();
|
|
21539
21999
|
this._diagnosticsCollector?.record("connection", "disconnected");
|
|
21540
22000
|
await this.teardownTransportAndSession();
|
|
21541
22001
|
this._isConnected$.next(false);
|
|
@@ -21922,15 +22382,20 @@ var SignalWire = class extends Destroyable {
|
|
|
21922
22382
|
prefs.preferredVideoInput = null;
|
|
21923
22383
|
await this._deviceController.clearDeviceState();
|
|
21924
22384
|
}
|
|
21925
|
-
/**
|
|
22385
|
+
/**
|
|
22386
|
+
* Destroys the client, clearing timers and releasing all resources.
|
|
22387
|
+
*
|
|
22388
|
+
* Intentionally destroying the client ends its session: the resume state
|
|
22389
|
+
* (`authorization_state` + protocol) and the attach records are both
|
|
22390
|
+
* cleared. Credentials and device preferences are preserved — use
|
|
22391
|
+
* {@link resetToDefaults} for a full wipe. To temporarily stop receiving
|
|
22392
|
+
* inbound calls while keeping the session alive, use `unregister()`.
|
|
22393
|
+
*/
|
|
21926
22394
|
destroy() {
|
|
21927
|
-
|
|
21928
|
-
|
|
21929
|
-
this._refreshTimerId = void 0;
|
|
21930
|
-
}
|
|
21931
|
-
this._deviceTokenManager?.destroy();
|
|
22395
|
+
this._refreshCoordinator?.destroy();
|
|
22396
|
+
this._refreshCoordinator = void 0;
|
|
21932
22397
|
this._dpopManager?.destroy();
|
|
21933
|
-
|
|
22398
|
+
this._clientSession.teardownSessionState();
|
|
21934
22399
|
this._transport.destroy();
|
|
21935
22400
|
this._clientSession.destroy();
|
|
21936
22401
|
try {
|
|
@@ -22045,7 +22510,7 @@ var StaticCredentialProvider = class {
|
|
|
22045
22510
|
/**
|
|
22046
22511
|
* Library version from package.json, injected at build time.
|
|
22047
22512
|
*/
|
|
22048
|
-
const version = "
|
|
22513
|
+
const version = "4.0.0-rc.1";
|
|
22049
22514
|
/**
|
|
22050
22515
|
* Flag indicating the library has been loaded and is ready to use.
|
|
22051
22516
|
* For UMD builds: `window.SignalWire.ready`
|
|
@@ -22067,7 +22532,7 @@ const ready = true;
|
|
|
22067
22532
|
*/
|
|
22068
22533
|
const emitReadyEvent = () => {
|
|
22069
22534
|
if (typeof window !== "undefined") {
|
|
22070
|
-
const event = new CustomEvent("signalwire:js:ready", { detail: { version: "
|
|
22535
|
+
const event = new CustomEvent("signalwire:js:ready", { detail: { version: "4.0.0-rc.1" } });
|
|
22071
22536
|
window.dispatchEvent(event);
|
|
22072
22537
|
}
|
|
22073
22538
|
};
|
|
@@ -22099,6 +22564,7 @@ exports.DPoPInitError = DPoPInitError;
|
|
|
22099
22564
|
exports.DeviceTokenError = DeviceTokenError;
|
|
22100
22565
|
exports.EmbedTokenCredentialProvider = EmbedTokenCredentialProvider;
|
|
22101
22566
|
exports.InvalidCredentialsError = InvalidCredentialsError;
|
|
22567
|
+
exports.MediaAccessError = MediaAccessError;
|
|
22102
22568
|
exports.MediaTrackError = MediaTrackError;
|
|
22103
22569
|
exports.MessageParseError = MessageParseError;
|
|
22104
22570
|
exports.OverconstrainedFallbackError = OverconstrainedFallbackError;
|