@signalwire/js 4.0.0-beta.12 → 4.0.0-beta.13
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 +1540 -706
- package/dist/browser.mjs.map +1 -1
- package/dist/browser.umd.js +1546 -707
- package/dist/browser.umd.js.map +1 -1
- package/dist/index.cjs +1411 -749
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +381 -37
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +381 -37
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1340 -681
- 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-Bn4Ij3VB.cjs} +601 -1
- package/dist/operators-Bn4Ij3VB.cjs.map +1 -0
- package/dist/{operators-CX_lCCJm.mjs → operators-Zxmwpb0j.mjs} +188 -2
- package/dist/operators-Zxmwpb0j.mjs.map +1 -0
- package/package.json +3 -3
- package/dist/operators-CX_lCCJm.mjs.map +0 -1
- package/dist/operators-D6a2J1KA.cjs.map +0 -1
package/dist/browser.mjs
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { randomFillSync, randomUUID } from "node:crypto";
|
|
2
|
-
|
|
3
1
|
//#region rolldown:runtime
|
|
4
2
|
var __create = Object.create;
|
|
5
3
|
var __defProp = Object.defineProperty;
|
|
@@ -9017,6 +9015,151 @@ var Destroyable = class {
|
|
|
9017
9015
|
}
|
|
9018
9016
|
};
|
|
9019
9017
|
|
|
9018
|
+
//#endregion
|
|
9019
|
+
//#region src/core/constants.ts
|
|
9020
|
+
const INVITE_VERSION = 1e3;
|
|
9021
|
+
const DEFAULT_ICE_CANDIDATE_TIMEOUT_MS = 600;
|
|
9022
|
+
const DEFAULT_ICE_GATHERING_TIMEOUT_MS = 6e3;
|
|
9023
|
+
const DEFAULT_RECONNECT_CALLS_TIMEOUT_MS = 300 * 1e3;
|
|
9024
|
+
const DEFAULT_CONNECTION_TIMEOUT_MS = 1e4;
|
|
9025
|
+
const DEFAULT_RECONNECT_DELAY_MIN_MS = 100;
|
|
9026
|
+
const DEFAULT_RECONNECT_DELAY_MAX_MS = 3e3;
|
|
9027
|
+
const DEFAULT_DEVICE_DEBOUNCE_TIME_MS = 1500;
|
|
9028
|
+
const DEFAULT_DEVICE_POLLING_INTERVAL_MS = 0;
|
|
9029
|
+
const PREFERENCES_STORAGE_KEY = "sw:preferences";
|
|
9030
|
+
/** Scope value that enables automatic token refresh. */
|
|
9031
|
+
const SAT_REFRESH_SCOPE = "sat:refresh";
|
|
9032
|
+
/** API endpoints for device token operations. */
|
|
9033
|
+
const DEVICE_TOKEN_ENDPOINT = "/api/fabric/subscriber/devices/token";
|
|
9034
|
+
const DEVICE_REFRESH_ENDPOINT = "/api/fabric/subscriber/devices/refresh";
|
|
9035
|
+
/** Default device token TTL in seconds (15 minutes). */
|
|
9036
|
+
const DEVICE_TOKEN_DEFAULT_EXPIRE_IN = 900;
|
|
9037
|
+
/** Buffer time in milliseconds before expiry to trigger refresh. */
|
|
9038
|
+
const DEVICE_TOKEN_REFRESH_BUFFER_MS = 3e4;
|
|
9039
|
+
/** Maximum retry attempts for device token refresh on transient failure. */
|
|
9040
|
+
const DEVICE_TOKEN_REFRESH_MAX_RETRIES = 3;
|
|
9041
|
+
/** Base delay in milliseconds for exponential backoff on refresh retry. */
|
|
9042
|
+
const DEVICE_TOKEN_REFRESH_RETRY_BASE_MS = 1e3;
|
|
9043
|
+
/** Maximum retry attempts for developer credential refresh on transient failure. */
|
|
9044
|
+
const CREDENTIAL_REFRESH_MAX_RETRIES = 5;
|
|
9045
|
+
/** Base delay in milliseconds for exponential backoff on credential refresh retry. */
|
|
9046
|
+
const CREDENTIAL_REFRESH_RETRY_BASE_MS = 1e3;
|
|
9047
|
+
/** Maximum delay in milliseconds for credential refresh backoff. */
|
|
9048
|
+
const CREDENTIAL_REFRESH_MAX_DELAY_MS = 3e4;
|
|
9049
|
+
/** Buffer in milliseconds before token expiry to trigger refresh. */
|
|
9050
|
+
const CREDENTIAL_REFRESH_BUFFER_MS = 5e3;
|
|
9051
|
+
/**
|
|
9052
|
+
* Clock-skew allowance (ms) for treating an in-memory credential as expired
|
|
9053
|
+
* when deciding whether a fresh (re)connect must re-mint the token before
|
|
9054
|
+
* authenticating. A token within this window of its `expiry_at` is treated as
|
|
9055
|
+
* stale so the reconnect re-mints via the credential provider instead of
|
|
9056
|
+
* replaying a dead token (which the server rejects with -32003).
|
|
9057
|
+
*/
|
|
9058
|
+
const CREDENTIAL_EXPIRY_SKEW_MS = 3e4;
|
|
9059
|
+
/**
|
|
9060
|
+
* Maximum time the coordinator will wait for `DeviceTokenManager.activate()`
|
|
9061
|
+
* to resolve before treating the activation as failed and falling back to
|
|
9062
|
+
* the developer-provided refresh path. Prevents a wedged HTTP layer from
|
|
9063
|
+
* leaving the session with no active refresh mechanism.
|
|
9064
|
+
*/
|
|
9065
|
+
const CREDENTIAL_ACTIVATE_TIMEOUT_MS = 3e4;
|
|
9066
|
+
/** JSON-RPC error code for requester validation failure (corrupted auth state). */
|
|
9067
|
+
const RPC_ERROR_REQUESTER_VALIDATION_FAILED = -32003;
|
|
9068
|
+
/** JSON-RPC error code for invalid params (e.g., missing authentication block). */
|
|
9069
|
+
const RPC_ERROR_INVALID_PARAMS = -32602;
|
|
9070
|
+
/** JSON-RPC error code for authentication failure (invalid token, missing DPoP, etc.). */
|
|
9071
|
+
const RPC_ERROR_AUTHENTICATION_FAILED = -32002;
|
|
9072
|
+
/** Error names browsers use for a media permission denial (user or policy). */
|
|
9073
|
+
const MEDIA_ACCESS_DENIAL_NAMES = [
|
|
9074
|
+
"NotAllowedError",
|
|
9075
|
+
"SecurityError",
|
|
9076
|
+
"PermissionDeniedError"
|
|
9077
|
+
];
|
|
9078
|
+
/** Default polling interval for RTCPeerConnection.getStats() in milliseconds. */
|
|
9079
|
+
const DEFAULT_STATS_POLLING_INTERVAL_MS = 1e3;
|
|
9080
|
+
/** Number of initial samples used to build a baseline for spike detection. */
|
|
9081
|
+
const DEFAULT_STATS_BASELINE_SAMPLES = 10;
|
|
9082
|
+
/** Duration in ms with no inbound audio packets before emitting a critical issue. */
|
|
9083
|
+
const DEFAULT_STATS_NO_PACKET_THRESHOLD_MS = 2e3;
|
|
9084
|
+
/** Multiplier applied to baseline RTT to detect a warning-level RTT spike. */
|
|
9085
|
+
const DEFAULT_STATS_RTT_SPIKE_MULTIPLIER = 3;
|
|
9086
|
+
/** Packet loss fraction (0-1) above which a warning is emitted. */
|
|
9087
|
+
const DEFAULT_STATS_PACKET_LOSS_THRESHOLD = .05;
|
|
9088
|
+
/** Multiplier applied to baseline jitter to detect a jitter spike. */
|
|
9089
|
+
const DEFAULT_STATS_JITTER_SPIKE_MULTIPLIER = 4;
|
|
9090
|
+
/** Number of seconds of metrics history to retain. */
|
|
9091
|
+
const DEFAULT_STATS_HISTORY_SIZE = 30;
|
|
9092
|
+
/** Maximum keyframe requests allowed within a single burst window. */
|
|
9093
|
+
const DEFAULT_KEYFRAME_MAX_BURST$1 = 3;
|
|
9094
|
+
/** Duration of the keyframe burst window in milliseconds. */
|
|
9095
|
+
const DEFAULT_KEYFRAME_BURST_WINDOW_MS$1 = 3e3;
|
|
9096
|
+
/** Cooldown period in ms after burst limit is reached before allowing more keyframes. */
|
|
9097
|
+
const DEFAULT_KEYFRAME_COOLDOWN_MS$1 = 1e4;
|
|
9098
|
+
/** Minimum time between re-INVITE attempts in milliseconds. */
|
|
9099
|
+
const DEFAULT_REINVITE_DEBOUNCE_TIME_MS = 1e4;
|
|
9100
|
+
/** Maximum number of re-INVITE attempts per call. */
|
|
9101
|
+
const DEFAULT_REINVITE_MAX_ATTEMPTS = 3;
|
|
9102
|
+
/** Timeout for a single re-INVITE attempt in milliseconds. */
|
|
9103
|
+
const DEFAULT_REINVITE_TIMEOUT_MS = 5e3;
|
|
9104
|
+
/** Debounce window in ms to collapse multiple detection signals into one trigger. */
|
|
9105
|
+
const DEFAULT_RECOVERY_DEBOUNCE_TIME_MS = 2e3;
|
|
9106
|
+
/** Cooldown period in ms between recovery attempts. */
|
|
9107
|
+
const DEFAULT_RECOVERY_COOLDOWN_MS = 1e4;
|
|
9108
|
+
/** Grace period in ms before treating ICE 'disconnected' as a failure. */
|
|
9109
|
+
const DEFAULT_ICE_DISCONNECTED_GRACE_PERIOD_MS = 3e3;
|
|
9110
|
+
/** Timeout for a single ICE restart attempt in milliseconds. */
|
|
9111
|
+
const DEFAULT_ICE_RESTART_TIMEOUT_MS$1 = 5e3;
|
|
9112
|
+
/** Maximum recovery attempts before emitting 'max_attempts_reached'. */
|
|
9113
|
+
const DEFAULT_MAX_RECOVERY_ATTEMPTS = 3;
|
|
9114
|
+
/** Upper bound in ms for waiting on iceGatheringState === 'complete' after an ICE restart. */
|
|
9115
|
+
const ICE_GATHERING_COMPLETE_TIMEOUT_MS = 1e4;
|
|
9116
|
+
/** Upper bound in ms for waiting on RTCPeerConnection.connectionState === 'connected' after a recovery ICE restart. */
|
|
9117
|
+
const PEER_CONNECTION_RECOVERY_WAIT_MS = 5e3;
|
|
9118
|
+
/** Polling interval in ms while waiting for RTCPeerConnection.connectionState to transition. */
|
|
9119
|
+
const PEER_CONNECTION_RECOVERY_POLL_MS = 100;
|
|
9120
|
+
/** Polling interval for LocalAudioPipeline.level$ (ms). ~30fps is smooth for meters. */
|
|
9121
|
+
const AUDIO_LEVEL_POLL_INTERVAL_MS = 33;
|
|
9122
|
+
/** RMS level threshold (0..1) above which the local participant is considered speaking. */
|
|
9123
|
+
const VAD_THRESHOLD = .03;
|
|
9124
|
+
/** Hold window in ms below the threshold before speaking$ flips back to false. */
|
|
9125
|
+
const VAD_HOLD_MS = 250;
|
|
9126
|
+
/** Whether to persist device selections to storage by default. */
|
|
9127
|
+
const DEFAULT_PERSIST_DEVICE_SELECTION = true;
|
|
9128
|
+
/** Whether to auto-apply device changes to active calls by default. */
|
|
9129
|
+
const DEFAULT_SYNC_DEVICES_TO_ACTIVE_CALLS = true;
|
|
9130
|
+
/** Storage keys for persisted device selections. */
|
|
9131
|
+
const DEVICE_STORAGE_KEY_AUDIO_INPUT = "sw:device:audioinput";
|
|
9132
|
+
const DEVICE_STORAGE_KEY_AUDIO_OUTPUT = "sw:device:audiooutput";
|
|
9133
|
+
const DEVICE_STORAGE_KEY_VIDEO_INPUT = "sw:device:videoinput";
|
|
9134
|
+
/** Whether to auto-mute video when the tab becomes hidden. */
|
|
9135
|
+
const DEFAULT_AUTO_MUTE_VIDEO_ON_HIDDEN = false;
|
|
9136
|
+
/** Whether to re-enumerate devices when the page becomes visible. */
|
|
9137
|
+
const DEFAULT_REFRESH_DEVICES_ON_VISIBLE = true;
|
|
9138
|
+
/** Whether to check peer connection health when the page becomes visible. */
|
|
9139
|
+
const DEFAULT_CHECK_CONNECTION_ON_VISIBLE = true;
|
|
9140
|
+
/** Whether automatic video degradation on low bandwidth is enabled. */
|
|
9141
|
+
const DEFAULT_ENABLE_AUTO_DEGRADATION = true;
|
|
9142
|
+
/** Bitrate in kbps below which video is automatically disabled. */
|
|
9143
|
+
const DEFAULT_DEGRADATION_BITRATE_THRESHOLD_KBPS = 150;
|
|
9144
|
+
/** Bitrate in kbps above which video is automatically re-enabled (hysteresis). */
|
|
9145
|
+
const DEFAULT_DEGRADATION_RECOVERY_THRESHOLD_KBPS = 300;
|
|
9146
|
+
/** Whether relay-only escalation is enabled as a last-resort recovery tier. */
|
|
9147
|
+
const DEFAULT_ENABLE_RELAY_FALLBACK = true;
|
|
9148
|
+
/** Whether to listen for browser online/offline/connection events. */
|
|
9149
|
+
const DEFAULT_ENABLE_NETWORK_CHANGE_DETECTION = true;
|
|
9150
|
+
/** Whether to intercept server-sent media-timeout hangups and attempt recovery. */
|
|
9151
|
+
const DEFAULT_ENABLE_SERVER_HANGUP_INTERCEPTION = true;
|
|
9152
|
+
/** Default video track constraints applied when video is enabled without explicit constraints. */
|
|
9153
|
+
const DEFAULT_VIDEO_CONSTRAINTS = {
|
|
9154
|
+
width: { ideal: 1280 },
|
|
9155
|
+
height: { ideal: 720 },
|
|
9156
|
+
aspectRatio: 16 / 9
|
|
9157
|
+
};
|
|
9158
|
+
/** Whether stereo Opus is enabled by default. */
|
|
9159
|
+
const DEFAULT_STEREO_AUDIO = false;
|
|
9160
|
+
/** Max average bitrate for stereo Opus in bits per second. */
|
|
9161
|
+
const DEFAULT_STEREO_MAX_AVERAGE_BITRATE = 51e4;
|
|
9162
|
+
|
|
9020
9163
|
//#endregion
|
|
9021
9164
|
//#region src/core/errors.ts
|
|
9022
9165
|
var UnexpectedError = class extends Error {
|
|
@@ -9146,6 +9289,20 @@ var CallCreateError = class extends Error {
|
|
|
9146
9289
|
this.name = "CallCreateError";
|
|
9147
9290
|
}
|
|
9148
9291
|
};
|
|
9292
|
+
var CallNotReadyError = class extends Error {
|
|
9293
|
+
constructor(callId, options) {
|
|
9294
|
+
super(`Call "${callId}" has no self member context yet: selfId/nodeId have not been received from the server`, options);
|
|
9295
|
+
this.callId = callId;
|
|
9296
|
+
this.name = "CallNotReadyError";
|
|
9297
|
+
}
|
|
9298
|
+
};
|
|
9299
|
+
var ParticipantNotReadyError = class extends Error {
|
|
9300
|
+
constructor(memberId, options) {
|
|
9301
|
+
super(`Participant "${memberId}" has no call context yet: its member state (call_id/node_id) has not been received from the server`, options);
|
|
9302
|
+
this.memberId = memberId;
|
|
9303
|
+
this.name = "ParticipantNotReadyError";
|
|
9304
|
+
}
|
|
9305
|
+
};
|
|
9149
9306
|
var JSONRPCError = class extends Error {
|
|
9150
9307
|
constructor(code, message, data, options, requestId) {
|
|
9151
9308
|
super(message, options);
|
|
@@ -9226,6 +9383,33 @@ var MediaTrackError = class extends Error {
|
|
|
9226
9383
|
this.name = "MediaTrackError";
|
|
9227
9384
|
}
|
|
9228
9385
|
};
|
|
9386
|
+
/** True when a `getUserMedia`/`getDisplayMedia` rejection is a permission denial. */
|
|
9387
|
+
function isMediaAccessDenial(originalError) {
|
|
9388
|
+
return originalError instanceof Error && MEDIA_ACCESS_DENIAL_NAMES.includes(originalError.name);
|
|
9389
|
+
}
|
|
9390
|
+
/**
|
|
9391
|
+
* Failure to acquire local media (camera, microphone, or screen capture)
|
|
9392
|
+
* via `getUserMedia`/`getDisplayMedia`.
|
|
9393
|
+
*
|
|
9394
|
+
* Non-fatal by default: screenshare and additional-device failures never
|
|
9395
|
+
* end the call, and main-connection failures degrade to receive-only when
|
|
9396
|
+
* possible. The wrapping site sets `fatal` to `true` only when the call
|
|
9397
|
+
* cannot continue (receive-only fallback disabled or no receive intent).
|
|
9398
|
+
*/
|
|
9399
|
+
var MediaAccessError = class extends Error {
|
|
9400
|
+
constructor(operation, media, originalError, fatal = false) {
|
|
9401
|
+
super(`Media access ${isMediaAccessDenial(originalError) ? "denied" : "failed"} for ${operation} (${media})`, { cause: originalError instanceof Error ? originalError : void 0 });
|
|
9402
|
+
this.operation = operation;
|
|
9403
|
+
this.media = media;
|
|
9404
|
+
this.originalError = originalError;
|
|
9405
|
+
this.fatal = fatal;
|
|
9406
|
+
this.name = "MediaAccessError";
|
|
9407
|
+
}
|
|
9408
|
+
/** True when the underlying failure is a permission denial (user or policy). */
|
|
9409
|
+
get denied() {
|
|
9410
|
+
return isMediaAccessDenial(this.originalError);
|
|
9411
|
+
}
|
|
9412
|
+
};
|
|
9229
9413
|
var DPoPInitError = class extends Error {
|
|
9230
9414
|
constructor(originalError, message = "Failed to initialize DPoP key pair") {
|
|
9231
9415
|
super(message, { cause: originalError instanceof Error ? originalError : void 0 });
|
|
@@ -9466,9 +9650,9 @@ var require_loglevel = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
9466
9650
|
defaultLogger$1 = new Logger();
|
|
9467
9651
|
defaultLogger$1.getLogger = function getLogger$1(name) {
|
|
9468
9652
|
if (typeof name !== "symbol" && typeof name !== "string" || name === "") throw new TypeError("You must supply a name when creating a logger.");
|
|
9469
|
-
var logger$
|
|
9470
|
-
if (!logger$
|
|
9471
|
-
return logger$
|
|
9653
|
+
var logger$33 = _loggersByName[name];
|
|
9654
|
+
if (!logger$33) logger$33 = _loggersByName[name] = new Logger(name, defaultLogger$1.methodFactory);
|
|
9655
|
+
return logger$33;
|
|
9472
9656
|
};
|
|
9473
9657
|
var _log = typeof window !== undefinedType ? window.log : void 0;
|
|
9474
9658
|
defaultLogger$1.noConflict = function() {
|
|
@@ -9504,8 +9688,8 @@ const defaultLoggerLevel = defaultLogger.levels.WARN;
|
|
|
9504
9688
|
defaultLogger.setLevel(defaultLoggerLevel);
|
|
9505
9689
|
let userLogger = null;
|
|
9506
9690
|
/** Replace the built-in logger with a custom implementation. Pass `null` to restore defaults. */
|
|
9507
|
-
const setLogger = (logger$
|
|
9508
|
-
userLogger = logger$
|
|
9691
|
+
const setLogger = (logger$33) => {
|
|
9692
|
+
userLogger = logger$33;
|
|
9509
9693
|
};
|
|
9510
9694
|
let debugOptions = {};
|
|
9511
9695
|
/** Configure debug options (e.g., `{ logWsTraffic: true }`). */
|
|
@@ -9549,8 +9733,8 @@ const wsTraffic = (options) => {
|
|
|
9549
9733
|
loggerInstance.debug(`${options.type.toUpperCase()}: \n`, msg, "\n");
|
|
9550
9734
|
};
|
|
9551
9735
|
const getLogger = () => {
|
|
9552
|
-
const logger$
|
|
9553
|
-
return new Proxy(logger$
|
|
9736
|
+
const logger$33 = getLoggerInstance();
|
|
9737
|
+
return new Proxy(logger$33, { get(_target, prop, _receiver) {
|
|
9554
9738
|
if (prop === "wsTraffic") return wsTraffic;
|
|
9555
9739
|
const instance = getLoggerInstance();
|
|
9556
9740
|
const value = Reflect.get(instance, prop);
|
|
@@ -9602,7 +9786,7 @@ const asyncRetry = async ({ asyncCallable, maxRetries: retries = DEFAULT_MAX_RET
|
|
|
9602
9786
|
|
|
9603
9787
|
//#endregion
|
|
9604
9788
|
//#region src/controllers/HTTPRequestController.ts
|
|
9605
|
-
const logger$
|
|
9789
|
+
const logger$32 = getLogger();
|
|
9606
9790
|
const GET_PARAMS = {
|
|
9607
9791
|
method: "GET",
|
|
9608
9792
|
headers: { Accept: "application/json" }
|
|
@@ -9666,7 +9850,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9666
9850
|
this._responses$.next(response);
|
|
9667
9851
|
return response;
|
|
9668
9852
|
} catch (error) {
|
|
9669
|
-
logger$
|
|
9853
|
+
logger$32.error("[HTTPRequestController] Request error:", error);
|
|
9670
9854
|
this._status$.next("error");
|
|
9671
9855
|
const err = error instanceof Error ? error : new Error("HTTP request failed", { cause: error });
|
|
9672
9856
|
this._errors$.next(err);
|
|
@@ -9693,7 +9877,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9693
9877
|
const url = this.buildURL(request.url);
|
|
9694
9878
|
const headers = this.buildHeaders(request.headers);
|
|
9695
9879
|
const timeout$5 = request.timeout ?? this.requestTimeout;
|
|
9696
|
-
logger$
|
|
9880
|
+
logger$32.debug("[HTTPRequestController] Executing request:", {
|
|
9697
9881
|
method: request.method,
|
|
9698
9882
|
url,
|
|
9699
9883
|
headers: Object.keys(headers).reduce((acc, key) => {
|
|
@@ -9713,7 +9897,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9713
9897
|
});
|
|
9714
9898
|
clearTimeout(timeoutId);
|
|
9715
9899
|
const httpResponse = await this.convertResponse(response);
|
|
9716
|
-
logger$
|
|
9900
|
+
logger$32.debug("[HTTPRequestController] Response received:", {
|
|
9717
9901
|
status: response.status,
|
|
9718
9902
|
statusText: response.statusText,
|
|
9719
9903
|
headers: [...response.headers.entries()],
|
|
@@ -9723,7 +9907,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9723
9907
|
} catch (error) {
|
|
9724
9908
|
clearTimeout(timeoutId);
|
|
9725
9909
|
if (error instanceof Error && error.name === "AbortError") throw new RequestTimeoutError(`Request timeout after ${timeout$5}ms`, { cause: error });
|
|
9726
|
-
logger$
|
|
9910
|
+
logger$32.error("[HTTPRequestController] Request failed:", error);
|
|
9727
9911
|
throw error;
|
|
9728
9912
|
}
|
|
9729
9913
|
}
|
|
@@ -9737,8 +9921,8 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9737
9921
|
const credential = this.getCredential();
|
|
9738
9922
|
if (credential.token) {
|
|
9739
9923
|
headers.Authorization = `Bearer ${credential.token}`;
|
|
9740
|
-
logger$
|
|
9741
|
-
} else logger$
|
|
9924
|
+
logger$32.debug("[HTTPRequestController] Using Bearer token auth, token length:", credential.token.length);
|
|
9925
|
+
} else logger$32.warn("[HTTPRequestController] No credentials available for authentication");
|
|
9742
9926
|
return headers;
|
|
9743
9927
|
}
|
|
9744
9928
|
/**
|
|
@@ -9875,130 +10059,6 @@ var DeviceHistoryManager = class {
|
|
|
9875
10059
|
}
|
|
9876
10060
|
};
|
|
9877
10061
|
|
|
9878
|
-
//#endregion
|
|
9879
|
-
//#region src/core/constants.ts
|
|
9880
|
-
const INVITE_VERSION = 1e3;
|
|
9881
|
-
const DEFAULT_ICE_CANDIDATE_TIMEOUT_MS = 600;
|
|
9882
|
-
const DEFAULT_ICE_GATHERING_TIMEOUT_MS = 6e3;
|
|
9883
|
-
const DEFAULT_RECONNECT_CALLS_TIMEOUT_MS = 300 * 1e3;
|
|
9884
|
-
const DEFAULT_CONNECTION_TIMEOUT_MS = 1e4;
|
|
9885
|
-
const DEFAULT_RECONNECT_DELAY_MIN_MS = 100;
|
|
9886
|
-
const DEFAULT_RECONNECT_DELAY_MAX_MS = 3e3;
|
|
9887
|
-
const DEFAULT_DEVICE_DEBOUNCE_TIME_MS = 1500;
|
|
9888
|
-
const DEFAULT_DEVICE_POLLING_INTERVAL_MS = 0;
|
|
9889
|
-
const PREFERENCES_STORAGE_KEY = "sw:preferences";
|
|
9890
|
-
/** Scope value that enables automatic token refresh. */
|
|
9891
|
-
const SAT_REFRESH_SCOPE = "sat:refresh";
|
|
9892
|
-
/** API endpoints for device token operations. */
|
|
9893
|
-
const DEVICE_TOKEN_ENDPOINT = "/api/fabric/subscriber/devices/token";
|
|
9894
|
-
const DEVICE_REFRESH_ENDPOINT = "/api/fabric/subscriber/devices/refresh";
|
|
9895
|
-
/** Default device token TTL in seconds (15 minutes). */
|
|
9896
|
-
const DEVICE_TOKEN_DEFAULT_EXPIRE_IN = 900;
|
|
9897
|
-
/** Buffer time in milliseconds before expiry to trigger refresh. */
|
|
9898
|
-
const DEVICE_TOKEN_REFRESH_BUFFER_MS = 3e4;
|
|
9899
|
-
/** Maximum retry attempts for device token refresh on transient failure. */
|
|
9900
|
-
const DEVICE_TOKEN_REFRESH_MAX_RETRIES = 3;
|
|
9901
|
-
/** Base delay in milliseconds for exponential backoff on refresh retry. */
|
|
9902
|
-
const DEVICE_TOKEN_REFRESH_RETRY_BASE_MS = 1e3;
|
|
9903
|
-
/** Maximum retry attempts for developer credential refresh on transient failure. */
|
|
9904
|
-
const CREDENTIAL_REFRESH_MAX_RETRIES = 5;
|
|
9905
|
-
/** Base delay in milliseconds for exponential backoff on credential refresh retry. */
|
|
9906
|
-
const CREDENTIAL_REFRESH_RETRY_BASE_MS = 1e3;
|
|
9907
|
-
/** Maximum delay in milliseconds for credential refresh backoff. */
|
|
9908
|
-
const CREDENTIAL_REFRESH_MAX_DELAY_MS = 3e4;
|
|
9909
|
-
/** Buffer in milliseconds before token expiry to trigger refresh. */
|
|
9910
|
-
const CREDENTIAL_REFRESH_BUFFER_MS = 5e3;
|
|
9911
|
-
/** JSON-RPC error code for requester validation failure (corrupted auth state). */
|
|
9912
|
-
const RPC_ERROR_REQUESTER_VALIDATION_FAILED = -32003;
|
|
9913
|
-
/** JSON-RPC error code for invalid params (e.g., missing authentication block). */
|
|
9914
|
-
const RPC_ERROR_INVALID_PARAMS = -32602;
|
|
9915
|
-
/** JSON-RPC error code for authentication failure (invalid token, missing DPoP, etc.). */
|
|
9916
|
-
const RPC_ERROR_AUTHENTICATION_FAILED = -32002;
|
|
9917
|
-
/** Default polling interval for RTCPeerConnection.getStats() in milliseconds. */
|
|
9918
|
-
const DEFAULT_STATS_POLLING_INTERVAL_MS = 1e3;
|
|
9919
|
-
/** Number of initial samples used to build a baseline for spike detection. */
|
|
9920
|
-
const DEFAULT_STATS_BASELINE_SAMPLES = 10;
|
|
9921
|
-
/** Duration in ms with no inbound audio packets before emitting a critical issue. */
|
|
9922
|
-
const DEFAULT_STATS_NO_PACKET_THRESHOLD_MS = 2e3;
|
|
9923
|
-
/** Multiplier applied to baseline RTT to detect a warning-level RTT spike. */
|
|
9924
|
-
const DEFAULT_STATS_RTT_SPIKE_MULTIPLIER = 3;
|
|
9925
|
-
/** Packet loss fraction (0-1) above which a warning is emitted. */
|
|
9926
|
-
const DEFAULT_STATS_PACKET_LOSS_THRESHOLD = .05;
|
|
9927
|
-
/** Multiplier applied to baseline jitter to detect a jitter spike. */
|
|
9928
|
-
const DEFAULT_STATS_JITTER_SPIKE_MULTIPLIER = 4;
|
|
9929
|
-
/** Number of seconds of metrics history to retain. */
|
|
9930
|
-
const DEFAULT_STATS_HISTORY_SIZE = 30;
|
|
9931
|
-
/** Maximum keyframe requests allowed within a single burst window. */
|
|
9932
|
-
const DEFAULT_KEYFRAME_MAX_BURST$1 = 3;
|
|
9933
|
-
/** Duration of the keyframe burst window in milliseconds. */
|
|
9934
|
-
const DEFAULT_KEYFRAME_BURST_WINDOW_MS$1 = 3e3;
|
|
9935
|
-
/** Cooldown period in ms after burst limit is reached before allowing more keyframes. */
|
|
9936
|
-
const DEFAULT_KEYFRAME_COOLDOWN_MS$1 = 1e4;
|
|
9937
|
-
/** Minimum time between re-INVITE attempts in milliseconds. */
|
|
9938
|
-
const DEFAULT_REINVITE_DEBOUNCE_TIME_MS = 1e4;
|
|
9939
|
-
/** Maximum number of re-INVITE attempts per call. */
|
|
9940
|
-
const DEFAULT_REINVITE_MAX_ATTEMPTS = 3;
|
|
9941
|
-
/** Timeout for a single re-INVITE attempt in milliseconds. */
|
|
9942
|
-
const DEFAULT_REINVITE_TIMEOUT_MS = 5e3;
|
|
9943
|
-
/** Debounce window in ms to collapse multiple detection signals into one trigger. */
|
|
9944
|
-
const DEFAULT_RECOVERY_DEBOUNCE_TIME_MS = 2e3;
|
|
9945
|
-
/** Cooldown period in ms between recovery attempts. */
|
|
9946
|
-
const DEFAULT_RECOVERY_COOLDOWN_MS = 1e4;
|
|
9947
|
-
/** Grace period in ms before treating ICE 'disconnected' as a failure. */
|
|
9948
|
-
const DEFAULT_ICE_DISCONNECTED_GRACE_PERIOD_MS = 3e3;
|
|
9949
|
-
/** Timeout for a single ICE restart attempt in milliseconds. */
|
|
9950
|
-
const DEFAULT_ICE_RESTART_TIMEOUT_MS$1 = 5e3;
|
|
9951
|
-
/** Maximum recovery attempts before emitting 'max_attempts_reached'. */
|
|
9952
|
-
const DEFAULT_MAX_RECOVERY_ATTEMPTS = 3;
|
|
9953
|
-
/** Upper bound in ms for waiting on iceGatheringState === 'complete' after an ICE restart. */
|
|
9954
|
-
const ICE_GATHERING_COMPLETE_TIMEOUT_MS = 1e4;
|
|
9955
|
-
/** Upper bound in ms for waiting on RTCPeerConnection.connectionState === 'connected' after a recovery ICE restart. */
|
|
9956
|
-
const PEER_CONNECTION_RECOVERY_WAIT_MS = 5e3;
|
|
9957
|
-
/** Polling interval in ms while waiting for RTCPeerConnection.connectionState to transition. */
|
|
9958
|
-
const PEER_CONNECTION_RECOVERY_POLL_MS = 100;
|
|
9959
|
-
/** Polling interval for LocalAudioPipeline.level$ (ms). ~30fps is smooth for meters. */
|
|
9960
|
-
const AUDIO_LEVEL_POLL_INTERVAL_MS = 33;
|
|
9961
|
-
/** RMS level threshold (0..1) above which the local participant is considered speaking. */
|
|
9962
|
-
const VAD_THRESHOLD = .03;
|
|
9963
|
-
/** Hold window in ms below the threshold before speaking$ flips back to false. */
|
|
9964
|
-
const VAD_HOLD_MS = 250;
|
|
9965
|
-
/** Whether to persist device selections to storage by default. */
|
|
9966
|
-
const DEFAULT_PERSIST_DEVICE_SELECTION = true;
|
|
9967
|
-
/** Whether to auto-apply device changes to active calls by default. */
|
|
9968
|
-
const DEFAULT_SYNC_DEVICES_TO_ACTIVE_CALLS = true;
|
|
9969
|
-
/** Storage keys for persisted device selections. */
|
|
9970
|
-
const DEVICE_STORAGE_KEY_AUDIO_INPUT = "sw:device:audioinput";
|
|
9971
|
-
const DEVICE_STORAGE_KEY_AUDIO_OUTPUT = "sw:device:audiooutput";
|
|
9972
|
-
const DEVICE_STORAGE_KEY_VIDEO_INPUT = "sw:device:videoinput";
|
|
9973
|
-
/** Whether to auto-mute video when the tab becomes hidden. */
|
|
9974
|
-
const DEFAULT_AUTO_MUTE_VIDEO_ON_HIDDEN = false;
|
|
9975
|
-
/** Whether to re-enumerate devices when the page becomes visible. */
|
|
9976
|
-
const DEFAULT_REFRESH_DEVICES_ON_VISIBLE = true;
|
|
9977
|
-
/** Whether to check peer connection health when the page becomes visible. */
|
|
9978
|
-
const DEFAULT_CHECK_CONNECTION_ON_VISIBLE = true;
|
|
9979
|
-
/** Whether automatic video degradation on low bandwidth is enabled. */
|
|
9980
|
-
const DEFAULT_ENABLE_AUTO_DEGRADATION = true;
|
|
9981
|
-
/** Bitrate in kbps below which video is automatically disabled. */
|
|
9982
|
-
const DEFAULT_DEGRADATION_BITRATE_THRESHOLD_KBPS = 150;
|
|
9983
|
-
/** Bitrate in kbps above which video is automatically re-enabled (hysteresis). */
|
|
9984
|
-
const DEFAULT_DEGRADATION_RECOVERY_THRESHOLD_KBPS = 300;
|
|
9985
|
-
/** Whether relay-only escalation is enabled as a last-resort recovery tier. */
|
|
9986
|
-
const DEFAULT_ENABLE_RELAY_FALLBACK = true;
|
|
9987
|
-
/** Whether to listen for browser online/offline/connection events. */
|
|
9988
|
-
const DEFAULT_ENABLE_NETWORK_CHANGE_DETECTION = true;
|
|
9989
|
-
/** Whether to intercept server-sent media-timeout hangups and attempt recovery. */
|
|
9990
|
-
const DEFAULT_ENABLE_SERVER_HANGUP_INTERCEPTION = true;
|
|
9991
|
-
/** Default video track constraints applied when video is enabled without explicit constraints. */
|
|
9992
|
-
const DEFAULT_VIDEO_CONSTRAINTS = {
|
|
9993
|
-
width: { ideal: 1280 },
|
|
9994
|
-
height: { ideal: 720 },
|
|
9995
|
-
aspectRatio: 16 / 9
|
|
9996
|
-
};
|
|
9997
|
-
/** Whether stereo Opus is enabled by default. */
|
|
9998
|
-
const DEFAULT_STEREO_AUDIO = false;
|
|
9999
|
-
/** Max average bitrate for stereo Opus in bits per second. */
|
|
10000
|
-
const DEFAULT_STEREO_MAX_AVERAGE_BITRATE = 51e4;
|
|
10001
|
-
|
|
10002
10062
|
//#endregion
|
|
10003
10063
|
//#region src/utils/time.ts
|
|
10004
10064
|
function fromSecToMs(seconds) {
|
|
@@ -10010,7 +10070,7 @@ function fromMsToSec(milliseconds) {
|
|
|
10010
10070
|
|
|
10011
10071
|
//#endregion
|
|
10012
10072
|
//#region src/containers/PreferencesContainer.ts
|
|
10013
|
-
const logger$
|
|
10073
|
+
const logger$31 = getLogger();
|
|
10014
10074
|
var PreferencesContainer = class PreferencesContainer {
|
|
10015
10075
|
static get instance() {
|
|
10016
10076
|
this._instance ??= new PreferencesContainer();
|
|
@@ -10672,7 +10732,7 @@ var ClientPreferences = class {
|
|
|
10672
10732
|
if (!this._storage) return;
|
|
10673
10733
|
const data = collectStoredPreferences();
|
|
10674
10734
|
this._storage.setItem(PREFERENCES_STORAGE_KEY, data, "local").catch((error) => {
|
|
10675
|
-
logger$
|
|
10735
|
+
logger$31.error(`[ClientPreferences] Failed to save preferences: ${String(error)}`);
|
|
10676
10736
|
});
|
|
10677
10737
|
}
|
|
10678
10738
|
/** Loads preferences from storage and applies them to the container. */
|
|
@@ -10681,7 +10741,7 @@ var ClientPreferences = class {
|
|
|
10681
10741
|
this._storage.getItem(PREFERENCES_STORAGE_KEY, "local").then((stored) => {
|
|
10682
10742
|
if (stored) applyStoredPreferences(stored);
|
|
10683
10743
|
}).catch((error) => {
|
|
10684
|
-
logger$
|
|
10744
|
+
logger$31.error(`[ClientPreferences] Failed to load preferences: ${String(error)}`);
|
|
10685
10745
|
});
|
|
10686
10746
|
}
|
|
10687
10747
|
};
|
|
@@ -10703,7 +10763,7 @@ function toError(value) {
|
|
|
10703
10763
|
//#endregion
|
|
10704
10764
|
//#region src/controllers/NavigatorDeviceController.ts
|
|
10705
10765
|
var import_cjs$29 = require_cjs();
|
|
10706
|
-
const logger$
|
|
10766
|
+
const logger$30 = getLogger();
|
|
10707
10767
|
/** Maps a device kind to its storage key. */
|
|
10708
10768
|
const DEVICE_STORAGE_KEYS = {
|
|
10709
10769
|
audioinput: DEVICE_STORAGE_KEY_AUDIO_INPUT,
|
|
@@ -10725,7 +10785,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10725
10785
|
super();
|
|
10726
10786
|
this.webRTCApiProvider = webRTCApiProvider;
|
|
10727
10787
|
this.deviceChangeHandler = () => {
|
|
10728
|
-
logger$
|
|
10788
|
+
logger$30.debug("[DeviceController] Device change detected");
|
|
10729
10789
|
this.enumerateDevices();
|
|
10730
10790
|
};
|
|
10731
10791
|
this._devicesState$ = this.createBehaviorSubject(initialDevicesState);
|
|
@@ -10790,13 +10850,13 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10790
10850
|
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$)));
|
|
10791
10851
|
}
|
|
10792
10852
|
get selectedAudioInputDevice$() {
|
|
10793
|
-
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$
|
|
10853
|
+
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))));
|
|
10794
10854
|
}
|
|
10795
10855
|
get selectedAudioOutputDevice$() {
|
|
10796
|
-
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$
|
|
10856
|
+
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))));
|
|
10797
10857
|
}
|
|
10798
10858
|
get selectedVideoInputDevice$() {
|
|
10799
|
-
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$
|
|
10859
|
+
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))));
|
|
10800
10860
|
}
|
|
10801
10861
|
get selectedAudioInputDevice() {
|
|
10802
10862
|
if (this._audioInputDisabled$.value) return null;
|
|
@@ -10871,7 +10931,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10871
10931
|
if (device) this.persistDeviceSelection("audioinput", device);
|
|
10872
10932
|
}
|
|
10873
10933
|
selectVideoInputDevice(device) {
|
|
10874
|
-
logger$
|
|
10934
|
+
logger$30.debug("[DeviceController] Setting selected video input device:", device);
|
|
10875
10935
|
if (this._videoInputDisabled$.value && device) this._videoInputDisabled$.next(false);
|
|
10876
10936
|
const previous = this._selectedDevicesState$.value.videoinput;
|
|
10877
10937
|
if (previous && previous.deviceId !== device?.deviceId) this._deviceHistory.push("videoinput", previous);
|
|
@@ -10928,7 +10988,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10928
10988
|
}
|
|
10929
10989
|
const fromHistory = this._deviceHistory.findInHistory(kind, devices);
|
|
10930
10990
|
if (fromHistory) {
|
|
10931
|
-
logger$
|
|
10991
|
+
logger$30.debug(`[DeviceController] Device disappeared, falling back to history: ${fromHistory.label}`);
|
|
10932
10992
|
this.emitDeviceRecovered(kind, selected, fromHistory, "device_disconnected");
|
|
10933
10993
|
return fromHistory;
|
|
10934
10994
|
}
|
|
@@ -10981,7 +11041,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10981
11041
|
try {
|
|
10982
11042
|
await this._storageManager.setItem(DEVICE_STORAGE_KEYS[kind], stored, "local");
|
|
10983
11043
|
} catch (error) {
|
|
10984
|
-
logger$
|
|
11044
|
+
logger$30.error(`[DeviceController] Failed to persist device selection for ${kind}:`, error);
|
|
10985
11045
|
}
|
|
10986
11046
|
}
|
|
10987
11047
|
async loadPersistedDevices() {
|
|
@@ -10997,7 +11057,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10997
11057
|
[kind]: stored
|
|
10998
11058
|
};
|
|
10999
11059
|
} catch (error) {
|
|
11000
|
-
logger$
|
|
11060
|
+
logger$30.error(`[DeviceController] Failed to load persisted device for ${kind}:`, error);
|
|
11001
11061
|
}
|
|
11002
11062
|
}
|
|
11003
11063
|
/** Clears device history, persisted selections, and re-enumerates devices. */
|
|
@@ -11015,7 +11075,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
11015
11075
|
this.disableDeviceMonitoring();
|
|
11016
11076
|
this.webRTCApiProvider.mediaDevices.addEventListener("devicechange", this.deviceChangeHandler);
|
|
11017
11077
|
if (PreferencesContainer.instance.devicePollingInterval > 0) this._devicesPoolingSubscription = (0, import_cjs$29.interval)(PreferencesContainer.instance.devicePollingInterval).subscribe(() => {
|
|
11018
|
-
logger$
|
|
11078
|
+
logger$30.debug("[DeviceController] Polling devices due to interval");
|
|
11019
11079
|
this.enumerateDevices();
|
|
11020
11080
|
});
|
|
11021
11081
|
this.enumerateDevices();
|
|
@@ -11041,13 +11101,13 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
11041
11101
|
videoinput: []
|
|
11042
11102
|
});
|
|
11043
11103
|
this._devicesState$.next(devicesByKind);
|
|
11044
|
-
logger$
|
|
11104
|
+
logger$30.debug("[DeviceController] Devices enumerated:", {
|
|
11045
11105
|
audioInputs: devicesByKind.audioinput.length,
|
|
11046
11106
|
audioOutputs: devicesByKind.audiooutput.length,
|
|
11047
11107
|
videoInputs: devicesByKind.videoinput.length
|
|
11048
11108
|
});
|
|
11049
11109
|
} catch (error) {
|
|
11050
|
-
logger$
|
|
11110
|
+
logger$30.error("[DeviceController] Failed to enumerate devices:", error);
|
|
11051
11111
|
this._errors$.next(toError(error));
|
|
11052
11112
|
}
|
|
11053
11113
|
}
|
|
@@ -11063,7 +11123,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
11063
11123
|
stream.getTracks().forEach((t) => t.stop());
|
|
11064
11124
|
return capabilities;
|
|
11065
11125
|
} catch (error) {
|
|
11066
|
-
logger$
|
|
11126
|
+
logger$30.error("[DeviceController] Failed to get device capabilities:", error);
|
|
11067
11127
|
this._errors$.next(toError(error));
|
|
11068
11128
|
throw error;
|
|
11069
11129
|
}
|
|
@@ -11314,7 +11374,7 @@ var DependencyContainer = class {
|
|
|
11314
11374
|
|
|
11315
11375
|
//#endregion
|
|
11316
11376
|
//#region src/controllers/CryptoController.ts
|
|
11317
|
-
const logger$
|
|
11377
|
+
const logger$29 = getLogger();
|
|
11318
11378
|
const DPOP_DB_NAME = "sw-dpop";
|
|
11319
11379
|
const DPOP_DB_VERSION = 1;
|
|
11320
11380
|
const DPOP_STORE_NAME = "keys";
|
|
@@ -11373,7 +11433,7 @@ async function loadKeyPairFromDB() {
|
|
|
11373
11433
|
tx.oncomplete = () => db.close();
|
|
11374
11434
|
});
|
|
11375
11435
|
} catch (error) {
|
|
11376
|
-
logger$
|
|
11436
|
+
logger$29.warn("[DPoP] Failed to load key pair from IndexedDB:", error);
|
|
11377
11437
|
return null;
|
|
11378
11438
|
}
|
|
11379
11439
|
}
|
|
@@ -11393,7 +11453,7 @@ async function saveKeyPairToDB(keyPair) {
|
|
|
11393
11453
|
};
|
|
11394
11454
|
});
|
|
11395
11455
|
} catch (error) {
|
|
11396
|
-
logger$
|
|
11456
|
+
logger$29.warn("[DPoP] Failed to save key pair to IndexedDB:", error);
|
|
11397
11457
|
}
|
|
11398
11458
|
}
|
|
11399
11459
|
async function deleteKeyPairFromDB() {
|
|
@@ -11412,7 +11472,7 @@ async function deleteKeyPairFromDB() {
|
|
|
11412
11472
|
};
|
|
11413
11473
|
});
|
|
11414
11474
|
} catch (error) {
|
|
11415
|
-
logger$
|
|
11475
|
+
logger$29.warn("[DPoP] Failed to delete key pair from IndexedDB:", error);
|
|
11416
11476
|
}
|
|
11417
11477
|
}
|
|
11418
11478
|
/**
|
|
@@ -11472,13 +11532,13 @@ var CryptoController = class {
|
|
|
11472
11532
|
this._publicJwk = await crypto.subtle.exportKey("jwk", stored.publicKey);
|
|
11473
11533
|
this._fingerprint = await computeJwkThumbprint(this._publicJwk);
|
|
11474
11534
|
this._initialized = true;
|
|
11475
|
-
logger$
|
|
11535
|
+
logger$29.debug("[DPoP] Key pair restored from IndexedDB, fingerprint:", this._fingerprint);
|
|
11476
11536
|
return this._fingerprint;
|
|
11477
11537
|
} catch (error) {
|
|
11478
|
-
logger$
|
|
11538
|
+
logger$29.warn("[DPoP] Stored key pair unusable, generating new one:", error);
|
|
11479
11539
|
await deleteKeyPairFromDB();
|
|
11480
11540
|
}
|
|
11481
|
-
logger$
|
|
11541
|
+
logger$29.debug("[DPoP] Generating RSA key pair");
|
|
11482
11542
|
this._keyPair = await crypto.subtle.generateKey({
|
|
11483
11543
|
name: "RSASSA-PKCS1-v1_5",
|
|
11484
11544
|
modulusLength: 2048,
|
|
@@ -11493,7 +11553,7 @@ var CryptoController = class {
|
|
|
11493
11553
|
this._fingerprint = await computeJwkThumbprint(this._publicJwk);
|
|
11494
11554
|
this._initialized = true;
|
|
11495
11555
|
await saveKeyPairToDB(this._keyPair);
|
|
11496
|
-
logger$
|
|
11556
|
+
logger$29.debug("[DPoP] Key pair generated and persisted, fingerprint:", this._fingerprint);
|
|
11497
11557
|
return this._fingerprint;
|
|
11498
11558
|
}
|
|
11499
11559
|
/**
|
|
@@ -11559,7 +11619,7 @@ var CryptoController = class {
|
|
|
11559
11619
|
this._fingerprint = null;
|
|
11560
11620
|
this._initialized = false;
|
|
11561
11621
|
deleteKeyPairFromDB();
|
|
11562
|
-
logger$
|
|
11622
|
+
logger$29.debug("[DPoP] Controller destroyed");
|
|
11563
11623
|
}
|
|
11564
11624
|
get publicJwk() {
|
|
11565
11625
|
if (!this._publicJwk) throw new DPoPInitError("CryptoController not initialized. Call init() first.");
|
|
@@ -11583,7 +11643,7 @@ var CryptoController = class {
|
|
|
11583
11643
|
//#endregion
|
|
11584
11644
|
//#region src/controllers/NetworkMonitor.ts
|
|
11585
11645
|
var import_cjs$28 = require_cjs();
|
|
11586
|
-
const logger$
|
|
11646
|
+
const logger$28 = getLogger();
|
|
11587
11647
|
/**
|
|
11588
11648
|
* Safely check whether we are running in a browser environment
|
|
11589
11649
|
* with `window` and the relevant event targets.
|
|
@@ -11640,7 +11700,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11640
11700
|
}
|
|
11641
11701
|
attachListeners() {
|
|
11642
11702
|
if (!hasBrowserNetworkEvents()) {
|
|
11643
|
-
logger$
|
|
11703
|
+
logger$28.debug("NetworkMonitor: no browser environment detected, skipping event listeners");
|
|
11644
11704
|
return;
|
|
11645
11705
|
}
|
|
11646
11706
|
window.addEventListener("online", this._onOnline);
|
|
@@ -11648,7 +11708,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11648
11708
|
const connection = getNetworkConnection();
|
|
11649
11709
|
if (connection) connection.addEventListener("change", this._onConnectionChange);
|
|
11650
11710
|
this._listenersAttached = true;
|
|
11651
|
-
logger$
|
|
11711
|
+
logger$28.debug("NetworkMonitor: event listeners attached");
|
|
11652
11712
|
}
|
|
11653
11713
|
removeListeners() {
|
|
11654
11714
|
if (!this._listenersAttached) return;
|
|
@@ -11659,10 +11719,10 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11659
11719
|
if (connection) connection.removeEventListener("change", this._onConnectionChange);
|
|
11660
11720
|
}
|
|
11661
11721
|
this._listenersAttached = false;
|
|
11662
|
-
logger$
|
|
11722
|
+
logger$28.debug("NetworkMonitor: event listeners removed");
|
|
11663
11723
|
}
|
|
11664
11724
|
handleOnline() {
|
|
11665
|
-
logger$
|
|
11725
|
+
logger$28.info("NetworkMonitor: browser went online");
|
|
11666
11726
|
this._isOnline$.next(true);
|
|
11667
11727
|
this._networkChange$.next({
|
|
11668
11728
|
type: "online",
|
|
@@ -11671,7 +11731,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11671
11731
|
});
|
|
11672
11732
|
}
|
|
11673
11733
|
handleOffline() {
|
|
11674
|
-
logger$
|
|
11734
|
+
logger$28.info("NetworkMonitor: browser went offline");
|
|
11675
11735
|
this._isOnline$.next(false);
|
|
11676
11736
|
this._networkChange$.next({
|
|
11677
11737
|
type: "offline",
|
|
@@ -11680,7 +11740,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11680
11740
|
}
|
|
11681
11741
|
handleConnectionChange() {
|
|
11682
11742
|
const networkType = getNetworkType();
|
|
11683
|
-
logger$
|
|
11743
|
+
logger$28.info(`NetworkMonitor: connection changed — effectiveType=${networkType ?? "unknown"}`);
|
|
11684
11744
|
this._networkChange$.next({
|
|
11685
11745
|
type: "connection_change",
|
|
11686
11746
|
timestamp: Date.now(),
|
|
@@ -11796,7 +11856,7 @@ function getNavigatorMediaDevices() {
|
|
|
11796
11856
|
//#endregion
|
|
11797
11857
|
//#region src/controllers/PreflightRunner.ts
|
|
11798
11858
|
var import_cjs$27 = require_cjs();
|
|
11799
|
-
const logger$
|
|
11859
|
+
const logger$27 = getLogger();
|
|
11800
11860
|
const DEFAULT_MEDIA_TEST_DURATION_S = 10;
|
|
11801
11861
|
const ICE_GATHERING_TIMEOUT_MS = 1e4;
|
|
11802
11862
|
const SIGNALING_RTT_TIMEOUT_MS = 5e3;
|
|
@@ -11845,7 +11905,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11845
11905
|
if (!this._options.skipMediaTest) try {
|
|
11846
11906
|
bandwidth = await this.testMediaBandwidth(destination);
|
|
11847
11907
|
} catch (error) {
|
|
11848
|
-
logger$
|
|
11908
|
+
logger$27.warn("[PreflightRunner] Media bandwidth test failed:", error);
|
|
11849
11909
|
warnings.push("Media bandwidth test failed");
|
|
11850
11910
|
}
|
|
11851
11911
|
return {
|
|
@@ -11857,7 +11917,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11857
11917
|
warnings
|
|
11858
11918
|
};
|
|
11859
11919
|
} catch (error) {
|
|
11860
|
-
logger$
|
|
11920
|
+
logger$27.error("[PreflightRunner] Preflight test failed:", error);
|
|
11861
11921
|
throw new PreflightError("preflight", error instanceof Error ? error : new Error(String(error)));
|
|
11862
11922
|
} finally {
|
|
11863
11923
|
this.destroy();
|
|
@@ -11888,7 +11948,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11888
11948
|
if (track.kind === "video" && track.readyState === "live") videoWorking = true;
|
|
11889
11949
|
}
|
|
11890
11950
|
} catch (error) {
|
|
11891
|
-
logger$
|
|
11951
|
+
logger$27.warn("[PreflightRunner] Device test failed:", error);
|
|
11892
11952
|
} finally {
|
|
11893
11953
|
if (audioStream) audioStream.getTracks().forEach((t) => t.stop());
|
|
11894
11954
|
}
|
|
@@ -11946,7 +12006,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11946
12006
|
rttMs
|
|
11947
12007
|
};
|
|
11948
12008
|
} catch (error) {
|
|
11949
|
-
logger$
|
|
12009
|
+
logger$27.warn("[PreflightRunner] ICE connectivity test failed:", error);
|
|
11950
12010
|
return {
|
|
11951
12011
|
type: "failed",
|
|
11952
12012
|
turnReachable: false,
|
|
@@ -11994,7 +12054,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11994
12054
|
//#endregion
|
|
11995
12055
|
//#region src/controllers/VisibilityController.ts
|
|
11996
12056
|
var import_cjs$26 = require_cjs();
|
|
11997
|
-
const logger$
|
|
12057
|
+
const logger$26 = getLogger();
|
|
11998
12058
|
/**
|
|
11999
12059
|
* Checks whether the document visibility API is available.
|
|
12000
12060
|
*/
|
|
@@ -12031,8 +12091,8 @@ var VisibilityController = class extends Destroyable {
|
|
|
12031
12091
|
this._boundHandler = this._handleVisibilityChange.bind(this);
|
|
12032
12092
|
if (this._hasVisibilityApi) {
|
|
12033
12093
|
document.addEventListener("visibilitychange", this._boundHandler);
|
|
12034
|
-
logger$
|
|
12035
|
-
} else logger$
|
|
12094
|
+
logger$26.debug("VisibilityController: listening for visibilitychange events");
|
|
12095
|
+
} else logger$26.debug("VisibilityController: document visibility API not available, defaulting to visible");
|
|
12036
12096
|
}
|
|
12037
12097
|
/**
|
|
12038
12098
|
* Observable of the current visibility state.
|
|
@@ -12057,7 +12117,7 @@ var VisibilityController = class extends Destroyable {
|
|
|
12057
12117
|
destroy() {
|
|
12058
12118
|
if (this._hasVisibilityApi) {
|
|
12059
12119
|
document.removeEventListener("visibilitychange", this._boundHandler);
|
|
12060
|
-
logger$
|
|
12120
|
+
logger$26.debug("VisibilityController: removed visibilitychange listener");
|
|
12061
12121
|
}
|
|
12062
12122
|
super.destroy();
|
|
12063
12123
|
}
|
|
@@ -12075,7 +12135,7 @@ var VisibilityController = class extends Destroyable {
|
|
|
12075
12135
|
timestamp: Date.now()
|
|
12076
12136
|
};
|
|
12077
12137
|
this._visibilityChange$.next(changeEvent);
|
|
12078
|
-
logger$
|
|
12138
|
+
logger$26.debug("VisibilityController: visibility changed", {
|
|
12079
12139
|
from: previousState,
|
|
12080
12140
|
to: newState
|
|
12081
12141
|
});
|
|
@@ -12150,22 +12210,17 @@ function unsafeStringify(arr, offset = 0) {
|
|
|
12150
12210
|
|
|
12151
12211
|
//#endregion
|
|
12152
12212
|
//#region ../../node_modules/uuid/dist-node/rng.js
|
|
12153
|
-
const
|
|
12154
|
-
let poolPtr = rnds8Pool.length;
|
|
12213
|
+
const rnds8 = new Uint8Array(16);
|
|
12155
12214
|
function rng() {
|
|
12156
|
-
|
|
12157
|
-
randomFillSync(rnds8Pool);
|
|
12158
|
-
poolPtr = 0;
|
|
12159
|
-
}
|
|
12160
|
-
return rnds8Pool.slice(poolPtr, poolPtr += 16);
|
|
12215
|
+
return crypto.getRandomValues(rnds8);
|
|
12161
12216
|
}
|
|
12162
12217
|
|
|
12163
|
-
//#endregion
|
|
12164
|
-
//#region ../../node_modules/uuid/dist-node/native.js
|
|
12165
|
-
var native_default = { randomUUID };
|
|
12166
|
-
|
|
12167
12218
|
//#endregion
|
|
12168
12219
|
//#region ../../node_modules/uuid/dist-node/v4.js
|
|
12220
|
+
function v4(options, buf, offset) {
|
|
12221
|
+
if (!buf && !options && crypto.randomUUID) return crypto.randomUUID();
|
|
12222
|
+
return _v4(options, buf, offset);
|
|
12223
|
+
}
|
|
12169
12224
|
function _v4(options, buf, offset) {
|
|
12170
12225
|
options = options || {};
|
|
12171
12226
|
const rnds = options.random ?? options.rng?.() ?? rng();
|
|
@@ -12180,10 +12235,6 @@ function _v4(options, buf, offset) {
|
|
|
12180
12235
|
}
|
|
12181
12236
|
return unsafeStringify(rnds);
|
|
12182
12237
|
}
|
|
12183
|
-
function v4(options, buf, offset) {
|
|
12184
|
-
if (native_default.randomUUID && !buf && !options) return native_default.randomUUID();
|
|
12185
|
-
return _v4(options, buf, offset);
|
|
12186
|
-
}
|
|
12187
12238
|
var v4_default = v4;
|
|
12188
12239
|
|
|
12189
12240
|
//#endregion
|
|
@@ -12325,7 +12376,7 @@ const RPCEventAckResponse = (id) => makeRPCResponse({
|
|
|
12325
12376
|
|
|
12326
12377
|
//#endregion
|
|
12327
12378
|
//#region src/managers/AttachManager.ts
|
|
12328
|
-
const logger$
|
|
12379
|
+
const logger$25 = getLogger();
|
|
12329
12380
|
var AttachManager = class {
|
|
12330
12381
|
constructor(storage, deviceController, reconnectCallsTimeout, attachKey) {
|
|
12331
12382
|
this.storage = storage;
|
|
@@ -12346,7 +12397,7 @@ var AttachManager = class {
|
|
|
12346
12397
|
try {
|
|
12347
12398
|
return await this.storage.getItem(this.attachKey) ?? {};
|
|
12348
12399
|
} catch (error) {
|
|
12349
|
-
logger$
|
|
12400
|
+
logger$25.warn("[AttachManager] Failed to retrieve attached calls from storage", error);
|
|
12350
12401
|
return {};
|
|
12351
12402
|
}
|
|
12352
12403
|
}
|
|
@@ -12354,7 +12405,7 @@ var AttachManager = class {
|
|
|
12354
12405
|
try {
|
|
12355
12406
|
await this.storage.setItem(this.attachKey, attached);
|
|
12356
12407
|
} catch (error) {
|
|
12357
|
-
logger$
|
|
12408
|
+
logger$25.warn("[AttachManager] Failed to write attached calls to storage", error);
|
|
12358
12409
|
}
|
|
12359
12410
|
}
|
|
12360
12411
|
/**
|
|
@@ -12373,7 +12424,7 @@ var AttachManager = class {
|
|
|
12373
12424
|
}
|
|
12374
12425
|
async attach(call) {
|
|
12375
12426
|
if (!call.to) {
|
|
12376
|
-
logger$
|
|
12427
|
+
logger$25.warn("[AttachManager] Skip attach for calls with no destination");
|
|
12377
12428
|
return;
|
|
12378
12429
|
}
|
|
12379
12430
|
const destination = call.to;
|
|
@@ -12426,15 +12477,15 @@ var AttachManager = class {
|
|
|
12426
12477
|
callId,
|
|
12427
12478
|
...options
|
|
12428
12479
|
});
|
|
12429
|
-
logger$
|
|
12480
|
+
logger$25.info(`[AttachManager] Reattached call ${callId} (attempt ${attempt})`);
|
|
12430
12481
|
succeeded = true;
|
|
12431
12482
|
break;
|
|
12432
12483
|
} catch (error) {
|
|
12433
|
-
logger$
|
|
12484
|
+
logger$25.warn(`[AttachManager] Reattach attempt ${attempt}/3 failed for call ${callId}:`, error);
|
|
12434
12485
|
if (attempt < 3) await new Promise((r) => setTimeout(r, (attempt + 1) * 1e3));
|
|
12435
12486
|
}
|
|
12436
12487
|
if (!succeeded) {
|
|
12437
|
-
logger$
|
|
12488
|
+
logger$25.warn(`[AttachManager] Reattach failed after 3 attempts for call ${callId}, removing reference`);
|
|
12438
12489
|
await this.detach({
|
|
12439
12490
|
id: callId,
|
|
12440
12491
|
mediaDirections: attachment.mediaDirections
|
|
@@ -13371,7 +13422,9 @@ const DEFAULT_MEMBER_CAPABILITIES = {
|
|
|
13371
13422
|
position: false,
|
|
13372
13423
|
meta: false,
|
|
13373
13424
|
remove: false,
|
|
13374
|
-
audioFlags: false
|
|
13425
|
+
audioFlags: false,
|
|
13426
|
+
denoise: false,
|
|
13427
|
+
lowbitrate: false
|
|
13375
13428
|
};
|
|
13376
13429
|
/**
|
|
13377
13430
|
* Default call capabilities with no permissions
|
|
@@ -13444,7 +13497,9 @@ function computeMemberCapabilities(flags, memberType) {
|
|
|
13444
13497
|
position: hasBooleanCapability(flags, memberType, null, "position"),
|
|
13445
13498
|
meta: hasBooleanCapability(flags, memberType, null, "meta"),
|
|
13446
13499
|
remove: hasBooleanCapability(flags, memberType, null, "remove"),
|
|
13447
|
-
audioFlags: hasBooleanCapability(flags, memberType, null, "audioflags")
|
|
13500
|
+
audioFlags: hasBooleanCapability(flags, memberType, null, "audioflags"),
|
|
13501
|
+
denoise: hasBooleanCapability(flags, memberType, null, "denoise"),
|
|
13502
|
+
lowbitrate: hasBooleanCapability(flags, memberType, null, "lowbitrate")
|
|
13448
13503
|
};
|
|
13449
13504
|
}
|
|
13450
13505
|
/**
|
|
@@ -13601,7 +13656,7 @@ function toggleHandraiseMethod(is) {
|
|
|
13601
13656
|
|
|
13602
13657
|
//#endregion
|
|
13603
13658
|
//#region src/core/entities/Participant.ts
|
|
13604
|
-
const logger$
|
|
13659
|
+
const logger$24 = getLogger();
|
|
13605
13660
|
const initialState = {};
|
|
13606
13661
|
/**
|
|
13607
13662
|
* Represents a participant in a call.
|
|
@@ -13611,9 +13666,9 @@ const initialState = {};
|
|
|
13611
13666
|
* the local participant with additional device control.
|
|
13612
13667
|
*/
|
|
13613
13668
|
var Participant = class extends Destroyable {
|
|
13614
|
-
constructor(id,
|
|
13669
|
+
constructor(id, callExecuteMethod, deviceController) {
|
|
13615
13670
|
super();
|
|
13616
|
-
this.
|
|
13671
|
+
this.callExecuteMethod = callExecuteMethod;
|
|
13617
13672
|
this.deviceController = deviceController;
|
|
13618
13673
|
this._state$ = this.createBehaviorSubject(initialState);
|
|
13619
13674
|
this.id = id;
|
|
@@ -13827,26 +13882,63 @@ var Participant = class extends Destroyable {
|
|
|
13827
13882
|
get nodeId() {
|
|
13828
13883
|
return this._state$.value.node_id;
|
|
13829
13884
|
}
|
|
13885
|
+
/** Call ID for this participant's leg, or `undefined` if not available. */
|
|
13886
|
+
get callId() {
|
|
13887
|
+
return this._state$.value.call_id;
|
|
13888
|
+
}
|
|
13830
13889
|
/** @internal */
|
|
13831
13890
|
get value() {
|
|
13832
13891
|
return this._state$.value;
|
|
13833
13892
|
}
|
|
13893
|
+
/**
|
|
13894
|
+
* Target triple for member RPCs, built from the participant's own state.
|
|
13895
|
+
* The backend locates the member's session by the target `call_id`/`node_id`,
|
|
13896
|
+
* so this must always be the participant's own call context — never the
|
|
13897
|
+
* local call's id (issue #19400).
|
|
13898
|
+
*
|
|
13899
|
+
* Reading it doubles as a readiness probe: it throws until the first full
|
|
13900
|
+
* member event (`member.joined`/`member.updated` or the `call.joined`
|
|
13901
|
+
* roster) arrives, and never regresses afterwards.
|
|
13902
|
+
*
|
|
13903
|
+
* @throws {ParticipantNotReadyError} If the member state has not been
|
|
13904
|
+
* received yet (e.g. a participant first seen via `member.talking`) — an
|
|
13905
|
+
* empty call context can never address the member, so fail fast instead of
|
|
13906
|
+
* sending a doomed RPC.
|
|
13907
|
+
*/
|
|
13908
|
+
get target() {
|
|
13909
|
+
const { call_id, node_id } = this._state$.value;
|
|
13910
|
+
if (!call_id || !node_id) throw new ParticipantNotReadyError(this.id);
|
|
13911
|
+
return {
|
|
13912
|
+
member_id: this.id,
|
|
13913
|
+
call_id,
|
|
13914
|
+
node_id
|
|
13915
|
+
};
|
|
13916
|
+
}
|
|
13917
|
+
/**
|
|
13918
|
+
* Executes a member RPC against this participant, injecting its own
|
|
13919
|
+
* {@link target} as the target.
|
|
13920
|
+
*
|
|
13921
|
+
* @throws {ParticipantNotReadyError} Via {@link target}, when the
|
|
13922
|
+
* member state has not been received yet.
|
|
13923
|
+
*/
|
|
13924
|
+
async executeMethod(method, args) {
|
|
13925
|
+
return this.callExecuteMethod(this.target, method, args);
|
|
13926
|
+
}
|
|
13834
13927
|
/** Toggles the deafened state (mutes/unmutes incoming audio). */
|
|
13835
13928
|
async toggleDeaf() {
|
|
13836
|
-
|
|
13837
|
-
await this.executeMethod(this.id, method, {});
|
|
13929
|
+
await this.executeMethod(toggleDeafMethod(this.deaf), {});
|
|
13838
13930
|
}
|
|
13839
13931
|
/** Toggles the hand-raised state. */
|
|
13840
13932
|
async toggleHandraise() {
|
|
13841
|
-
await this.executeMethod(
|
|
13933
|
+
await this.executeMethod(toggleHandraiseMethod(this.handraised), {});
|
|
13842
13934
|
}
|
|
13843
13935
|
/** Mutes the participant's audio. */
|
|
13844
13936
|
async mute() {
|
|
13845
|
-
await this.executeMethod(
|
|
13937
|
+
await this.executeMethod("call.mute", { channels: ["audio"] });
|
|
13846
13938
|
}
|
|
13847
13939
|
/** Unmutes the participant's audio. */
|
|
13848
13940
|
async unmute() {
|
|
13849
|
-
await this.executeMethod(
|
|
13941
|
+
await this.executeMethod("call.unmute", { channels: ["audio"] });
|
|
13850
13942
|
}
|
|
13851
13943
|
/** Toggles the participant's audio mute state. */
|
|
13852
13944
|
async toggleMute() {
|
|
@@ -13854,11 +13946,11 @@ var Participant = class extends Destroyable {
|
|
|
13854
13946
|
}
|
|
13855
13947
|
/** Mutes the participant's video. */
|
|
13856
13948
|
async muteVideo() {
|
|
13857
|
-
await this.executeMethod(
|
|
13949
|
+
await this.executeMethod("call.mute", { channels: ["video"] });
|
|
13858
13950
|
}
|
|
13859
13951
|
/** Unmutes the participant's video. */
|
|
13860
13952
|
async unmuteVideo() {
|
|
13861
|
-
await this.executeMethod(
|
|
13953
|
+
await this.executeMethod("call.unmute", { channels: ["video"] });
|
|
13862
13954
|
}
|
|
13863
13955
|
/** Toggles the participant's video mute state. */
|
|
13864
13956
|
async toggleMuteVideo() {
|
|
@@ -13866,7 +13958,7 @@ var Participant = class extends Destroyable {
|
|
|
13866
13958
|
}
|
|
13867
13959
|
/** Toggles echo cancellation on the audio input. */
|
|
13868
13960
|
async toggleEchoCancellation() {
|
|
13869
|
-
await this.executeMethod(
|
|
13961
|
+
await this.executeMethod("call.audioflags.set", {
|
|
13870
13962
|
echo_cancellation: !this.echoCancellation,
|
|
13871
13963
|
auto_gain: this.autoGain,
|
|
13872
13964
|
noise_suppression: this.noiseSuppression
|
|
@@ -13874,7 +13966,7 @@ var Participant = class extends Destroyable {
|
|
|
13874
13966
|
}
|
|
13875
13967
|
/** Toggles automatic gain control on the audio input. */
|
|
13876
13968
|
async toggleAudioInputAutoGain() {
|
|
13877
|
-
await this.executeMethod(
|
|
13969
|
+
await this.executeMethod("call.audioflags.set", {
|
|
13878
13970
|
echo_cancellation: this.echoCancellation,
|
|
13879
13971
|
auto_gain: !this.autoGain,
|
|
13880
13972
|
noise_suppression: this.noiseSuppression
|
|
@@ -13882,14 +13974,15 @@ var Participant = class extends Destroyable {
|
|
|
13882
13974
|
}
|
|
13883
13975
|
/** Toggles noise suppression on the audio input. */
|
|
13884
13976
|
async toggleNoiseSuppression() {
|
|
13885
|
-
await this.executeMethod(
|
|
13977
|
+
await this.executeMethod("call.audioflags.set", {
|
|
13886
13978
|
echo_cancellation: this.echoCancellation,
|
|
13887
13979
|
auto_gain: this.autoGain,
|
|
13888
13980
|
noise_suppression: !this.noiseSuppression
|
|
13889
13981
|
});
|
|
13890
13982
|
}
|
|
13983
|
+
/** Toggles low-bitrate mode for this participant's media. */
|
|
13891
13984
|
async toggleLowbitrate() {
|
|
13892
|
-
|
|
13985
|
+
await this.executeMethod("call.lowbitrate.set", { lowbitrate: !this.lowbitrate });
|
|
13893
13986
|
}
|
|
13894
13987
|
/**
|
|
13895
13988
|
* Adjusts the **conference-only** microphone energy gate / sensitivity level
|
|
@@ -13905,7 +13998,7 @@ var Participant = class extends Destroyable {
|
|
|
13905
13998
|
* (integer, larger values are more sensitive).
|
|
13906
13999
|
*/
|
|
13907
14000
|
async setAudioInputSensitivity(value) {
|
|
13908
|
-
await this.executeMethod(
|
|
14001
|
+
await this.executeMethod("call.microphone.sensitivity.set", { sensitivity: value });
|
|
13909
14002
|
}
|
|
13910
14003
|
/**
|
|
13911
14004
|
* Sets the **server-side** microphone volume on this participant's bridged
|
|
@@ -13918,7 +14011,7 @@ var Participant = class extends Destroyable {
|
|
|
13918
14011
|
* @param value - Volume level (0-100).
|
|
13919
14012
|
*/
|
|
13920
14013
|
async setAudioInputVolume(value) {
|
|
13921
|
-
await this.executeMethod(
|
|
14014
|
+
await this.executeMethod("call.microphone.volume.set", { volume: value });
|
|
13922
14015
|
}
|
|
13923
14016
|
/**
|
|
13924
14017
|
* Sets the **server-side** speaker volume on this participant's bridged call
|
|
@@ -13932,28 +14025,31 @@ var Participant = class extends Destroyable {
|
|
|
13932
14025
|
* @param value - Volume level (0-100).
|
|
13933
14026
|
*/
|
|
13934
14027
|
async setAudioOutputVolume(value) {
|
|
13935
|
-
await this.executeMethod(
|
|
14028
|
+
await this.executeMethod("call.speaker.volume.set", { volume: value });
|
|
13936
14029
|
}
|
|
13937
14030
|
/**
|
|
13938
14031
|
* Sets the participant's position in the video layout.
|
|
14032
|
+
*
|
|
14033
|
+
* Requires the `member.position` capability. The gateway requires a
|
|
14034
|
+
* `targets` array of `{ target, position }` entries (issue #19400). A
|
|
14035
|
+
* resolved promise does not guarantee a visible change: the backend silently
|
|
14036
|
+
* returns `200` (no-op) for non-conference targets.
|
|
14037
|
+
*
|
|
13939
14038
|
* @param value - The {@link VideoPosition} to assign (e.g. `'auto'`, `'reserved-0'`).
|
|
13940
14039
|
*/
|
|
13941
14040
|
async setPosition(value) {
|
|
13942
|
-
await this.executeMethod(
|
|
14041
|
+
await this.executeMethod("call.member.position.set", { targets: [{
|
|
14042
|
+
target: this.target,
|
|
14043
|
+
position: value
|
|
14044
|
+
}] });
|
|
13943
14045
|
}
|
|
13944
14046
|
/** Removes this participant from the call. */
|
|
13945
14047
|
async remove() {
|
|
13946
|
-
|
|
13947
|
-
const target = {
|
|
13948
|
-
member_id: this.id,
|
|
13949
|
-
call_id: state.call_id ?? "",
|
|
13950
|
-
node_id: state.node_id ?? ""
|
|
13951
|
-
};
|
|
13952
|
-
await this.executeMethod(target, "call.member.remove", {});
|
|
14048
|
+
await this.executeMethod("call.member.remove", { targets: [this.target] });
|
|
13953
14049
|
}
|
|
13954
14050
|
/** Ends the call for this participant. */
|
|
13955
14051
|
async end() {
|
|
13956
|
-
await this.executeMethod(
|
|
14052
|
+
await this.executeMethod("call.end", {});
|
|
13957
14053
|
}
|
|
13958
14054
|
/**
|
|
13959
14055
|
* Replaces custom metadata for this participant.
|
|
@@ -13973,7 +14069,7 @@ var Participant = class extends Destroyable {
|
|
|
13973
14069
|
}
|
|
13974
14070
|
/** Destroys the participant, releasing all subscriptions and references. */
|
|
13975
14071
|
destroy() {
|
|
13976
|
-
this.
|
|
14072
|
+
this.callExecuteMethod = void 0;
|
|
13977
14073
|
super.destroy();
|
|
13978
14074
|
}
|
|
13979
14075
|
};
|
|
@@ -13985,8 +14081,8 @@ var Participant = class extends Destroyable {
|
|
|
13985
14081
|
*/
|
|
13986
14082
|
var SelfParticipant = class extends Participant {
|
|
13987
14083
|
/** @internal */
|
|
13988
|
-
constructor(id,
|
|
13989
|
-
super(id,
|
|
14084
|
+
constructor(id, callExecuteMethod, vertoManager, deviceController) {
|
|
14085
|
+
super(id, callExecuteMethod, deviceController);
|
|
13990
14086
|
this.vertoManager = vertoManager;
|
|
13991
14087
|
this._studioAudio$ = this.createBehaviorSubject(false);
|
|
13992
14088
|
this.capabilities = new SelfCapabilities();
|
|
@@ -14010,7 +14106,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14010
14106
|
async enableStudioAudio() {
|
|
14011
14107
|
if (this._studioAudio$.value) return;
|
|
14012
14108
|
this._studioAudio$.next(true);
|
|
14013
|
-
await this.executeMethod(
|
|
14109
|
+
await this.executeMethod("call.audioflags.set", {
|
|
14014
14110
|
echo_cancellation: false,
|
|
14015
14111
|
auto_gain: false,
|
|
14016
14112
|
noise_suppression: false
|
|
@@ -14023,18 +14119,27 @@ var SelfParticipant = class extends Participant {
|
|
|
14023
14119
|
async disableStudioAudio() {
|
|
14024
14120
|
if (!this._studioAudio$.value) return;
|
|
14025
14121
|
this._studioAudio$.next(false);
|
|
14026
|
-
await this.executeMethod(
|
|
14122
|
+
await this.executeMethod("call.audioflags.set", {
|
|
14027
14123
|
echo_cancellation: true,
|
|
14028
14124
|
auto_gain: true,
|
|
14029
14125
|
noise_suppression: true
|
|
14030
14126
|
});
|
|
14031
14127
|
}
|
|
14032
|
-
/**
|
|
14128
|
+
/**
|
|
14129
|
+
* Starts sharing the local screen.
|
|
14130
|
+
*
|
|
14131
|
+
* The call is unaffected when acquisition fails.
|
|
14132
|
+
*
|
|
14133
|
+
* @throws The raw `getDisplayMedia` error. A dismissed picker or a
|
|
14134
|
+
* permission denial rejects with a `NotAllowedError` `DOMException` —
|
|
14135
|
+
* inspect `error.name` to tell benign cancels apart from real failures.
|
|
14136
|
+
*/
|
|
14033
14137
|
async startScreenShare() {
|
|
14034
14138
|
try {
|
|
14035
14139
|
await this.vertoManager.addScreenMedia();
|
|
14036
14140
|
} catch (error) {
|
|
14037
|
-
logger$
|
|
14141
|
+
logger$24.error("[Participant.startScreenShare] Screen share error:", error);
|
|
14142
|
+
throw error;
|
|
14038
14143
|
}
|
|
14039
14144
|
}
|
|
14040
14145
|
/** Observable of the current screen share status. */
|
|
@@ -14049,12 +14154,20 @@ var SelfParticipant = class extends Participant {
|
|
|
14049
14154
|
async stopScreenShare() {
|
|
14050
14155
|
return this.vertoManager.removeScreenMedia();
|
|
14051
14156
|
}
|
|
14052
|
-
/**
|
|
14157
|
+
/**
|
|
14158
|
+
* Adds an additional media input device to the call.
|
|
14159
|
+
*
|
|
14160
|
+
* The call is unaffected when acquisition fails.
|
|
14161
|
+
*
|
|
14162
|
+
* @throws The raw `getUserMedia` error (e.g. `NotAllowedError` on
|
|
14163
|
+
* permission denial) — inspect `error.name` to decide how to react.
|
|
14164
|
+
*/
|
|
14053
14165
|
async addAdditionalDevice(options) {
|
|
14054
14166
|
try {
|
|
14055
14167
|
await this.vertoManager.addInputDevice(options);
|
|
14056
14168
|
} catch (error) {
|
|
14057
|
-
logger$
|
|
14169
|
+
logger$24.error("[Participant.addAdditionalDevice] Additional device error:", error);
|
|
14170
|
+
throw error;
|
|
14058
14171
|
}
|
|
14059
14172
|
}
|
|
14060
14173
|
/** Removes an additional media input device by ID. */
|
|
@@ -14116,7 +14229,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14116
14229
|
*/
|
|
14117
14230
|
exitStudioModeIfActive() {
|
|
14118
14231
|
if (this._studioAudio$.value) {
|
|
14119
|
-
logger$
|
|
14232
|
+
logger$24.debug("[SelfParticipant] Exiting studio audio mode due to individual flag toggle");
|
|
14120
14233
|
this._studioAudio$.next(false);
|
|
14121
14234
|
}
|
|
14122
14235
|
}
|
|
@@ -14140,7 +14253,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14140
14253
|
try {
|
|
14141
14254
|
await super.mute();
|
|
14142
14255
|
} catch (error) {
|
|
14143
|
-
logger$
|
|
14256
|
+
logger$24.warn("[Participant.toggleAudioInput] Server Error while muting audio input, proceeding with local toggle anyway", error);
|
|
14144
14257
|
} finally {
|
|
14145
14258
|
this.vertoManager.muteMainAudioInputDevice();
|
|
14146
14259
|
}
|
|
@@ -14150,7 +14263,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14150
14263
|
try {
|
|
14151
14264
|
await super.unmute();
|
|
14152
14265
|
} catch (error) {
|
|
14153
|
-
logger$
|
|
14266
|
+
logger$24.warn("[Participant.toggleAudioInput] Server Error while unmuting audio input, proceeding with local toggle anyway", error);
|
|
14154
14267
|
} finally {
|
|
14155
14268
|
await this.vertoManager.unmuteMainAudioInputDevice();
|
|
14156
14269
|
}
|
|
@@ -14160,7 +14273,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14160
14273
|
try {
|
|
14161
14274
|
await super.muteVideo();
|
|
14162
14275
|
} catch (error) {
|
|
14163
|
-
logger$
|
|
14276
|
+
logger$24.warn("[Participant.toggleVideoInput] Server Error while muting video input, proceeding with local toggle anyway", error);
|
|
14164
14277
|
} finally {
|
|
14165
14278
|
this.vertoManager.muteMainVideoInputDevice();
|
|
14166
14279
|
}
|
|
@@ -14170,7 +14283,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14170
14283
|
try {
|
|
14171
14284
|
await super.unmuteVideo();
|
|
14172
14285
|
} catch (error) {
|
|
14173
|
-
logger$
|
|
14286
|
+
logger$24.warn("[Participant.toggleVideoInput] Server Error while unmuting video input, proceeding with local toggle anyway", error);
|
|
14174
14287
|
} finally {
|
|
14175
14288
|
await this.vertoManager.unmuteMainVideoInputDevice();
|
|
14176
14289
|
}
|
|
@@ -14375,7 +14488,7 @@ function filterAs(predicate, resultPath) {
|
|
|
14375
14488
|
//#endregion
|
|
14376
14489
|
//#region src/operators/throwOnRPCError.ts
|
|
14377
14490
|
var import_cjs$21 = require_cjs();
|
|
14378
|
-
const logger$
|
|
14491
|
+
const logger$23 = getLogger();
|
|
14379
14492
|
/**
|
|
14380
14493
|
* RxJS operator that throws a {@link JSONRPCError} when the RPC response contains an error.
|
|
14381
14494
|
* Passes successful responses through unchanged.
|
|
@@ -14383,14 +14496,14 @@ const logger$22 = getLogger();
|
|
|
14383
14496
|
function throwOnRPCError() {
|
|
14384
14497
|
return (0, import_cjs$21.map)((response) => {
|
|
14385
14498
|
if (response.error) {
|
|
14386
|
-
logger$
|
|
14499
|
+
logger$23.error("[throwOnRPCError] RPC error response:", {
|
|
14387
14500
|
code: response.error.code,
|
|
14388
14501
|
message: response.error.message,
|
|
14389
14502
|
data: response.error.data
|
|
14390
14503
|
});
|
|
14391
14504
|
throw new JSONRPCError(response.error.code, response.error.message, response.error.data);
|
|
14392
14505
|
}
|
|
14393
|
-
logger$
|
|
14506
|
+
logger$23.debug("[throwOnRPCError] RPC successful response:", response);
|
|
14394
14507
|
return response;
|
|
14395
14508
|
});
|
|
14396
14509
|
}
|
|
@@ -14398,7 +14511,7 @@ function throwOnRPCError() {
|
|
|
14398
14511
|
//#endregion
|
|
14399
14512
|
//#region src/managers/CallEventsManager.ts
|
|
14400
14513
|
var import_cjs$20 = require_cjs();
|
|
14401
|
-
const logger$
|
|
14514
|
+
const logger$22 = getLogger();
|
|
14402
14515
|
const initialSessionState = {};
|
|
14403
14516
|
/** @internal */
|
|
14404
14517
|
var CallEventsManager = class extends Destroyable {
|
|
@@ -14502,7 +14615,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14502
14615
|
}
|
|
14503
14616
|
initSubscriptions() {
|
|
14504
14617
|
this.subscribeTo(this.callJoinedEvent$, (callJoinedEvent) => {
|
|
14505
|
-
logger$
|
|
14618
|
+
logger$22.debug("[CallEventsManager] Handling call.joined event for call/session IDs:", {
|
|
14506
14619
|
callId: callJoinedEvent.call_id,
|
|
14507
14620
|
roomSessionId: callJoinedEvent.room_session_id
|
|
14508
14621
|
});
|
|
@@ -14529,19 +14642,19 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14529
14642
|
if (this._self$.value?.capabilities.setLayout) this.updateLayouts();
|
|
14530
14643
|
});
|
|
14531
14644
|
this.subscribeTo(this.memberUpdates$, (member) => {
|
|
14532
|
-
logger$
|
|
14645
|
+
logger$22.debug("[CallEventsManager] Handling member update event for member ID:", member);
|
|
14533
14646
|
this.upsertParticipant(member);
|
|
14534
14647
|
});
|
|
14535
14648
|
this.subscribeTo(this.webRtcCallSession.memberLeft$, (memberLeftEvent) => {
|
|
14536
|
-
logger$
|
|
14649
|
+
logger$22.debug("[CallEventsManager] Handling member.left event for member ID:", memberLeftEvent.member.member_id);
|
|
14537
14650
|
const participants = { ...this._participants$.value };
|
|
14538
14651
|
if (memberLeftEvent.member.member_id in participants) {
|
|
14539
14652
|
delete participants[memberLeftEvent.member.member_id];
|
|
14540
14653
|
this._participants$.next(participants);
|
|
14541
|
-
} else logger$
|
|
14654
|
+
} else logger$22.warn(`[CallEventsManager] Received member.left event for unknown member ID: ${memberLeftEvent.member.member_id}`);
|
|
14542
14655
|
});
|
|
14543
14656
|
this.subscribeTo(this.webRtcCallSession.callUpdated$, (callUpdatedEvent) => {
|
|
14544
|
-
logger$
|
|
14657
|
+
logger$22.debug("[CallEventsManager] Handling call.updated event:", callUpdatedEvent);
|
|
14545
14658
|
const roomSession = callUpdatedEvent.room_session;
|
|
14546
14659
|
this._sessionState$.next({
|
|
14547
14660
|
...this._sessionState$.value,
|
|
@@ -14556,7 +14669,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14556
14669
|
});
|
|
14557
14670
|
});
|
|
14558
14671
|
this.subscribeTo(this.layoutChangedEvent$, (layoutChangedEvent) => {
|
|
14559
|
-
logger$
|
|
14672
|
+
logger$22.debug("[CallEventsManager] Handling layout.changed event:", layoutChangedEvent);
|
|
14560
14673
|
this._sessionState$.next({
|
|
14561
14674
|
...this._sessionState$.value,
|
|
14562
14675
|
layout_name: layoutChangedEvent.id,
|
|
@@ -14566,10 +14679,10 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14566
14679
|
});
|
|
14567
14680
|
}
|
|
14568
14681
|
updateParticipantPositions(layoutChangedEvent) {
|
|
14569
|
-
if (Object.keys(this._participants$.value).length > 0 && !layoutChangedEvent.layers.some((layer) => !!layer.member_id)) logger$
|
|
14682
|
+
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.");
|
|
14570
14683
|
layoutChangedEvent.layers.filter((layer) => !!layer.member_id).filter((layer) => {
|
|
14571
14684
|
if (!(layer.member_id in this._participants$.value)) {
|
|
14572
|
-
logger$
|
|
14685
|
+
logger$22.warn(`[CallEventsManager] Skipping layout layer for unknown member_id: ${layer.member_id}`);
|
|
14573
14686
|
return false;
|
|
14574
14687
|
}
|
|
14575
14688
|
return true;
|
|
@@ -14592,7 +14705,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14592
14705
|
layouts: response.result.layouts
|
|
14593
14706
|
});
|
|
14594
14707
|
}).catch((error) => {
|
|
14595
|
-
logger$
|
|
14708
|
+
logger$22.error("[CallEventsManager] Error fetching layouts:", error);
|
|
14596
14709
|
});
|
|
14597
14710
|
}
|
|
14598
14711
|
updateParticipants(members) {
|
|
@@ -14608,7 +14721,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14608
14721
|
}
|
|
14609
14722
|
const participant = this._participants$.value[member.member_id];
|
|
14610
14723
|
const oldValue = participant.value;
|
|
14611
|
-
logger$
|
|
14724
|
+
logger$22.debug("[CallEventsManager] Updating participant:", member.member_id, {
|
|
14612
14725
|
oldValue,
|
|
14613
14726
|
newValue: member
|
|
14614
14727
|
});
|
|
@@ -14621,17 +14734,17 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14621
14734
|
}
|
|
14622
14735
|
get callJoinedEvent$() {
|
|
14623
14736
|
return this.cachedObservable("callJoinedEvent$", () => this.webRtcCallSession.callEvent$.pipe((0, import_cjs$20.filter)(isCallJoinedPayload), (0, import_cjs$20.tap)((event) => {
|
|
14624
|
-
logger$
|
|
14737
|
+
logger$22.debug("[CallEventsManager] Call joined event:", event);
|
|
14625
14738
|
})));
|
|
14626
14739
|
}
|
|
14627
14740
|
get layoutChangedEvent$() {
|
|
14628
14741
|
return this.cachedObservable("layoutChangedEvent$", () => this.webRtcCallSession.callEvent$.pipe(filterAs(isLayoutChangedPayload, "layout"), (0, import_cjs$20.tap)((event) => {
|
|
14629
|
-
logger$
|
|
14742
|
+
logger$22.debug("[CallEventsManager] Layout changed event:", event);
|
|
14630
14743
|
})));
|
|
14631
14744
|
}
|
|
14632
14745
|
get memberUpdates$() {
|
|
14633
14746
|
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) => {
|
|
14634
|
-
logger$
|
|
14747
|
+
logger$22.debug("[CallEventsManager] Member update event:", event);
|
|
14635
14748
|
})));
|
|
14636
14749
|
}
|
|
14637
14750
|
destroy() {
|
|
@@ -14888,7 +15001,7 @@ function appendStereoParams(fmtpLine, maxBitrate) {
|
|
|
14888
15001
|
//#endregion
|
|
14889
15002
|
//#region src/controllers/ICEGatheringController.ts
|
|
14890
15003
|
var import_cjs$19 = require_cjs();
|
|
14891
|
-
const logger$
|
|
15004
|
+
const logger$21 = getLogger();
|
|
14892
15005
|
var ICEGatheringController = class extends Destroyable {
|
|
14893
15006
|
constructor(peerConnection, peerConnectionControllerNegotiating$, options = {}) {
|
|
14894
15007
|
super();
|
|
@@ -14896,23 +15009,23 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14896
15009
|
this.peerConnectionControllerNegotiating$ = peerConnectionControllerNegotiating$;
|
|
14897
15010
|
this.onicegatheringstatechangeHandler = () => {
|
|
14898
15011
|
const { iceGatheringState } = this.peerConnection;
|
|
14899
|
-
logger$
|
|
15012
|
+
logger$21.debug(`[ICEGatheringController] ICE gathering state changed to: ${iceGatheringState}`);
|
|
14900
15013
|
if (iceGatheringState === "gathering") this._iceCandidatesState.next({
|
|
14901
15014
|
state: "gathering",
|
|
14902
15015
|
validSDP: false
|
|
14903
15016
|
});
|
|
14904
15017
|
};
|
|
14905
15018
|
this.onicecandidateHandler = (event) => {
|
|
14906
|
-
logger$
|
|
15019
|
+
logger$21.debug("[ICEGatheringController] ICE candidate event received:", event.candidate);
|
|
14907
15020
|
this.removeTimer("iceCandidateTimer");
|
|
14908
15021
|
if (event.candidate) this.iceCandidateTimer = setTimeout(() => {
|
|
14909
15022
|
if (this.peerConnection.iceGatheringState !== "complete") {
|
|
14910
|
-
logger$
|
|
15023
|
+
logger$21.warn("[ICEGatheringController] ICE candidate timeout, using current SDP");
|
|
14911
15024
|
this.handleICECandidateTimeout();
|
|
14912
15025
|
}
|
|
14913
15026
|
}, this.iceCandidateTimeout);
|
|
14914
15027
|
else {
|
|
14915
|
-
logger$
|
|
15028
|
+
logger$21.debug("[ICEGatheringController] ICE gathering completed: null candidate received");
|
|
14916
15029
|
this.removeTimer("iceGatheringTimer");
|
|
14917
15030
|
this.handleICEGatheringComplete();
|
|
14918
15031
|
}
|
|
@@ -14930,7 +15043,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14930
15043
|
this.setupEventListeners();
|
|
14931
15044
|
this.iceGatheringTimer = setTimeout(() => {
|
|
14932
15045
|
if (this.peerConnection.iceGatheringState !== "complete") {
|
|
14933
|
-
logger$
|
|
15046
|
+
logger$21.warn("[ICEGatheringController] ICE gathering timeout, using current SDP");
|
|
14934
15047
|
this.handleICEGatheringTimeout();
|
|
14935
15048
|
}
|
|
14936
15049
|
}, this.iceGatheringTimeout);
|
|
@@ -14957,9 +15070,9 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14957
15070
|
this.relayOnly = value;
|
|
14958
15071
|
}
|
|
14959
15072
|
handleICEGatheringComplete() {
|
|
14960
|
-
logger$
|
|
14961
|
-
logger$
|
|
14962
|
-
logger$
|
|
15073
|
+
logger$21.debug("[ICEGatheringController] Handling ICE gathering complete");
|
|
15074
|
+
logger$21.debug(`[ICEGatheringController] Checking ICE gathering state: ${this.peerConnection.iceGatheringState}`);
|
|
15075
|
+
logger$21.debug("[ICEGatheringController] ICE gathering complete");
|
|
14963
15076
|
this._iceCandidatesState.next({
|
|
14964
15077
|
state: "complete",
|
|
14965
15078
|
validSDP: this.hasValidLocalDescriptionSDP
|
|
@@ -14975,21 +15088,21 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14975
15088
|
this.removeTimer("iceGatheringTimer");
|
|
14976
15089
|
const validSDP = this.hasValidLocalDescriptionSDP;
|
|
14977
15090
|
if (validSDP) {
|
|
14978
|
-
logger$
|
|
15091
|
+
logger$21.debug("[ICEGatheringController] Local SDP is valid");
|
|
14979
15092
|
this._iceCandidatesState.next({
|
|
14980
15093
|
state: "timeout",
|
|
14981
15094
|
validSDP
|
|
14982
15095
|
});
|
|
14983
15096
|
this.stopGathering();
|
|
14984
|
-
} else logger$
|
|
15097
|
+
} else logger$21.debug("### ICE gathering timeout\n", this.peerConnection.localDescription?.sdp);
|
|
14985
15098
|
}
|
|
14986
15099
|
handleICECandidateTimeout() {
|
|
14987
15100
|
if (this.iceCandidateTimer) this.removeTimer("iceCandidateTimer");
|
|
14988
|
-
logger$
|
|
15101
|
+
logger$21.warn("[ICEGatheringController] ICE candidate timeout");
|
|
14989
15102
|
const validSDP = this.hasValidLocalDescriptionSDP;
|
|
14990
15103
|
if (!validSDP && !this.relayOnly) this.restartICEGatheringWithRelayOnly();
|
|
14991
15104
|
else {
|
|
14992
|
-
logger$
|
|
15105
|
+
logger$21.debug("[ICEGatheringController] Using current SDP due to ICE candidate timeout");
|
|
14993
15106
|
this._iceCandidatesState.next({
|
|
14994
15107
|
state: "timeout",
|
|
14995
15108
|
validSDP
|
|
@@ -14998,7 +15111,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14998
15111
|
}
|
|
14999
15112
|
}
|
|
15000
15113
|
restartICEGatheringWithRelayOnly() {
|
|
15001
|
-
logger$
|
|
15114
|
+
logger$21.debug("[ICEGatheringController] Restarting ICE gathering with relay-only candidates");
|
|
15002
15115
|
this.relayOnly = true;
|
|
15003
15116
|
this.peerConnection.setConfiguration({
|
|
15004
15117
|
...this.peerConnection.getConfiguration(),
|
|
@@ -15013,7 +15126,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
15013
15126
|
}
|
|
15014
15127
|
}
|
|
15015
15128
|
clearAllTimers() {
|
|
15016
|
-
logger$
|
|
15129
|
+
logger$21.debug("[ICEGatheringController] Clearing all timers");
|
|
15017
15130
|
this.removeTimer("iceGatheringTimer");
|
|
15018
15131
|
this.removeTimer("iceCandidateTimer");
|
|
15019
15132
|
}
|
|
@@ -15022,7 +15135,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
15022
15135
|
this.peerConnection.removeEventListener("icecandidate", this.onicecandidateHandler);
|
|
15023
15136
|
}
|
|
15024
15137
|
destroy() {
|
|
15025
|
-
logger$
|
|
15138
|
+
logger$21.debug("[ICEGatheringController] Destroying ICEGatheringController");
|
|
15026
15139
|
this.clearAllTimers();
|
|
15027
15140
|
this.removeEventListeners();
|
|
15028
15141
|
super.destroy();
|
|
@@ -15032,7 +15145,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
15032
15145
|
//#endregion
|
|
15033
15146
|
//#region src/controllers/LocalAudioPipeline.ts
|
|
15034
15147
|
var import_cjs$18 = require_cjs();
|
|
15035
|
-
const logger$
|
|
15148
|
+
const logger$20 = getLogger();
|
|
15036
15149
|
/**
|
|
15037
15150
|
* Web Audio pipeline for the local microphone stream.
|
|
15038
15151
|
*
|
|
@@ -15144,7 +15257,7 @@ var LocalAudioPipeline = class extends Destroyable {
|
|
|
15144
15257
|
try {
|
|
15145
15258
|
this._inputSource.disconnect();
|
|
15146
15259
|
} catch (error) {
|
|
15147
|
-
logger$
|
|
15260
|
+
logger$20.debug("[LocalAudioPipeline] input disconnect warning:", error);
|
|
15148
15261
|
}
|
|
15149
15262
|
this._inputSource = null;
|
|
15150
15263
|
}
|
|
@@ -15154,7 +15267,7 @@ var LocalAudioPipeline = class extends Destroyable {
|
|
|
15154
15267
|
this._inputSource = this._audioContext.createMediaStreamSource(this._inputStream);
|
|
15155
15268
|
this._inputSource.connect(this._gainNode);
|
|
15156
15269
|
if (this._audioContext.state === "suspended") this._audioContext.resume().catch((error) => {
|
|
15157
|
-
logger$
|
|
15270
|
+
logger$20.warn("[LocalAudioPipeline] AudioContext resume failed:", error);
|
|
15158
15271
|
});
|
|
15159
15272
|
}
|
|
15160
15273
|
destroy() {
|
|
@@ -15169,7 +15282,7 @@ var LocalAudioPipeline = class extends Destroyable {
|
|
|
15169
15282
|
this._analyser.disconnect();
|
|
15170
15283
|
} catch {}
|
|
15171
15284
|
this._audioContext.close().catch((error) => {
|
|
15172
|
-
logger$
|
|
15285
|
+
logger$20.debug("[LocalAudioPipeline] audio context close warning:", error);
|
|
15173
15286
|
});
|
|
15174
15287
|
super.destroy();
|
|
15175
15288
|
}
|
|
@@ -15196,7 +15309,7 @@ var LocalAudioPipeline = class extends Destroyable {
|
|
|
15196
15309
|
//#endregion
|
|
15197
15310
|
//#region src/controllers/LocalStreamController.ts
|
|
15198
15311
|
var import_cjs$17 = require_cjs();
|
|
15199
|
-
const logger$
|
|
15312
|
+
const logger$19 = getLogger();
|
|
15200
15313
|
var LocalStreamController = class extends Destroyable {
|
|
15201
15314
|
constructor(options) {
|
|
15202
15315
|
super();
|
|
@@ -15234,28 +15347,30 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15234
15347
|
* Build the local media stream based on the provided options.
|
|
15235
15348
|
*/
|
|
15236
15349
|
async buildLocalStream() {
|
|
15237
|
-
logger$
|
|
15350
|
+
logger$19.debug("[LocalStreamController] Building local media stream.");
|
|
15238
15351
|
let stream;
|
|
15239
15352
|
if (this.options.inputAudioStream ?? this.options.inputVideoStream) {
|
|
15240
15353
|
const tracks = [...this.options.inputAudioStream?.getTracks() ?? [], ...this.options.inputVideoStream?.getTracks() ?? []];
|
|
15241
15354
|
stream = new MediaStream(tracks);
|
|
15242
15355
|
} else if (this.options.propose === "screenshare") {
|
|
15243
|
-
logger$
|
|
15356
|
+
logger$19.debug("[LocalStreamController] Requesting display media for screen sharing with audio:", Boolean(this.options.inputAudioDeviceConstraints));
|
|
15244
15357
|
stream = await this.options.getDisplayMedia({
|
|
15245
15358
|
video: true,
|
|
15246
15359
|
audio: Boolean(this.options.inputAudioDeviceConstraints)
|
|
15247
15360
|
});
|
|
15248
|
-
logger$
|
|
15361
|
+
logger$19.debug("[LocalStreamController] Screen share media obtained:", stream);
|
|
15249
15362
|
} else {
|
|
15250
15363
|
const constraints = {
|
|
15251
15364
|
audio: this.options.inputAudioDeviceConstraints,
|
|
15252
15365
|
video: this.options.inputVideoDeviceConstraints
|
|
15253
15366
|
};
|
|
15254
|
-
logger$
|
|
15367
|
+
logger$19.debug("[LocalStreamController] Requesting user media with constraints:", constraints);
|
|
15255
15368
|
stream = await this.options.getUserMedia(constraints);
|
|
15256
|
-
logger$
|
|
15369
|
+
logger$19.debug("[LocalStreamController] User media obtained:", stream);
|
|
15257
15370
|
}
|
|
15258
15371
|
this._localStream$.next(stream);
|
|
15372
|
+
this._localAudioTracks$.next(stream.getAudioTracks());
|
|
15373
|
+
this._localVideoTracks$.next(stream.getVideoTracks());
|
|
15259
15374
|
return stream;
|
|
15260
15375
|
}
|
|
15261
15376
|
/**
|
|
@@ -15270,7 +15385,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15270
15385
|
this._localStream$.next(localStream);
|
|
15271
15386
|
if (track.kind === "video") this._localVideoTracks$.next(localStream.getVideoTracks());
|
|
15272
15387
|
else this._localAudioTracks$.next(localStream.getAudioTracks());
|
|
15273
|
-
logger$
|
|
15388
|
+
logger$19.debug(`[LocalStreamController] ${track.kind} track added:`, track.id);
|
|
15274
15389
|
return localStream;
|
|
15275
15390
|
}
|
|
15276
15391
|
/**
|
|
@@ -15282,7 +15397,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15282
15397
|
const stream = this._localStream$.value;
|
|
15283
15398
|
const track = stream?.getTracks().find((t) => t.id === trackId);
|
|
15284
15399
|
if (!track) {
|
|
15285
|
-
logger$
|
|
15400
|
+
logger$19.debug(`[LocalStreamController] track not found: ${trackId}`);
|
|
15286
15401
|
return;
|
|
15287
15402
|
}
|
|
15288
15403
|
track.removeEventListener("ended", this.mediaTrackEndedHandler);
|
|
@@ -15291,7 +15406,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15291
15406
|
this._localStream$.next(stream);
|
|
15292
15407
|
if (track.kind === "video") this._localVideoTracks$.next(stream?.getVideoTracks() ?? []);
|
|
15293
15408
|
else this._localAudioTracks$.next(stream?.getAudioTracks() ?? []);
|
|
15294
|
-
logger$
|
|
15409
|
+
logger$19.debug(`[LocalStreamController] ${track.kind} track removed:`, trackId);
|
|
15295
15410
|
return track;
|
|
15296
15411
|
}
|
|
15297
15412
|
/**
|
|
@@ -15326,7 +15441,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15326
15441
|
*/
|
|
15327
15442
|
stopAllTracks() {
|
|
15328
15443
|
this._localStream$.value?.getTracks().forEach((track) => {
|
|
15329
|
-
logger$
|
|
15444
|
+
logger$19.debug(`[LocalStreamController] Stopping local track: ${track.kind}`);
|
|
15330
15445
|
track.removeEventListener("ended", this.mediaTrackEndedHandler);
|
|
15331
15446
|
track.stop();
|
|
15332
15447
|
});
|
|
@@ -15342,7 +15457,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15342
15457
|
|
|
15343
15458
|
//#endregion
|
|
15344
15459
|
//#region src/controllers/TransceiverController.ts
|
|
15345
|
-
const logger$
|
|
15460
|
+
const logger$18 = getLogger();
|
|
15346
15461
|
const getDirection = (send, recv) => {
|
|
15347
15462
|
if (send && recv) return "sendrecv";
|
|
15348
15463
|
else if (send && !recv) return "sendonly";
|
|
@@ -15423,6 +15538,12 @@ var TransceiverController = class extends Destroyable {
|
|
|
15423
15538
|
scaleResolutionDownBy: Number(rid) * 6 || 1
|
|
15424
15539
|
}));
|
|
15425
15540
|
}
|
|
15541
|
+
/**
|
|
15542
|
+
* Resolve the current MediaTrackConstraints for an input kind, normalising
|
|
15543
|
+
* boolean shorthand to an empty object. Public so the surrounding
|
|
15544
|
+
* RTCPeerConnectionController can drive its own pipeline-aware getUserMedia
|
|
15545
|
+
* call with the same effective constraints the transceiver would have used.
|
|
15546
|
+
*/
|
|
15426
15547
|
getConstraintsFor(kind) {
|
|
15427
15548
|
const constraints = kind === "audio" ? this.inputAudioDeviceConstraints : this.inputVideoDeviceConstraints;
|
|
15428
15549
|
return typeof constraints === "boolean" ? {} : constraints;
|
|
@@ -15444,7 +15565,7 @@ var TransceiverController = class extends Destroyable {
|
|
|
15444
15565
|
sendEncodings: isAudio ? void 0 : this.sendEncodings,
|
|
15445
15566
|
streams: direction === "recvonly" ? void 0 : [localStream]
|
|
15446
15567
|
};
|
|
15447
|
-
logger$
|
|
15568
|
+
logger$18.debug(`[TransceiverController] Setting up transceiver sender for local ${track.kind} track:`, {
|
|
15448
15569
|
transceiver,
|
|
15449
15570
|
transceiverParams
|
|
15450
15571
|
});
|
|
@@ -15452,11 +15573,11 @@ var TransceiverController = class extends Destroyable {
|
|
|
15452
15573
|
await transceiver.sender.replaceTrack(track);
|
|
15453
15574
|
transceiver.direction = transceiverParams.direction;
|
|
15454
15575
|
if (transceiverParams.streams?.some((stream) => Boolean(stream))) {
|
|
15455
|
-
logger$
|
|
15576
|
+
logger$18.debug(`[TransceiverController] Setting streams for transceiver sender for local ${track.kind} track:`, transceiverParams.streams);
|
|
15456
15577
|
transceiver.sender.setStreams(...transceiverParams.streams);
|
|
15457
15578
|
}
|
|
15458
15579
|
} else {
|
|
15459
|
-
logger$
|
|
15580
|
+
logger$18.debug(`[TransceiverController] Adding new transceiver for local ${track.kind} track:`, track.id);
|
|
15460
15581
|
this.peerConnection.addTransceiver(track, transceiverParams);
|
|
15461
15582
|
}
|
|
15462
15583
|
}
|
|
@@ -15470,13 +15591,13 @@ var TransceiverController = class extends Destroyable {
|
|
|
15470
15591
|
if (options.updateTransceiverDirection) transceiver.direction = "inactive";
|
|
15471
15592
|
}
|
|
15472
15593
|
} catch (error) {
|
|
15473
|
-
logger$
|
|
15594
|
+
logger$18.error("[TransceiverController] stopTrackSender error", kind, error);
|
|
15474
15595
|
this.options.onError?.(new MediaTrackError("stopTrackSender", kind, error));
|
|
15475
15596
|
}
|
|
15476
15597
|
}
|
|
15477
15598
|
async restoreTrackSender(kind) {
|
|
15478
15599
|
try {
|
|
15479
|
-
logger$
|
|
15600
|
+
logger$18.debug("[TransceiverController] restoreTrackSender called", kind);
|
|
15480
15601
|
const constraints = {};
|
|
15481
15602
|
const transceivers = this.transceiverByKind(kind);
|
|
15482
15603
|
for (const transceiver of transceivers) {
|
|
@@ -15486,23 +15607,23 @@ var TransceiverController = class extends Destroyable {
|
|
|
15486
15607
|
if (trackKind === "audio" || trackKind === "video") constraints[trackKind] = this.getConstraintsFor(trackKind);
|
|
15487
15608
|
}
|
|
15488
15609
|
}
|
|
15489
|
-
logger$
|
|
15610
|
+
logger$18.debug("[TransceiverController] restoreTrackSender constraints:", constraints);
|
|
15490
15611
|
if (Object.keys(constraints).length === 0) {
|
|
15491
|
-
logger$
|
|
15612
|
+
logger$18.warn("[TransceiverController] restoreTrackSender: no tracks need restoration", kind);
|
|
15492
15613
|
return;
|
|
15493
15614
|
}
|
|
15494
15615
|
const newTracks = (await this.options.getUserMedia(constraints)).getTracks();
|
|
15495
|
-
logger$
|
|
15616
|
+
logger$18.debug("[TransceiverController] restoreTrackSender new tracks:", newTracks);
|
|
15496
15617
|
for (const newTrack of newTracks) {
|
|
15497
15618
|
this.options.localStreamController.addTrack(newTrack);
|
|
15498
15619
|
const trackKind = newTrack.kind;
|
|
15499
15620
|
const transceiverOfKind = this.transceiverByKind(trackKind)[0];
|
|
15500
15621
|
transceiverOfKind.direction = trackKind === "audio" ? this.audioDirection : this.videoDirection;
|
|
15501
|
-
logger$
|
|
15622
|
+
logger$18.debug("[TransceiverController] restoreTrackSender setting direction for", trackKind, transceiverOfKind.direction);
|
|
15502
15623
|
await transceiverOfKind.sender.replaceTrack(newTrack);
|
|
15503
15624
|
}
|
|
15504
15625
|
} catch (error) {
|
|
15505
|
-
logger$
|
|
15626
|
+
logger$18.error("[TransceiverController] restoreTrackSender error", kind, error);
|
|
15506
15627
|
this.options.onError?.(new MediaTrackError("restoreTrackSender", kind, error));
|
|
15507
15628
|
}
|
|
15508
15629
|
}
|
|
@@ -15543,14 +15664,14 @@ var TransceiverController = class extends Destroyable {
|
|
|
15543
15664
|
};
|
|
15544
15665
|
try {
|
|
15545
15666
|
await track.applyConstraints(constraintsToApply);
|
|
15546
|
-
logger$
|
|
15547
|
-
logger$
|
|
15667
|
+
logger$18.debug(`[TransceiverController] Updated ${kind} sender constraints:`, constraintsToApply);
|
|
15668
|
+
logger$18.debug(`[TransceiverController] Updated ${kind} sender constraints:`, track.getConstraints());
|
|
15548
15669
|
} catch (error) {
|
|
15549
|
-
logger$
|
|
15670
|
+
logger$18.warn(`[TransceiverController] applyConstraints failed for ${kind} track ${track.id}, attempting track replacement fallback:`, error);
|
|
15550
15671
|
try {
|
|
15551
15672
|
await this.replaceTrackFallback(sender, track, kind, constraintsToApply);
|
|
15552
15673
|
} catch (fallbackError) {
|
|
15553
|
-
logger$
|
|
15674
|
+
logger$18.warn(`[TransceiverController] Track replacement fallback also failed for ${kind} track:`, fallbackError);
|
|
15554
15675
|
this.options.onError?.(new MediaTrackError("updateSendersConstraints", kind, fallbackError));
|
|
15555
15676
|
}
|
|
15556
15677
|
}
|
|
@@ -15578,7 +15699,7 @@ var TransceiverController = class extends Destroyable {
|
|
|
15578
15699
|
if (!newTrack) throw new MediaTrackError("replaceTrackFallback", kind, /* @__PURE__ */ new Error("getUserMedia returned no track of the requested kind"));
|
|
15579
15700
|
await sender.replaceTrack(newTrack);
|
|
15580
15701
|
this.options.localStreamController.addTrack(newTrack);
|
|
15581
|
-
logger$
|
|
15702
|
+
logger$18.debug(`[TransceiverController] Track replacement fallback succeeded for ${kind}. New track: ${newTrack.id}`);
|
|
15582
15703
|
}
|
|
15583
15704
|
getMediaDirections() {
|
|
15584
15705
|
if (this.peerConnection.connectionState === "connected") return this.peerConnection.getTransceivers().reduce((acc, transceiver) => {
|
|
@@ -15609,7 +15730,7 @@ var TransceiverController = class extends Destroyable {
|
|
|
15609
15730
|
//#endregion
|
|
15610
15731
|
//#region src/controllers/RTCPeerConnectionController.ts
|
|
15611
15732
|
var import_cjs$16 = require_cjs();
|
|
15612
|
-
const logger$
|
|
15733
|
+
const logger$17 = getLogger();
|
|
15613
15734
|
var RTCPeerConnectionController = class extends Destroyable {
|
|
15614
15735
|
constructor(options = {}, remoteSessionDescription, deviceController) {
|
|
15615
15736
|
super();
|
|
@@ -15625,43 +15746,43 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15625
15746
|
this.oniceconnectionstatechangeHandler = () => {
|
|
15626
15747
|
if (this.peerConnection) {
|
|
15627
15748
|
const { iceConnectionState } = this.peerConnection;
|
|
15628
|
-
logger$
|
|
15749
|
+
logger$17.debug(`[RTCPeerConnectionController] ICE connection state changed to: ${iceConnectionState}`);
|
|
15629
15750
|
this._iceConnectionState$.next(this.peerConnection.iceConnectionState);
|
|
15630
15751
|
}
|
|
15631
15752
|
};
|
|
15632
15753
|
this.onconnectionstatechangeHandler = () => {
|
|
15633
15754
|
if (this.peerConnection) {
|
|
15634
15755
|
const { connectionState } = this.peerConnection;
|
|
15635
|
-
logger$
|
|
15756
|
+
logger$17.debug(`[RTCPeerConnectionController] Connection state changed to: ${connectionState}`);
|
|
15636
15757
|
if (connectionState === "connected") this.removeConnectionTimer();
|
|
15637
15758
|
this._connectionState$.next(this.peerConnection.connectionState);
|
|
15638
15759
|
}
|
|
15639
15760
|
};
|
|
15640
15761
|
this.onsignalingstatechangeHandler = () => {
|
|
15641
|
-
logger$
|
|
15762
|
+
logger$17.debug(`[RTCPeerConnectionController] Signaling state changed to: ${this.peerConnection?.signalingState}`);
|
|
15642
15763
|
};
|
|
15643
15764
|
this.onicegatheringstatechangeHandler = () => {
|
|
15644
15765
|
if (this.peerConnection) this._iceGatheringState$.next(this.peerConnection.iceGatheringState);
|
|
15645
15766
|
};
|
|
15646
15767
|
this.onnegotiationneededHandler = (event) => {
|
|
15647
|
-
logger$
|
|
15768
|
+
logger$17.debug("[RTCPeerConnectionController] Negotiation needed event received.", event);
|
|
15648
15769
|
this.negotiationNeeded$.next();
|
|
15649
15770
|
};
|
|
15650
15771
|
this.updateSelectedInputDevice = async (kind, deviceInfo) => {
|
|
15651
15772
|
try {
|
|
15652
15773
|
const { localStream } = this;
|
|
15653
15774
|
if (!localStream) {
|
|
15654
|
-
logger$
|
|
15775
|
+
logger$17.warn("[RTCPeerConnectionController] No local stream available to update input device.");
|
|
15655
15776
|
return;
|
|
15656
15777
|
}
|
|
15657
|
-
logger$
|
|
15778
|
+
logger$17.debug(`[RTCPeerConnectionController] Updating selected ${kind} input device:`, localStream.getTracks());
|
|
15658
15779
|
const track = localStream.getTracks().find((track$1) => track$1.kind === kind);
|
|
15659
15780
|
if (track) {
|
|
15660
15781
|
this.transceiverController?.stopTrackSender(kind);
|
|
15661
15782
|
this.localStreamController.removeTrack(track.id);
|
|
15662
|
-
logger$
|
|
15783
|
+
logger$17.debug(`[RTCPeerConnectionController] Stopped existing ${kind} track: ${track.id}`, localStream.getTracks());
|
|
15663
15784
|
if (!deviceInfo) {
|
|
15664
|
-
logger$
|
|
15785
|
+
logger$17.debug(`[RTCPeerConnectionController] ${kind} input device selected: none`);
|
|
15665
15786
|
return;
|
|
15666
15787
|
}
|
|
15667
15788
|
const streamTrack = (await this.getUserMedia({ [kind]: {
|
|
@@ -15669,16 +15790,16 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15669
15790
|
...this.deviceController.deviceInfoToConstraints(deviceInfo)
|
|
15670
15791
|
} })).getTracks().find((t) => t.kind === kind);
|
|
15671
15792
|
if (streamTrack) {
|
|
15672
|
-
logger$
|
|
15793
|
+
logger$17.debug(`[RTCPeerConnectionController] Adding new ${kind} track: ${streamTrack.id}`);
|
|
15673
15794
|
this.localStreamController.addTrack(streamTrack);
|
|
15674
15795
|
await this.transceiverController?.replaceSenderTrack(kind, streamTrack);
|
|
15675
|
-
logger$
|
|
15796
|
+
logger$17.debug(`[RTCPeerConnectionController] Added new ${kind} track: ${streamTrack.id}`, this.localStream?.getTracks());
|
|
15676
15797
|
}
|
|
15677
15798
|
}
|
|
15678
|
-
logger$
|
|
15799
|
+
logger$17.debug(`[RTCPeerConnectionController] ${kind} input device selected:`, deviceInfo?.label);
|
|
15679
15800
|
} catch (error) {
|
|
15680
|
-
logger$
|
|
15681
|
-
this._errors$.next(
|
|
15801
|
+
logger$17.error(`[RTCPeerConnectionController] Failed to select ${kind} input device:`, error);
|
|
15802
|
+
this._errors$.next(new MediaTrackError("updateSelectedInputDevice", kind, error));
|
|
15682
15803
|
throw error;
|
|
15683
15804
|
}
|
|
15684
15805
|
};
|
|
@@ -15749,7 +15870,19 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15749
15870
|
return this._memberId;
|
|
15750
15871
|
}
|
|
15751
15872
|
stopTrackSender(kind, options = { updateTransceiverDirection: false }) {
|
|
15752
|
-
|
|
15873
|
+
const audioCovered = kind === "audio" || kind === "both";
|
|
15874
|
+
if (audioCovered && this._localAudioPipeline) this.stopRawAudioInputForPipeline();
|
|
15875
|
+
if (!audioCovered) this.transceiverController?.stopTrackSender(kind, options);
|
|
15876
|
+
else if (kind === "both") this.transceiverController?.stopTrackSender("video", options);
|
|
15877
|
+
else if (!this._localAudioPipeline) this.transceiverController?.stopTrackSender(kind, options);
|
|
15878
|
+
}
|
|
15879
|
+
stopRawAudioInputForPipeline() {
|
|
15880
|
+
const rawTracks = this.localStreamController.localAudioTracks;
|
|
15881
|
+
for (const track of rawTracks) if (track.readyState === "live") {
|
|
15882
|
+
track.stop();
|
|
15883
|
+
this.localStreamController.removeTrack(track.id);
|
|
15884
|
+
}
|
|
15885
|
+
this._localAudioPipeline?.setInputTrack(null);
|
|
15753
15886
|
}
|
|
15754
15887
|
get isNegotiating$() {
|
|
15755
15888
|
return this._isNegotiating$.asObservable();
|
|
@@ -15888,7 +16021,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15888
16021
|
case "main":
|
|
15889
16022
|
default: return {
|
|
15890
16023
|
...options,
|
|
15891
|
-
offerToReceiveAudio: true,
|
|
16024
|
+
offerToReceiveAudio: this.options.receiveAudio ?? true,
|
|
15892
16025
|
offerToReceiveVideo: this.options.receiveVideo ?? Boolean(this.inputVideoDeviceConstraints)
|
|
15893
16026
|
};
|
|
15894
16027
|
}
|
|
@@ -15915,15 +16048,15 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15915
16048
|
this.setupPeerConnection();
|
|
15916
16049
|
this.subscribeTo(this.negotiationNeeded$.pipe((0, import_cjs$16.auditTime)(0), (0, import_cjs$16.exhaustMap)(async () => this.startNegotiation())), {
|
|
15917
16050
|
next: () => {
|
|
15918
|
-
logger$
|
|
16051
|
+
logger$17.debug("[RTCPeerConnectionController] Start Negotiation completed successfully");
|
|
15919
16052
|
},
|
|
15920
16053
|
error: (error) => {
|
|
15921
|
-
logger$
|
|
16054
|
+
logger$17.error("[RTCPeerConnectionController] Start Negotiation error:", error);
|
|
15922
16055
|
this._errors$.next(toError(error));
|
|
15923
16056
|
}
|
|
15924
16057
|
});
|
|
15925
16058
|
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]) => {
|
|
15926
|
-
logger$
|
|
16059
|
+
logger$17.debug(`[RTCPeerConnectionController] Selected input device changed for:`, {
|
|
15927
16060
|
kind,
|
|
15928
16061
|
deviceInfo
|
|
15929
16062
|
});
|
|
@@ -15940,7 +16073,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15940
16073
|
this._initialized$.next(true);
|
|
15941
16074
|
}
|
|
15942
16075
|
} catch (error) {
|
|
15943
|
-
logger$
|
|
16076
|
+
logger$17.error("[RTCPeerConnectionController] Initialization error:", error);
|
|
15944
16077
|
this._errors$.next(toError(error));
|
|
15945
16078
|
this.destroy();
|
|
15946
16079
|
}
|
|
@@ -15972,22 +16105,22 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15972
16105
|
}
|
|
15973
16106
|
async startNegotiation() {
|
|
15974
16107
|
if (this.isNegotiating) {
|
|
15975
|
-
logger$
|
|
16108
|
+
logger$17.debug("[RTCPeerConnectionController] Negotiation already in progress, skipping.");
|
|
15976
16109
|
return;
|
|
15977
16110
|
}
|
|
15978
16111
|
this.setupEventListeners();
|
|
15979
16112
|
if (this.type === "answer") {
|
|
15980
|
-
logger$
|
|
16113
|
+
logger$17.debug("[RTCPeerConnectionController] This is an answer type still, skipping offer creation.");
|
|
15981
16114
|
return;
|
|
15982
16115
|
}
|
|
15983
16116
|
this._isNegotiating$.next(true);
|
|
15984
|
-
logger$
|
|
16117
|
+
logger$17.debug("[RTCPeerConnectionController] Starting negotiation.");
|
|
15985
16118
|
try {
|
|
15986
16119
|
const { offerOptions } = this;
|
|
15987
|
-
logger$
|
|
16120
|
+
logger$17.debug("[RTCPeerConnectionController] Creating offer with options:", offerOptions);
|
|
15988
16121
|
await this.createOffer(offerOptions);
|
|
15989
16122
|
} catch (error) {
|
|
15990
|
-
logger$
|
|
16123
|
+
logger$17.error("[RTCPeerConnectionController] Error during negotiation:", error);
|
|
15991
16124
|
this._errors$.next(toError(error));
|
|
15992
16125
|
}
|
|
15993
16126
|
}
|
|
@@ -16003,14 +16136,14 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16003
16136
|
let readyToConnect = status !== "failed";
|
|
16004
16137
|
try {
|
|
16005
16138
|
if (status === "received" && sdp) {
|
|
16006
|
-
logger$
|
|
16139
|
+
logger$17.debug("[RTCPeerConnectionController] Received answer SDP:", sdp);
|
|
16007
16140
|
await this._setRemoteDescription({
|
|
16008
16141
|
type: "answer",
|
|
16009
16142
|
sdp
|
|
16010
16143
|
});
|
|
16011
16144
|
}
|
|
16012
16145
|
} catch (error) {
|
|
16013
|
-
logger$
|
|
16146
|
+
logger$17.error("[RTCPeerConnectionController] Error updating answer status:", error);
|
|
16014
16147
|
this._errors$.next(toError(error));
|
|
16015
16148
|
readyToConnect = false;
|
|
16016
16149
|
} finally {
|
|
@@ -16029,7 +16162,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16029
16162
|
await this.handleOfferReceived();
|
|
16030
16163
|
break;
|
|
16031
16164
|
case "failed":
|
|
16032
|
-
logger$
|
|
16165
|
+
logger$17.error("[RTCPeerConnectionController] Offer failed to be processed by remote.");
|
|
16033
16166
|
break;
|
|
16034
16167
|
case "sent":
|
|
16035
16168
|
default:
|
|
@@ -16042,13 +16175,14 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16042
16175
|
*/
|
|
16043
16176
|
async acceptInbound(mediaOverrides) {
|
|
16044
16177
|
if (mediaOverrides) {
|
|
16045
|
-
const { audio, video, receiveAudio, receiveVideo } = mediaOverrides;
|
|
16178
|
+
const { audio, video, receiveAudio, receiveVideo, fallbackToReceiveOnly } = mediaOverrides;
|
|
16046
16179
|
this.options = {
|
|
16047
16180
|
...this.options,
|
|
16048
16181
|
...audio !== void 0 ? { audio } : {},
|
|
16049
16182
|
...video !== void 0 ? { video } : {},
|
|
16050
16183
|
...receiveAudio !== void 0 ? { receiveAudio } : {},
|
|
16051
|
-
...receiveVideo !== void 0 ? { receiveVideo } : {}
|
|
16184
|
+
...receiveVideo !== void 0 ? { receiveVideo } : {},
|
|
16185
|
+
...fallbackToReceiveOnly !== void 0 ? { fallbackToReceiveOnly } : {}
|
|
16052
16186
|
};
|
|
16053
16187
|
this.transceiverController?.updateOptions({
|
|
16054
16188
|
receiveAudio: this.receiveAudio,
|
|
@@ -16061,7 +16195,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16061
16195
|
}
|
|
16062
16196
|
await this.setupLocalTracks();
|
|
16063
16197
|
const { answerOptions } = this;
|
|
16064
|
-
logger$
|
|
16198
|
+
logger$17.debug("[RTCPeerConnectionController] Creating inbound answer with options:", answerOptions);
|
|
16065
16199
|
await this.createAnswer(answerOptions);
|
|
16066
16200
|
}
|
|
16067
16201
|
async handleOfferReceived() {
|
|
@@ -16069,7 +16203,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16069
16203
|
this._isNegotiating$.next(true);
|
|
16070
16204
|
await this._setRemoteDescription(this.sdpInit);
|
|
16071
16205
|
const { answerOptions } = this;
|
|
16072
|
-
logger$
|
|
16206
|
+
logger$17.debug("[RTCPeerConnectionController] Creating answer with options:", answerOptions);
|
|
16073
16207
|
await this.createAnswer(answerOptions);
|
|
16074
16208
|
}
|
|
16075
16209
|
readyToConnect() {
|
|
@@ -16077,7 +16211,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16077
16211
|
this.connectionTimer = setTimeout(() => {
|
|
16078
16212
|
this.removeConnectionTimer();
|
|
16079
16213
|
if (this.peerConnection?.connectionState !== "connected") {
|
|
16080
|
-
logger$
|
|
16214
|
+
logger$17.debug("[RTCPeerConnectionController] Connection timeout, restarting ICE gathering with relay only.");
|
|
16081
16215
|
this.iceGatheringController.restartICEGatheringWithRelayOnly();
|
|
16082
16216
|
}
|
|
16083
16217
|
}, this.connectionTimeout);
|
|
@@ -16099,14 +16233,14 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16099
16233
|
const stereo = this.options.stereo ?? PreferencesContainer.instance.stereoAudio;
|
|
16100
16234
|
if (preferredAudioCodecs.length > 0 || preferredVideoCodecs.length > 0) {
|
|
16101
16235
|
result = setCodecPreferences(result, preferredAudioCodecs, preferredVideoCodecs);
|
|
16102
|
-
logger$
|
|
16236
|
+
logger$17.debug("[RTCPeerConnectionController] Applied codec preferences to SDP", {
|
|
16103
16237
|
preferredAudioCodecs,
|
|
16104
16238
|
preferredVideoCodecs
|
|
16105
16239
|
});
|
|
16106
16240
|
}
|
|
16107
16241
|
if (stereo) {
|
|
16108
16242
|
result = enableStereoOpus(result);
|
|
16109
|
-
logger$
|
|
16243
|
+
logger$17.debug("[RTCPeerConnectionController] Applied stereo Opus to SDP");
|
|
16110
16244
|
}
|
|
16111
16245
|
return Promise.resolve(result);
|
|
16112
16246
|
}
|
|
@@ -16162,25 +16296,25 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16162
16296
|
...this.peerConnection.getConfiguration(),
|
|
16163
16297
|
iceTransportPolicy: "relay"
|
|
16164
16298
|
});
|
|
16165
|
-
logger$
|
|
16299
|
+
logger$17.debug("[RTCPeerConnectionController] ICE transport policy set to relay-only");
|
|
16166
16300
|
} catch (error) {
|
|
16167
|
-
logger$
|
|
16301
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to set relay-only policy:", error);
|
|
16168
16302
|
}
|
|
16169
16303
|
this.setupEventListeners();
|
|
16170
16304
|
this._isNegotiating$.next(true);
|
|
16171
|
-
logger$
|
|
16305
|
+
logger$17.debug(`[RTCPeerConnectionController] Triggering ICE restart${relayOnly ? " (relay-only)" : ""}.`);
|
|
16172
16306
|
try {
|
|
16173
16307
|
const offer = await this.peerConnection.createOffer({ iceRestart: true });
|
|
16174
16308
|
await this.setLocalDescription(offer);
|
|
16175
16309
|
} catch (error) {
|
|
16176
|
-
logger$
|
|
16310
|
+
logger$17.error("[RTCPeerConnectionController] ICE restart offer failed:", error);
|
|
16177
16311
|
this._errors$.next(toError(error));
|
|
16178
16312
|
this.negotiationEnded();
|
|
16179
16313
|
if (policyChanged) this.restoreIceTransportPolicy();
|
|
16180
16314
|
throw error;
|
|
16181
16315
|
}
|
|
16182
16316
|
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) => {
|
|
16183
|
-
logger$
|
|
16317
|
+
logger$17.warn("[RTCPeerConnectionController] Error waiting for ICE gathering to complete:", error);
|
|
16184
16318
|
this.restoreIceTransportPolicy();
|
|
16185
16319
|
});
|
|
16186
16320
|
}
|
|
@@ -16190,9 +16324,9 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16190
16324
|
...this.peerConnection.getConfiguration(),
|
|
16191
16325
|
iceTransportPolicy: this.options.relayOnly ? "relay" : "all"
|
|
16192
16326
|
});
|
|
16193
|
-
logger$
|
|
16327
|
+
logger$17.debug("[RTCPeerConnectionController] ICE transport policy restored");
|
|
16194
16328
|
} catch (error) {
|
|
16195
|
-
logger$
|
|
16329
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to restore ICE transport policy:", error);
|
|
16196
16330
|
}
|
|
16197
16331
|
}
|
|
16198
16332
|
/**
|
|
@@ -16204,13 +16338,25 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16204
16338
|
await this.setupRemoteTracks();
|
|
16205
16339
|
}
|
|
16206
16340
|
async setupLocalTracks() {
|
|
16207
|
-
logger$
|
|
16208
|
-
|
|
16341
|
+
logger$17.debug("[RTCPeerConnectionController] Setting up local tracks/transceivers.");
|
|
16342
|
+
if (this.hasNoLocalMediaToSend()) {
|
|
16343
|
+
if (!this.receiveAudio && !this.receiveVideo) throw new InvalidParams("Call requests no media: enable audio/video or receiveAudio/receiveVideo");
|
|
16344
|
+
logger$17.debug("[RTCPeerConnectionController] No local media requested; negotiating receive-only.");
|
|
16345
|
+
this.setupReceiveOnlyTransceivers();
|
|
16346
|
+
return;
|
|
16347
|
+
}
|
|
16348
|
+
let localStream;
|
|
16349
|
+
try {
|
|
16350
|
+
localStream = this.localStream ?? await this.localStreamController.buildLocalStream();
|
|
16351
|
+
} catch (error) {
|
|
16352
|
+
this.handleLocalMediaFailure(error);
|
|
16353
|
+
return;
|
|
16354
|
+
}
|
|
16209
16355
|
if (this.transceiverController?.useAddStream ?? false) {
|
|
16210
|
-
logger$
|
|
16356
|
+
logger$17.warn("[RTCPeerConnectionController] Using deprecated addStream API to add local stream.");
|
|
16211
16357
|
this.peerConnection?.addStream(localStream);
|
|
16212
16358
|
if (!this.isNegotiating) {
|
|
16213
|
-
logger$
|
|
16359
|
+
logger$17.debug("[RTCPeerConnectionController] Forcing negotiationneeded after local tracks setup.");
|
|
16214
16360
|
this.negotiationNeeded$.next();
|
|
16215
16361
|
}
|
|
16216
16362
|
return;
|
|
@@ -16226,12 +16372,54 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16226
16372
|
const transceivers = (kind === "audio" ? this.transceiverController?.audioTransceivers : this.transceiverController?.videoTransceivers) ?? [];
|
|
16227
16373
|
await this.transceiverController?.setupTransceiverSender(track, localStream, transceivers[index]);
|
|
16228
16374
|
} else {
|
|
16229
|
-
logger$
|
|
16375
|
+
logger$17.debug(`[RTCPeerConnectionController] Using addTrack for local ${kind} track:`, track.id);
|
|
16230
16376
|
this.peerConnection?.addTrack(track, localStream);
|
|
16231
16377
|
}
|
|
16232
16378
|
}
|
|
16233
16379
|
}
|
|
16234
16380
|
}
|
|
16381
|
+
/** True for a main connection with no local media to send. */
|
|
16382
|
+
hasNoLocalMediaToSend() {
|
|
16383
|
+
const hasInputStreams = Boolean(this.options.inputAudioStream ?? this.options.inputVideoStream);
|
|
16384
|
+
return this.propose === "main" && !this.localStream && !hasInputStreams && !this.inputAudioDeviceConstraints && !this.inputVideoDeviceConstraints;
|
|
16385
|
+
}
|
|
16386
|
+
/** The media kinds this connection wants to send: 'audiovideo' | 'video' | 'audio'. */
|
|
16387
|
+
get requestedMediaKinds() {
|
|
16388
|
+
const wantsAudio = Boolean(this.inputAudioDeviceConstraints);
|
|
16389
|
+
const wantsVideo = Boolean(this.inputVideoDeviceConstraints);
|
|
16390
|
+
if (wantsAudio && wantsVideo) return "audiovideo";
|
|
16391
|
+
return wantsVideo ? "video" : "audio";
|
|
16392
|
+
}
|
|
16393
|
+
/**
|
|
16394
|
+
* Handle a local media acquisition failure with a typed, semantically
|
|
16395
|
+
* accurate MediaAccessError created at the acquisition site:
|
|
16396
|
+
* - Auxiliary connections (screenshare / additional-device) throw a
|
|
16397
|
+
* non-fatal error — VertoManager surfaces it and the call is unaffected.
|
|
16398
|
+
* - The main connection degrades to receive-only when allowed (default),
|
|
16399
|
+
* otherwise fails with a fatal error.
|
|
16400
|
+
*/
|
|
16401
|
+
handleLocalMediaFailure(error) {
|
|
16402
|
+
if (this.propose === "screenshare") throw new MediaAccessError("startScreenShare", "screen", error, false);
|
|
16403
|
+
if (this.propose === "additional-device") throw new MediaAccessError("addInputDevice", this.requestedMediaKinds, error, false);
|
|
16404
|
+
const canReceive = this.receiveAudio || this.receiveVideo;
|
|
16405
|
+
if (!((this.options.fallbackToReceiveOnly ?? true) && canReceive)) throw new MediaAccessError("acquireLocalMedia", this.requestedMediaKinds, error, true);
|
|
16406
|
+
logger$17.warn("[RTCPeerConnectionController] Local media unavailable; continuing receive-only:", error);
|
|
16407
|
+
this._errors$.next(new MediaAccessError("acquireLocalMedia", this.requestedMediaKinds, error, false));
|
|
16408
|
+
this.setupReceiveOnlyTransceivers();
|
|
16409
|
+
}
|
|
16410
|
+
/**
|
|
16411
|
+
* Negotiate receive-only m-lines when there are no local tracks to send.
|
|
16412
|
+
* Only offer-type connections add transceivers — answer-type connections
|
|
16413
|
+
* reuse the transceivers created from the remote offer.
|
|
16414
|
+
*/
|
|
16415
|
+
setupReceiveOnlyTransceivers() {
|
|
16416
|
+
if (this.type !== "offer") return;
|
|
16417
|
+
if (this.transceiverController?.useAddTransceivers ?? false) {
|
|
16418
|
+
this.peerConnection?.addTransceiver("audio", { direction: this.receiveAudio ? "recvonly" : "inactive" });
|
|
16419
|
+
this.peerConnection?.addTransceiver("video", { direction: this.receiveVideo ? "recvonly" : "inactive" });
|
|
16420
|
+
}
|
|
16421
|
+
if (!this.isNegotiating) this.negotiationNeeded$.next();
|
|
16422
|
+
}
|
|
16235
16423
|
async getUserMedia(constraints) {
|
|
16236
16424
|
return (this.options.webRTCApiProvider?.mediaDevices ?? navigator.mediaDevices).getUserMedia(constraints);
|
|
16237
16425
|
}
|
|
@@ -16243,7 +16431,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16243
16431
|
async setupRemoteTracks() {
|
|
16244
16432
|
if (!this.peerConnection) throw new DependencyError("RTCPeerConnection is not initialized");
|
|
16245
16433
|
this.peerConnection.ontrack = (event) => {
|
|
16246
|
-
logger$
|
|
16434
|
+
logger$17.debug("[RTCPeerConnectionController] Remote track received:", event.track.kind);
|
|
16247
16435
|
if (event.streams[0]) this._remoteStream$.next(event.streams[0]);
|
|
16248
16436
|
else {
|
|
16249
16437
|
const existingTracks = this._remoteStream$.value?.getTracks() ?? [];
|
|
@@ -16254,8 +16442,27 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16254
16442
|
await this.transceiverController?.setupRemoteTransceivers(this.type);
|
|
16255
16443
|
}
|
|
16256
16444
|
async restoreTrackSender(kind) {
|
|
16257
|
-
|
|
16258
|
-
if (
|
|
16445
|
+
const audioCovered = kind === "audio" || kind === "both";
|
|
16446
|
+
if (audioCovered && this._localAudioPipeline) await this.restoreRawAudioInputForPipeline();
|
|
16447
|
+
if (!audioCovered) await this.transceiverController?.restoreTrackSender(kind);
|
|
16448
|
+
else if (kind === "both") await this.transceiverController?.restoreTrackSender("video");
|
|
16449
|
+
else if (!this._localAudioPipeline) await this.transceiverController?.restoreTrackSender(kind);
|
|
16450
|
+
}
|
|
16451
|
+
async restoreRawAudioInputForPipeline() {
|
|
16452
|
+
if (!this._localAudioPipeline) return;
|
|
16453
|
+
const constraints = this.transceiverController?.getConstraintsFor("audio") ?? {};
|
|
16454
|
+
let stream;
|
|
16455
|
+
try {
|
|
16456
|
+
stream = await this.getUserMedia({ audio: constraints });
|
|
16457
|
+
} catch (error) {
|
|
16458
|
+
logger$17.error("[RTCPeerConnectionController] Failed to re-acquire mic for pipeline restore:", error);
|
|
16459
|
+
this._errors$.next(new MediaTrackError("restoreAudioPipelineInput", "audio", error));
|
|
16460
|
+
return;
|
|
16461
|
+
}
|
|
16462
|
+
const newTrack = stream.getAudioTracks().at(0);
|
|
16463
|
+
if (!newTrack) return;
|
|
16464
|
+
this.localStreamController.addTrack(newTrack);
|
|
16465
|
+
this._localAudioPipeline.setInputTrack(newTrack);
|
|
16259
16466
|
}
|
|
16260
16467
|
/**
|
|
16261
16468
|
* Return the lazily-created {@link LocalAudioPipeline}, constructing it on
|
|
@@ -16270,7 +16477,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16270
16477
|
try {
|
|
16271
16478
|
this._localAudioPipeline = new LocalAudioPipeline();
|
|
16272
16479
|
} catch (error) {
|
|
16273
|
-
logger$
|
|
16480
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to create LocalAudioPipeline:", error);
|
|
16274
16481
|
return null;
|
|
16275
16482
|
}
|
|
16276
16483
|
this.subscribeTo(this.localStreamController.localAudioTracks$, () => {
|
|
@@ -16292,7 +16499,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16292
16499
|
try {
|
|
16293
16500
|
await sender.replaceTrack(this._localAudioPipeline.outputTrack);
|
|
16294
16501
|
} catch (error) {
|
|
16295
|
-
logger$
|
|
16502
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to route audio sender through pipeline:", error);
|
|
16296
16503
|
}
|
|
16297
16504
|
}
|
|
16298
16505
|
/**
|
|
@@ -16308,10 +16515,10 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16308
16515
|
try {
|
|
16309
16516
|
const localStream = this.localStreamController.addTrack(track);
|
|
16310
16517
|
this.peerConnection.addTrack(track, localStream);
|
|
16311
|
-
logger$
|
|
16518
|
+
logger$17.debug(`[RTCPeerConnectionController] ${track.kind} track added:`, track.id);
|
|
16312
16519
|
} catch (error) {
|
|
16313
|
-
logger$
|
|
16314
|
-
this._errors$.next(
|
|
16520
|
+
logger$17.error(`[RTCPeerConnectionController] Failed to add ${track.kind} track:`, error);
|
|
16521
|
+
this._errors$.next(new MediaTrackError("addLocalTrack", track.kind, error));
|
|
16315
16522
|
throw error;
|
|
16316
16523
|
}
|
|
16317
16524
|
}
|
|
@@ -16327,16 +16534,16 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16327
16534
|
}
|
|
16328
16535
|
const sender = this.peerConnection.getSenders().find((sender$1) => sender$1.track?.id === trackId);
|
|
16329
16536
|
if (!sender) {
|
|
16330
|
-
logger$
|
|
16537
|
+
logger$17.debug(`[RTCPeerConnectionController] track not found: ${trackId}`);
|
|
16331
16538
|
return;
|
|
16332
16539
|
}
|
|
16333
16540
|
try {
|
|
16334
16541
|
this.peerConnection.removeTrack(sender);
|
|
16335
16542
|
this.localStreamController.removeTrack(trackId);
|
|
16336
|
-
logger$
|
|
16543
|
+
logger$17.debug(`[RTCPeerConnectionController] ${sender.track?.kind} track removed:`, trackId);
|
|
16337
16544
|
} catch (error) {
|
|
16338
|
-
logger$
|
|
16339
|
-
this._errors$.next(
|
|
16545
|
+
logger$17.error(`[RTCPeerConnectionController] Failed to remove ${sender.track?.kind} track:`, error);
|
|
16546
|
+
this._errors$.next(new MediaTrackError("removeLocalTrack", sender.track?.kind ?? "unknown", error));
|
|
16340
16547
|
throw error;
|
|
16341
16548
|
}
|
|
16342
16549
|
}
|
|
@@ -16362,7 +16569,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16362
16569
|
async replaceAudioTrackWithConstraints(constraints) {
|
|
16363
16570
|
const senders = this.peerConnection?.getSenders().filter((s) => s.track?.kind === "audio" && s.track.readyState === "live");
|
|
16364
16571
|
if (!senders || senders.length === 0) {
|
|
16365
|
-
logger$
|
|
16572
|
+
logger$17.warn("[RTCPeerConnectionController] No live audio sender to replace");
|
|
16366
16573
|
return;
|
|
16367
16574
|
}
|
|
16368
16575
|
for (const sender of senders) {
|
|
@@ -16380,7 +16587,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16380
16587
|
const newTrack = (await this.getUserMedia({ audio: mergedConstraints })).getAudioTracks()[0];
|
|
16381
16588
|
await sender.replaceTrack(newTrack);
|
|
16382
16589
|
this.localStreamController.addTrack(newTrack);
|
|
16383
|
-
logger$
|
|
16590
|
+
logger$17.debug(`[RTCPeerConnectionController] Audio track replaced for server-pushed params. New track: ${newTrack.id}`);
|
|
16384
16591
|
}
|
|
16385
16592
|
}
|
|
16386
16593
|
/**
|
|
@@ -16388,7 +16595,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16388
16595
|
* Completes all observables to prevent memory leaks.
|
|
16389
16596
|
*/
|
|
16390
16597
|
destroy() {
|
|
16391
|
-
logger$
|
|
16598
|
+
logger$17.debug(`[RTCPeerConnectionController] Destroying RTCPeerConnectionController. ${this.propose}`);
|
|
16392
16599
|
this.removeConnectionTimer();
|
|
16393
16600
|
this._iceGatheringController?.destroy();
|
|
16394
16601
|
this._localAudioPipeline?.destroy();
|
|
@@ -16414,7 +16621,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16414
16621
|
}
|
|
16415
16622
|
stopRemoteTracks() {
|
|
16416
16623
|
this._remoteStream$.value?.getTracks().forEach((track) => {
|
|
16417
|
-
logger$
|
|
16624
|
+
logger$17.debug(`[RTCPeerConnectionController] Stopping remote track: ${track.kind}`);
|
|
16418
16625
|
track.stop();
|
|
16419
16626
|
});
|
|
16420
16627
|
}
|
|
@@ -16431,7 +16638,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16431
16638
|
...params,
|
|
16432
16639
|
sdp: finalRemote
|
|
16433
16640
|
};
|
|
16434
|
-
logger$
|
|
16641
|
+
logger$17.debug("[RTCPeerConnectionController] Setting remote description:", answer);
|
|
16435
16642
|
return this.peerConnection.setRemoteDescription(answer);
|
|
16436
16643
|
}
|
|
16437
16644
|
};
|
|
@@ -16446,7 +16653,11 @@ function isVertoInviteMessage(value) {
|
|
|
16446
16653
|
const msg = value;
|
|
16447
16654
|
return msg.method === "verto.invite" && isObject(msg.params) && hasProperty(msg.params, "sdp") && hasProperty(msg.params, "callID");
|
|
16448
16655
|
}
|
|
16449
|
-
|
|
16656
|
+
/**
|
|
16657
|
+
* Guards inbound `verto.bye` frames (server → client). Outbound bye messages are
|
|
16658
|
+
* built locally and never type-guarded, so only the inbound shape is asserted.
|
|
16659
|
+
*/
|
|
16660
|
+
function isVertoByeInboundMessage(value) {
|
|
16450
16661
|
if (!isVertoMethodMessage(value)) return false;
|
|
16451
16662
|
return value.method === "verto.bye";
|
|
16452
16663
|
}
|
|
@@ -16466,11 +16677,19 @@ function isVertoMediaParamsInnerParams(value) {
|
|
|
16466
16677
|
function isVertoPingInnerParams(value) {
|
|
16467
16678
|
return isObject(value) && hasProperty(value, "jsonrpc") && value.jsonrpc === "2.0" && hasProperty(value, "method") && value.method === "verto.ping";
|
|
16468
16679
|
}
|
|
16680
|
+
/**
|
|
16681
|
+
* Guards the params of an inbound `verto.bye` frame (top-level `callID`). Use
|
|
16682
|
+
* this to recognise the bye payload extracted by `vertoBye$`, e.g. when racing
|
|
16683
|
+
* it against a boolean answer/reject signal.
|
|
16684
|
+
*/
|
|
16685
|
+
function isVertoByeInboundParamsGuard(value) {
|
|
16686
|
+
return isObject(value) && hasProperty(value, "callID") && typeof value.callID === "string";
|
|
16687
|
+
}
|
|
16469
16688
|
|
|
16470
16689
|
//#endregion
|
|
16471
16690
|
//#region src/managers/VertoManager.ts
|
|
16472
16691
|
var import_cjs$15 = require_cjs();
|
|
16473
|
-
const logger$
|
|
16692
|
+
const logger$16 = getLogger();
|
|
16474
16693
|
/**
|
|
16475
16694
|
* Decide what value goes on the `node_id` field of a `webrtc.verto` envelope.
|
|
16476
16695
|
*
|
|
@@ -16526,7 +16745,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16526
16745
|
try {
|
|
16527
16746
|
await this.executeVerto(vertoModifyMessage);
|
|
16528
16747
|
} catch (error) {
|
|
16529
|
-
logger$
|
|
16748
|
+
logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto hold:", error);
|
|
16530
16749
|
throw error;
|
|
16531
16750
|
}
|
|
16532
16751
|
}
|
|
@@ -16539,7 +16758,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16539
16758
|
try {
|
|
16540
16759
|
await this.executeVerto(vertoModifyMessage);
|
|
16541
16760
|
} catch (error) {
|
|
16542
|
-
logger$
|
|
16761
|
+
logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto unhold:", error);
|
|
16543
16762
|
throw error;
|
|
16544
16763
|
}
|
|
16545
16764
|
}
|
|
@@ -16592,25 +16811,25 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16592
16811
|
if (event.member_id) this.setSelfIdIfNull(event.member_id);
|
|
16593
16812
|
});
|
|
16594
16813
|
this.subscribeTo(this.vertoMedia$, (event) => {
|
|
16595
|
-
logger$
|
|
16596
|
-
this._signalingStatus$.next("ringing");
|
|
16814
|
+
logger$16.debug("[WebRTCManager] Received Verto media event (early media SDP):", event);
|
|
16597
16815
|
const { sdp, callID } = event;
|
|
16816
|
+
this.emitMainSignalingStatus(callID, "ringing");
|
|
16598
16817
|
this._rtcPeerConnectionsMap.get(callID)?.updateAnswerStatus({
|
|
16599
16818
|
status: "received",
|
|
16600
16819
|
sdp
|
|
16601
16820
|
});
|
|
16602
16821
|
});
|
|
16603
16822
|
this.subscribeTo(this.vertoAnswer$, (event) => {
|
|
16604
|
-
logger$
|
|
16605
|
-
this._signalingStatus$.next("connecting");
|
|
16823
|
+
logger$16.debug("[WebRTCManager] Received Verto answer event:", event);
|
|
16606
16824
|
const { sdp, callID } = event;
|
|
16825
|
+
this.emitMainSignalingStatus(callID, "connecting");
|
|
16607
16826
|
this._rtcPeerConnectionsMap.get(callID)?.updateAnswerStatus({
|
|
16608
16827
|
status: "received",
|
|
16609
16828
|
sdp
|
|
16610
16829
|
});
|
|
16611
16830
|
});
|
|
16612
16831
|
this.subscribeTo(this.vertoMediaParams$, (event) => {
|
|
16613
|
-
logger$
|
|
16832
|
+
logger$16.debug("[WebRTCManager] Received Verto mediaParams event:", event);
|
|
16614
16833
|
const { mediaParams, callID } = event;
|
|
16615
16834
|
const rtcPeerConnController = this._rtcPeerConnectionsMap.get(callID);
|
|
16616
16835
|
const { audio, video } = mediaParams;
|
|
@@ -16624,7 +16843,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16624
16843
|
timestamp: Date.now()
|
|
16625
16844
|
});
|
|
16626
16845
|
} catch (error) {
|
|
16627
|
-
logger$
|
|
16846
|
+
logger$16.warn("[WebRTCManager] Error applying server-pushed media params:", error);
|
|
16628
16847
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
16629
16848
|
}
|
|
16630
16849
|
})();
|
|
@@ -16646,13 +16865,13 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16646
16865
|
*/
|
|
16647
16866
|
setNodeIdIfNull(nodeId) {
|
|
16648
16867
|
if (!this._nodeId$.value && nodeId) {
|
|
16649
|
-
logger$
|
|
16868
|
+
logger$16.debug(`[WebRTCManager] Early node_id set: ${nodeId}`);
|
|
16650
16869
|
this._nodeId$.next(nodeId);
|
|
16651
16870
|
}
|
|
16652
16871
|
}
|
|
16653
16872
|
setSelfIdIfNull(selfId) {
|
|
16654
16873
|
if (!this._selfId$.value && selfId) {
|
|
16655
|
-
logger$
|
|
16874
|
+
logger$16.debug(`[WebRTCManager] Early selfId set: ${selfId}`);
|
|
16656
16875
|
this._selfId$.next(selfId);
|
|
16657
16876
|
}
|
|
16658
16877
|
}
|
|
@@ -16661,7 +16880,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16661
16880
|
const vertoPongMessage = VertoPong({ ...vertoPing });
|
|
16662
16881
|
await this.executeVerto(vertoPongMessage);
|
|
16663
16882
|
} catch (error) {
|
|
16664
|
-
logger$
|
|
16883
|
+
logger$16.warn("[WebRTCManager] Call might disconnect, error sending Verto pong:", error);
|
|
16665
16884
|
this.onError?.(new VertoPongError(error));
|
|
16666
16885
|
}
|
|
16667
16886
|
}
|
|
@@ -16671,7 +16890,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16671
16890
|
if (audio) await this.mainPeerConnection.updateSendersConstraints("audio", audio);
|
|
16672
16891
|
if (video) await this.mainPeerConnection.updateSendersConstraints("video", video);
|
|
16673
16892
|
} catch (error) {
|
|
16674
|
-
logger$
|
|
16893
|
+
logger$16.warn("[WebRTCManager] Error updating media constraints:", error);
|
|
16675
16894
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
16676
16895
|
throw error;
|
|
16677
16896
|
}
|
|
@@ -16701,20 +16920,20 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16701
16920
|
try {
|
|
16702
16921
|
const pc = this.mainPeerConnection.peerConnection;
|
|
16703
16922
|
if (!pc) {
|
|
16704
|
-
logger$
|
|
16923
|
+
logger$16.warn("[WebRTCManager] No peer connection for keyframe request");
|
|
16705
16924
|
return;
|
|
16706
16925
|
}
|
|
16707
16926
|
const videoReceiver = pc.getReceivers().find((r) => r.track.kind === "video");
|
|
16708
16927
|
if (!videoReceiver) {
|
|
16709
|
-
logger$
|
|
16928
|
+
logger$16.warn("[WebRTCManager] No video receiver for keyframe request");
|
|
16710
16929
|
return;
|
|
16711
16930
|
}
|
|
16712
16931
|
if (typeof videoReceiver.requestKeyFrame === "function") {
|
|
16713
16932
|
videoReceiver.requestKeyFrame();
|
|
16714
|
-
logger$
|
|
16715
|
-
} else logger$
|
|
16933
|
+
logger$16.debug("[WebRTCManager] Keyframe requested via RTCRtpReceiver.requestKeyFrame()");
|
|
16934
|
+
} else logger$16.debug("[WebRTCManager] requestKeyFrame() not supported, skipping");
|
|
16716
16935
|
} catch (error) {
|
|
16717
|
-
logger$
|
|
16936
|
+
logger$16.warn("[WebRTCManager] Keyframe request failed (non-fatal):", error);
|
|
16718
16937
|
}
|
|
16719
16938
|
}
|
|
16720
16939
|
/**
|
|
@@ -16732,13 +16951,13 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16732
16951
|
try {
|
|
16733
16952
|
const controller = this.mainPeerConnection;
|
|
16734
16953
|
if (!controller.peerConnection) {
|
|
16735
|
-
logger$
|
|
16954
|
+
logger$16.warn("[WebRTCManager] No peer connection for ICE restart");
|
|
16736
16955
|
return;
|
|
16737
16956
|
}
|
|
16738
16957
|
await controller.triggerIceRestart(relayOnly);
|
|
16739
|
-
logger$
|
|
16958
|
+
logger$16.info(`[WebRTCManager] ICE restart initiated${relayOnly ? " (relay-only)" : ""}`);
|
|
16740
16959
|
} catch (error) {
|
|
16741
|
-
logger$
|
|
16960
|
+
logger$16.error("[WebRTCManager] ICE restart failed:", error);
|
|
16742
16961
|
throw error;
|
|
16743
16962
|
}
|
|
16744
16963
|
}
|
|
@@ -16756,13 +16975,13 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16756
16975
|
const entries = Array.from(this._rtcPeerConnectionsMap.entries());
|
|
16757
16976
|
for (const [id, controller] of entries) try {
|
|
16758
16977
|
if (!controller.peerConnection) {
|
|
16759
|
-
logger$
|
|
16978
|
+
logger$16.debug(`[WebRTCManager] No peer connection for leg ${id}, skipping ICE restart`);
|
|
16760
16979
|
continue;
|
|
16761
16980
|
}
|
|
16762
16981
|
await controller.triggerIceRestart(relayOnly);
|
|
16763
|
-
logger$
|
|
16982
|
+
logger$16.info(`[WebRTCManager] ICE restart initiated for leg ${id}${relayOnly ? " (relay-only)" : ""}`);
|
|
16764
16983
|
} catch (error) {
|
|
16765
|
-
logger$
|
|
16984
|
+
logger$16.warn(`[WebRTCManager] ICE restart failed for leg ${id}:`, error);
|
|
16766
16985
|
}
|
|
16767
16986
|
}
|
|
16768
16987
|
/**
|
|
@@ -16774,7 +16993,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16774
16993
|
requestKeyframeAll() {
|
|
16775
16994
|
for (const [id, controller] of this._rtcPeerConnectionsMap) {
|
|
16776
16995
|
if (controller.isScreenShare) {
|
|
16777
|
-
logger$
|
|
16996
|
+
logger$16.debug(`[WebRTCManager] Skipping keyframe for send-only screen share leg ${id}`);
|
|
16778
16997
|
continue;
|
|
16779
16998
|
}
|
|
16780
16999
|
try {
|
|
@@ -16784,10 +17003,10 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16784
17003
|
if (!videoReceiver) continue;
|
|
16785
17004
|
if (typeof videoReceiver.requestKeyFrame === "function") {
|
|
16786
17005
|
videoReceiver.requestKeyFrame();
|
|
16787
|
-
logger$
|
|
17006
|
+
logger$16.debug(`[WebRTCManager] Keyframe requested for leg ${id}`);
|
|
16788
17007
|
}
|
|
16789
17008
|
} catch (error) {
|
|
16790
|
-
logger$
|
|
17009
|
+
logger$16.warn(`[WebRTCManager] Keyframe request failed for leg ${id} (non-fatal):`, error);
|
|
16791
17010
|
}
|
|
16792
17011
|
}
|
|
16793
17012
|
}
|
|
@@ -16804,7 +17023,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16804
17023
|
return this.cachedObservable("vertoMediaParams$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoMediaParamsInnerParams, "params"), (0, import_cjs$15.takeUntil)(this.destroyed$)));
|
|
16805
17024
|
}
|
|
16806
17025
|
get vertoBye$() {
|
|
16807
|
-
return this.cachedObservable("vertoBye$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(
|
|
17026
|
+
return this.cachedObservable("vertoBye$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoByeInboundMessage, "params"), (0, import_cjs$15.takeUntil)(this.destroyed$)));
|
|
16808
17027
|
}
|
|
16809
17028
|
get vertoAttach$() {
|
|
16810
17029
|
return this.cachedObservable("vertoAttach$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoAttachMessage, "params"), (0, import_cjs$15.takeUntil)(this.destroyed$)));
|
|
@@ -16848,7 +17067,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16848
17067
|
default:
|
|
16849
17068
|
}
|
|
16850
17069
|
} catch (error) {
|
|
16851
|
-
logger$
|
|
17070
|
+
logger$16.error(`[WebRTCManager] Error sending Verto ${vertoMethod}:`, error);
|
|
16852
17071
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
16853
17072
|
if (vertoMethod === "verto.modify") this.onModifyFailed?.();
|
|
16854
17073
|
}
|
|
@@ -16863,19 +17082,33 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16863
17082
|
sdp
|
|
16864
17083
|
});
|
|
16865
17084
|
} catch (error) {
|
|
16866
|
-
logger$
|
|
17085
|
+
logger$16.warn("[WebRTCManager] Error processing modify response:", error);
|
|
16867
17086
|
const modifyError = error instanceof Error ? error : new Error(String(error), { cause: error });
|
|
16868
17087
|
this.onError?.(modifyError);
|
|
16869
17088
|
}
|
|
16870
17089
|
}
|
|
16871
17090
|
}
|
|
17091
|
+
emitMainSignalingStatus(callId, status) {
|
|
17092
|
+
const rtcPeerConnController = this._rtcPeerConnectionsMap.get(callId);
|
|
17093
|
+
if (!rtcPeerConnController) {
|
|
17094
|
+
const signalingError = new DependencyError(`Cannot emit signaling status, RTCPeerConnectionController not found for callID: ${callId}`);
|
|
17095
|
+
logger$16.error("[WebRTCManager] Failed to emit signaling status:", {
|
|
17096
|
+
callId,
|
|
17097
|
+
status,
|
|
17098
|
+
signalingError
|
|
17099
|
+
});
|
|
17100
|
+
this.onError?.(signalingError);
|
|
17101
|
+
return;
|
|
17102
|
+
}
|
|
17103
|
+
if (rtcPeerConnController.isMainDevice) this._signalingStatus$.next(status);
|
|
17104
|
+
}
|
|
16872
17105
|
processInviteResponse(response, rtcPeerConnController) {
|
|
16873
17106
|
if (!response.error && getValueFrom(response, "result.result.result.message") === "CALL CREATED") {
|
|
16874
|
-
this.
|
|
17107
|
+
this.emitMainSignalingStatus(rtcPeerConnController.id, "trying");
|
|
16875
17108
|
this._nodeId$.next(getValueFrom(response, "result.node_id") ?? null);
|
|
16876
17109
|
const memberId = getValueFrom(response, "result.result.result.memberID") ?? null;
|
|
16877
17110
|
const callId = getValueFrom(response, "result.result.result.callID") ?? null;
|
|
16878
|
-
logger$
|
|
17111
|
+
logger$16.debug("[WebRTCManager] Verto invite response:", {
|
|
16879
17112
|
callId,
|
|
16880
17113
|
memberId,
|
|
16881
17114
|
response
|
|
@@ -16885,14 +17118,14 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16885
17118
|
if (callId) {
|
|
16886
17119
|
this.webRtcCallSession.addCallId(callId);
|
|
16887
17120
|
this.attachManager.attach(this.buildAttachableCall(callId));
|
|
16888
|
-
} else logger$
|
|
17121
|
+
} else logger$16.warn("[WebRTCManager] Cannot attach call, missing callId:", {
|
|
16889
17122
|
nodeId: this.nodeId,
|
|
16890
17123
|
callId
|
|
16891
17124
|
});
|
|
16892
|
-
logger$
|
|
16893
|
-
logger$
|
|
17125
|
+
logger$16.info("[WebRTCManager] Verto invite successful");
|
|
17126
|
+
logger$16.debug(`[WebRTCManager] nodeid: ${this._nodeId$.value}, selfId: ${this._selfId$.value}`);
|
|
16894
17127
|
} else {
|
|
16895
|
-
logger$
|
|
17128
|
+
logger$16.error("[WebRTCManager] Verto invite failed:", response);
|
|
16896
17129
|
const inviteError = response.error ? new JSONRPCError(response.error.code, response.error.message, response.error.data) : /* @__PURE__ */ new Error("Verto invite failed: unexpected response");
|
|
16897
17130
|
this.onError?.(inviteError);
|
|
16898
17131
|
}
|
|
@@ -16919,6 +17152,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16919
17152
|
inputVideoStream: options.inputVideoStream,
|
|
16920
17153
|
receiveAudio: options.receiveAudio,
|
|
16921
17154
|
receiveVideo: options.receiveVideo,
|
|
17155
|
+
fallbackToReceiveOnly: options.fallbackToReceiveOnly,
|
|
16922
17156
|
webRTCApiProvider: this.webRTCApiProvider,
|
|
16923
17157
|
preferredVideoCodecs: options.preferredVideoCodecs,
|
|
16924
17158
|
preferredAudioCodecs: options.preferredAudioCodecs,
|
|
@@ -16937,17 +17171,17 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16937
17171
|
if (options.initOffer) this.handleInboundAnswer(rtcPeerConnController);
|
|
16938
17172
|
}
|
|
16939
17173
|
async handleInboundAnswer(rtcPeerConnController) {
|
|
16940
|
-
logger$
|
|
17174
|
+
logger$16.debug("[WebRTCManager] Waiting for inbound call to be accepted or rejected");
|
|
16941
17175
|
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);
|
|
16942
17176
|
if (vertoByeOrAccepted === null) {
|
|
16943
|
-
logger$
|
|
17177
|
+
logger$16.debug("[WebRTCManager] Inbound answer handler aborted (destroyed).");
|
|
16944
17178
|
return;
|
|
16945
17179
|
}
|
|
16946
|
-
if (
|
|
16947
|
-
logger$
|
|
17180
|
+
if (isVertoByeInboundParamsGuard(vertoByeOrAccepted)) {
|
|
17181
|
+
logger$16.info("[WebRTCManager] Inbound call ended by remote before answer.");
|
|
16948
17182
|
this.callSession?.destroy();
|
|
16949
17183
|
} else if (!vertoByeOrAccepted) {
|
|
16950
|
-
logger$
|
|
17184
|
+
logger$16.info("[WebRTCManager] Inbound call rejected by user.");
|
|
16951
17185
|
try {
|
|
16952
17186
|
await this.bye("USER_BUSY");
|
|
16953
17187
|
} finally {
|
|
@@ -16955,19 +17189,19 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16955
17189
|
this.callSession?.destroy();
|
|
16956
17190
|
}
|
|
16957
17191
|
} else {
|
|
16958
|
-
logger$
|
|
17192
|
+
logger$16.debug("[WebRTCManager] Inbound call accepted, creating SDP answer");
|
|
16959
17193
|
const answerOptions = this.webRtcCallSession.answerMediaOptions;
|
|
16960
17194
|
try {
|
|
16961
17195
|
await rtcPeerConnController.acceptInbound(answerOptions);
|
|
16962
17196
|
} catch (error) {
|
|
16963
|
-
logger$
|
|
17197
|
+
logger$16.error("[WebRTCManager] Error creating inbound answer:", error);
|
|
16964
17198
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
16965
17199
|
}
|
|
16966
17200
|
}
|
|
16967
17201
|
}
|
|
16968
17202
|
setupVertoAttachHandler() {
|
|
16969
17203
|
this.subscribeTo(this.vertoAttach$, async (vertoAttach) => {
|
|
16970
|
-
logger$
|
|
17204
|
+
logger$16.debug("[WebRTCManager] Received Verto attach event for existing call:", vertoAttach);
|
|
16971
17205
|
const { callID } = vertoAttach;
|
|
16972
17206
|
await this.attachManager.attach({
|
|
16973
17207
|
nodeId: this.nodeId ?? void 0,
|
|
@@ -17039,17 +17273,17 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17039
17273
|
};
|
|
17040
17274
|
}
|
|
17041
17275
|
async sendLocalDescriptionOnceAccepted(vertoMessageRequest, rtcPeerConnectionController) {
|
|
17042
|
-
logger$
|
|
17276
|
+
logger$16.debug("[WebRTCManager] Waiting for call to be accepted or ended before sending answer");
|
|
17043
17277
|
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);
|
|
17044
17278
|
if (vertoByeOrAccepted === null) {
|
|
17045
|
-
logger$
|
|
17279
|
+
logger$16.debug("[WebRTCManager] Destroyed while waiting for call acceptance");
|
|
17046
17280
|
return;
|
|
17047
17281
|
}
|
|
17048
|
-
if (
|
|
17049
|
-
logger$
|
|
17282
|
+
if (isVertoByeInboundParamsGuard(vertoByeOrAccepted)) {
|
|
17283
|
+
logger$16.info("[WebRTCManager] Call ended before answer was sent.");
|
|
17050
17284
|
this.callSession?.destroy();
|
|
17051
17285
|
} else if (!vertoByeOrAccepted) {
|
|
17052
|
-
logger$
|
|
17286
|
+
logger$16.info("[WebRTCManager] Call was not accepted, sending verto.bye.");
|
|
17053
17287
|
try {
|
|
17054
17288
|
await this.bye("USER_BUSY");
|
|
17055
17289
|
} finally {
|
|
@@ -17057,14 +17291,14 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17057
17291
|
this.callSession?.destroy();
|
|
17058
17292
|
}
|
|
17059
17293
|
} else {
|
|
17060
|
-
logger$
|
|
17294
|
+
logger$16.debug("[WebRTCManager] Call accepted, sending answer");
|
|
17061
17295
|
try {
|
|
17062
|
-
this.
|
|
17296
|
+
this.emitMainSignalingStatus(rtcPeerConnectionController.id, "connecting");
|
|
17063
17297
|
await this.sendLocalDescription(vertoMessageRequest, rtcPeerConnectionController);
|
|
17064
17298
|
await rtcPeerConnectionController.updateAnswerStatus({ status: "sent" });
|
|
17065
17299
|
await this.attachManager.attach(this.buildAttachableCall());
|
|
17066
17300
|
} catch (error) {
|
|
17067
|
-
logger$
|
|
17301
|
+
logger$16.error("[WebRTCManager] Error sending Verto answer:", error);
|
|
17068
17302
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
17069
17303
|
await rtcPeerConnectionController.updateAnswerStatus({ status: "failed" });
|
|
17070
17304
|
}
|
|
@@ -17145,9 +17379,11 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17145
17379
|
await this.initAdditionalPeerConnection("screenshare", options);
|
|
17146
17380
|
}
|
|
17147
17381
|
async initAdditionalPeerConnection(propose, options) {
|
|
17382
|
+
const isScreenShare = propose === "screenshare";
|
|
17383
|
+
let firstPeerConnectionError;
|
|
17148
17384
|
let rtcPeerConnController = null;
|
|
17149
17385
|
try {
|
|
17150
|
-
this._screenShareStatus$.next("starting");
|
|
17386
|
+
if (isScreenShare) this._screenShareStatus$.next("starting");
|
|
17151
17387
|
rtcPeerConnController = new RTCPeerConnectionController({
|
|
17152
17388
|
...options,
|
|
17153
17389
|
...this.RTCPeerConnectionConfig,
|
|
@@ -17155,21 +17391,27 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17155
17391
|
webRTCApiProvider: this.webRTCApiProvider
|
|
17156
17392
|
}, void 0, this.deviceController);
|
|
17157
17393
|
this.setupLocalDescriptionHandler(rtcPeerConnController);
|
|
17158
|
-
if (
|
|
17394
|
+
if (isScreenShare) this._screenShareId = rtcPeerConnController.id;
|
|
17159
17395
|
this._rtcPeerConnectionsMap.set(rtcPeerConnController.id, rtcPeerConnController);
|
|
17160
17396
|
this._rtcPeerConnections$.next(Array.from(this._rtcPeerConnectionsMap.values()));
|
|
17161
17397
|
this.subscribeTo(rtcPeerConnController.errors$, (error) => {
|
|
17162
|
-
|
|
17398
|
+
firstPeerConnectionError ??= error;
|
|
17399
|
+
this.onError?.(error, { fatal: false });
|
|
17163
17400
|
});
|
|
17164
17401
|
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$)));
|
|
17165
|
-
this._screenShareStatus$.next("started");
|
|
17166
|
-
logger$
|
|
17402
|
+
if (isScreenShare) this._screenShareStatus$.next("started");
|
|
17403
|
+
logger$16.info(`[WebRTCManager] Additional peer connection connected (${propose}).`);
|
|
17167
17404
|
return rtcPeerConnController.id;
|
|
17168
17405
|
} catch (error) {
|
|
17169
|
-
logger$
|
|
17170
|
-
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
17406
|
+
logger$16.warn("[WebRTCManager] Error initializing additional peer connection:", error);
|
|
17171
17407
|
if (rtcPeerConnController) rtcPeerConnController.destroy();
|
|
17172
|
-
this._screenShareStatus$.next("none");
|
|
17408
|
+
if (isScreenShare) this._screenShareStatus$.next("none");
|
|
17409
|
+
if (firstPeerConnectionError) throw firstPeerConnectionError instanceof MediaAccessError && firstPeerConnectionError.originalError instanceof Error ? firstPeerConnectionError.originalError : firstPeerConnectionError;
|
|
17410
|
+
if (error instanceof import_cjs$15.EmptyError) {
|
|
17411
|
+
logger$16.debug("[WebRTCManager] Additional peer connection aborted before connecting.");
|
|
17412
|
+
return;
|
|
17413
|
+
}
|
|
17414
|
+
throw error instanceof Error ? error : new Error(String(error), { cause: error });
|
|
17173
17415
|
}
|
|
17174
17416
|
}
|
|
17175
17417
|
async removeInputDevices(id) {
|
|
@@ -17185,9 +17427,9 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17185
17427
|
if (removeTrack) return this.mainPeerConnection.stopTrackSender(removeTrack, { updateTransceiverDirection: true });
|
|
17186
17428
|
}
|
|
17187
17429
|
async removeScreenMedia() {
|
|
17188
|
-
if (!["starting", "started"].includes(this._screenShareStatus$.value)) logger$
|
|
17430
|
+
if (!["starting", "started"].includes(this._screenShareStatus$.value)) logger$16.warn("[WebRTCManager] No active screen share to stop.");
|
|
17189
17431
|
if (!this._screenShareId) {
|
|
17190
|
-
logger$
|
|
17432
|
+
logger$16.debug("[WebRTCManager] No screen share peer connection found.");
|
|
17191
17433
|
return;
|
|
17192
17434
|
}
|
|
17193
17435
|
this._screenShareStatus$.next("stopping");
|
|
@@ -17216,7 +17458,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17216
17458
|
dialogParams: this.dialogParams(rtcPeerConnController)
|
|
17217
17459
|
}));
|
|
17218
17460
|
} catch (error) {
|
|
17219
|
-
logger$
|
|
17461
|
+
logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto bye:", error);
|
|
17220
17462
|
throw error;
|
|
17221
17463
|
}
|
|
17222
17464
|
}
|
|
@@ -17234,7 +17476,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17234
17476
|
try {
|
|
17235
17477
|
await this.executeVerto(vertoInfoMessage);
|
|
17236
17478
|
} catch (error) {
|
|
17237
|
-
logger$
|
|
17479
|
+
logger$16.warn("[WebRTCManager] Error sending DTMF digits:", error);
|
|
17238
17480
|
throw error;
|
|
17239
17481
|
}
|
|
17240
17482
|
}
|
|
@@ -17245,10 +17487,10 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17245
17487
|
action: "transfer"
|
|
17246
17488
|
});
|
|
17247
17489
|
try {
|
|
17248
|
-
logger$
|
|
17490
|
+
logger$16.debug("[WebRTCManager] Transferring call with options:", options);
|
|
17249
17491
|
await this.executeVerto(message);
|
|
17250
17492
|
} catch (error) {
|
|
17251
|
-
logger$
|
|
17493
|
+
logger$16.error("[WebRTCManager] Error transferring call:", error);
|
|
17252
17494
|
throw error;
|
|
17253
17495
|
}
|
|
17254
17496
|
}
|
|
@@ -17265,7 +17507,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17265
17507
|
//#endregion
|
|
17266
17508
|
//#region src/controllers/RemoteAudioMeter.ts
|
|
17267
17509
|
var import_cjs$14 = require_cjs();
|
|
17268
|
-
const logger$
|
|
17510
|
+
const logger$15 = getLogger();
|
|
17269
17511
|
/**
|
|
17270
17512
|
* Read-only audio level meter for a remote MediaStream. Attaches an
|
|
17271
17513
|
* AnalyserNode to a MediaStreamAudioSourceNode so it observes the stream
|
|
@@ -17300,7 +17542,7 @@ var RemoteAudioMeter = class extends Destroyable {
|
|
|
17300
17542
|
try {
|
|
17301
17543
|
this._source.disconnect();
|
|
17302
17544
|
} catch (error) {
|
|
17303
|
-
logger$
|
|
17545
|
+
logger$15.debug("[RemoteAudioMeter] source disconnect warning:", error);
|
|
17304
17546
|
}
|
|
17305
17547
|
this._source = null;
|
|
17306
17548
|
this._stream = null;
|
|
@@ -17317,7 +17559,7 @@ var RemoteAudioMeter = class extends Destroyable {
|
|
|
17317
17559
|
this._source = null;
|
|
17318
17560
|
}
|
|
17319
17561
|
this._audioContext.close().catch((error) => {
|
|
17320
|
-
logger$
|
|
17562
|
+
logger$15.debug("[RemoteAudioMeter] audio context close warning:", error);
|
|
17321
17563
|
});
|
|
17322
17564
|
super.destroy();
|
|
17323
17565
|
}
|
|
@@ -17336,7 +17578,7 @@ var RemoteAudioMeter = class extends Destroyable {
|
|
|
17336
17578
|
//#endregion
|
|
17337
17579
|
//#region src/controllers/RTCStatsMonitor.ts
|
|
17338
17580
|
var import_cjs$13 = require_cjs();
|
|
17339
|
-
const logger$
|
|
17581
|
+
const logger$14 = getLogger();
|
|
17340
17582
|
const DEFAULT_POLLING_INTERVAL_MS = 1e3;
|
|
17341
17583
|
const DEFAULT_BASELINE_SAMPLES = 10;
|
|
17342
17584
|
const DEFAULT_NO_AUDIO_PACKET_THRESHOLD_MS = 2e3;
|
|
@@ -17426,9 +17668,9 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
17426
17668
|
const now = Date.now();
|
|
17427
17669
|
this.lastAudioPacketChangeTime = now;
|
|
17428
17670
|
this.lastVideoPacketChangeTime = now;
|
|
17429
|
-
logger$
|
|
17671
|
+
logger$14.debug("[RTCStatsMonitor] Starting stats monitoring");
|
|
17430
17672
|
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) => {
|
|
17431
|
-
logger$
|
|
17673
|
+
logger$14.warn("[RTCStatsMonitor] Failed to get stats:", err);
|
|
17432
17674
|
return import_cjs$13.EMPTY;
|
|
17433
17675
|
}))), (0, import_cjs$13.filter)(() => this.running), (0, import_cjs$13.map)((report) => this.extractSample(report))), (sample$1) => this._sample$.next(sample$1));
|
|
17434
17676
|
this.subscribeTo(this._sample$.pipe((0, import_cjs$13.take)(this.baselineSampleCount), (0, import_cjs$13.toArray)(), (0, import_cjs$13.map)((samples) => ({
|
|
@@ -17436,7 +17678,7 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
17436
17678
|
jitter: samples.reduce((s, b) => s + b.audioJitter, 0) / samples.length,
|
|
17437
17679
|
ready: true
|
|
17438
17680
|
}))), (baseline) => {
|
|
17439
|
-
logger$
|
|
17681
|
+
logger$14.debug(`[RTCStatsMonitor] Baseline established: rtt=${baseline.rtt.toFixed(1)}ms, jitter=${baseline.jitter.toFixed(1)}ms`);
|
|
17440
17682
|
this._baseline$.next(baseline);
|
|
17441
17683
|
});
|
|
17442
17684
|
this.subscribeTo(this._sample$.pipe((0, import_cjs$13.scan)((acc, sample$1) => ({
|
|
@@ -17473,10 +17715,10 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
17473
17715
|
stop() {
|
|
17474
17716
|
if (!this.running) return;
|
|
17475
17717
|
this.running = false;
|
|
17476
|
-
logger$
|
|
17718
|
+
logger$14.debug("[RTCStatsMonitor] Stopping stats monitoring");
|
|
17477
17719
|
}
|
|
17478
17720
|
destroy() {
|
|
17479
|
-
logger$
|
|
17721
|
+
logger$14.debug("[RTCStatsMonitor] Destroying RTCStatsMonitor");
|
|
17480
17722
|
this.stop();
|
|
17481
17723
|
super.destroy();
|
|
17482
17724
|
}
|
|
@@ -17605,7 +17847,7 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
17605
17847
|
//#endregion
|
|
17606
17848
|
//#region src/managers/CallRecoveryManager.ts
|
|
17607
17849
|
var import_cjs$12 = require_cjs();
|
|
17608
|
-
const logger$
|
|
17850
|
+
const logger$13 = getLogger();
|
|
17609
17851
|
const DEFAULT_DEBOUNCE_TIME_MS = 2e3;
|
|
17610
17852
|
const DEFAULT_COOLDOWN_MS = 1e4;
|
|
17611
17853
|
const DEFAULT_ICE_GRACE_PERIOD_MS = 3e3;
|
|
@@ -17702,10 +17944,10 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17702
17944
|
*/
|
|
17703
17945
|
async requestIceRestart() {
|
|
17704
17946
|
if (this._recoveryState$.value === "recovering") {
|
|
17705
|
-
logger$
|
|
17947
|
+
logger$13.info("CallRecoveryManager: manual ICE restart skipped — recovery already in progress");
|
|
17706
17948
|
return;
|
|
17707
17949
|
}
|
|
17708
|
-
logger$
|
|
17950
|
+
logger$13.info("CallRecoveryManager: manual ICE restart requested");
|
|
17709
17951
|
this.transitionTo("recovering");
|
|
17710
17952
|
await this.executeIceRestart(false);
|
|
17711
17953
|
this.startCooldown();
|
|
@@ -17721,7 +17963,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17721
17963
|
* WebSocket reconnect or call state recovers to 'connected'.
|
|
17722
17964
|
*/
|
|
17723
17965
|
reset() {
|
|
17724
|
-
logger$
|
|
17966
|
+
logger$13.info("CallRecoveryManager: resetting counters");
|
|
17725
17967
|
this._attemptCount = 0;
|
|
17726
17968
|
this._keyframeBurstCount = 0;
|
|
17727
17969
|
this._keyframeBurstStart = 0;
|
|
@@ -17736,7 +17978,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17736
17978
|
*/
|
|
17737
17979
|
notifyModifyFailed() {
|
|
17738
17980
|
if (this._recoveryState$.value === "cooldown" || this._recoveryState$.value === "idle") {
|
|
17739
|
-
logger$
|
|
17981
|
+
logger$13.info("CallRecoveryManager: verto.modify failed — re-entering recovery");
|
|
17740
17982
|
this._cooldownUntil = 0;
|
|
17741
17983
|
this.transitionTo("idle");
|
|
17742
17984
|
this.pushTrigger({
|
|
@@ -17760,7 +18002,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17760
18002
|
reason: `bandwidth ${bitrateKbps}kbps below threshold ${this._config.degradationBitrateThreshold}kbps`,
|
|
17761
18003
|
timestamp: Date.now()
|
|
17762
18004
|
});
|
|
17763
|
-
logger$
|
|
18005
|
+
logger$13.warn(`CallRecoveryManager: disabling video — bandwidth ${bitrateKbps}kbps < ${this._config.degradationBitrateThreshold}kbps`);
|
|
17764
18006
|
} else if (wasConstrained && bitrateKbps >= this._config.degradationRecoveryThreshold) {
|
|
17765
18007
|
this._bandwidthConstrained$.next(false);
|
|
17766
18008
|
this._callbacks.enableVideo();
|
|
@@ -17769,7 +18011,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17769
18011
|
reason: `bandwidth ${bitrateKbps}kbps recovered above ${this._config.degradationRecoveryThreshold}kbps`,
|
|
17770
18012
|
timestamp: Date.now()
|
|
17771
18013
|
});
|
|
17772
|
-
logger$
|
|
18014
|
+
logger$13.info(`CallRecoveryManager: restoring video — bandwidth ${bitrateKbps}kbps >= ${this._config.degradationRecoveryThreshold}kbps`);
|
|
17773
18015
|
}
|
|
17774
18016
|
}
|
|
17775
18017
|
/**
|
|
@@ -17788,14 +18030,14 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17788
18030
|
handleWebSocketReconnect() {
|
|
17789
18031
|
const pcState = this._callbacks.getPeerConnectionState();
|
|
17790
18032
|
if (pcState === "connected" || pcState === "completed") {
|
|
17791
|
-
logger$
|
|
18033
|
+
logger$13.info("CallRecoveryManager: signal-only reconnect — peer connection still alive");
|
|
17792
18034
|
this.emitEvent({
|
|
17793
18035
|
action: "signal_reconnect",
|
|
17794
18036
|
reason: "WebSocket reconnected, peer connection still connected",
|
|
17795
18037
|
timestamp: Date.now()
|
|
17796
18038
|
});
|
|
17797
18039
|
} else {
|
|
17798
|
-
logger$
|
|
18040
|
+
logger$13.info("CallRecoveryManager: full reconnect — peer connection also down");
|
|
17799
18041
|
this.emitEvent({
|
|
17800
18042
|
action: "full_reconnect",
|
|
17801
18043
|
reason: "WebSocket reconnected, peer connection not connected — ICE restart needed",
|
|
@@ -17819,7 +18061,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17819
18061
|
}), (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$))), {
|
|
17820
18062
|
next: () => {},
|
|
17821
18063
|
error: (err) => {
|
|
17822
|
-
logger$
|
|
18064
|
+
logger$13.error("CallRecoveryManager: pipeline error", err);
|
|
17823
18065
|
this.transitionTo("idle");
|
|
17824
18066
|
}
|
|
17825
18067
|
});
|
|
@@ -17846,27 +18088,27 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17846
18088
|
reason: `no packet loss for ${this._config.packetLossRecoveryDelaySec}s — restoring video`,
|
|
17847
18089
|
timestamp: Date.now()
|
|
17848
18090
|
});
|
|
17849
|
-
logger$
|
|
18091
|
+
logger$13.info(`CallRecoveryManager: restoring video — no packet loss for ${this._config.packetLossRecoveryDelaySec}s`);
|
|
17850
18092
|
});
|
|
17851
18093
|
}
|
|
17852
18094
|
passGateChecks(signalingReady) {
|
|
17853
18095
|
if (this._callbacks.isNegotiating()) {
|
|
17854
|
-
logger$
|
|
18096
|
+
logger$13.debug("CallRecoveryManager: gate blocked — negotiation in progress");
|
|
17855
18097
|
this.transitionTo("idle");
|
|
17856
18098
|
return false;
|
|
17857
18099
|
}
|
|
17858
18100
|
if (!signalingReady) {
|
|
17859
|
-
logger$
|
|
18101
|
+
logger$13.debug("CallRecoveryManager: gate blocked — signaling not ready");
|
|
17860
18102
|
this.transitionTo("idle");
|
|
17861
18103
|
return false;
|
|
17862
18104
|
}
|
|
17863
18105
|
if (!this._callbacks.isCallConnected()) {
|
|
17864
|
-
logger$
|
|
18106
|
+
logger$13.debug("CallRecoveryManager: gate blocked — call not connected");
|
|
17865
18107
|
this.transitionTo("idle");
|
|
17866
18108
|
return false;
|
|
17867
18109
|
}
|
|
17868
18110
|
if (this.isCooldownActive()) {
|
|
17869
|
-
logger$
|
|
18111
|
+
logger$13.debug("CallRecoveryManager: gate blocked — cooldown active");
|
|
17870
18112
|
this.transitionTo("cooldown");
|
|
17871
18113
|
return false;
|
|
17872
18114
|
}
|
|
@@ -17877,9 +18119,9 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17877
18119
|
}
|
|
17878
18120
|
executeTieredRecovery(trigger) {
|
|
17879
18121
|
this.transitionTo("recovering");
|
|
17880
|
-
logger$
|
|
18122
|
+
logger$13.info(`CallRecoveryManager: starting tiered recovery — source=${trigger.source} detail=${trigger.detail}`);
|
|
17881
18123
|
return (0, import_cjs$12.from)(this.runTiers(trigger)).pipe((0, import_cjs$12.tap)(() => this.startCooldown()), (0, import_cjs$12.catchError)((err) => {
|
|
17882
|
-
logger$
|
|
18124
|
+
logger$13.error("CallRecoveryManager: tiered recovery failed", err);
|
|
17883
18125
|
this.startCooldown();
|
|
17884
18126
|
return import_cjs$12.EMPTY;
|
|
17885
18127
|
}));
|
|
@@ -17887,7 +18129,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17887
18129
|
async runTiers(trigger) {
|
|
17888
18130
|
this.executeKeyframe(trigger.detail);
|
|
17889
18131
|
if (trigger.issueType && DEGRADATION_ONLY_ISSUES.has(trigger.issueType)) {
|
|
17890
|
-
logger$
|
|
18132
|
+
logger$13.debug(`CallRecoveryManager: degradation-only issue (${trigger.issueType}) — Tier 1 only, skipping ICE restart`);
|
|
17891
18133
|
return;
|
|
17892
18134
|
}
|
|
17893
18135
|
if (this._attemptCount < this._config.maxAttempts) {
|
|
@@ -17904,13 +18146,13 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17904
18146
|
maxAttempts: this._config.maxAttempts,
|
|
17905
18147
|
timestamp: Date.now()
|
|
17906
18148
|
});
|
|
17907
|
-
logger$
|
|
18149
|
+
logger$13.warn("CallRecoveryManager: max recovery attempts reached");
|
|
17908
18150
|
}
|
|
17909
18151
|
}
|
|
17910
18152
|
executeKeyframe(reason) {
|
|
17911
18153
|
const now = Date.now();
|
|
17912
18154
|
if (now < this._keyframeCooldownUntil) {
|
|
17913
|
-
logger$
|
|
18155
|
+
logger$13.debug("CallRecoveryManager: keyframe request skipped — cooldown active");
|
|
17914
18156
|
return;
|
|
17915
18157
|
}
|
|
17916
18158
|
if (now - this._keyframeBurstStart > this._config.keyframeBurstWindowMs) {
|
|
@@ -17919,7 +18161,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17919
18161
|
}
|
|
17920
18162
|
if (this._keyframeBurstCount >= this._config.keyframeMaxBurst) {
|
|
17921
18163
|
this._keyframeCooldownUntil = now + this._config.keyframeCooldownMs;
|
|
17922
|
-
logger$
|
|
18164
|
+
logger$13.debug(`CallRecoveryManager: keyframe burst limit reached (${this._config.keyframeMaxBurst}), cooldown until ${this._keyframeCooldownUntil}`);
|
|
17923
18165
|
return;
|
|
17924
18166
|
}
|
|
17925
18167
|
this._keyframeBurstCount += 1;
|
|
@@ -17929,12 +18171,12 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17929
18171
|
reason,
|
|
17930
18172
|
timestamp: now
|
|
17931
18173
|
});
|
|
17932
|
-
logger$
|
|
18174
|
+
logger$13.debug(`CallRecoveryManager: keyframe requested (burst ${this._keyframeBurstCount}/${this._config.keyframeMaxBurst})`);
|
|
17933
18175
|
}
|
|
17934
18176
|
async executeIceRestart(relayOnly) {
|
|
17935
18177
|
this._attemptCount += 1;
|
|
17936
18178
|
const tier = relayOnly ? "Tier 3 (relay-only)" : "Tier 2 (standard)";
|
|
17937
|
-
logger$
|
|
18179
|
+
logger$13.info(`CallRecoveryManager: ${tier} ICE restart — attempt ${this._attemptCount}/${this._config.maxAttempts}`);
|
|
17938
18180
|
this.emitEvent({
|
|
17939
18181
|
action: "reinvite_started",
|
|
17940
18182
|
reason: `${tier} ICE restart`,
|
|
@@ -17951,7 +18193,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17951
18193
|
maxAttempts: this._config.maxAttempts,
|
|
17952
18194
|
timestamp: Date.now()
|
|
17953
18195
|
});
|
|
17954
|
-
logger$
|
|
18196
|
+
logger$13.info(`CallRecoveryManager: ${tier} ICE restart succeeded`);
|
|
17955
18197
|
this._attemptCount = 0;
|
|
17956
18198
|
return true;
|
|
17957
18199
|
}
|
|
@@ -17962,7 +18204,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17962
18204
|
maxAttempts: this._config.maxAttempts,
|
|
17963
18205
|
timestamp: Date.now()
|
|
17964
18206
|
});
|
|
17965
|
-
logger$
|
|
18207
|
+
logger$13.warn(`CallRecoveryManager: ${tier} ICE restart failed`);
|
|
17966
18208
|
return false;
|
|
17967
18209
|
} catch {
|
|
17968
18210
|
this.emitEvent({
|
|
@@ -17972,7 +18214,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17972
18214
|
maxAttempts: this._config.maxAttempts,
|
|
17973
18215
|
timestamp: Date.now()
|
|
17974
18216
|
});
|
|
17975
|
-
logger$
|
|
18217
|
+
logger$13.warn(`CallRecoveryManager: ${tier} ICE restart timed out`);
|
|
17976
18218
|
return false;
|
|
17977
18219
|
}
|
|
17978
18220
|
}
|
|
@@ -17993,7 +18235,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17993
18235
|
transitionTo(state) {
|
|
17994
18236
|
const prev = this._recoveryState$.value;
|
|
17995
18237
|
if (prev !== state) {
|
|
17996
|
-
logger$
|
|
18238
|
+
logger$13.debug(`CallRecoveryManager: state ${prev} -> ${state}`);
|
|
17997
18239
|
this._recoveryState$.next(state);
|
|
17998
18240
|
}
|
|
17999
18241
|
}
|
|
@@ -18096,7 +18338,7 @@ function mosToQualityLevel(mos) {
|
|
|
18096
18338
|
//#endregion
|
|
18097
18339
|
//#region src/core/entities/Call.ts
|
|
18098
18340
|
var import_cjs$11 = require_cjs();
|
|
18099
|
-
const logger$
|
|
18341
|
+
const logger$12 = getLogger();
|
|
18100
18342
|
/**
|
|
18101
18343
|
* Ratio between the critical and warning RTT spike multipliers.
|
|
18102
18344
|
* Warning threshold = baseline * warningMultiplier (default 3x)
|
|
@@ -18114,7 +18356,7 @@ const fromDestinationParams = (destination) => {
|
|
|
18114
18356
|
});
|
|
18115
18357
|
return params;
|
|
18116
18358
|
} catch (error) {
|
|
18117
|
-
logger$
|
|
18359
|
+
logger$12.warn(`Failed to parse destination URI: ${destination}`, error);
|
|
18118
18360
|
return {};
|
|
18119
18361
|
}
|
|
18120
18362
|
};
|
|
@@ -18248,7 +18490,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18248
18490
|
/** Toggles the call lock state, preventing or allowing new participants from joining. */
|
|
18249
18491
|
async toggleLock() {
|
|
18250
18492
|
const method = this.locked ? "call.unlock" : "call.lock";
|
|
18251
|
-
await this.executeMethod(this.
|
|
18493
|
+
await this.executeMethod(this.callSelf, method, {});
|
|
18252
18494
|
}
|
|
18253
18495
|
/**
|
|
18254
18496
|
* Toggles the hold state of the call (pauses/resumes local media transmission).
|
|
@@ -18297,14 +18539,24 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18297
18539
|
*
|
|
18298
18540
|
* Constructs call context (node_id, call_id, member_id) and sends the RPC request.
|
|
18299
18541
|
*
|
|
18300
|
-
* @param target - Target
|
|
18542
|
+
* @param target - Target {@link MemberTarget} triple, or the local member's
|
|
18543
|
+
* ID string for self-operations (any other string is rejected — a bare
|
|
18544
|
+
* member id cannot carry the remote member's own call context).
|
|
18301
18545
|
* @param method - Verto method name (e.g. `'call.mute'`, `'call.member.remove'`).
|
|
18302
18546
|
* @param args - Parameters for the RPC method.
|
|
18303
18547
|
* @returns The RPC response.
|
|
18548
|
+
* @throws {CallNotReadyError} If the call has no self member context yet.
|
|
18549
|
+
* @throws {InvalidParams} If a string target is not the local member's ID.
|
|
18304
18550
|
* @throws {JSONRPCError} If the RPC call returns an error.
|
|
18305
18551
|
*/
|
|
18306
18552
|
async executeMethod(target, method, args) {
|
|
18307
|
-
const
|
|
18553
|
+
const self = this.callSelf;
|
|
18554
|
+
if (typeof target === "string" && target !== self.member_id) throw new InvalidParams(`Target member ID ${target} does not match call's self member ID ${self.member_id}`);
|
|
18555
|
+
const params = {
|
|
18556
|
+
...args,
|
|
18557
|
+
self,
|
|
18558
|
+
target: typeof target === "string" ? self : target
|
|
18559
|
+
};
|
|
18308
18560
|
const request = buildRPCRequest({
|
|
18309
18561
|
method,
|
|
18310
18562
|
params
|
|
@@ -18314,29 +18566,27 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18314
18566
|
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);
|
|
18315
18567
|
return response;
|
|
18316
18568
|
} catch (error) {
|
|
18317
|
-
logger$
|
|
18569
|
+
logger$12.error(`[Call] Error executing method ${method} with params`, params, error);
|
|
18318
18570
|
throw error;
|
|
18319
18571
|
}
|
|
18320
18572
|
}
|
|
18321
|
-
|
|
18322
|
-
|
|
18323
|
-
|
|
18324
|
-
|
|
18325
|
-
|
|
18326
|
-
|
|
18327
|
-
|
|
18328
|
-
|
|
18329
|
-
|
|
18330
|
-
|
|
18331
|
-
|
|
18573
|
+
/**
|
|
18574
|
+
* The local leg's member triple — sent as `self` in every member RPC
|
|
18575
|
+
* envelope, and as the `target` of call-scoped self-operations (e.g. lock,
|
|
18576
|
+
* layout).
|
|
18577
|
+
*
|
|
18578
|
+
* @throws {CallNotReadyError} Before `call.joined` delivers the self member
|
|
18579
|
+
* context (`selfId`/`nodeId`) — an RPC without it cannot be routed, so fail
|
|
18580
|
+
* fast instead of sending a doomed request.
|
|
18581
|
+
*/
|
|
18582
|
+
get callSelf() {
|
|
18583
|
+
const node_id = this.nodeId;
|
|
18584
|
+
const member_id = this.vertoManager.selfId;
|
|
18585
|
+
if (!node_id || !member_id) throw new CallNotReadyError(this.id);
|
|
18332
18586
|
return {
|
|
18333
|
-
|
|
18334
|
-
|
|
18335
|
-
|
|
18336
|
-
node_id: this.nodeId ?? "",
|
|
18337
|
-
call_id: this.id,
|
|
18338
|
-
member_id: target
|
|
18339
|
-
}
|
|
18587
|
+
node_id,
|
|
18588
|
+
call_id: this.id,
|
|
18589
|
+
member_id
|
|
18340
18590
|
};
|
|
18341
18591
|
}
|
|
18342
18592
|
/** Observable of the current call status (e.g. `'ringing'`, `'connected'`). */
|
|
@@ -18517,9 +18767,9 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18517
18767
|
*/
|
|
18518
18768
|
initResilienceSubsystems() {
|
|
18519
18769
|
const pc = this.rtcPeerConnection;
|
|
18520
|
-
logger$
|
|
18770
|
+
logger$12.debug(`[Call] initResilienceSubsystems: pc=${pc ? "exists" : "undefined"}, connectionState=${pc?.connectionState}`);
|
|
18521
18771
|
if (!pc) {
|
|
18522
|
-
logger$
|
|
18772
|
+
logger$12.warn("[Call] No peer connection available, skipping resilience init");
|
|
18523
18773
|
return;
|
|
18524
18774
|
}
|
|
18525
18775
|
try {
|
|
@@ -18554,14 +18804,14 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18554
18804
|
disableVideo: () => {
|
|
18555
18805
|
try {
|
|
18556
18806
|
this.vertoManager.muteMainVideoInputDevice();
|
|
18557
|
-
logger$
|
|
18807
|
+
logger$12.debug("[Call] Recovery manager disabled video");
|
|
18558
18808
|
} catch {
|
|
18559
|
-
logger$
|
|
18809
|
+
logger$12.debug("[Call] Recovery manager failed to disable video");
|
|
18560
18810
|
}
|
|
18561
18811
|
},
|
|
18562
18812
|
enableVideo: () => {
|
|
18563
18813
|
this.vertoManager.unmuteMainVideoInputDevice().catch(() => {
|
|
18564
|
-
logger$
|
|
18814
|
+
logger$12.debug("[Call] Recovery manager failed to enable video");
|
|
18565
18815
|
});
|
|
18566
18816
|
},
|
|
18567
18817
|
isNegotiating: () => this.vertoManager.mainPeerConnection.isNegotiating,
|
|
@@ -18611,7 +18861,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18611
18861
|
this.subscribeTo(this._recoveryManager.recoveryEvent$, (event) => {
|
|
18612
18862
|
this._recoveryEvent$.next(event);
|
|
18613
18863
|
if (event.action === "max_attempts_reached") {
|
|
18614
|
-
logger$
|
|
18864
|
+
logger$12.warn("[Call] All recovery attempts exhausted, terminating call");
|
|
18615
18865
|
this.emitError({
|
|
18616
18866
|
kind: "network",
|
|
18617
18867
|
fatal: true,
|
|
@@ -18631,13 +18881,13 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18631
18881
|
else if (event.type === "online") this._recoveryManager?.handleWebSocketReconnect();
|
|
18632
18882
|
});
|
|
18633
18883
|
this.subscribeTo(this.clientSession.authenticated$.pipe((0, import_cjs$11.skip)(1), (0, import_cjs$11.filter)(Boolean)), () => {
|
|
18634
|
-
logger$
|
|
18884
|
+
logger$12.debug("[Call] WebSocket reconnected — notifying recovery manager");
|
|
18635
18885
|
this._recoveryManager?.handleWebSocketReconnect();
|
|
18636
18886
|
});
|
|
18637
18887
|
this._statsMonitor.start();
|
|
18638
|
-
logger$
|
|
18888
|
+
logger$12.debug("[Call] Resilience subsystems initialized for call", this.id);
|
|
18639
18889
|
} catch (error) {
|
|
18640
|
-
logger$
|
|
18890
|
+
logger$12.warn("[Call] Failed to initialize resilience subsystems:", error);
|
|
18641
18891
|
}
|
|
18642
18892
|
}
|
|
18643
18893
|
/**
|
|
@@ -18720,19 +18970,19 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18720
18970
|
}
|
|
18721
18971
|
isCallSessionEvent(event) {
|
|
18722
18972
|
try {
|
|
18723
|
-
logger$
|
|
18973
|
+
logger$12.debug("[Call] Checking if event is for this call session:", event);
|
|
18724
18974
|
const callId = getValueFrom(event, "params.params.callID") ?? getValueFrom(event, "params.call_id");
|
|
18725
18975
|
const roomSessionId = getValueFrom(event, "params.room_session_id");
|
|
18726
|
-
logger$
|
|
18976
|
+
logger$12.debug(`[Call] Extracted session identifiers callID: ${callId} and roomSessionID: ${roomSessionId} from event:`);
|
|
18727
18977
|
return callId === this.id || !!callId && this.callEventsManager.isCallIdValid(callId) || !!roomSessionId && this.callEventsManager.isRoomSessionIdValid(roomSessionId);
|
|
18728
18978
|
} catch (error) {
|
|
18729
|
-
logger$
|
|
18979
|
+
logger$12.error("[Call] Error checking if event is for this call session:", error);
|
|
18730
18980
|
return false;
|
|
18731
18981
|
}
|
|
18732
18982
|
}
|
|
18733
18983
|
get callSessionEvents$() {
|
|
18734
18984
|
return this.cachedObservable("callSessionEvents$", () => this.clientSession.signalingEvent$.pipe((0, import_cjs$11.filter)((event) => this.isCallSessionEvent(event)), (0, import_cjs$11.tap)((event) => {
|
|
18735
|
-
logger$
|
|
18985
|
+
logger$12.debug("[Call] Received call session event:", event);
|
|
18736
18986
|
}), (0, import_cjs$11.takeUntil)(this.destroyed$), (0, import_cjs$11.share)()));
|
|
18737
18987
|
}
|
|
18738
18988
|
/** Observable of call-updated events. */
|
|
@@ -18802,16 +19052,16 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18802
19052
|
this._customSubscriptions.set(eventType, filtered$);
|
|
18803
19053
|
}, (error) => {
|
|
18804
19054
|
this._customSubscriptions.delete(eventType);
|
|
18805
|
-
logger$
|
|
19055
|
+
logger$12.warn(`[Call] verto.subscribe for '${eventType}' failed, not caching:`, error);
|
|
18806
19056
|
});
|
|
18807
19057
|
this._customSubscriptions.set(eventType, filtered$);
|
|
18808
19058
|
return filtered$;
|
|
18809
19059
|
}
|
|
18810
19060
|
get webrtcMessages$() {
|
|
18811
|
-
return this.cachedObservable("webrtcMessages$", () => this.callSessionEvents$.pipe(filterAs(isWebrtcMessageMetadata, "params"), (0, import_cjs$11.tap)((event) => logger$
|
|
19061
|
+
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)()));
|
|
18812
19062
|
}
|
|
18813
19063
|
get callEvent$() {
|
|
18814
|
-
return this.cachedObservable("callEvent$", () => this.callSessionEvents$.pipe(filterAs(isSignalwireCallMetadata, "params"), (0, import_cjs$11.tap)((event) => logger$
|
|
19064
|
+
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)()));
|
|
18815
19065
|
}
|
|
18816
19066
|
get layoutEvent$() {
|
|
18817
19067
|
return this.cachedObservable("layoutEvent$", () => this.callEvent$.pipe(filterAs(isLayoutChangedMetadata, "params")));
|
|
@@ -18889,11 +19139,27 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18889
19139
|
return this.deferEmission(this._answered$.asObservable());
|
|
18890
19140
|
}
|
|
18891
19141
|
/**
|
|
18892
|
-
* Sets the call layout and participant positions.
|
|
19142
|
+
* Sets the call layout and, optionally, individual participant positions.
|
|
19143
|
+
*
|
|
19144
|
+
* The gateway `call.layout.set` DTO has **no** `positions` member, so when
|
|
19145
|
+
* `positions` is provided this method issues a `call.member.position.set`
|
|
19146
|
+
* request per member (via {@link Participant.setPosition}, which keys each
|
|
19147
|
+
* position by that member's own call context) alongside `call.layout.set`
|
|
19148
|
+
* (issue #19400, Flag #6).
|
|
19149
|
+
*
|
|
19150
|
+
* **These operations are NOT atomic.** The layout is applied first, then each
|
|
19151
|
+
* member position sequentially, so members may briefly flash into their
|
|
19152
|
+
* default slots before being moved to the requested positions. Targeted
|
|
19153
|
+
* members are validated upfront, though: when any of them has no
|
|
19154
|
+
* {@link Participant.target | member call context} yet, the whole call
|
|
19155
|
+
* rejects before any request is sent and the layout is left unchanged.
|
|
18893
19156
|
*
|
|
18894
19157
|
* @param layout - Layout name (must be one of {@link layouts}).
|
|
18895
|
-
* @param positions -
|
|
19158
|
+
* @param positions - Optional map of member IDs to {@link VideoPosition} values.
|
|
19159
|
+
* When omitted or empty, only the layout is changed.
|
|
18896
19160
|
* @throws {InvalidParams} If the layout is not in the available {@link layouts}.
|
|
19161
|
+
* @throws {ParticipantNotReadyError} If a targeted member's call context has
|
|
19162
|
+
* not been received yet — thrown before any request is sent.
|
|
18897
19163
|
*
|
|
18898
19164
|
* @example
|
|
18899
19165
|
* ```ts
|
|
@@ -18904,11 +19170,19 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18904
19170
|
*/
|
|
18905
19171
|
async setLayout(layout, positions) {
|
|
18906
19172
|
if (!this.layouts.includes(layout)) throw new InvalidParams(`Layout ${layout} is not available in the current call layouts: ${this.layouts.join(", ")}`);
|
|
19173
|
+
const targets = [];
|
|
19174
|
+
for (const [memberId, position] of Object.entries(positions ?? {})) {
|
|
19175
|
+
const participant = this.participants.find((p) => p.id === memberId);
|
|
19176
|
+
if (!participant) {
|
|
19177
|
+
logger$12.warn(`[Call] setLayout: member ${memberId} not found in participants; skipping position ${position}`);
|
|
19178
|
+
continue;
|
|
19179
|
+
}
|
|
19180
|
+
participant.target;
|
|
19181
|
+
targets.push([participant, position]);
|
|
19182
|
+
}
|
|
18907
19183
|
const selfId = await (0, import_cjs$11.firstValueFrom)(this.selfId$.pipe((0, import_cjs$11.filter)((id) => id !== null)));
|
|
18908
|
-
await this.executeMethod(selfId, "call.layout.set", {
|
|
18909
|
-
|
|
18910
|
-
positions
|
|
18911
|
-
});
|
|
19184
|
+
await this.executeMethod(selfId, "call.layout.set", { layout });
|
|
19185
|
+
for (const [participant, position] of targets) await participant.setPosition(position);
|
|
18912
19186
|
}
|
|
18913
19187
|
/**
|
|
18914
19188
|
* Transfers the call to another destination.
|
|
@@ -18940,7 +19214,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18940
19214
|
setLocalMicrophoneGain(value) {
|
|
18941
19215
|
const pipeline = this.vertoManager.ensureLocalAudioPipeline();
|
|
18942
19216
|
if (!pipeline) {
|
|
18943
|
-
logger$
|
|
19217
|
+
logger$12.warn("[Call] setLocalMicrophoneGain: audio pipeline unavailable");
|
|
18944
19218
|
return;
|
|
18945
19219
|
}
|
|
18946
19220
|
const percent = Math.max(0, Math.min(200, value));
|
|
@@ -18985,7 +19259,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18985
19259
|
enablePushToTalk() {
|
|
18986
19260
|
const pipeline = this.vertoManager.ensureLocalAudioPipeline();
|
|
18987
19261
|
if (!pipeline) {
|
|
18988
|
-
logger$
|
|
19262
|
+
logger$12.warn("[Call] enablePushToTalk: audio pipeline unavailable");
|
|
18989
19263
|
return;
|
|
18990
19264
|
}
|
|
18991
19265
|
pipeline.setPTTActive(false);
|
|
@@ -19082,6 +19356,7 @@ function inferCallErrorKind(error) {
|
|
|
19082
19356
|
if (error instanceof RPCTimeoutError) return "timeout";
|
|
19083
19357
|
if (error instanceof JSONRPCError) return "signaling";
|
|
19084
19358
|
if (error instanceof MediaTrackError) return "media";
|
|
19359
|
+
if (error instanceof MediaAccessError) return "media";
|
|
19085
19360
|
if (error instanceof WebSocketConnectionError || error instanceof TransportConnectionError) return "network";
|
|
19086
19361
|
return "internal";
|
|
19087
19362
|
}
|
|
@@ -19098,6 +19373,7 @@ const RECOVERABLE_RPC_CODES = new Set([
|
|
|
19098
19373
|
function isFatalError(error) {
|
|
19099
19374
|
if (error instanceof VertoPongError) return false;
|
|
19100
19375
|
if (error instanceof MediaTrackError) return false;
|
|
19376
|
+
if (error instanceof MediaAccessError) return error.fatal;
|
|
19101
19377
|
if (error instanceof RPCTimeoutError) return false;
|
|
19102
19378
|
if (error instanceof JSONRPCError && RECOVERABLE_RPC_CODES.has(error.code)) return false;
|
|
19103
19379
|
return true;
|
|
@@ -19123,10 +19399,10 @@ var CallFactory = class {
|
|
|
19123
19399
|
return {
|
|
19124
19400
|
vertoManager: new WebRTCVertoManager(callInstance, this.attachManager, this.deviceController, this.webRTCApiProvider, {
|
|
19125
19401
|
nodeId: options.nodeId,
|
|
19126
|
-
onError: (error) => {
|
|
19402
|
+
onError: (error, options$1) => {
|
|
19127
19403
|
const callError = {
|
|
19128
19404
|
kind: inferCallErrorKind(error),
|
|
19129
|
-
fatal: isFatalError(error),
|
|
19405
|
+
fatal: options$1?.fatal ?? isFatalError(error),
|
|
19130
19406
|
error,
|
|
19131
19407
|
callId: callInstance.id
|
|
19132
19408
|
};
|
|
@@ -19148,7 +19424,7 @@ var CallFactory = class {
|
|
|
19148
19424
|
//#endregion
|
|
19149
19425
|
//#region src/behaviors/Collection.ts
|
|
19150
19426
|
var import_cjs$10 = require_cjs();
|
|
19151
|
-
const logger$
|
|
19427
|
+
const logger$11 = getLogger();
|
|
19152
19428
|
var Fetcher = class {
|
|
19153
19429
|
constructor(endpoint, params, http) {
|
|
19154
19430
|
this.endpoint = endpoint;
|
|
@@ -19172,7 +19448,7 @@ var Fetcher = class {
|
|
|
19172
19448
|
this.hasMore = !!this.nextUrl;
|
|
19173
19449
|
return result.data.filter(this.filter).map(this.mapper);
|
|
19174
19450
|
}
|
|
19175
|
-
logger$
|
|
19451
|
+
logger$11.error("Failed to fetch entity");
|
|
19176
19452
|
return [];
|
|
19177
19453
|
}
|
|
19178
19454
|
async id(v) {
|
|
@@ -19248,7 +19524,7 @@ var EntityCollection = class extends Destroyable {
|
|
|
19248
19524
|
this._hasMore$.next(this.fetchController.hasMore ?? false);
|
|
19249
19525
|
this._loading$.next(false);
|
|
19250
19526
|
} catch (error) {
|
|
19251
|
-
logger$
|
|
19527
|
+
logger$11.error(`Failed to fetch initial collection data`, error);
|
|
19252
19528
|
this._hasMore$.next(this.fetchController.hasMore ?? false);
|
|
19253
19529
|
this._loading$.next(false);
|
|
19254
19530
|
this.onError?.(new CollectionFetchError("fetchMore", error));
|
|
@@ -19262,7 +19538,7 @@ var EntityCollection = class extends Destroyable {
|
|
|
19262
19538
|
if (data) this.upsertData(data);
|
|
19263
19539
|
return data;
|
|
19264
19540
|
} catch (error) {
|
|
19265
|
-
logger$
|
|
19541
|
+
logger$11.error(`Failed to fetch data for (${String(key)}:${String(value)}) :`, error);
|
|
19266
19542
|
this._loading$.next(false);
|
|
19267
19543
|
this.onError?.(new CollectionFetchError(`tryFetch(${String(key)})`, error));
|
|
19268
19544
|
}
|
|
@@ -19511,13 +19787,13 @@ var Address = class extends Destroyable {
|
|
|
19511
19787
|
//#endregion
|
|
19512
19788
|
//#region src/core/utils.ts
|
|
19513
19789
|
var import_cjs$8 = require_cjs();
|
|
19514
|
-
const logger$
|
|
19790
|
+
const logger$10 = getLogger();
|
|
19515
19791
|
const isRPCConnectResult = (e) => {
|
|
19516
|
-
logger$
|
|
19792
|
+
logger$10.debug("isRPCConnectResult check:", e);
|
|
19517
19793
|
if (!e || typeof e !== "object") return false;
|
|
19518
19794
|
const result = e;
|
|
19519
19795
|
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";
|
|
19520
|
-
logger$
|
|
19796
|
+
logger$10.debug("isRPCConnectResult check result:", is);
|
|
19521
19797
|
return is;
|
|
19522
19798
|
};
|
|
19523
19799
|
var PendingRPC = class PendingRPC {
|
|
@@ -19526,7 +19802,7 @@ var PendingRPC = class PendingRPC {
|
|
|
19526
19802
|
}
|
|
19527
19803
|
constructor(request, responses$, options) {
|
|
19528
19804
|
this.id = v4_default();
|
|
19529
|
-
logger$
|
|
19805
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}: method:${request.method}] Creating PendingRPC`);
|
|
19530
19806
|
this.request = request;
|
|
19531
19807
|
const timeoutMs = options?.timeoutMs ?? PendingRPC.defaultTimeoutMs;
|
|
19532
19808
|
const signal = options?.signal;
|
|
@@ -19552,22 +19828,22 @@ var PendingRPC = class PendingRPC {
|
|
|
19552
19828
|
isSettled = true;
|
|
19553
19829
|
if (response.error) {
|
|
19554
19830
|
const rpcError = new JSONRPCError(response.error.code, response.error.message, response.error.data, void 0, request.id);
|
|
19555
|
-
logger$
|
|
19831
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with RPC error:`, rpcError);
|
|
19556
19832
|
reject(rpcError);
|
|
19557
19833
|
} else {
|
|
19558
|
-
logger$
|
|
19834
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Resolving promise with response:`, response);
|
|
19559
19835
|
resolve(response);
|
|
19560
19836
|
}
|
|
19561
19837
|
subscription.unsubscribe();
|
|
19562
19838
|
},
|
|
19563
19839
|
error: (error) => {
|
|
19564
|
-
logger$
|
|
19840
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with error:`, error);
|
|
19565
19841
|
isSettled = true;
|
|
19566
19842
|
reject(error);
|
|
19567
19843
|
subscription.unsubscribe();
|
|
19568
19844
|
},
|
|
19569
19845
|
complete: () => {
|
|
19570
|
-
logger$
|
|
19846
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Observable completed`);
|
|
19571
19847
|
if (!isSettled) reject(new RPCTimeoutError(request.id, timeoutMs));
|
|
19572
19848
|
subscription.unsubscribe();
|
|
19573
19849
|
}
|
|
@@ -19588,7 +19864,18 @@ var PendingRPC = class PendingRPC {
|
|
|
19588
19864
|
//#endregion
|
|
19589
19865
|
//#region src/managers/ClientSessionManager.ts
|
|
19590
19866
|
var import_cjs$7 = require_cjs();
|
|
19591
|
-
const logger$
|
|
19867
|
+
const logger$9 = getLogger();
|
|
19868
|
+
/**
|
|
19869
|
+
* Decide whether an error emitted on `call.errors$` during dial should
|
|
19870
|
+
* abort the dial. A non-fatal MediaAccessError means the call degraded to
|
|
19871
|
+
* receive-only and still connects — everything else rejects `dial()` with
|
|
19872
|
+
* the real cause.
|
|
19873
|
+
*
|
|
19874
|
+
* Pure function — exported for unit testing.
|
|
19875
|
+
*/
|
|
19876
|
+
function shouldAbortDial(callError) {
|
|
19877
|
+
return callError.fatal || !(callError.error instanceof MediaAccessError);
|
|
19878
|
+
}
|
|
19592
19879
|
const getAddressSearchURI = (options) => {
|
|
19593
19880
|
const to = options.to?.split("?")[0];
|
|
19594
19881
|
const from$9 = options.from?.startsWith("subscriber://") ? options.from.replace("subscriber://", "") : options.from;
|
|
@@ -19686,7 +19973,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19686
19973
|
try {
|
|
19687
19974
|
return await this.transport.execute(request, options);
|
|
19688
19975
|
} catch (error) {
|
|
19689
|
-
logger$
|
|
19976
|
+
logger$9.debug("[Session] Execute Error", error);
|
|
19690
19977
|
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
19691
19978
|
throw error;
|
|
19692
19979
|
}
|
|
@@ -19700,13 +19987,13 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19700
19987
|
return true;
|
|
19701
19988
|
}
|
|
19702
19989
|
setupMessageHandlers() {
|
|
19703
|
-
logger$
|
|
19990
|
+
logger$9.debug("[Session] Setting up message handlers");
|
|
19704
19991
|
this.subscribeTo(this.authStateEvent$, async (authStateEvent) => {
|
|
19705
|
-
logger$
|
|
19992
|
+
logger$9.debug("[Session] Authorization state event received:", authStateEvent);
|
|
19706
19993
|
try {
|
|
19707
19994
|
await this.updateAuthorizationStateInStorage(authStateEvent.authorization_state);
|
|
19708
19995
|
} catch (error) {
|
|
19709
|
-
logger$
|
|
19996
|
+
logger$9.error("[Session] Failed to handle authorization state update:", error);
|
|
19710
19997
|
this._errors$.next(new AuthStateHandlerError(error));
|
|
19711
19998
|
}
|
|
19712
19999
|
});
|
|
@@ -19714,29 +20001,29 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19714
20001
|
if (this._authState$.value.kind === "authenticated") this._authState$.next({ kind: "unauthenticated" });
|
|
19715
20002
|
});
|
|
19716
20003
|
this.subscribeTo(this.transport.connectionStatus$.pipe((0, import_cjs$7.filter)((status) => status === "connected"), (0, import_cjs$7.exhaustMap)(() => {
|
|
19717
|
-
logger$
|
|
20004
|
+
logger$9.debug("[Session] Connection established, initiating authentication");
|
|
19718
20005
|
return (0, import_cjs$7.from)(this.authenticate()).pipe((0, import_cjs$7.catchError)((error) => {
|
|
19719
20006
|
this.handleAuthenticationError(error).catch((err) => {
|
|
19720
|
-
logger$
|
|
20007
|
+
logger$9.error("[Session] Error handling authentication failure:", err);
|
|
19721
20008
|
});
|
|
19722
20009
|
return import_cjs$7.EMPTY;
|
|
19723
20010
|
}));
|
|
19724
20011
|
})), void 0);
|
|
19725
20012
|
this.subscribeTo(this.vertoInvite$, async (invite) => {
|
|
19726
|
-
logger$
|
|
20013
|
+
logger$9.debug("[Session] Verto invite received:", invite);
|
|
19727
20014
|
try {
|
|
19728
20015
|
await this.createInboundCall(invite);
|
|
19729
20016
|
} catch (error) {
|
|
19730
|
-
logger$
|
|
20017
|
+
logger$9.error("[Session] Error handling Verto invite:", error);
|
|
19731
20018
|
this._errors$.next(new VertoInviteHandlerError(error));
|
|
19732
20019
|
}
|
|
19733
20020
|
});
|
|
19734
20021
|
this.subscribeTo(this.vertoAttach$, async (attach) => {
|
|
19735
|
-
logger$
|
|
20022
|
+
logger$9.debug("[Session] Verto attach received:", attach);
|
|
19736
20023
|
try {
|
|
19737
20024
|
await this.handleVertoAttach(attach);
|
|
19738
20025
|
} catch (error) {
|
|
19739
|
-
logger$
|
|
20026
|
+
logger$9.error("[Session] Error handling Verto attach:", error);
|
|
19740
20027
|
this._errors$.next(new VertoAttachHandlerError(error));
|
|
19741
20028
|
}
|
|
19742
20029
|
});
|
|
@@ -19746,36 +20033,36 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19746
20033
|
const storedState = await this.storage.getItem(this.authorizationStateKey);
|
|
19747
20034
|
this.authorizationState$.next(storedState ?? void 0);
|
|
19748
20035
|
} catch (error) {
|
|
19749
|
-
logger$
|
|
20036
|
+
logger$9.error("Failed to retrieve authorization state from storage:", error);
|
|
19750
20037
|
this.authorizationState$.next(void 0);
|
|
19751
20038
|
}
|
|
19752
20039
|
}
|
|
19753
20040
|
async updateAuthorizationStateInStorage(authorizationState) {
|
|
19754
20041
|
if (!authorizationState) {
|
|
19755
|
-
logger$
|
|
20042
|
+
logger$9.debug("[Session] Removing authorization state from storage");
|
|
19756
20043
|
try {
|
|
19757
20044
|
await this.storage.removeItem(this.authorizationStateKey);
|
|
19758
20045
|
this.authorizationState$.next(void 0);
|
|
19759
20046
|
} catch (error) {
|
|
19760
|
-
logger$
|
|
20047
|
+
logger$9.error("Failed to remove authorization state from storage:", error);
|
|
19761
20048
|
throw error;
|
|
19762
20049
|
}
|
|
19763
20050
|
return;
|
|
19764
20051
|
}
|
|
19765
20052
|
try {
|
|
19766
|
-
logger$
|
|
20053
|
+
logger$9.debug("[Session] Updating authorization state in storage");
|
|
19767
20054
|
await this.storage.setItem(this.authorizationStateKey, authorizationState);
|
|
19768
20055
|
this.authorizationState$.next(authorizationState);
|
|
19769
20056
|
} catch (error) {
|
|
19770
|
-
logger$
|
|
20057
|
+
logger$9.error("Failed to retrieve authorization state from storage:", error);
|
|
19771
20058
|
throw error;
|
|
19772
20059
|
}
|
|
19773
20060
|
}
|
|
19774
20061
|
get authStateEvent$() {
|
|
19775
20062
|
return this.cachedObservable("authStateEvent$", () => this.signalingEvent$.pipe((0, import_cjs$7.tap)((msg) => {
|
|
19776
|
-
logger$
|
|
20063
|
+
logger$9.debug("[Session] Received incoming message:", msg);
|
|
19777
20064
|
}), filterAs(isSignalwireAuthorizationStateMetadata, "params"), (0, import_cjs$7.tap)((event) => {
|
|
19778
|
-
logger$
|
|
20065
|
+
logger$9.debug("[Session] Authorization state event received:", event.authorization_state);
|
|
19779
20066
|
})));
|
|
19780
20067
|
}
|
|
19781
20068
|
get signalingEvent$() {
|
|
@@ -19813,42 +20100,72 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19813
20100
|
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 })));
|
|
19814
20101
|
}
|
|
19815
20102
|
async handleAuthenticationError(error) {
|
|
19816
|
-
logger$
|
|
19817
|
-
const isRecoverableAuthError = error instanceof JSONRPCError && (error.code === RPC_ERROR_REQUESTER_VALIDATION_FAILED || error.code === RPC_ERROR_INVALID_PARAMS || error.code === RPC_ERROR_AUTHENTICATION_FAILED);
|
|
20103
|
+
logger$9.error("Authentication error:", error);
|
|
20104
|
+
const isRecoverableAuthError$1 = error instanceof JSONRPCError && (error.code === RPC_ERROR_REQUESTER_VALIDATION_FAILED || error.code === RPC_ERROR_INVALID_PARAMS || error.code === RPC_ERROR_AUTHENTICATION_FAILED);
|
|
19818
20105
|
const hasStoredState = await (0, import_cjs$7.firstValueFrom)(this.authorizationState$.pipe((0, import_cjs$7.take)(1))) !== void 0;
|
|
19819
|
-
if (isRecoverableAuthError && hasStoredState) {
|
|
19820
|
-
logger$
|
|
20106
|
+
if (isRecoverableAuthError$1 && hasStoredState) {
|
|
20107
|
+
logger$9.debug("[Session] Recoverable auth error — cleaning up stored state and reconnecting fresh");
|
|
19821
20108
|
try {
|
|
19822
20109
|
await this.cleanupStoredConnectionParams();
|
|
19823
20110
|
} catch (cleanupError) {
|
|
19824
|
-
logger$
|
|
20111
|
+
logger$9.error("Failed to cleanup stored connection params:", cleanupError);
|
|
19825
20112
|
} finally {
|
|
19826
20113
|
this.transport.reconnect();
|
|
19827
20114
|
}
|
|
19828
20115
|
} else this._errors$.next(error);
|
|
19829
20116
|
}
|
|
20117
|
+
/**
|
|
20118
|
+
* Clear the resume state (authorization_state + protocol) only.
|
|
20119
|
+
*
|
|
20120
|
+
* This is the stale-auth-state recovery helper used by handleAuthError:
|
|
20121
|
+
* the server rejected a reconnect, so the resume state is discarded and a
|
|
20122
|
+
* fresh connect follows. Attach records are deliberately preserved — the
|
|
20123
|
+
* session lives on through the reconnect and reattachCalls() needs the
|
|
20124
|
+
* stored call references afterwards. Do NOT add detachAll() here.
|
|
20125
|
+
*
|
|
20126
|
+
* For public teardown (disconnect/destroy), use {@link teardownSessionState}
|
|
20127
|
+
* instead, which clears the attach records as well.
|
|
20128
|
+
*/
|
|
19830
20129
|
async cleanupStoredConnectionParams() {
|
|
19831
20130
|
await this.transport.setProtocol(void 0);
|
|
19832
20131
|
await this.updateAuthorizationStateInStorage(void 0);
|
|
19833
20132
|
this._authorization$.next(void 0);
|
|
19834
20133
|
}
|
|
20134
|
+
/**
|
|
20135
|
+
* Public-teardown helper for disconnect()/destroy(). Clears the resume
|
|
20136
|
+
* state (authorization_state + protocol) AND the attach records as one
|
|
20137
|
+
* atomic unit.
|
|
20138
|
+
*
|
|
20139
|
+
* The two stores are coupled: the backend only honors attach records
|
|
20140
|
+
* within the session identified by the resume state, so ending the
|
|
20141
|
+
* session must clear both. Clearing one without the other strands records
|
|
20142
|
+
* no future session can honor (disconnect) or revives a session the
|
|
20143
|
+
* developer explicitly ended (destroy).
|
|
20144
|
+
*
|
|
20145
|
+
* Distinct from {@link cleanupStoredConnectionParams}, which keeps the
|
|
20146
|
+
* attach records for the stale-auth-state recovery path.
|
|
20147
|
+
*/
|
|
20148
|
+
async teardownSessionState() {
|
|
20149
|
+
await this.cleanupStoredConnectionParams();
|
|
20150
|
+
await this.attachManager.detachAll();
|
|
20151
|
+
}
|
|
19835
20152
|
async updateAuthState(authorization_state) {
|
|
19836
20153
|
try {
|
|
19837
20154
|
await this.storage.setItem(this.authorizationStateKey, authorization_state);
|
|
19838
20155
|
} catch (error) {
|
|
19839
|
-
logger$
|
|
20156
|
+
logger$9.error("Failed to update authorization state in storage:", error);
|
|
19840
20157
|
this._errors$.next(new AuthStateHandlerError(error));
|
|
19841
20158
|
}
|
|
19842
20159
|
}
|
|
19843
20160
|
async reauthenticate(token, dpopToken, options) {
|
|
19844
|
-
logger$
|
|
20161
|
+
logger$9.debug("[Session] Re-authenticating session");
|
|
19845
20162
|
try {
|
|
19846
20163
|
let resolvedDpopToken = dpopToken;
|
|
19847
20164
|
if (!resolvedDpopToken && this.dpopManager?.initialized) try {
|
|
19848
20165
|
resolvedDpopToken = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
|
|
19849
20166
|
} catch (error) {
|
|
19850
20167
|
if (this.clientBound) throw error;
|
|
19851
|
-
logger$
|
|
20168
|
+
logger$9.warn("[Session] Failed to create DPoP proof for reauthenticate:", error);
|
|
19852
20169
|
}
|
|
19853
20170
|
const request = RPCReauthenticate({
|
|
19854
20171
|
project: this._authorization$.value?.project_id ?? "",
|
|
@@ -19856,24 +20173,24 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19856
20173
|
...resolvedDpopToken ? { dpop_token: resolvedDpopToken } : {}
|
|
19857
20174
|
});
|
|
19858
20175
|
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) => {
|
|
19859
|
-
logger$
|
|
20176
|
+
logger$9.error("[Session] Re-authentication RPC failed:", err);
|
|
19860
20177
|
throw err;
|
|
19861
20178
|
})));
|
|
19862
20179
|
if (options?.clientBound) this._wasClientBound = true;
|
|
19863
|
-
logger$
|
|
20180
|
+
logger$9.debug("[Session] Re-authentication successful, updating stored auth state");
|
|
19864
20181
|
} catch (error) {
|
|
19865
|
-
logger$
|
|
20182
|
+
logger$9.error("[Session] Re-authentication failed:", error);
|
|
19866
20183
|
this._errors$.next(new AuthStateHandlerError(error));
|
|
19867
20184
|
throw error;
|
|
19868
20185
|
}
|
|
19869
20186
|
}
|
|
19870
20187
|
async authenticate() {
|
|
19871
|
-
logger$
|
|
20188
|
+
logger$9.debug("[Session] Starting authentication process");
|
|
19872
20189
|
const persistedParams = await (0, import_cjs$7.firstValueFrom)((0, import_cjs$7.combineLatest)({
|
|
19873
20190
|
protocol: this.transport.protocol$,
|
|
19874
20191
|
authorization_state: this.authorizationState$
|
|
19875
20192
|
}).pipe((0, import_cjs$7.take)(1)));
|
|
19876
|
-
logger$
|
|
20193
|
+
logger$9.debug("[Session] Persisted params:\n", {
|
|
19877
20194
|
protocol: persistedParams.protocol,
|
|
19878
20195
|
authStateLength: persistedParams.authorization_state?.length
|
|
19879
20196
|
});
|
|
@@ -19881,16 +20198,20 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19881
20198
|
const storedToken = this.getCredential().token;
|
|
19882
20199
|
const isReconnect = hasReconnectState && storedToken;
|
|
19883
20200
|
let dpopToken;
|
|
19884
|
-
if (isReconnect) logger$
|
|
19885
|
-
else
|
|
19886
|
-
|
|
19887
|
-
|
|
20201
|
+
if (isReconnect) logger$9.debug("[Session] Reconnecting with stored jwt_token + authorization_state");
|
|
20202
|
+
else {
|
|
20203
|
+
const credential = this.getCredential();
|
|
20204
|
+
const credentialExpired = credential.expiry_at !== void 0 && credential.expiry_at <= Date.now() + CREDENTIAL_EXPIRY_SKEW_MS;
|
|
20205
|
+
if (this.onBeforeReconnect && (this.clientBound || credentialExpired)) {
|
|
20206
|
+
logger$9.debug("[Session] Refreshing credentials before fresh connect");
|
|
20207
|
+
await this.onBeforeReconnect();
|
|
20208
|
+
}
|
|
19888
20209
|
}
|
|
19889
|
-
if (
|
|
20210
|
+
if (this.dpopManager?.initialized) try {
|
|
19890
20211
|
dpopToken = await this.dpopManager.createRpcProof({ method: "signalwire.connect" });
|
|
19891
20212
|
} catch (error) {
|
|
19892
20213
|
if (this.clientBound) throw error;
|
|
19893
|
-
logger$
|
|
20214
|
+
logger$9.warn("[Session] Failed to create DPoP proof for connect, proceeding without:", error);
|
|
19894
20215
|
}
|
|
19895
20216
|
const rpcConnectRequest = RPCConnect({
|
|
19896
20217
|
authentication: isReconnect ? { jwt_token: storedToken } : this.authentication,
|
|
@@ -19907,26 +20228,27 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19907
20228
|
} : {}
|
|
19908
20229
|
});
|
|
19909
20230
|
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)(() => {
|
|
19910
|
-
logger$
|
|
20231
|
+
logger$9.debug("[Session] Response passed filter, processing authentication result");
|
|
19911
20232
|
}), (0, import_cjs$7.take)(1), (0, import_cjs$7.catchError)((err) => {
|
|
19912
|
-
logger$
|
|
20233
|
+
logger$9.error("[Session] Authentication RPC failed:", err);
|
|
19913
20234
|
throw err;
|
|
19914
20235
|
})));
|
|
19915
|
-
logger$
|
|
20236
|
+
logger$9.debug("[Session] Processing authentication result:", {
|
|
19916
20237
|
hasProtocol: !!response.protocol,
|
|
19917
20238
|
hasAuthorization: !!response.authorization,
|
|
19918
20239
|
hasIceServers: !!response.ice_servers
|
|
19919
20240
|
});
|
|
19920
20241
|
if (response.protocol) await this.transport.setProtocol(response.protocol);
|
|
19921
20242
|
this._authorization$.next(response.authorization);
|
|
20243
|
+
if (response.authorization.cnf?.jkt) this._wasClientBound = true;
|
|
19922
20244
|
this._iceServers$.next(response.ice_servers ?? []);
|
|
19923
20245
|
this._authState$.next({ kind: "authenticated" });
|
|
19924
|
-
logger$
|
|
20246
|
+
logger$9.debug("[Session] Authentication completed successfully");
|
|
19925
20247
|
}
|
|
19926
20248
|
async disconnect() {
|
|
19927
20249
|
this.transport.disconnect();
|
|
19928
20250
|
this._authState$.next({ kind: "unauthenticated" });
|
|
19929
|
-
await this.
|
|
20251
|
+
await this.teardownSessionState();
|
|
19930
20252
|
}
|
|
19931
20253
|
async createInboundCall(invite) {
|
|
19932
20254
|
const callSession = await this.createCall({
|
|
@@ -19957,11 +20279,11 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19957
20279
|
async handleVertoAttach(attach) {
|
|
19958
20280
|
const { callID } = attach;
|
|
19959
20281
|
if (callID in this._calls$.value) {
|
|
19960
|
-
logger$
|
|
20282
|
+
logger$9.debug(`[Session] Verto attach for existing call ${callID}, deferring to per-call handler`);
|
|
19961
20283
|
return;
|
|
19962
20284
|
}
|
|
19963
20285
|
const storedOptions = await this.attachManager.consumePendingAttachment(callID);
|
|
19964
|
-
logger$
|
|
20286
|
+
logger$9.debug(`[Session] Creating reattached call for callID: ${callID}`);
|
|
19965
20287
|
const callSession = await this.createCall({
|
|
19966
20288
|
nodeId: attach.node_id,
|
|
19967
20289
|
callId: callID,
|
|
@@ -19985,14 +20307,14 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19985
20307
|
to: destinationURI,
|
|
19986
20308
|
...options
|
|
19987
20309
|
});
|
|
19988
|
-
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)))));
|
|
20310
|
+
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)))));
|
|
19989
20311
|
this._calls$.next({
|
|
19990
20312
|
[`${callSession.id}`]: callSession,
|
|
19991
20313
|
...this._calls$.value
|
|
19992
20314
|
});
|
|
19993
20315
|
return callSession;
|
|
19994
20316
|
} catch (error) {
|
|
19995
|
-
logger$
|
|
20317
|
+
logger$9.error("[Session] Error creating outbound call:", error);
|
|
19996
20318
|
callSession?.destroy();
|
|
19997
20319
|
const callError = new CallCreateError(error instanceof import_cjs$7.TimeoutError ? "Call create timeout" : "Call creation failed", error, "outbound");
|
|
19998
20320
|
this._errors$.next(callError);
|
|
@@ -20010,7 +20332,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
20010
20332
|
address = this._directory.get(addressId);
|
|
20011
20333
|
if (!address) throw new DependencyError(`Address ID: ${addressId} not found`);
|
|
20012
20334
|
} catch {
|
|
20013
|
-
logger$
|
|
20335
|
+
logger$9.warn(`[Session] Directory lookup failed for ${addressURI}, proceeding with raw URI`);
|
|
20014
20336
|
}
|
|
20015
20337
|
const callSession = this.callFactory.createCall(address, { ...options });
|
|
20016
20338
|
this.subscribeTo(callSession.status$.pipe((0, import_cjs$7.filter)((status) => status === "destroyed"), (0, import_cjs$7.take)(1)), () => {
|
|
@@ -20019,7 +20341,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
20019
20341
|
});
|
|
20020
20342
|
return callSession;
|
|
20021
20343
|
} catch (error) {
|
|
20022
|
-
logger$
|
|
20344
|
+
logger$9.error("[Session] Error creating call session:", error);
|
|
20023
20345
|
throw new CallCreateError("Call create error", error, options.initOffer ? "inbound" : "outbound");
|
|
20024
20346
|
}
|
|
20025
20347
|
}
|
|
@@ -20038,6 +20360,14 @@ var ClientSessionWrapper = class {
|
|
|
20038
20360
|
get authenticated() {
|
|
20039
20361
|
return this.clientSessionManager.authenticated;
|
|
20040
20362
|
}
|
|
20363
|
+
/**
|
|
20364
|
+
* Whether the session is using a Client Bound SAT (DPoP). Sticky — set
|
|
20365
|
+
* when the binding is established or restored from a resumed session's
|
|
20366
|
+
* server authorization.
|
|
20367
|
+
*/
|
|
20368
|
+
get clientBound() {
|
|
20369
|
+
return this.clientSessionManager.clientBound;
|
|
20370
|
+
}
|
|
20041
20371
|
get signalingEvent$() {
|
|
20042
20372
|
return this.clientSessionManager.signalingEvent$;
|
|
20043
20373
|
}
|
|
@@ -20068,7 +20398,7 @@ const isString = (obj) => typeof obj === "string";
|
|
|
20068
20398
|
//#endregion
|
|
20069
20399
|
//#region src/managers/ConversationsManager.ts
|
|
20070
20400
|
var import_cjs$6 = require_cjs();
|
|
20071
|
-
const logger$
|
|
20401
|
+
const logger$8 = getLogger();
|
|
20072
20402
|
var ConversationMessagesFetcher = class extends Fetcher {
|
|
20073
20403
|
constructor(groupId, http) {
|
|
20074
20404
|
super(`/api/fabric/conversations/${groupId}/messages`, "page_size=100", http);
|
|
@@ -20108,13 +20438,13 @@ var ConversationsManager = class {
|
|
|
20108
20438
|
}
|
|
20109
20439
|
throw new ConversationError("Join Failed - Unexpected response");
|
|
20110
20440
|
} catch (error) {
|
|
20111
|
-
logger$
|
|
20441
|
+
logger$8.error("[ConversationsManager] Failed to join conversation:", error);
|
|
20112
20442
|
throw error;
|
|
20113
20443
|
}
|
|
20114
20444
|
}
|
|
20115
20445
|
async getConversationMessageCollection(addressId) {
|
|
20116
20446
|
const groupId = this.groupIds.get(addressId) ?? await this.join(addressId);
|
|
20117
|
-
return Promise.resolve(new ConversationMessageCollection(groupId, this.clientSession.signalingEvent$.pipe(filterAs(isConversationMessageMetadata, "params"), (0, import_cjs$6.tap)((event) => logger$
|
|
20447
|
+
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));
|
|
20118
20448
|
}
|
|
20119
20449
|
async sendText(text, destinationAddressId) {
|
|
20120
20450
|
const groupId = this.groupIds.get(destinationAddressId) ?? await this.join(destinationAddressId);
|
|
@@ -20131,7 +20461,7 @@ var ConversationsManager = class {
|
|
|
20131
20461
|
})).ok) return;
|
|
20132
20462
|
throw new ConversationError("Send Text Failed - Unexpected response");
|
|
20133
20463
|
} catch (error) {
|
|
20134
|
-
logger$
|
|
20464
|
+
logger$8.error("[ConversationsManager] Failed to send text message:", error);
|
|
20135
20465
|
throw error;
|
|
20136
20466
|
}
|
|
20137
20467
|
}
|
|
@@ -20140,7 +20470,7 @@ var ConversationsManager = class {
|
|
|
20140
20470
|
//#endregion
|
|
20141
20471
|
//#region src/managers/DeviceTokenManager.ts
|
|
20142
20472
|
var import_cjs$5 = require_cjs();
|
|
20143
|
-
const logger$
|
|
20473
|
+
const logger$7 = getLogger();
|
|
20144
20474
|
/**
|
|
20145
20475
|
* Resolves the token expiry timestamp (epoch seconds) using a 3-tier priority chain:
|
|
20146
20476
|
* 1. `data.expires_at` — server-provided absolute timestamp
|
|
@@ -20150,7 +20480,7 @@ const logger$6 = getLogger();
|
|
|
20150
20480
|
function resolveExpiresAt(data) {
|
|
20151
20481
|
if (data.expires_at) return data.expires_at;
|
|
20152
20482
|
if (data.expires_in) return Math.floor(Date.now() / 1e3) + data.expires_in;
|
|
20153
|
-
logger$
|
|
20483
|
+
logger$7.warn("[DeviceToken] Could not determine token expiry, using default");
|
|
20154
20484
|
return Math.floor(Date.now() / 1e3) + DEVICE_TOKEN_DEFAULT_EXPIRE_IN;
|
|
20155
20485
|
}
|
|
20156
20486
|
/**
|
|
@@ -20183,11 +20513,12 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20183
20513
|
this.getCredential = getCredential;
|
|
20184
20514
|
this._currentToken$ = this.createBehaviorSubject(null);
|
|
20185
20515
|
this._refreshInProgress = false;
|
|
20516
|
+
this._paused = false;
|
|
20186
20517
|
this._effectiveExpireIn = DEVICE_TOKEN_DEFAULT_EXPIRE_IN;
|
|
20187
20518
|
this.subscribeTo(this._currentToken$.pipe((0, import_cjs$5.filter)(Boolean), (0, import_cjs$5.switchMap)((tokenData) => {
|
|
20188
20519
|
const expiresAt = resolveExpiresAt(tokenData);
|
|
20189
20520
|
const refreshIn = Math.max(expiresAt * 1e3 - Date.now() - DEVICE_TOKEN_REFRESH_BUFFER_MS, 1e3);
|
|
20190
|
-
logger$
|
|
20521
|
+
logger$7.debug(`[DeviceToken] Scheduling Client Bound SAT refresh in ${refreshIn}ms`);
|
|
20191
20522
|
return (0, import_cjs$5.timer)(refreshIn);
|
|
20192
20523
|
})), () => {
|
|
20193
20524
|
this.executeRefresh();
|
|
@@ -20201,7 +20532,12 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20201
20532
|
* Activates the Client Bound SAT flow when the user's token has
|
|
20202
20533
|
* `sat:refresh` scope.
|
|
20203
20534
|
*
|
|
20204
|
-
*
|
|
20535
|
+
* Returns an {@link ActivationResult} indicating whether the manager
|
|
20536
|
+
* took ownership of refresh duties. The caller must use the `activated`
|
|
20537
|
+
* boolean to decide whether to keep its own refresh path armed — when
|
|
20538
|
+
* `activated` is `false`, the caller is responsible for refresh.
|
|
20539
|
+
*
|
|
20540
|
+
* Steps on success:
|
|
20205
20541
|
* 1. Check user's `sat_claims` for `sat:refresh` scope
|
|
20206
20542
|
* 2. Call `/api/fabric/subscriber/devices/token` with a DPoP proof
|
|
20207
20543
|
* 3. Reauthenticate the session with the Client Bound SAT + DPoP proof
|
|
@@ -20210,32 +20546,66 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20210
20546
|
async activate(user, session, updateCredential) {
|
|
20211
20547
|
const { satClaims } = user;
|
|
20212
20548
|
if (!satClaims?.scope?.includes(SAT_REFRESH_SCOPE)) {
|
|
20213
|
-
logger$
|
|
20214
|
-
return
|
|
20549
|
+
logger$7.debug("[DeviceToken] No sat:refresh scope, skipping Client Bound SAT activation");
|
|
20550
|
+
return {
|
|
20551
|
+
activated: false,
|
|
20552
|
+
reason: "no-scope"
|
|
20553
|
+
};
|
|
20215
20554
|
}
|
|
20216
20555
|
this._session = session;
|
|
20217
20556
|
this._updateCredential = updateCredential;
|
|
20218
20557
|
try {
|
|
20558
|
+
const cached = this._currentToken$.value;
|
|
20559
|
+
if (cached && this.isTokenFresh(cached)) {
|
|
20560
|
+
logger$7.debug("[DeviceToken] Reusing cached Client Bound SAT — skipping /devices/token");
|
|
20561
|
+
const rpcProof$1 = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
|
|
20562
|
+
await session.reauthenticate(cached.token, rpcProof$1, { clientBound: true });
|
|
20563
|
+
updateCredential({ token: cached.token });
|
|
20564
|
+
return { activated: true };
|
|
20565
|
+
}
|
|
20219
20566
|
const tokenData = await this.obtainToken();
|
|
20220
20567
|
if (!tokenData.expires_at && !tokenData.expires_in && satClaims.expires_at) tokenData.expires_at = satClaims.expires_at;
|
|
20221
20568
|
this._effectiveExpireIn = resolveExpireIn(tokenData);
|
|
20222
20569
|
const rpcProof = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
|
|
20223
20570
|
await session.reauthenticate(tokenData.token, rpcProof, { clientBound: true });
|
|
20224
20571
|
updateCredential({ token: tokenData.token });
|
|
20225
|
-
logger$
|
|
20226
|
-
this.
|
|
20572
|
+
logger$7.info("[DeviceToken] Client Bound SAT activated successfully");
|
|
20573
|
+
this.emitCurrentToken(tokenData);
|
|
20574
|
+
return { activated: true };
|
|
20227
20575
|
} catch (error) {
|
|
20228
|
-
logger$
|
|
20576
|
+
logger$7.error("[DeviceToken] Failed to activate Client Bound SAT:", error);
|
|
20229
20577
|
this.errorHandler(new DPoPInitError(error, "Failed to activate Client Bound SAT"));
|
|
20230
|
-
|
|
20231
|
-
|
|
20232
|
-
|
|
20233
|
-
|
|
20234
|
-
expires_at: expiresAt
|
|
20235
|
-
});
|
|
20578
|
+
return {
|
|
20579
|
+
activated: false,
|
|
20580
|
+
reason: "endpoint-failed"
|
|
20581
|
+
};
|
|
20236
20582
|
}
|
|
20237
20583
|
}
|
|
20238
20584
|
/**
|
|
20585
|
+
* Emit a freshly received token to the reactive pipeline, stamping an
|
|
20586
|
+
* absolute `expires_at` when the response carried only `expires_in`.
|
|
20587
|
+
* Resolving the expiry at RECEIVE time (not at read time) is what lets
|
|
20588
|
+
* {@link refreshNowIfDue} detect due-ness on resume: a bare `expires_in`
|
|
20589
|
+
* re-resolved later would always compute a full TTL from "now" and never
|
|
20590
|
+
* cross the refresh buffer.
|
|
20591
|
+
*/
|
|
20592
|
+
emitCurrentToken(token) {
|
|
20593
|
+
const stamped = token.expires_at ? token : {
|
|
20594
|
+
...token,
|
|
20595
|
+
expires_at: resolveExpiresAt(token)
|
|
20596
|
+
};
|
|
20597
|
+
this._currentToken$.next(stamped);
|
|
20598
|
+
}
|
|
20599
|
+
/**
|
|
20600
|
+
* Returns true when the cached token has enough headroom before expiry to
|
|
20601
|
+
* be safely reused on reactivation. The headroom matches the refresh
|
|
20602
|
+
* buffer, so a token within the refresh window is treated as stale (the
|
|
20603
|
+
* reactive pipeline is about to refresh it anyway).
|
|
20604
|
+
*/
|
|
20605
|
+
isTokenFresh(token) {
|
|
20606
|
+
return resolveExpiresAt(token) * 1e3 - Date.now() > DEVICE_TOKEN_REFRESH_BUFFER_MS;
|
|
20607
|
+
}
|
|
20608
|
+
/**
|
|
20239
20609
|
* Obtains a Client Bound SAT from `/api/fabric/subscriber/devices/token`.
|
|
20240
20610
|
* Returns the full {@link DeviceTokenResponse} including expiry metadata.
|
|
20241
20611
|
*/
|
|
@@ -20265,7 +20635,7 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20265
20635
|
* handled by the reactive pipeline).
|
|
20266
20636
|
*/
|
|
20267
20637
|
async refreshToken(session, currentToken, updateCredential) {
|
|
20268
|
-
logger$
|
|
20638
|
+
logger$7.debug("[DeviceToken] Refreshing Client Bound SAT");
|
|
20269
20639
|
const dpopProof = await this.dpopManager.createHttpProof({
|
|
20270
20640
|
method: "POST",
|
|
20271
20641
|
uri: DEVICE_REFRESH_ENDPOINT,
|
|
@@ -20287,7 +20657,7 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20287
20657
|
const rpcProof = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
|
|
20288
20658
|
await session.reauthenticate(data.token, rpcProof);
|
|
20289
20659
|
updateCredential({ token: data.token });
|
|
20290
|
-
logger$
|
|
20660
|
+
logger$7.info("[DeviceToken] Client Bound SAT refreshed successfully");
|
|
20291
20661
|
return data;
|
|
20292
20662
|
}
|
|
20293
20663
|
/**
|
|
@@ -20296,18 +20666,22 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20296
20666
|
* On all retries exhausted, emits to `errorHandler`.
|
|
20297
20667
|
*/
|
|
20298
20668
|
async executeRefresh() {
|
|
20669
|
+
if (this._paused) {
|
|
20670
|
+
logger$7.debug("[DeviceToken] Manager paused, skipping refresh");
|
|
20671
|
+
return;
|
|
20672
|
+
}
|
|
20299
20673
|
if (this._refreshInProgress) {
|
|
20300
|
-
logger$
|
|
20674
|
+
logger$7.debug("[DeviceToken] Refresh already in progress, skipping");
|
|
20301
20675
|
return;
|
|
20302
20676
|
}
|
|
20303
20677
|
const session = this._session;
|
|
20304
20678
|
const updateCredential = this._updateCredential;
|
|
20305
20679
|
if (!session || !updateCredential) {
|
|
20306
|
-
logger$
|
|
20680
|
+
logger$7.warn("[DeviceToken] Cannot refresh: session or updateCredential not set");
|
|
20307
20681
|
return;
|
|
20308
20682
|
}
|
|
20309
20683
|
if (!session.authenticated) {
|
|
20310
|
-
logger$
|
|
20684
|
+
logger$7.debug("[DeviceToken] Session not authenticated, deferring refresh");
|
|
20311
20685
|
return;
|
|
20312
20686
|
}
|
|
20313
20687
|
this._refreshInProgress = true;
|
|
@@ -20315,9 +20689,9 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20315
20689
|
const currentToken = this.getCredential().token;
|
|
20316
20690
|
if (!currentToken) throw new TokenRefreshError("No current token available for refresh");
|
|
20317
20691
|
const newTokenData = await this.retryRefresh(session, currentToken, updateCredential);
|
|
20318
|
-
this.
|
|
20692
|
+
this.emitCurrentToken(newTokenData);
|
|
20319
20693
|
} catch (error) {
|
|
20320
|
-
logger$
|
|
20694
|
+
logger$7.error("[DeviceToken] Automatic Client Bound SAT refresh failed:", error);
|
|
20321
20695
|
this.errorHandler(error instanceof TokenRefreshError ? error : new TokenRefreshError("Automatic token refresh failed", error));
|
|
20322
20696
|
} finally {
|
|
20323
20697
|
this._refreshInProgress = false;
|
|
@@ -20335,18 +20709,324 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20335
20709
|
lastError = error;
|
|
20336
20710
|
if (attempt < DEVICE_TOKEN_REFRESH_MAX_RETRIES - 1) {
|
|
20337
20711
|
const delay$1 = DEVICE_TOKEN_REFRESH_RETRY_BASE_MS * Math.pow(2, attempt);
|
|
20338
|
-
logger$
|
|
20712
|
+
logger$7.warn(`[DeviceToken] Refresh attempt ${attempt + 1} failed, retrying in ${delay$1}ms`);
|
|
20339
20713
|
await new Promise((resolve) => setTimeout(resolve, delay$1));
|
|
20340
20714
|
}
|
|
20341
20715
|
}
|
|
20342
20716
|
throw lastError instanceof Error ? lastError : new TokenRefreshError("All refresh retries exhausted", lastError);
|
|
20343
20717
|
}
|
|
20718
|
+
/**
|
|
20719
|
+
* Force an immediate refresh when the cached Client Bound SAT is already
|
|
20720
|
+
* past its refresh window. Called on resume from suspension where
|
|
20721
|
+
* background-tab throttling can delay the reactive timer past the buffer.
|
|
20722
|
+
* A no-op when no token is cached or it still has headroom; the normal
|
|
20723
|
+
* {@link executeRefresh} guards (paused / in-progress / unauthenticated)
|
|
20724
|
+
* still apply.
|
|
20725
|
+
*/
|
|
20726
|
+
refreshNowIfDue() {
|
|
20727
|
+
const token = this._currentToken$.value;
|
|
20728
|
+
if (!token) return;
|
|
20729
|
+
if (resolveExpiresAt(token) * 1e3 - Date.now() <= DEVICE_TOKEN_REFRESH_BUFFER_MS) {
|
|
20730
|
+
logger$7.debug("[DeviceToken] Resume: cached SAT past refresh window; refreshing now");
|
|
20731
|
+
this.executeRefresh();
|
|
20732
|
+
}
|
|
20733
|
+
}
|
|
20734
|
+
/**
|
|
20735
|
+
* Stops the reactive refresh pipeline from firing. Use when the underlying
|
|
20736
|
+
* session is being torn down (e.g., during {@link SignalWire.disconnect})
|
|
20737
|
+
* so a scheduled refresh cannot fire against a destroyed session.
|
|
20738
|
+
*
|
|
20739
|
+
* The manager's state (cached token, effective TTL, subscriptions) is
|
|
20740
|
+
* preserved — call {@link resume} to re-enable firing after reconnect.
|
|
20741
|
+
*/
|
|
20742
|
+
pause() {
|
|
20743
|
+
this._paused = true;
|
|
20744
|
+
}
|
|
20745
|
+
/** Re-enables the reactive refresh pipeline after a previous {@link pause}. */
|
|
20746
|
+
resume() {
|
|
20747
|
+
this._paused = false;
|
|
20748
|
+
}
|
|
20344
20749
|
/** Cleans up the manager, cancelling the reactive pipeline and all subscriptions. */
|
|
20345
20750
|
destroy() {
|
|
20346
20751
|
super.destroy();
|
|
20347
20752
|
}
|
|
20348
20753
|
};
|
|
20349
20754
|
|
|
20755
|
+
//#endregion
|
|
20756
|
+
//#region src/managers/CredentialRefreshCoordinator.ts
|
|
20757
|
+
const logger$6 = getLogger();
|
|
20758
|
+
const defaultDeviceTokenManagerFactory = (dpopManager, http, errorHandler, getCredential) => new DeviceTokenManager(dpopManager, http, errorHandler, getCredential);
|
|
20759
|
+
/**
|
|
20760
|
+
* Centralizes credential-refresh ownership across the two competing
|
|
20761
|
+
* mechanisms — developer-provided `CredentialProvider.refresh()` and the
|
|
20762
|
+
* Client Bound SAT path via {@link DeviceTokenManager}.
|
|
20763
|
+
*
|
|
20764
|
+
* Maintains the invariant: **at most one refresh mechanism is armed at a
|
|
20765
|
+
* time, and at least one is armed whenever the current credential has an
|
|
20766
|
+
* `expiry_at`**.
|
|
20767
|
+
*
|
|
20768
|
+
* Replaces the previous design where refresh state was distributed across
|
|
20769
|
+
* `SignalWire` (timer field, scheduler method, activation helper) and
|
|
20770
|
+
* `DeviceTokenManager` (reactive pipeline). Centralizing the invariant in
|
|
20771
|
+
* one component eliminates the bug class that produced issue #19074.
|
|
20772
|
+
*
|
|
20773
|
+
* Race-safety:
|
|
20774
|
+
* - `_activating` flag prevents overlapping `activate()` calls from racing.
|
|
20775
|
+
* - `_activationGeneration` lets late resolutions detect they've been
|
|
20776
|
+
* preempted by a newer activation (e.g., reconnect during in-flight
|
|
20777
|
+
* `obtainToken`).
|
|
20778
|
+
*/
|
|
20779
|
+
var CredentialRefreshCoordinator = class extends Destroyable {
|
|
20780
|
+
constructor(dpopManager, deps) {
|
|
20781
|
+
super();
|
|
20782
|
+
this.deps = deps;
|
|
20783
|
+
this._activating = false;
|
|
20784
|
+
this._activationGeneration = 0;
|
|
20785
|
+
this._developerRefreshInProgress = false;
|
|
20786
|
+
if (dpopManager?.initialized) this._deviceTokenManager = (deps.deviceTokenManagerFactory ?? defaultDeviceTokenManagerFactory)(dpopManager, deps.http, (error) => deps.notifier.onError(error), () => deps.store.read());
|
|
20787
|
+
}
|
|
20788
|
+
/** True when the Client Bound SAT path is available (DPoP initialized). */
|
|
20789
|
+
get clientBoundSATAvailable() {
|
|
20790
|
+
return this._deviceTokenManager !== void 0;
|
|
20791
|
+
}
|
|
20792
|
+
/** True when the developer-provided refresh timer is currently armed. */
|
|
20793
|
+
get developerRefreshArmed() {
|
|
20794
|
+
return this._developerTimerId !== void 0;
|
|
20795
|
+
}
|
|
20796
|
+
/**
|
|
20797
|
+
* Arms the developer-provided refresh timer to fire shortly before
|
|
20798
|
+
* `expiresAt`. Replaces any previously scheduled developer refresh.
|
|
20799
|
+
*
|
|
20800
|
+
* Idempotent — multiple calls just reschedule. On retry exhaustion,
|
|
20801
|
+
* invokes `deps.onRefreshExhausted` so the orchestrator can disconnect.
|
|
20802
|
+
*/
|
|
20803
|
+
scheduleDeveloperRefresh(provider, expiresAt, attempt = 0) {
|
|
20804
|
+
this._activeProvider = provider;
|
|
20805
|
+
if (this._developerTimerId !== void 0) clearTimeout(this._developerTimerId);
|
|
20806
|
+
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);
|
|
20807
|
+
this._developerTimerId = setTimeout(() => {
|
|
20808
|
+
this._developerTimerId = void 0;
|
|
20809
|
+
this.executeDeveloperRefresh(provider, expiresAt, attempt);
|
|
20810
|
+
}, refreshInterval);
|
|
20811
|
+
}
|
|
20812
|
+
/**
|
|
20813
|
+
* Runs the developer-provided refresh once: mints a new credential, stores
|
|
20814
|
+
* and persists it, reauthenticates the live session (via the notifier), and
|
|
20815
|
+
* reschedules against the new expiry. On failure retries with backoff up to
|
|
20816
|
+
* {@link CREDENTIAL_REFRESH_MAX_RETRIES}, then signals exhaustion.
|
|
20817
|
+
*
|
|
20818
|
+
* Shared by the scheduled timer tick and {@link forceRefreshIfDue}. The
|
|
20819
|
+
* `_developerRefreshInProgress` guard prevents the two from overlapping.
|
|
20820
|
+
*/
|
|
20821
|
+
async executeDeveloperRefresh(provider, expiresAt, attempt) {
|
|
20822
|
+
if (this._developerRefreshInProgress) {
|
|
20823
|
+
logger$6.debug("[Coordinator] Developer refresh already in progress; skipping");
|
|
20824
|
+
return;
|
|
20825
|
+
}
|
|
20826
|
+
this._developerRefreshInProgress = true;
|
|
20827
|
+
try {
|
|
20828
|
+
const newCredentials = await this.refreshCredential(provider);
|
|
20829
|
+
this.deps.store.write(newCredentials);
|
|
20830
|
+
this.deps.store.persist(newCredentials);
|
|
20831
|
+
try {
|
|
20832
|
+
await this.deps.notifier.onCredentialRefreshed(newCredentials);
|
|
20833
|
+
} catch (reauthError) {
|
|
20834
|
+
logger$6.warn("[Coordinator] onCredentialRefreshed rejected (non-fatal):", reauthError);
|
|
20835
|
+
}
|
|
20836
|
+
logger$6.info("[Coordinator] Credentials refreshed successfully.");
|
|
20837
|
+
if (newCredentials.expiry_at) this.scheduleDeveloperRefresh(provider, newCredentials.expiry_at, 0);
|
|
20838
|
+
} catch (error) {
|
|
20839
|
+
const nextAttempt = attempt + 1;
|
|
20840
|
+
logger$6.error(`[Coordinator] Credential refresh failed (attempt ${nextAttempt}/${CREDENTIAL_REFRESH_MAX_RETRIES}):`, error);
|
|
20841
|
+
this.deps.notifier.onError(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
20842
|
+
if (nextAttempt < CREDENTIAL_REFRESH_MAX_RETRIES) this.scheduleDeveloperRefresh(provider, expiresAt, nextAttempt);
|
|
20843
|
+
else {
|
|
20844
|
+
logger$6.error("[Coordinator] Credential refresh exhausted all retries. Disconnecting.");
|
|
20845
|
+
this.deps.notifier.onError(new TokenRefreshError("Credential refresh failed after max retries"));
|
|
20846
|
+
this.deps.notifier.onRefreshExhausted();
|
|
20847
|
+
}
|
|
20848
|
+
} finally {
|
|
20849
|
+
this._developerRefreshInProgress = false;
|
|
20850
|
+
}
|
|
20851
|
+
}
|
|
20852
|
+
/**
|
|
20853
|
+
* Force an immediate refresh when the current credential is already past its
|
|
20854
|
+
* scheduled refresh window. Called on resume from suspension, where
|
|
20855
|
+
* background-tab timer throttling can delay the armed refresh well past
|
|
20856
|
+
* expiry, leaving the live session stale.
|
|
20857
|
+
*
|
|
20858
|
+
* Routes to whichever mechanism is armed: the developer timer if armed,
|
|
20859
|
+
* otherwise the Client Bound SAT pipeline. A no-op when nothing is due.
|
|
20860
|
+
*/
|
|
20861
|
+
forceRefreshIfDue() {
|
|
20862
|
+
if (this._developerTimerId !== void 0 && this._activeProvider) {
|
|
20863
|
+
const expiry = this.deps.store.read().expiry_at;
|
|
20864
|
+
if (expiry !== void 0 && Date.now() >= expiry - CREDENTIAL_REFRESH_BUFFER_MS) {
|
|
20865
|
+
logger$6.debug("[Coordinator] Resume: credential past refresh window; forcing refresh");
|
|
20866
|
+
clearTimeout(this._developerTimerId);
|
|
20867
|
+
this._developerTimerId = void 0;
|
|
20868
|
+
this.executeDeveloperRefresh(this._activeProvider, expiry, 0);
|
|
20869
|
+
}
|
|
20870
|
+
return;
|
|
20871
|
+
}
|
|
20872
|
+
this._deviceTokenManager?.refreshNowIfDue();
|
|
20873
|
+
}
|
|
20874
|
+
/**
|
|
20875
|
+
* Sync the credential's expiry from the server-provided authorization (the
|
|
20876
|
+
* `signalwire.connect` result). SATs are opaque JWE, so
|
|
20877
|
+
* `fabric_subscriber.expires_at` is the authoritative expiry of the token
|
|
20878
|
+
* the session actually connected with — the provider-reported `expiry_at`
|
|
20879
|
+
* is only a hint (and may be wrong or absent). Corrects the stored
|
|
20880
|
+
* credential and re-arms the developer refresh timer against the real
|
|
20881
|
+
* deadline when the provider supports `refresh()`.
|
|
20882
|
+
*/
|
|
20883
|
+
syncExpiryFromAuthorization(authorization, provider) {
|
|
20884
|
+
const expiresAtSec = authorization?.fabric_subscriber?.expires_at;
|
|
20885
|
+
if (!expiresAtSec) return;
|
|
20886
|
+
const expiryAt = expiresAtSec * 1e3;
|
|
20887
|
+
const credential = this.deps.store.read();
|
|
20888
|
+
if (credential.expiry_at === expiryAt) return;
|
|
20889
|
+
logger$6.debug(`[Coordinator] Correcting credential expiry from server authorization: ${new Date(expiryAt).toISOString()}`);
|
|
20890
|
+
const updated = {
|
|
20891
|
+
...credential,
|
|
20892
|
+
expiry_at: expiryAt
|
|
20893
|
+
};
|
|
20894
|
+
this.deps.store.write(updated);
|
|
20895
|
+
this.deps.store.persist(updated);
|
|
20896
|
+
if (provider?.refresh) this.scheduleDeveloperRefresh(provider, expiryAt);
|
|
20897
|
+
}
|
|
20898
|
+
/**
|
|
20899
|
+
* Invoke `provider.refresh()` deduped against any concurrent developer
|
|
20900
|
+
* refresh. Concurrent callers — the scheduled tick, a resume-forced refresh,
|
|
20901
|
+
* and the orchestrator's -32003 recovery / reconnect re-mint — share one
|
|
20902
|
+
* in-flight promise, so a provider backed by one-time-use rotating refresh
|
|
20903
|
+
* tokens is never invoked twice in parallel.
|
|
20904
|
+
*
|
|
20905
|
+
* The caller owns applying the returned credential (store write, session
|
|
20906
|
+
* reauth, rescheduling); this method only serializes the network call.
|
|
20907
|
+
*/
|
|
20908
|
+
async refreshCredential(provider) {
|
|
20909
|
+
if (this._refreshInFlight) return this._refreshInFlight;
|
|
20910
|
+
if (!provider.refresh) throw new InvalidCredentialsError("Credential provider does not support refresh");
|
|
20911
|
+
const run = provider.refresh();
|
|
20912
|
+
this._refreshInFlight = run;
|
|
20913
|
+
const clear = () => {
|
|
20914
|
+
if (this._refreshInFlight === run) this._refreshInFlight = void 0;
|
|
20915
|
+
};
|
|
20916
|
+
run.then(clear, clear);
|
|
20917
|
+
return run;
|
|
20918
|
+
}
|
|
20919
|
+
/**
|
|
20920
|
+
* Cancels any scheduled developer-provided refresh. Idempotent.
|
|
20921
|
+
*
|
|
20922
|
+
* @internal Used by the coordinator's own activation flow. External
|
|
20923
|
+
* callers should use {@link suspend} for disconnect-time quiescence —
|
|
20924
|
+
* `suspend()` also pauses the internal Client Bound SAT pipeline.
|
|
20925
|
+
*/
|
|
20926
|
+
cancelDeveloperRefresh() {
|
|
20927
|
+
if (this._developerTimerId !== void 0) {
|
|
20928
|
+
clearTimeout(this._developerTimerId);
|
|
20929
|
+
this._developerTimerId = void 0;
|
|
20930
|
+
}
|
|
20931
|
+
}
|
|
20932
|
+
/**
|
|
20933
|
+
* Suspends both refresh paths — cancels the developer timer and pauses
|
|
20934
|
+
* the internal reactive pipeline. Use when the underlying session is
|
|
20935
|
+
* being torn down (e.g., {@link SignalWire.disconnect}). The next
|
|
20936
|
+
* {@link activate} call re-enables the internal pipeline.
|
|
20937
|
+
*
|
|
20938
|
+
* The internal manager's cached token survives — see
|
|
20939
|
+
* {@link DeviceTokenManager.pause} — so a subsequent reconnect can
|
|
20940
|
+
* skip the `/devices/token` exchange entirely.
|
|
20941
|
+
*/
|
|
20942
|
+
suspend() {
|
|
20943
|
+
this.cancelDeveloperRefresh();
|
|
20944
|
+
this._deviceTokenManager?.pause();
|
|
20945
|
+
}
|
|
20946
|
+
/**
|
|
20947
|
+
* Asks the Client Bound SAT path to take over refresh. If it accepts,
|
|
20948
|
+
* the developer-provided timer (if any) is cancelled. If it declines, the
|
|
20949
|
+
* developer timer remains armed and a `credential_refresh_fallback`
|
|
20950
|
+
* warning is emitted.
|
|
20951
|
+
*
|
|
20952
|
+
* **Idempotent** — re-entrant calls during an in-flight activation are
|
|
20953
|
+
* dropped. Use `_activationGeneration` to detect stale resolutions
|
|
20954
|
+
* (e.g., a reconnect-triggered activate() that races with an earlier one).
|
|
20955
|
+
*/
|
|
20956
|
+
async activate(user, session) {
|
|
20957
|
+
if (this._activating) {
|
|
20958
|
+
logger$6.debug("[Coordinator] activate() in flight; ignoring re-entrant call");
|
|
20959
|
+
return;
|
|
20960
|
+
}
|
|
20961
|
+
if (!this._deviceTokenManager) return;
|
|
20962
|
+
this._deviceTokenManager.resume();
|
|
20963
|
+
const generation = ++this._activationGeneration;
|
|
20964
|
+
this._activating = true;
|
|
20965
|
+
try {
|
|
20966
|
+
const result = await this.withActivationTimeout(this._deviceTokenManager.activate(user, session, (cred) => this.deps.store.merge(cred)));
|
|
20967
|
+
if (generation !== this._activationGeneration) {
|
|
20968
|
+
logger$6.debug("[Coordinator] activate() result discarded (preempted by newer activation)");
|
|
20969
|
+
return;
|
|
20970
|
+
}
|
|
20971
|
+
if (result.activated) {
|
|
20972
|
+
this.cancelDeveloperRefresh();
|
|
20973
|
+
logger$6.debug("[Coordinator] Developer refresh disabled — Client Bound SAT owns refresh");
|
|
20974
|
+
return;
|
|
20975
|
+
}
|
|
20976
|
+
logger$6.warn(`[SignalWire] [SW-REFRESH-FALLBACK] Client Bound SAT declined (reason=${result.reason}); using developer-provided refresh handler.`);
|
|
20977
|
+
this.deps.notifier.onWarning({
|
|
20978
|
+
code: "credential_refresh_fallback",
|
|
20979
|
+
source: "CredentialProvider",
|
|
20980
|
+
reason: result.reason,
|
|
20981
|
+
message: `Client Bound SAT activation declined (${result.reason}); using developer-provided refresh.`
|
|
20982
|
+
});
|
|
20983
|
+
} finally {
|
|
20984
|
+
this._activating = false;
|
|
20985
|
+
}
|
|
20986
|
+
}
|
|
20987
|
+
destroy() {
|
|
20988
|
+
this.cancelDeveloperRefresh();
|
|
20989
|
+
this._deviceTokenManager?.destroy();
|
|
20990
|
+
this._deviceTokenManager = void 0;
|
|
20991
|
+
super.destroy();
|
|
20992
|
+
}
|
|
20993
|
+
/**
|
|
20994
|
+
* Races the manager's `activate()` against a hard timeout. A wedged HTTP
|
|
20995
|
+
* layer (e.g., proxy issues) could otherwise hang the activation
|
|
20996
|
+
* indefinitely, leaving the session with no refresh mechanism while the
|
|
20997
|
+
* `_activating` guard blocks subsequent retries.
|
|
20998
|
+
*
|
|
20999
|
+
* On timeout, the manager is paused immediately. The inner `activate()`
|
|
21000
|
+
* may still complete in the background and emit to `_currentToken$`,
|
|
21001
|
+
* which would normally arm the reactive refresh pipeline; pausing
|
|
21002
|
+
* prevents that pipeline from firing while the developer refresh path
|
|
21003
|
+
* is the active mechanism — preserving the "at most one mechanism
|
|
21004
|
+
* armed" invariant. The next `activate()` call resumes the manager.
|
|
21005
|
+
*/
|
|
21006
|
+
async withActivationTimeout(inner) {
|
|
21007
|
+
return new Promise((resolve) => {
|
|
21008
|
+
const timer$4 = setTimeout(() => {
|
|
21009
|
+
this._deviceTokenManager?.pause();
|
|
21010
|
+
resolve({
|
|
21011
|
+
activated: false,
|
|
21012
|
+
reason: "activation-timeout"
|
|
21013
|
+
});
|
|
21014
|
+
}, CREDENTIAL_ACTIVATE_TIMEOUT_MS);
|
|
21015
|
+
inner.then((result) => {
|
|
21016
|
+
clearTimeout(timer$4);
|
|
21017
|
+
resolve(result);
|
|
21018
|
+
}, (error) => {
|
|
21019
|
+
clearTimeout(timer$4);
|
|
21020
|
+
this.deps.notifier.onError(error instanceof Error ? error : new Error(String(error)));
|
|
21021
|
+
resolve({
|
|
21022
|
+
activated: false,
|
|
21023
|
+
reason: "endpoint-failed"
|
|
21024
|
+
});
|
|
21025
|
+
});
|
|
21026
|
+
});
|
|
21027
|
+
}
|
|
21028
|
+
};
|
|
21029
|
+
|
|
20350
21030
|
//#endregion
|
|
20351
21031
|
//#region src/managers/DiagnosticsCollector.ts
|
|
20352
21032
|
const logger$5 = getLogger();
|
|
@@ -20867,6 +21547,10 @@ var TransportManager = class extends Destroyable {
|
|
|
20867
21547
|
if (!isSignalwireRequest(message)) return true;
|
|
20868
21548
|
const eventChannel = message.params.event_channel;
|
|
20869
21549
|
if (!eventChannel) return true;
|
|
21550
|
+
if (message.params.event_type.startsWith("conversation.")) {
|
|
21551
|
+
logger$2.debug(`[Transport] Received conversation event: ${message.params.event_type} (event_channel: ${eventChannel})`);
|
|
21552
|
+
return true;
|
|
21553
|
+
}
|
|
20870
21554
|
const currentProtocol = this._currentProtocol;
|
|
20871
21555
|
if (!currentProtocol) return true;
|
|
20872
21556
|
if (!eventChannel.includes(currentProtocol)) {
|
|
@@ -21010,6 +21694,33 @@ var TransportManager = class extends Destroyable {
|
|
|
21010
21694
|
}
|
|
21011
21695
|
};
|
|
21012
21696
|
|
|
21697
|
+
//#endregion
|
|
21698
|
+
//#region src/utils/authRecovery.ts
|
|
21699
|
+
/**
|
|
21700
|
+
* Walk an error's `error`/`cause` chain looking for a {@link JSONRPCError}.
|
|
21701
|
+
* Errors thrown by call creation are wrapped (e.g. `CallCreateError`), so the
|
|
21702
|
+
* underlying signaling error is nested. Bounded by a visited set to guard
|
|
21703
|
+
* against cyclic causes.
|
|
21704
|
+
*/
|
|
21705
|
+
function findJSONRPCError(error) {
|
|
21706
|
+
const seen = /* @__PURE__ */ new Set();
|
|
21707
|
+
let current = error;
|
|
21708
|
+
while (current instanceof Error && !seen.has(current)) {
|
|
21709
|
+
seen.add(current);
|
|
21710
|
+
if (current instanceof JSONRPCError) return current;
|
|
21711
|
+
current = current.error ?? current.cause;
|
|
21712
|
+
}
|
|
21713
|
+
}
|
|
21714
|
+
/**
|
|
21715
|
+
* Whether an error is a session-recoverable authentication failure
|
|
21716
|
+
* (`-32002` authentication failed or `-32003` requester validation failed)
|
|
21717
|
+
* that a credential re-mint + retry can heal.
|
|
21718
|
+
*/
|
|
21719
|
+
function isRecoverableAuthError(error) {
|
|
21720
|
+
const rpcError = findJSONRPCError(error);
|
|
21721
|
+
return rpcError !== void 0 && (rpcError.code === RPC_ERROR_REQUESTER_VALIDATION_FAILED || rpcError.code === RPC_ERROR_AUTHENTICATION_FAILED);
|
|
21722
|
+
}
|
|
21723
|
+
|
|
21013
21724
|
//#endregion
|
|
21014
21725
|
//#region src/clients/SignalWire.ts
|
|
21015
21726
|
var import_cjs$1 = require_cjs();
|
|
@@ -21060,6 +21771,7 @@ var SignalWire = class extends Destroyable {
|
|
|
21060
21771
|
this._isConnected$ = this.createBehaviorSubject(false);
|
|
21061
21772
|
this._isRegistered$ = this.createBehaviorSubject(false);
|
|
21062
21773
|
this._errors$ = this.createReplaySubject(1);
|
|
21774
|
+
this._warnings$ = this.createReplaySubject(10);
|
|
21063
21775
|
this._options = {};
|
|
21064
21776
|
this._deps = new DependencyContainer();
|
|
21065
21777
|
this._credentialProvider = credentialProvider;
|
|
@@ -21119,6 +21831,29 @@ var SignalWire = class extends Destroyable {
|
|
|
21119
21831
|
*/
|
|
21120
21832
|
async resolveCredentials() {
|
|
21121
21833
|
const fingerprint = await this.initDPoP();
|
|
21834
|
+
this._refreshCoordinator = new CredentialRefreshCoordinator(this._dpopManager, {
|
|
21835
|
+
http: this._deps.http,
|
|
21836
|
+
notifier: {
|
|
21837
|
+
onError: (error) => this._errors$.next(error),
|
|
21838
|
+
onWarning: (warning) => this._warnings$.next(warning),
|
|
21839
|
+
onRefreshExhausted: () => void this.disconnect(),
|
|
21840
|
+
onCredentialRefreshed: async (credential) => this.reauthenticateLiveSession(credential)
|
|
21841
|
+
},
|
|
21842
|
+
store: {
|
|
21843
|
+
read: () => this._deps.credential,
|
|
21844
|
+
write: (credential) => {
|
|
21845
|
+
this._deps.credential = credential;
|
|
21846
|
+
},
|
|
21847
|
+
merge: (partial) => {
|
|
21848
|
+
this._deps.credential = {
|
|
21849
|
+
...this._deps.credential,
|
|
21850
|
+
...partial
|
|
21851
|
+
};
|
|
21852
|
+
this.persistCredential(this._deps.credential);
|
|
21853
|
+
},
|
|
21854
|
+
persist: (credential) => this.persistCredential(credential)
|
|
21855
|
+
}
|
|
21856
|
+
});
|
|
21122
21857
|
if (this._credentialProvider) return this.validateCredentials(this._credentialProvider, void 0, fingerprint);
|
|
21123
21858
|
for (const scope of this._deps.persistSession ? ["local", "session"] : ["session"]) try {
|
|
21124
21859
|
const cached = await this._deps.storage.getItem("sw:cached_credential", scope);
|
|
@@ -21148,11 +21883,32 @@ var SignalWire = class extends Destroyable {
|
|
|
21148
21883
|
logger$1.error("[SignalWire] Provided credentials have expired.");
|
|
21149
21884
|
throw new InvalidCredentialsError("Provided credentials have expired.");
|
|
21150
21885
|
}
|
|
21151
|
-
if (_credentials.expiry_at && credentialProvider?.refresh) this.
|
|
21886
|
+
if (_credentials.expiry_at && credentialProvider?.refresh) this._refreshCoordinator?.scheduleDeveloperRefresh(credentialProvider, _credentials.expiry_at);
|
|
21887
|
+
else if (_credentials.expiry_at && !credentialProvider?.refresh) {
|
|
21888
|
+
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.`);
|
|
21889
|
+
this._warnings$.next({
|
|
21890
|
+
code: "credential_no_refresh_handler",
|
|
21891
|
+
source: "CredentialProvider",
|
|
21892
|
+
message: "Credential has expiry_at but no refresh handler. Session will terminate at expiry unless the SAT carries 'sat:refresh' scope.",
|
|
21893
|
+
expiresAt: _credentials.expiry_at
|
|
21894
|
+
});
|
|
21895
|
+
}
|
|
21152
21896
|
this._deps.credential = _credentials;
|
|
21153
21897
|
this.persistCredential(_credentials);
|
|
21154
|
-
|
|
21155
|
-
|
|
21898
|
+
await this.reauthenticateLiveSession(_credentials);
|
|
21899
|
+
}
|
|
21900
|
+
/**
|
|
21901
|
+
* Reauthenticate the currently-open session with a freshly obtained
|
|
21902
|
+
* credential so the new token takes effect on the live socket immediately —
|
|
21903
|
+
* not just on the next reconnect. No-op when the session is not
|
|
21904
|
+
* connected/authenticated or the credential carries no token (e.g. an
|
|
21905
|
+
* authorization-state-only refresh). Non-fatal: reauth failures surface on
|
|
21906
|
+
* `errors$` without aborting the refresh that triggered this.
|
|
21907
|
+
*/
|
|
21908
|
+
async reauthenticateLiveSession(credential) {
|
|
21909
|
+
if (!this.isConnected || !this._clientSession.authenticated || !credential.token) return;
|
|
21910
|
+
try {
|
|
21911
|
+
await this._clientSession.reauthenticate(credential.token);
|
|
21156
21912
|
logger$1.info("[SignalWire] Session refreshed with new credentials.");
|
|
21157
21913
|
} catch (error) {
|
|
21158
21914
|
logger$1.error("[SignalWire] Failed to refresh session with new credentials:", error);
|
|
@@ -21160,33 +21916,101 @@ var SignalWire = class extends Destroyable {
|
|
|
21160
21916
|
}
|
|
21161
21917
|
}
|
|
21162
21918
|
/**
|
|
21163
|
-
*
|
|
21164
|
-
*
|
|
21165
|
-
*
|
|
21919
|
+
* One-shot recovery for a live session that failed with a recoverable auth
|
|
21920
|
+
* error (`-32002`/`-32003`) because its token went stale — e.g. a refresh
|
|
21921
|
+
* timer that was throttled while the tab was backgrounded.
|
|
21922
|
+
*
|
|
21923
|
+
* Two steps, first that succeeds wins:
|
|
21924
|
+
* 1. Reauthenticate with the in-memory token (a fresh DPoP proof is
|
|
21925
|
+
* generated automatically). Heals a session whose token was already
|
|
21926
|
+
* refreshed by the coordinator but never applied to the open socket,
|
|
21927
|
+
* and a client-bound session whose server-side auth merely drifted.
|
|
21928
|
+
* 2. Re-mint via the developer provider's `refresh()` and reauthenticate.
|
|
21929
|
+
* Skipped for client-bound sessions (the DeviceTokenManager owns their
|
|
21930
|
+
* refresh; re-minting a base SAT here would drop the DPoP binding).
|
|
21931
|
+
*
|
|
21932
|
+
* @param allowRemint - Whether step 2 (provider re-mint) may run. Callers
|
|
21933
|
+
* pass `false` for non-auth failures so a transient network error never
|
|
21934
|
+
* escalates to a token re-mint; step 1 (in-memory reauth) always runs.
|
|
21935
|
+
* @returns `true` if the session was reauthenticated, `false` otherwise.
|
|
21166
21936
|
*/
|
|
21167
|
-
|
|
21168
|
-
|
|
21169
|
-
|
|
21170
|
-
|
|
21171
|
-
|
|
21172
|
-
|
|
21173
|
-
|
|
21174
|
-
|
|
21175
|
-
|
|
21176
|
-
|
|
21177
|
-
|
|
21178
|
-
|
|
21179
|
-
|
|
21180
|
-
|
|
21181
|
-
|
|
21182
|
-
|
|
21183
|
-
|
|
21184
|
-
|
|
21185
|
-
|
|
21186
|
-
|
|
21187
|
-
|
|
21937
|
+
async recoverStaleCredential(allowRemint = true) {
|
|
21938
|
+
const currentToken = this._deps.credential.token;
|
|
21939
|
+
if (currentToken) try {
|
|
21940
|
+
await this._clientSession.reauthenticate(currentToken);
|
|
21941
|
+
return true;
|
|
21942
|
+
} catch (error) {
|
|
21943
|
+
logger$1.debug("[SignalWire] In-memory reauth failed during recovery:", error);
|
|
21944
|
+
}
|
|
21945
|
+
if (!allowRemint || this._clientSession.clientBound || !this._credentialProvider?.refresh) return false;
|
|
21946
|
+
try {
|
|
21947
|
+
const newCredentials = await this.remintCredential(this._credentialProvider);
|
|
21948
|
+
if (!newCredentials.token) return false;
|
|
21949
|
+
this._deps.credential = newCredentials;
|
|
21950
|
+
this.persistCredential(newCredentials);
|
|
21951
|
+
await this._clientSession.reauthenticate(newCredentials.token);
|
|
21952
|
+
if (newCredentials.expiry_at) this._refreshCoordinator?.scheduleDeveloperRefresh(this._credentialProvider, newCredentials.expiry_at);
|
|
21953
|
+
return true;
|
|
21954
|
+
} catch (error) {
|
|
21955
|
+
logger$1.error("[SignalWire] Credential recovery failed:", error);
|
|
21956
|
+
return false;
|
|
21957
|
+
}
|
|
21958
|
+
}
|
|
21959
|
+
/**
|
|
21960
|
+
* Re-mint a credential via `provider.refresh()`, routed through the
|
|
21961
|
+
* coordinator's shared in-flight guard so concurrent re-mint paths (a
|
|
21962
|
+
* scheduled/resume refresh, -32003 recovery, and reconnect) never fire a
|
|
21963
|
+
* second `provider.refresh()` in parallel — which rotating one-time-use
|
|
21964
|
+
* refresh tokens reject. Falls back to a direct call only if the coordinator
|
|
21965
|
+
* has not been constructed yet.
|
|
21966
|
+
*/
|
|
21967
|
+
async remintCredential(provider) {
|
|
21968
|
+
if (this._refreshCoordinator) return this._refreshCoordinator.refreshCredential(provider);
|
|
21969
|
+
if (!provider.refresh) throw new InvalidCredentialsError("Credential provider does not support refresh");
|
|
21970
|
+
return provider.refresh();
|
|
21971
|
+
}
|
|
21972
|
+
/**
|
|
21973
|
+
* Re-mint credentials before a fresh (re)connect (`onBeforeReconnect` hook).
|
|
21974
|
+
* The session invokes this only when it is client-bound OR the in-memory
|
|
21975
|
+
* token is expired. The re-mint mechanism depends on the binding:
|
|
21976
|
+
* - Client-bound: `authenticate()` with the DPoP fingerprint to obtain a
|
|
21977
|
+
* fresh base SAT the upcoming reconnect can re-bind (the
|
|
21978
|
+
* DeviceTokenManager re-activates afterwards).
|
|
21979
|
+
* - Unbound: the developer's non-interactive `refresh()` handler.
|
|
21980
|
+
* `authenticate()` is deliberately NOT used here — it may be interactive
|
|
21981
|
+
* (a login prompt) and must not fire on a background reconnect.
|
|
21982
|
+
*
|
|
21983
|
+
* Rejects on failure so the session aborts the reconnect rather than
|
|
21984
|
+
* replaying a stale token.
|
|
21985
|
+
*/
|
|
21986
|
+
async refreshCredentialForReconnect() {
|
|
21987
|
+
if (!this._credentialProvider) return;
|
|
21988
|
+
try {
|
|
21989
|
+
let newCredentials;
|
|
21990
|
+
if (this._clientSession.clientBound) {
|
|
21991
|
+
const fingerprint = this._dpopManager?.initialized ? this._dpopManager.fingerprint : void 0;
|
|
21992
|
+
logger$1.debug("[SignalWire] Re-minting client-bound base SAT before reconnect");
|
|
21993
|
+
newCredentials = await this._credentialProvider.authenticate(fingerprint ? { fingerprint } : void 0);
|
|
21994
|
+
} else if (this._credentialProvider.refresh) {
|
|
21995
|
+
logger$1.debug("[SignalWire] Refreshing unbound credential before reconnect");
|
|
21996
|
+
newCredentials = await this.remintCredential(this._credentialProvider);
|
|
21997
|
+
} else {
|
|
21998
|
+
logger$1.warn("[SignalWire] [SW-NO-REFRESH-HANDLER] Token expired on reconnect but no refresh handler; reconnecting with the existing token.");
|
|
21999
|
+
return;
|
|
21188
22000
|
}
|
|
21189
|
-
|
|
22001
|
+
if (!newCredentials.token) {
|
|
22002
|
+
logger$1.warn("[SignalWire] Re-minted credential has no token; keeping the existing credential for reconnect.");
|
|
22003
|
+
return;
|
|
22004
|
+
}
|
|
22005
|
+
this._deps.credential = newCredentials;
|
|
22006
|
+
this.persistCredential(newCredentials);
|
|
22007
|
+
if (newCredentials.expiry_at && this._credentialProvider.refresh) this._refreshCoordinator?.scheduleDeveloperRefresh(this._credentialProvider, newCredentials.expiry_at);
|
|
22008
|
+
logger$1.debug("[SignalWire] Credential refreshed successfully for reconnect");
|
|
22009
|
+
} catch (error) {
|
|
22010
|
+
logger$1.error("[SignalWire] Failed to refresh credentials for reconnect:", error);
|
|
22011
|
+
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
22012
|
+
throw error;
|
|
22013
|
+
}
|
|
21190
22014
|
}
|
|
21191
22015
|
/** Persist credential to localStorage when persistSession is enabled. */
|
|
21192
22016
|
persistCredential(credential) {
|
|
@@ -21274,51 +22098,20 @@ var SignalWire = class extends Destroyable {
|
|
|
21274
22098
|
this._attachManager = new AttachManager(this._deps.storage, this._deps.deviceController, PreferencesContainer.instance.reconnectCallsTimeout, this._deps.attachedCallsKey);
|
|
21275
22099
|
this._clientSession = new ClientSessionManager(() => this._deps.credential, this._transport, this._deps.storage, this._deps.authorizationStateKey, this._deps.deviceController, this._attachManager, this._deps.webRTCApiProvider, this._dpopManager, this._networkMonitor?.networkChange$);
|
|
21276
22100
|
this._publicSession = new ClientSessionWrapper(this._clientSession);
|
|
21277
|
-
this._clientSession.onBeforeReconnect = async () =>
|
|
21278
|
-
if (!this._credentialProvider) return;
|
|
21279
|
-
try {
|
|
21280
|
-
const fingerprint = this._dpopManager?.initialized ? this._dpopManager.fingerprint : void 0;
|
|
21281
|
-
logger$1.debug("[SignalWire] Credential expired, refreshing before reconnect");
|
|
21282
|
-
const newCredentials = await this._credentialProvider.authenticate(fingerprint ? { fingerprint } : void 0);
|
|
21283
|
-
this._deps.credential = newCredentials;
|
|
21284
|
-
logger$1.debug("[SignalWire] Credential refreshed successfully for reconnect");
|
|
21285
|
-
} catch (error) {
|
|
21286
|
-
logger$1.error("[SignalWire] Failed to refresh credentials for reconnect:", error);
|
|
21287
|
-
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
21288
|
-
throw error;
|
|
21289
|
-
}
|
|
21290
|
-
};
|
|
22101
|
+
this._clientSession.onBeforeReconnect = async () => this.refreshCredentialForReconnect();
|
|
21291
22102
|
this.subscribeTo(this._clientSession.errors$, (error) => {
|
|
21292
22103
|
this._errors$.next(error);
|
|
21293
22104
|
});
|
|
22105
|
+
this.subscribeTo(this._clientSession.authorization$, (authorization) => {
|
|
22106
|
+
this._refreshCoordinator?.syncExpiryFromAuthorization(authorization, this._credentialProvider);
|
|
22107
|
+
});
|
|
21294
22108
|
await this._clientSession.connect();
|
|
21295
|
-
|
|
21296
|
-
if (this._refreshTimerId) {
|
|
21297
|
-
clearTimeout(this._refreshTimerId);
|
|
21298
|
-
this._refreshTimerId = void 0;
|
|
21299
|
-
logger$1.debug("[SignalWire] Developer refresh disabled — Client Bound SAT activation starting");
|
|
21300
|
-
}
|
|
21301
|
-
this._deviceTokenManager = new DeviceTokenManager(this._dpopManager, this._deps.http, (error) => this._errors$.next(error), () => this._deps.credential);
|
|
21302
|
-
await this._deviceTokenManager.activate(this._deps.user, this._clientSession, (cred) => {
|
|
21303
|
-
this._deps.credential = {
|
|
21304
|
-
...this._deps.credential,
|
|
21305
|
-
...cred
|
|
21306
|
-
};
|
|
21307
|
-
});
|
|
21308
|
-
}
|
|
22109
|
+
await this._refreshCoordinator?.activate(this._deps.user, this._clientSession);
|
|
21309
22110
|
this.subscribeTo(this._clientSession.authenticated$.pipe((0, import_cjs$1.skip)(1), (0, import_cjs$1.filter)(Boolean)), async () => {
|
|
21310
22111
|
try {
|
|
21311
|
-
|
|
21312
|
-
await this._deviceTokenManager.activate(this._deps.user, this._clientSession, (cred) => {
|
|
21313
|
-
this._deps.credential = {
|
|
21314
|
-
...this._deps.credential,
|
|
21315
|
-
...cred
|
|
21316
|
-
};
|
|
21317
|
-
});
|
|
21318
|
-
logger$1.debug("[SignalWire] Client Bound SAT re-activated after reconnect");
|
|
21319
|
-
}
|
|
22112
|
+
await this._refreshCoordinator?.activate(this._deps.user, this._clientSession);
|
|
21320
22113
|
} catch (error) {
|
|
21321
|
-
logger$1.error("[SignalWire]
|
|
22114
|
+
logger$1.error("[SignalWire] Refresh re-arm after reconnect failed (non-fatal):", error);
|
|
21322
22115
|
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
21323
22116
|
}
|
|
21324
22117
|
try {
|
|
@@ -21404,6 +22197,19 @@ var SignalWire = class extends Destroyable {
|
|
|
21404
22197
|
get errors$() {
|
|
21405
22198
|
return this.deferEmission(this._errors$.asObservable());
|
|
21406
22199
|
}
|
|
22200
|
+
/**
|
|
22201
|
+
* Observable stream of non-fatal SDK warnings.
|
|
22202
|
+
*
|
|
22203
|
+
* Subscribe to detect SDK behaviors that affect session liveness or developer-facing
|
|
22204
|
+
* contracts but do not warrant disconnection — e.g., a fallback from Client Bound SAT
|
|
22205
|
+
* refresh to the developer-provided `refresh()` because the SAT lacks `sat:refresh`
|
|
22206
|
+
* scope. Discriminated by `code`.
|
|
22207
|
+
*
|
|
22208
|
+
* Independent from {@link errors$}: existing error consumers are not notified.
|
|
22209
|
+
*/
|
|
22210
|
+
get warnings$() {
|
|
22211
|
+
return this.deferEmission(this._warnings$.asObservable());
|
|
22212
|
+
}
|
|
21407
22213
|
/** Platform WebRTC capabilities detected at construction time. */
|
|
21408
22214
|
get platformCapabilities() {
|
|
21409
22215
|
this._platformCapabilities ??= detectPlatformCapabilities(this._options.webRTCApiProvider);
|
|
@@ -21461,6 +22267,13 @@ var SignalWire = class extends Destroyable {
|
|
|
21461
22267
|
}
|
|
21462
22268
|
try {
|
|
21463
22269
|
this._visibilityController = new VisibilityController();
|
|
22270
|
+
this.subscribeTo(this._visibilityController.visibilityChange$.pipe((0, import_cjs$1.filter)((event) => event.to === "visible")), () => {
|
|
22271
|
+
try {
|
|
22272
|
+
this._refreshCoordinator?.forceRefreshIfDue();
|
|
22273
|
+
} catch (error) {
|
|
22274
|
+
logger$1.warn("[SignalWire] Resume credential revalidation failed (non-fatal):", error);
|
|
22275
|
+
}
|
|
22276
|
+
});
|
|
21464
22277
|
this.subscribeTo(this._visibilityController.visibilityChange$.pipe((0, import_cjs$1.filter)((event) => event.to === "visible" && PreferencesContainer.instance.refreshDevicesOnVisible)), () => {
|
|
21465
22278
|
logger$1.debug("[SignalWire] Page visible, re-enumerating devices");
|
|
21466
22279
|
try {
|
|
@@ -21472,7 +22285,7 @@ var SignalWire = class extends Destroyable {
|
|
|
21472
22285
|
logger$1.warn("[SignalWire] Failed to initialize VisibilityController:", error);
|
|
21473
22286
|
}
|
|
21474
22287
|
try {
|
|
21475
|
-
this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "
|
|
22288
|
+
this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "4.0.0-rc.2" });
|
|
21476
22289
|
} catch (error) {
|
|
21477
22290
|
logger$1.warn("[SignalWire] Failed to initialize DiagnosticsCollector:", error);
|
|
21478
22291
|
}
|
|
@@ -21480,14 +22293,19 @@ var SignalWire = class extends Destroyable {
|
|
|
21480
22293
|
/**
|
|
21481
22294
|
* Disconnects the WebSocket and tears down the current session.
|
|
21482
22295
|
*
|
|
22296
|
+
* Ends the session identified by the protocol and clears its persisted
|
|
22297
|
+
* resume state (`authorization_state` + protocol) and attach records
|
|
22298
|
+
* together — a later {@link connect} with the same credentials starts a
|
|
22299
|
+
* fresh session and cannot reattach to the ended session's calls.
|
|
22300
|
+
* Credentials and device preferences are preserved. To temporarily stop
|
|
22301
|
+
* receiving inbound calls while keeping the session alive, use
|
|
22302
|
+
* `unregister()` instead.
|
|
22303
|
+
*
|
|
21483
22304
|
* The client can be reconnected by calling {@link connect} again,
|
|
21484
22305
|
* which creates a fresh transport and session.
|
|
21485
22306
|
*/
|
|
21486
22307
|
async disconnect() {
|
|
21487
|
-
|
|
21488
|
-
clearTimeout(this._refreshTimerId);
|
|
21489
|
-
this._refreshTimerId = void 0;
|
|
21490
|
-
}
|
|
22308
|
+
this._refreshCoordinator?.suspend();
|
|
21491
22309
|
this._diagnosticsCollector?.record("connection", "disconnected");
|
|
21492
22310
|
await this.teardownTransportAndSession();
|
|
21493
22311
|
this._isConnected$.next(false);
|
|
@@ -21538,21 +22356,23 @@ var SignalWire = class extends Destroyable {
|
|
|
21538
22356
|
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
21539
22357
|
throw error;
|
|
21540
22358
|
}
|
|
21541
|
-
logger$1.debug("[SignalWire] Failed to register user,
|
|
21542
|
-
|
|
21543
|
-
|
|
21544
|
-
logger$1.debug("[SignalWire]
|
|
22359
|
+
logger$1.debug("[SignalWire] Failed to register user, attempting credential recovery...");
|
|
22360
|
+
let failureCause = error;
|
|
22361
|
+
if (await this.recoverStaleCredential(isRecoverableAuthError(error))) try {
|
|
22362
|
+
logger$1.debug("[SignalWire] Recovery succeeded, retrying register()");
|
|
21545
22363
|
await this._transport.execute(RPCExecute({
|
|
21546
22364
|
method: "subscriber.online",
|
|
21547
22365
|
params: {}
|
|
21548
22366
|
}));
|
|
21549
22367
|
this._isRegistered$.next(true);
|
|
21550
|
-
|
|
21551
|
-
|
|
21552
|
-
|
|
21553
|
-
|
|
21554
|
-
throw registerError;
|
|
22368
|
+
return;
|
|
22369
|
+
} catch (retryError) {
|
|
22370
|
+
logger$1.error("[SignalWire] Register retry after recovery failed:", retryError);
|
|
22371
|
+
failureCause = retryError;
|
|
21555
22372
|
}
|
|
22373
|
+
const registerError = new InvalidCredentialsError("Failed to register user, and credential recovery also failed. Please check your credentials.", { cause: failureCause instanceof Error ? failureCause : new Error(String(failureCause), { cause: failureCause }) });
|
|
22374
|
+
this._errors$.next(registerError);
|
|
22375
|
+
throw registerError;
|
|
21556
22376
|
}
|
|
21557
22377
|
}
|
|
21558
22378
|
/**
|
|
@@ -21605,7 +22425,15 @@ var SignalWire = class extends Destroyable {
|
|
|
21605
22425
|
};
|
|
21606
22426
|
await this.waitAuthentication();
|
|
21607
22427
|
logger$1.debug("[SignalWire] Dialing with options:", computed_options);
|
|
21608
|
-
|
|
22428
|
+
try {
|
|
22429
|
+
return await this._clientSession.createOutboundCall(destination, computed_options);
|
|
22430
|
+
} catch (error) {
|
|
22431
|
+
if (!isRecoverableAuthError(error)) throw error;
|
|
22432
|
+
logger$1.debug("[SignalWire] Dial hit a recoverable auth error; recovering credential and retrying once");
|
|
22433
|
+
if (!await this.recoverStaleCredential()) throw error;
|
|
22434
|
+
await this.waitAuthentication();
|
|
22435
|
+
return this._clientSession.createOutboundCall(destination, computed_options);
|
|
22436
|
+
}
|
|
21609
22437
|
}
|
|
21610
22438
|
/**
|
|
21611
22439
|
* Runs a multi-phase connectivity test against the given destination.
|
|
@@ -21874,17 +22702,23 @@ var SignalWire = class extends Destroyable {
|
|
|
21874
22702
|
prefs.preferredVideoInput = null;
|
|
21875
22703
|
await this._deviceController.clearDeviceState();
|
|
21876
22704
|
}
|
|
21877
|
-
/**
|
|
22705
|
+
/**
|
|
22706
|
+
* Destroys the client, clearing timers and releasing all resources.
|
|
22707
|
+
*
|
|
22708
|
+
* Intentionally destroying the client ends its session: the resume state
|
|
22709
|
+
* (`authorization_state` + protocol) and the attach records are both
|
|
22710
|
+
* cleared. Credentials and device preferences are preserved — use
|
|
22711
|
+
* {@link resetToDefaults} for a full wipe. To temporarily stop receiving
|
|
22712
|
+
* inbound calls while keeping the session alive, use `unregister()`.
|
|
22713
|
+
*/
|
|
21878
22714
|
destroy() {
|
|
21879
|
-
|
|
21880
|
-
|
|
21881
|
-
this._refreshTimerId = void 0;
|
|
21882
|
-
}
|
|
21883
|
-
this._deviceTokenManager?.destroy();
|
|
22715
|
+
this._refreshCoordinator?.destroy();
|
|
22716
|
+
this._refreshCoordinator = void 0;
|
|
21884
22717
|
this._dpopManager?.destroy();
|
|
21885
|
-
|
|
21886
|
-
|
|
21887
|
-
this.
|
|
22718
|
+
const session = this._clientSession;
|
|
22719
|
+
session?.teardownSessionState();
|
|
22720
|
+
this._transport?.destroy();
|
|
22721
|
+
session?.destroy();
|
|
21888
22722
|
try {
|
|
21889
22723
|
this._networkMonitor?.destroy();
|
|
21890
22724
|
} catch {}
|
|
@@ -21997,7 +22831,7 @@ var StaticCredentialProvider = class {
|
|
|
21997
22831
|
/**
|
|
21998
22832
|
* Library version from package.json, injected at build time.
|
|
21999
22833
|
*/
|
|
22000
|
-
const version = "
|
|
22834
|
+
const version = "4.0.0-rc.2";
|
|
22001
22835
|
/**
|
|
22002
22836
|
* Flag indicating the library has been loaded and is ready to use.
|
|
22003
22837
|
* For UMD builds: `window.SignalWire.ready`
|
|
@@ -22019,7 +22853,7 @@ const ready = true;
|
|
|
22019
22853
|
*/
|
|
22020
22854
|
const emitReadyEvent = () => {
|
|
22021
22855
|
if (typeof window !== "undefined") {
|
|
22022
|
-
const event = new CustomEvent("signalwire:js:ready", { detail: { version: "
|
|
22856
|
+
const event = new CustomEvent("signalwire:js:ready", { detail: { version: "4.0.0-rc.2" } });
|
|
22023
22857
|
window.dispatchEvent(event);
|
|
22024
22858
|
}
|
|
22025
22859
|
};
|
|
@@ -22043,5 +22877,5 @@ emitReadyEvent();
|
|
|
22043
22877
|
if (typeof process === "undefined") globalThis.process = { env: { NODE_ENV: "production" } };
|
|
22044
22878
|
|
|
22045
22879
|
//#endregion
|
|
22046
|
-
export { Address, CallCreateError, ClientPreferences, CollectionFetchError, DPoPInitError, DeviceTokenError, EmbedTokenCredentialProvider, InvalidCredentialsError, MediaTrackError, MessageParseError, OverconstrainedFallbackError, Participant, PreflightError, RecoveryError, SelfCapabilities, SelfParticipant, SignalWire, StaticCredentialProvider, TokenRefreshError, UnexpectedError, User, VertoPongError, WebRTCCall, embeddableCall, getLogger, isSelfParticipant, ready, setDebugOptions, setLogLevel, setLogger, version };
|
|
22880
|
+
export { Address, CallCreateError, CallNotReadyError, ClientPreferences, CollectionFetchError, DPoPInitError, DeviceTokenError, EmbedTokenCredentialProvider, InvalidCredentialsError, MediaAccessError, MediaTrackError, MessageParseError, OverconstrainedFallbackError, Participant, ParticipantNotReadyError, PreflightError, RecoveryError, SelfCapabilities, SelfParticipant, SignalWire, StaticCredentialProvider, TokenRefreshError, UnexpectedError, User, VertoPongError, WebRTCCall, embeddableCall, getLogger, isSelfParticipant, ready, setDebugOptions, setLogLevel, setLogger, version };
|
|
22047
22881
|
//# sourceMappingURL=browser.mjs.map
|