@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.umd.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
(function(global, factory) {
|
|
2
|
-
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports
|
|
3
|
-
typeof define === 'function' && define.amd ? define(['exports'
|
|
4
|
-
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.SignalWire = {})
|
|
5
|
-
})(this, function(exports
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.SignalWire = {})));
|
|
5
|
+
})(this, function(exports) {
|
|
6
6
|
//#region rolldown:runtime
|
|
7
7
|
var __create = Object.create;
|
|
8
8
|
var __defProp = Object.defineProperty;
|
|
@@ -9021,6 +9021,151 @@ var Destroyable = class {
|
|
|
9021
9021
|
}
|
|
9022
9022
|
};
|
|
9023
9023
|
|
|
9024
|
+
//#endregion
|
|
9025
|
+
//#region src/core/constants.ts
|
|
9026
|
+
const INVITE_VERSION = 1e3;
|
|
9027
|
+
const DEFAULT_ICE_CANDIDATE_TIMEOUT_MS = 600;
|
|
9028
|
+
const DEFAULT_ICE_GATHERING_TIMEOUT_MS = 6e3;
|
|
9029
|
+
const DEFAULT_RECONNECT_CALLS_TIMEOUT_MS = 300 * 1e3;
|
|
9030
|
+
const DEFAULT_CONNECTION_TIMEOUT_MS = 1e4;
|
|
9031
|
+
const DEFAULT_RECONNECT_DELAY_MIN_MS = 100;
|
|
9032
|
+
const DEFAULT_RECONNECT_DELAY_MAX_MS = 3e3;
|
|
9033
|
+
const DEFAULT_DEVICE_DEBOUNCE_TIME_MS = 1500;
|
|
9034
|
+
const DEFAULT_DEVICE_POLLING_INTERVAL_MS = 0;
|
|
9035
|
+
const PREFERENCES_STORAGE_KEY = "sw:preferences";
|
|
9036
|
+
/** Scope value that enables automatic token refresh. */
|
|
9037
|
+
const SAT_REFRESH_SCOPE = "sat:refresh";
|
|
9038
|
+
/** API endpoints for device token operations. */
|
|
9039
|
+
const DEVICE_TOKEN_ENDPOINT = "/api/fabric/subscriber/devices/token";
|
|
9040
|
+
const DEVICE_REFRESH_ENDPOINT = "/api/fabric/subscriber/devices/refresh";
|
|
9041
|
+
/** Default device token TTL in seconds (15 minutes). */
|
|
9042
|
+
const DEVICE_TOKEN_DEFAULT_EXPIRE_IN = 900;
|
|
9043
|
+
/** Buffer time in milliseconds before expiry to trigger refresh. */
|
|
9044
|
+
const DEVICE_TOKEN_REFRESH_BUFFER_MS = 3e4;
|
|
9045
|
+
/** Maximum retry attempts for device token refresh on transient failure. */
|
|
9046
|
+
const DEVICE_TOKEN_REFRESH_MAX_RETRIES = 3;
|
|
9047
|
+
/** Base delay in milliseconds for exponential backoff on refresh retry. */
|
|
9048
|
+
const DEVICE_TOKEN_REFRESH_RETRY_BASE_MS = 1e3;
|
|
9049
|
+
/** Maximum retry attempts for developer credential refresh on transient failure. */
|
|
9050
|
+
const CREDENTIAL_REFRESH_MAX_RETRIES = 5;
|
|
9051
|
+
/** Base delay in milliseconds for exponential backoff on credential refresh retry. */
|
|
9052
|
+
const CREDENTIAL_REFRESH_RETRY_BASE_MS = 1e3;
|
|
9053
|
+
/** Maximum delay in milliseconds for credential refresh backoff. */
|
|
9054
|
+
const CREDENTIAL_REFRESH_MAX_DELAY_MS = 3e4;
|
|
9055
|
+
/** Buffer in milliseconds before token expiry to trigger refresh. */
|
|
9056
|
+
const CREDENTIAL_REFRESH_BUFFER_MS = 5e3;
|
|
9057
|
+
/**
|
|
9058
|
+
* Clock-skew allowance (ms) for treating an in-memory credential as expired
|
|
9059
|
+
* when deciding whether a fresh (re)connect must re-mint the token before
|
|
9060
|
+
* authenticating. A token within this window of its `expiry_at` is treated as
|
|
9061
|
+
* stale so the reconnect re-mints via the credential provider instead of
|
|
9062
|
+
* replaying a dead token (which the server rejects with -32003).
|
|
9063
|
+
*/
|
|
9064
|
+
const CREDENTIAL_EXPIRY_SKEW_MS = 3e4;
|
|
9065
|
+
/**
|
|
9066
|
+
* Maximum time the coordinator will wait for `DeviceTokenManager.activate()`
|
|
9067
|
+
* to resolve before treating the activation as failed and falling back to
|
|
9068
|
+
* the developer-provided refresh path. Prevents a wedged HTTP layer from
|
|
9069
|
+
* leaving the session with no active refresh mechanism.
|
|
9070
|
+
*/
|
|
9071
|
+
const CREDENTIAL_ACTIVATE_TIMEOUT_MS = 3e4;
|
|
9072
|
+
/** JSON-RPC error code for requester validation failure (corrupted auth state). */
|
|
9073
|
+
const RPC_ERROR_REQUESTER_VALIDATION_FAILED = -32003;
|
|
9074
|
+
/** JSON-RPC error code for invalid params (e.g., missing authentication block). */
|
|
9075
|
+
const RPC_ERROR_INVALID_PARAMS = -32602;
|
|
9076
|
+
/** JSON-RPC error code for authentication failure (invalid token, missing DPoP, etc.). */
|
|
9077
|
+
const RPC_ERROR_AUTHENTICATION_FAILED = -32002;
|
|
9078
|
+
/** Error names browsers use for a media permission denial (user or policy). */
|
|
9079
|
+
const MEDIA_ACCESS_DENIAL_NAMES = [
|
|
9080
|
+
"NotAllowedError",
|
|
9081
|
+
"SecurityError",
|
|
9082
|
+
"PermissionDeniedError"
|
|
9083
|
+
];
|
|
9084
|
+
/** Default polling interval for RTCPeerConnection.getStats() in milliseconds. */
|
|
9085
|
+
const DEFAULT_STATS_POLLING_INTERVAL_MS = 1e3;
|
|
9086
|
+
/** Number of initial samples used to build a baseline for spike detection. */
|
|
9087
|
+
const DEFAULT_STATS_BASELINE_SAMPLES = 10;
|
|
9088
|
+
/** Duration in ms with no inbound audio packets before emitting a critical issue. */
|
|
9089
|
+
const DEFAULT_STATS_NO_PACKET_THRESHOLD_MS = 2e3;
|
|
9090
|
+
/** Multiplier applied to baseline RTT to detect a warning-level RTT spike. */
|
|
9091
|
+
const DEFAULT_STATS_RTT_SPIKE_MULTIPLIER = 3;
|
|
9092
|
+
/** Packet loss fraction (0-1) above which a warning is emitted. */
|
|
9093
|
+
const DEFAULT_STATS_PACKET_LOSS_THRESHOLD = .05;
|
|
9094
|
+
/** Multiplier applied to baseline jitter to detect a jitter spike. */
|
|
9095
|
+
const DEFAULT_STATS_JITTER_SPIKE_MULTIPLIER = 4;
|
|
9096
|
+
/** Number of seconds of metrics history to retain. */
|
|
9097
|
+
const DEFAULT_STATS_HISTORY_SIZE = 30;
|
|
9098
|
+
/** Maximum keyframe requests allowed within a single burst window. */
|
|
9099
|
+
const DEFAULT_KEYFRAME_MAX_BURST$1 = 3;
|
|
9100
|
+
/** Duration of the keyframe burst window in milliseconds. */
|
|
9101
|
+
const DEFAULT_KEYFRAME_BURST_WINDOW_MS$1 = 3e3;
|
|
9102
|
+
/** Cooldown period in ms after burst limit is reached before allowing more keyframes. */
|
|
9103
|
+
const DEFAULT_KEYFRAME_COOLDOWN_MS$1 = 1e4;
|
|
9104
|
+
/** Minimum time between re-INVITE attempts in milliseconds. */
|
|
9105
|
+
const DEFAULT_REINVITE_DEBOUNCE_TIME_MS = 1e4;
|
|
9106
|
+
/** Maximum number of re-INVITE attempts per call. */
|
|
9107
|
+
const DEFAULT_REINVITE_MAX_ATTEMPTS = 3;
|
|
9108
|
+
/** Timeout for a single re-INVITE attempt in milliseconds. */
|
|
9109
|
+
const DEFAULT_REINVITE_TIMEOUT_MS = 5e3;
|
|
9110
|
+
/** Debounce window in ms to collapse multiple detection signals into one trigger. */
|
|
9111
|
+
const DEFAULT_RECOVERY_DEBOUNCE_TIME_MS = 2e3;
|
|
9112
|
+
/** Cooldown period in ms between recovery attempts. */
|
|
9113
|
+
const DEFAULT_RECOVERY_COOLDOWN_MS = 1e4;
|
|
9114
|
+
/** Grace period in ms before treating ICE 'disconnected' as a failure. */
|
|
9115
|
+
const DEFAULT_ICE_DISCONNECTED_GRACE_PERIOD_MS = 3e3;
|
|
9116
|
+
/** Timeout for a single ICE restart attempt in milliseconds. */
|
|
9117
|
+
const DEFAULT_ICE_RESTART_TIMEOUT_MS$1 = 5e3;
|
|
9118
|
+
/** Maximum recovery attempts before emitting 'max_attempts_reached'. */
|
|
9119
|
+
const DEFAULT_MAX_RECOVERY_ATTEMPTS = 3;
|
|
9120
|
+
/** Upper bound in ms for waiting on iceGatheringState === 'complete' after an ICE restart. */
|
|
9121
|
+
const ICE_GATHERING_COMPLETE_TIMEOUT_MS = 1e4;
|
|
9122
|
+
/** Upper bound in ms for waiting on RTCPeerConnection.connectionState === 'connected' after a recovery ICE restart. */
|
|
9123
|
+
const PEER_CONNECTION_RECOVERY_WAIT_MS = 5e3;
|
|
9124
|
+
/** Polling interval in ms while waiting for RTCPeerConnection.connectionState to transition. */
|
|
9125
|
+
const PEER_CONNECTION_RECOVERY_POLL_MS = 100;
|
|
9126
|
+
/** Polling interval for LocalAudioPipeline.level$ (ms). ~30fps is smooth for meters. */
|
|
9127
|
+
const AUDIO_LEVEL_POLL_INTERVAL_MS = 33;
|
|
9128
|
+
/** RMS level threshold (0..1) above which the local participant is considered speaking. */
|
|
9129
|
+
const VAD_THRESHOLD = .03;
|
|
9130
|
+
/** Hold window in ms below the threshold before speaking$ flips back to false. */
|
|
9131
|
+
const VAD_HOLD_MS = 250;
|
|
9132
|
+
/** Whether to persist device selections to storage by default. */
|
|
9133
|
+
const DEFAULT_PERSIST_DEVICE_SELECTION = true;
|
|
9134
|
+
/** Whether to auto-apply device changes to active calls by default. */
|
|
9135
|
+
const DEFAULT_SYNC_DEVICES_TO_ACTIVE_CALLS = true;
|
|
9136
|
+
/** Storage keys for persisted device selections. */
|
|
9137
|
+
const DEVICE_STORAGE_KEY_AUDIO_INPUT = "sw:device:audioinput";
|
|
9138
|
+
const DEVICE_STORAGE_KEY_AUDIO_OUTPUT = "sw:device:audiooutput";
|
|
9139
|
+
const DEVICE_STORAGE_KEY_VIDEO_INPUT = "sw:device:videoinput";
|
|
9140
|
+
/** Whether to auto-mute video when the tab becomes hidden. */
|
|
9141
|
+
const DEFAULT_AUTO_MUTE_VIDEO_ON_HIDDEN = false;
|
|
9142
|
+
/** Whether to re-enumerate devices when the page becomes visible. */
|
|
9143
|
+
const DEFAULT_REFRESH_DEVICES_ON_VISIBLE = true;
|
|
9144
|
+
/** Whether to check peer connection health when the page becomes visible. */
|
|
9145
|
+
const DEFAULT_CHECK_CONNECTION_ON_VISIBLE = true;
|
|
9146
|
+
/** Whether automatic video degradation on low bandwidth is enabled. */
|
|
9147
|
+
const DEFAULT_ENABLE_AUTO_DEGRADATION = true;
|
|
9148
|
+
/** Bitrate in kbps below which video is automatically disabled. */
|
|
9149
|
+
const DEFAULT_DEGRADATION_BITRATE_THRESHOLD_KBPS = 150;
|
|
9150
|
+
/** Bitrate in kbps above which video is automatically re-enabled (hysteresis). */
|
|
9151
|
+
const DEFAULT_DEGRADATION_RECOVERY_THRESHOLD_KBPS = 300;
|
|
9152
|
+
/** Whether relay-only escalation is enabled as a last-resort recovery tier. */
|
|
9153
|
+
const DEFAULT_ENABLE_RELAY_FALLBACK = true;
|
|
9154
|
+
/** Whether to listen for browser online/offline/connection events. */
|
|
9155
|
+
const DEFAULT_ENABLE_NETWORK_CHANGE_DETECTION = true;
|
|
9156
|
+
/** Whether to intercept server-sent media-timeout hangups and attempt recovery. */
|
|
9157
|
+
const DEFAULT_ENABLE_SERVER_HANGUP_INTERCEPTION = true;
|
|
9158
|
+
/** Default video track constraints applied when video is enabled without explicit constraints. */
|
|
9159
|
+
const DEFAULT_VIDEO_CONSTRAINTS = {
|
|
9160
|
+
width: { ideal: 1280 },
|
|
9161
|
+
height: { ideal: 720 },
|
|
9162
|
+
aspectRatio: 16 / 9
|
|
9163
|
+
};
|
|
9164
|
+
/** Whether stereo Opus is enabled by default. */
|
|
9165
|
+
const DEFAULT_STEREO_AUDIO = false;
|
|
9166
|
+
/** Max average bitrate for stereo Opus in bits per second. */
|
|
9167
|
+
const DEFAULT_STEREO_MAX_AVERAGE_BITRATE = 51e4;
|
|
9168
|
+
|
|
9024
9169
|
//#endregion
|
|
9025
9170
|
//#region src/core/errors.ts
|
|
9026
9171
|
var UnexpectedError = class extends Error {
|
|
@@ -9150,6 +9295,20 @@ var CallCreateError = class extends Error {
|
|
|
9150
9295
|
this.name = "CallCreateError";
|
|
9151
9296
|
}
|
|
9152
9297
|
};
|
|
9298
|
+
var CallNotReadyError = class extends Error {
|
|
9299
|
+
constructor(callId, options) {
|
|
9300
|
+
super(`Call "${callId}" has no self member context yet: selfId/nodeId have not been received from the server`, options);
|
|
9301
|
+
this.callId = callId;
|
|
9302
|
+
this.name = "CallNotReadyError";
|
|
9303
|
+
}
|
|
9304
|
+
};
|
|
9305
|
+
var ParticipantNotReadyError = class extends Error {
|
|
9306
|
+
constructor(memberId, options) {
|
|
9307
|
+
super(`Participant "${memberId}" has no call context yet: its member state (call_id/node_id) has not been received from the server`, options);
|
|
9308
|
+
this.memberId = memberId;
|
|
9309
|
+
this.name = "ParticipantNotReadyError";
|
|
9310
|
+
}
|
|
9311
|
+
};
|
|
9153
9312
|
var JSONRPCError = class extends Error {
|
|
9154
9313
|
constructor(code, message, data, options, requestId) {
|
|
9155
9314
|
super(message, options);
|
|
@@ -9230,6 +9389,33 @@ var MediaTrackError = class extends Error {
|
|
|
9230
9389
|
this.name = "MediaTrackError";
|
|
9231
9390
|
}
|
|
9232
9391
|
};
|
|
9392
|
+
/** True when a `getUserMedia`/`getDisplayMedia` rejection is a permission denial. */
|
|
9393
|
+
function isMediaAccessDenial(originalError) {
|
|
9394
|
+
return originalError instanceof Error && MEDIA_ACCESS_DENIAL_NAMES.includes(originalError.name);
|
|
9395
|
+
}
|
|
9396
|
+
/**
|
|
9397
|
+
* Failure to acquire local media (camera, microphone, or screen capture)
|
|
9398
|
+
* via `getUserMedia`/`getDisplayMedia`.
|
|
9399
|
+
*
|
|
9400
|
+
* Non-fatal by default: screenshare and additional-device failures never
|
|
9401
|
+
* end the call, and main-connection failures degrade to receive-only when
|
|
9402
|
+
* possible. The wrapping site sets `fatal` to `true` only when the call
|
|
9403
|
+
* cannot continue (receive-only fallback disabled or no receive intent).
|
|
9404
|
+
*/
|
|
9405
|
+
var MediaAccessError = class extends Error {
|
|
9406
|
+
constructor(operation, media, originalError, fatal = false) {
|
|
9407
|
+
super(`Media access ${isMediaAccessDenial(originalError) ? "denied" : "failed"} for ${operation} (${media})`, { cause: originalError instanceof Error ? originalError : void 0 });
|
|
9408
|
+
this.operation = operation;
|
|
9409
|
+
this.media = media;
|
|
9410
|
+
this.originalError = originalError;
|
|
9411
|
+
this.fatal = fatal;
|
|
9412
|
+
this.name = "MediaAccessError";
|
|
9413
|
+
}
|
|
9414
|
+
/** True when the underlying failure is a permission denial (user or policy). */
|
|
9415
|
+
get denied() {
|
|
9416
|
+
return isMediaAccessDenial(this.originalError);
|
|
9417
|
+
}
|
|
9418
|
+
};
|
|
9233
9419
|
var DPoPInitError = class extends Error {
|
|
9234
9420
|
constructor(originalError, message = "Failed to initialize DPoP key pair") {
|
|
9235
9421
|
super(message, { cause: originalError instanceof Error ? originalError : void 0 });
|
|
@@ -9470,9 +9656,9 @@ var require_loglevel = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
9470
9656
|
defaultLogger$1 = new Logger();
|
|
9471
9657
|
defaultLogger$1.getLogger = function getLogger$1(name) {
|
|
9472
9658
|
if (typeof name !== "symbol" && typeof name !== "string" || name === "") throw new TypeError("You must supply a name when creating a logger.");
|
|
9473
|
-
var logger$
|
|
9474
|
-
if (!logger$
|
|
9475
|
-
return logger$
|
|
9659
|
+
var logger$33 = _loggersByName[name];
|
|
9660
|
+
if (!logger$33) logger$33 = _loggersByName[name] = new Logger(name, defaultLogger$1.methodFactory);
|
|
9661
|
+
return logger$33;
|
|
9476
9662
|
};
|
|
9477
9663
|
var _log = typeof window !== undefinedType ? window.log : void 0;
|
|
9478
9664
|
defaultLogger$1.noConflict = function() {
|
|
@@ -9508,8 +9694,8 @@ const defaultLoggerLevel = defaultLogger.levels.WARN;
|
|
|
9508
9694
|
defaultLogger.setLevel(defaultLoggerLevel);
|
|
9509
9695
|
let userLogger = null;
|
|
9510
9696
|
/** Replace the built-in logger with a custom implementation. Pass `null` to restore defaults. */
|
|
9511
|
-
const setLogger = (logger$
|
|
9512
|
-
userLogger = logger$
|
|
9697
|
+
const setLogger = (logger$33) => {
|
|
9698
|
+
userLogger = logger$33;
|
|
9513
9699
|
};
|
|
9514
9700
|
let debugOptions = {};
|
|
9515
9701
|
/** Configure debug options (e.g., `{ logWsTraffic: true }`). */
|
|
@@ -9553,8 +9739,8 @@ const wsTraffic = (options) => {
|
|
|
9553
9739
|
loggerInstance.debug(`${options.type.toUpperCase()}: \n`, msg, "\n");
|
|
9554
9740
|
};
|
|
9555
9741
|
const getLogger = () => {
|
|
9556
|
-
const logger$
|
|
9557
|
-
return new Proxy(logger$
|
|
9742
|
+
const logger$33 = getLoggerInstance();
|
|
9743
|
+
return new Proxy(logger$33, { get(_target, prop, _receiver) {
|
|
9558
9744
|
if (prop === "wsTraffic") return wsTraffic;
|
|
9559
9745
|
const instance = getLoggerInstance();
|
|
9560
9746
|
const value = Reflect.get(instance, prop);
|
|
@@ -9606,7 +9792,7 @@ const asyncRetry = async ({ asyncCallable, maxRetries: retries = DEFAULT_MAX_RET
|
|
|
9606
9792
|
|
|
9607
9793
|
//#endregion
|
|
9608
9794
|
//#region src/controllers/HTTPRequestController.ts
|
|
9609
|
-
const logger$
|
|
9795
|
+
const logger$32 = getLogger();
|
|
9610
9796
|
const GET_PARAMS = {
|
|
9611
9797
|
method: "GET",
|
|
9612
9798
|
headers: { Accept: "application/json" }
|
|
@@ -9670,7 +9856,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9670
9856
|
this._responses$.next(response);
|
|
9671
9857
|
return response;
|
|
9672
9858
|
} catch (error) {
|
|
9673
|
-
logger$
|
|
9859
|
+
logger$32.error("[HTTPRequestController] Request error:", error);
|
|
9674
9860
|
this._status$.next("error");
|
|
9675
9861
|
const err = error instanceof Error ? error : new Error("HTTP request failed", { cause: error });
|
|
9676
9862
|
this._errors$.next(err);
|
|
@@ -9697,7 +9883,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9697
9883
|
const url = this.buildURL(request.url);
|
|
9698
9884
|
const headers = this.buildHeaders(request.headers);
|
|
9699
9885
|
const timeout$5 = request.timeout ?? this.requestTimeout;
|
|
9700
|
-
logger$
|
|
9886
|
+
logger$32.debug("[HTTPRequestController] Executing request:", {
|
|
9701
9887
|
method: request.method,
|
|
9702
9888
|
url,
|
|
9703
9889
|
headers: Object.keys(headers).reduce((acc, key) => {
|
|
@@ -9717,7 +9903,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9717
9903
|
});
|
|
9718
9904
|
clearTimeout(timeoutId);
|
|
9719
9905
|
const httpResponse = await this.convertResponse(response);
|
|
9720
|
-
logger$
|
|
9906
|
+
logger$32.debug("[HTTPRequestController] Response received:", {
|
|
9721
9907
|
status: response.status,
|
|
9722
9908
|
statusText: response.statusText,
|
|
9723
9909
|
headers: [...response.headers.entries()],
|
|
@@ -9727,7 +9913,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9727
9913
|
} catch (error) {
|
|
9728
9914
|
clearTimeout(timeoutId);
|
|
9729
9915
|
if (error instanceof Error && error.name === "AbortError") throw new RequestTimeoutError(`Request timeout after ${timeout$5}ms`, { cause: error });
|
|
9730
|
-
logger$
|
|
9916
|
+
logger$32.error("[HTTPRequestController] Request failed:", error);
|
|
9731
9917
|
throw error;
|
|
9732
9918
|
}
|
|
9733
9919
|
}
|
|
@@ -9741,8 +9927,8 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9741
9927
|
const credential = this.getCredential();
|
|
9742
9928
|
if (credential.token) {
|
|
9743
9929
|
headers.Authorization = `Bearer ${credential.token}`;
|
|
9744
|
-
logger$
|
|
9745
|
-
} else logger$
|
|
9930
|
+
logger$32.debug("[HTTPRequestController] Using Bearer token auth, token length:", credential.token.length);
|
|
9931
|
+
} else logger$32.warn("[HTTPRequestController] No credentials available for authentication");
|
|
9746
9932
|
return headers;
|
|
9747
9933
|
}
|
|
9748
9934
|
/**
|
|
@@ -9879,130 +10065,6 @@ var DeviceHistoryManager = class {
|
|
|
9879
10065
|
}
|
|
9880
10066
|
};
|
|
9881
10067
|
|
|
9882
|
-
//#endregion
|
|
9883
|
-
//#region src/core/constants.ts
|
|
9884
|
-
const INVITE_VERSION = 1e3;
|
|
9885
|
-
const DEFAULT_ICE_CANDIDATE_TIMEOUT_MS = 600;
|
|
9886
|
-
const DEFAULT_ICE_GATHERING_TIMEOUT_MS = 6e3;
|
|
9887
|
-
const DEFAULT_RECONNECT_CALLS_TIMEOUT_MS = 300 * 1e3;
|
|
9888
|
-
const DEFAULT_CONNECTION_TIMEOUT_MS = 1e4;
|
|
9889
|
-
const DEFAULT_RECONNECT_DELAY_MIN_MS = 100;
|
|
9890
|
-
const DEFAULT_RECONNECT_DELAY_MAX_MS = 3e3;
|
|
9891
|
-
const DEFAULT_DEVICE_DEBOUNCE_TIME_MS = 1500;
|
|
9892
|
-
const DEFAULT_DEVICE_POLLING_INTERVAL_MS = 0;
|
|
9893
|
-
const PREFERENCES_STORAGE_KEY = "sw:preferences";
|
|
9894
|
-
/** Scope value that enables automatic token refresh. */
|
|
9895
|
-
const SAT_REFRESH_SCOPE = "sat:refresh";
|
|
9896
|
-
/** API endpoints for device token operations. */
|
|
9897
|
-
const DEVICE_TOKEN_ENDPOINT = "/api/fabric/subscriber/devices/token";
|
|
9898
|
-
const DEVICE_REFRESH_ENDPOINT = "/api/fabric/subscriber/devices/refresh";
|
|
9899
|
-
/** Default device token TTL in seconds (15 minutes). */
|
|
9900
|
-
const DEVICE_TOKEN_DEFAULT_EXPIRE_IN = 900;
|
|
9901
|
-
/** Buffer time in milliseconds before expiry to trigger refresh. */
|
|
9902
|
-
const DEVICE_TOKEN_REFRESH_BUFFER_MS = 3e4;
|
|
9903
|
-
/** Maximum retry attempts for device token refresh on transient failure. */
|
|
9904
|
-
const DEVICE_TOKEN_REFRESH_MAX_RETRIES = 3;
|
|
9905
|
-
/** Base delay in milliseconds for exponential backoff on refresh retry. */
|
|
9906
|
-
const DEVICE_TOKEN_REFRESH_RETRY_BASE_MS = 1e3;
|
|
9907
|
-
/** Maximum retry attempts for developer credential refresh on transient failure. */
|
|
9908
|
-
const CREDENTIAL_REFRESH_MAX_RETRIES = 5;
|
|
9909
|
-
/** Base delay in milliseconds for exponential backoff on credential refresh retry. */
|
|
9910
|
-
const CREDENTIAL_REFRESH_RETRY_BASE_MS = 1e3;
|
|
9911
|
-
/** Maximum delay in milliseconds for credential refresh backoff. */
|
|
9912
|
-
const CREDENTIAL_REFRESH_MAX_DELAY_MS = 3e4;
|
|
9913
|
-
/** Buffer in milliseconds before token expiry to trigger refresh. */
|
|
9914
|
-
const CREDENTIAL_REFRESH_BUFFER_MS = 5e3;
|
|
9915
|
-
/** JSON-RPC error code for requester validation failure (corrupted auth state). */
|
|
9916
|
-
const RPC_ERROR_REQUESTER_VALIDATION_FAILED = -32003;
|
|
9917
|
-
/** JSON-RPC error code for invalid params (e.g., missing authentication block). */
|
|
9918
|
-
const RPC_ERROR_INVALID_PARAMS = -32602;
|
|
9919
|
-
/** JSON-RPC error code for authentication failure (invalid token, missing DPoP, etc.). */
|
|
9920
|
-
const RPC_ERROR_AUTHENTICATION_FAILED = -32002;
|
|
9921
|
-
/** Default polling interval for RTCPeerConnection.getStats() in milliseconds. */
|
|
9922
|
-
const DEFAULT_STATS_POLLING_INTERVAL_MS = 1e3;
|
|
9923
|
-
/** Number of initial samples used to build a baseline for spike detection. */
|
|
9924
|
-
const DEFAULT_STATS_BASELINE_SAMPLES = 10;
|
|
9925
|
-
/** Duration in ms with no inbound audio packets before emitting a critical issue. */
|
|
9926
|
-
const DEFAULT_STATS_NO_PACKET_THRESHOLD_MS = 2e3;
|
|
9927
|
-
/** Multiplier applied to baseline RTT to detect a warning-level RTT spike. */
|
|
9928
|
-
const DEFAULT_STATS_RTT_SPIKE_MULTIPLIER = 3;
|
|
9929
|
-
/** Packet loss fraction (0-1) above which a warning is emitted. */
|
|
9930
|
-
const DEFAULT_STATS_PACKET_LOSS_THRESHOLD = .05;
|
|
9931
|
-
/** Multiplier applied to baseline jitter to detect a jitter spike. */
|
|
9932
|
-
const DEFAULT_STATS_JITTER_SPIKE_MULTIPLIER = 4;
|
|
9933
|
-
/** Number of seconds of metrics history to retain. */
|
|
9934
|
-
const DEFAULT_STATS_HISTORY_SIZE = 30;
|
|
9935
|
-
/** Maximum keyframe requests allowed within a single burst window. */
|
|
9936
|
-
const DEFAULT_KEYFRAME_MAX_BURST$1 = 3;
|
|
9937
|
-
/** Duration of the keyframe burst window in milliseconds. */
|
|
9938
|
-
const DEFAULT_KEYFRAME_BURST_WINDOW_MS$1 = 3e3;
|
|
9939
|
-
/** Cooldown period in ms after burst limit is reached before allowing more keyframes. */
|
|
9940
|
-
const DEFAULT_KEYFRAME_COOLDOWN_MS$1 = 1e4;
|
|
9941
|
-
/** Minimum time between re-INVITE attempts in milliseconds. */
|
|
9942
|
-
const DEFAULT_REINVITE_DEBOUNCE_TIME_MS = 1e4;
|
|
9943
|
-
/** Maximum number of re-INVITE attempts per call. */
|
|
9944
|
-
const DEFAULT_REINVITE_MAX_ATTEMPTS = 3;
|
|
9945
|
-
/** Timeout for a single re-INVITE attempt in milliseconds. */
|
|
9946
|
-
const DEFAULT_REINVITE_TIMEOUT_MS = 5e3;
|
|
9947
|
-
/** Debounce window in ms to collapse multiple detection signals into one trigger. */
|
|
9948
|
-
const DEFAULT_RECOVERY_DEBOUNCE_TIME_MS = 2e3;
|
|
9949
|
-
/** Cooldown period in ms between recovery attempts. */
|
|
9950
|
-
const DEFAULT_RECOVERY_COOLDOWN_MS = 1e4;
|
|
9951
|
-
/** Grace period in ms before treating ICE 'disconnected' as a failure. */
|
|
9952
|
-
const DEFAULT_ICE_DISCONNECTED_GRACE_PERIOD_MS = 3e3;
|
|
9953
|
-
/** Timeout for a single ICE restart attempt in milliseconds. */
|
|
9954
|
-
const DEFAULT_ICE_RESTART_TIMEOUT_MS$1 = 5e3;
|
|
9955
|
-
/** Maximum recovery attempts before emitting 'max_attempts_reached'. */
|
|
9956
|
-
const DEFAULT_MAX_RECOVERY_ATTEMPTS = 3;
|
|
9957
|
-
/** Upper bound in ms for waiting on iceGatheringState === 'complete' after an ICE restart. */
|
|
9958
|
-
const ICE_GATHERING_COMPLETE_TIMEOUT_MS = 1e4;
|
|
9959
|
-
/** Upper bound in ms for waiting on RTCPeerConnection.connectionState === 'connected' after a recovery ICE restart. */
|
|
9960
|
-
const PEER_CONNECTION_RECOVERY_WAIT_MS = 5e3;
|
|
9961
|
-
/** Polling interval in ms while waiting for RTCPeerConnection.connectionState to transition. */
|
|
9962
|
-
const PEER_CONNECTION_RECOVERY_POLL_MS = 100;
|
|
9963
|
-
/** Polling interval for LocalAudioPipeline.level$ (ms). ~30fps is smooth for meters. */
|
|
9964
|
-
const AUDIO_LEVEL_POLL_INTERVAL_MS = 33;
|
|
9965
|
-
/** RMS level threshold (0..1) above which the local participant is considered speaking. */
|
|
9966
|
-
const VAD_THRESHOLD = .03;
|
|
9967
|
-
/** Hold window in ms below the threshold before speaking$ flips back to false. */
|
|
9968
|
-
const VAD_HOLD_MS = 250;
|
|
9969
|
-
/** Whether to persist device selections to storage by default. */
|
|
9970
|
-
const DEFAULT_PERSIST_DEVICE_SELECTION = true;
|
|
9971
|
-
/** Whether to auto-apply device changes to active calls by default. */
|
|
9972
|
-
const DEFAULT_SYNC_DEVICES_TO_ACTIVE_CALLS = true;
|
|
9973
|
-
/** Storage keys for persisted device selections. */
|
|
9974
|
-
const DEVICE_STORAGE_KEY_AUDIO_INPUT = "sw:device:audioinput";
|
|
9975
|
-
const DEVICE_STORAGE_KEY_AUDIO_OUTPUT = "sw:device:audiooutput";
|
|
9976
|
-
const DEVICE_STORAGE_KEY_VIDEO_INPUT = "sw:device:videoinput";
|
|
9977
|
-
/** Whether to auto-mute video when the tab becomes hidden. */
|
|
9978
|
-
const DEFAULT_AUTO_MUTE_VIDEO_ON_HIDDEN = false;
|
|
9979
|
-
/** Whether to re-enumerate devices when the page becomes visible. */
|
|
9980
|
-
const DEFAULT_REFRESH_DEVICES_ON_VISIBLE = true;
|
|
9981
|
-
/** Whether to check peer connection health when the page becomes visible. */
|
|
9982
|
-
const DEFAULT_CHECK_CONNECTION_ON_VISIBLE = true;
|
|
9983
|
-
/** Whether automatic video degradation on low bandwidth is enabled. */
|
|
9984
|
-
const DEFAULT_ENABLE_AUTO_DEGRADATION = true;
|
|
9985
|
-
/** Bitrate in kbps below which video is automatically disabled. */
|
|
9986
|
-
const DEFAULT_DEGRADATION_BITRATE_THRESHOLD_KBPS = 150;
|
|
9987
|
-
/** Bitrate in kbps above which video is automatically re-enabled (hysteresis). */
|
|
9988
|
-
const DEFAULT_DEGRADATION_RECOVERY_THRESHOLD_KBPS = 300;
|
|
9989
|
-
/** Whether relay-only escalation is enabled as a last-resort recovery tier. */
|
|
9990
|
-
const DEFAULT_ENABLE_RELAY_FALLBACK = true;
|
|
9991
|
-
/** Whether to listen for browser online/offline/connection events. */
|
|
9992
|
-
const DEFAULT_ENABLE_NETWORK_CHANGE_DETECTION = true;
|
|
9993
|
-
/** Whether to intercept server-sent media-timeout hangups and attempt recovery. */
|
|
9994
|
-
const DEFAULT_ENABLE_SERVER_HANGUP_INTERCEPTION = true;
|
|
9995
|
-
/** Default video track constraints applied when video is enabled without explicit constraints. */
|
|
9996
|
-
const DEFAULT_VIDEO_CONSTRAINTS = {
|
|
9997
|
-
width: { ideal: 1280 },
|
|
9998
|
-
height: { ideal: 720 },
|
|
9999
|
-
aspectRatio: 16 / 9
|
|
10000
|
-
};
|
|
10001
|
-
/** Whether stereo Opus is enabled by default. */
|
|
10002
|
-
const DEFAULT_STEREO_AUDIO = false;
|
|
10003
|
-
/** Max average bitrate for stereo Opus in bits per second. */
|
|
10004
|
-
const DEFAULT_STEREO_MAX_AVERAGE_BITRATE = 51e4;
|
|
10005
|
-
|
|
10006
10068
|
//#endregion
|
|
10007
10069
|
//#region src/utils/time.ts
|
|
10008
10070
|
function fromSecToMs(seconds) {
|
|
@@ -10014,7 +10076,7 @@ function fromMsToSec(milliseconds) {
|
|
|
10014
10076
|
|
|
10015
10077
|
//#endregion
|
|
10016
10078
|
//#region src/containers/PreferencesContainer.ts
|
|
10017
|
-
const logger$
|
|
10079
|
+
const logger$31 = getLogger();
|
|
10018
10080
|
var PreferencesContainer = class PreferencesContainer {
|
|
10019
10081
|
static get instance() {
|
|
10020
10082
|
this._instance ??= new PreferencesContainer();
|
|
@@ -10676,7 +10738,7 @@ var ClientPreferences = class {
|
|
|
10676
10738
|
if (!this._storage) return;
|
|
10677
10739
|
const data = collectStoredPreferences();
|
|
10678
10740
|
this._storage.setItem(PREFERENCES_STORAGE_KEY, data, "local").catch((error) => {
|
|
10679
|
-
logger$
|
|
10741
|
+
logger$31.error(`[ClientPreferences] Failed to save preferences: ${String(error)}`);
|
|
10680
10742
|
});
|
|
10681
10743
|
}
|
|
10682
10744
|
/** Loads preferences from storage and applies them to the container. */
|
|
@@ -10685,7 +10747,7 @@ var ClientPreferences = class {
|
|
|
10685
10747
|
this._storage.getItem(PREFERENCES_STORAGE_KEY, "local").then((stored) => {
|
|
10686
10748
|
if (stored) applyStoredPreferences(stored);
|
|
10687
10749
|
}).catch((error) => {
|
|
10688
|
-
logger$
|
|
10750
|
+
logger$31.error(`[ClientPreferences] Failed to load preferences: ${String(error)}`);
|
|
10689
10751
|
});
|
|
10690
10752
|
}
|
|
10691
10753
|
};
|
|
@@ -10707,7 +10769,7 @@ function toError(value) {
|
|
|
10707
10769
|
//#endregion
|
|
10708
10770
|
//#region src/controllers/NavigatorDeviceController.ts
|
|
10709
10771
|
var import_cjs$29 = require_cjs();
|
|
10710
|
-
const logger$
|
|
10772
|
+
const logger$30 = getLogger();
|
|
10711
10773
|
/** Maps a device kind to its storage key. */
|
|
10712
10774
|
const DEVICE_STORAGE_KEYS = {
|
|
10713
10775
|
audioinput: DEVICE_STORAGE_KEY_AUDIO_INPUT,
|
|
@@ -10729,7 +10791,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10729
10791
|
super();
|
|
10730
10792
|
this.webRTCApiProvider = webRTCApiProvider;
|
|
10731
10793
|
this.deviceChangeHandler = () => {
|
|
10732
|
-
logger$
|
|
10794
|
+
logger$30.debug("[DeviceController] Device change detected");
|
|
10733
10795
|
this.enumerateDevices();
|
|
10734
10796
|
};
|
|
10735
10797
|
this._devicesState$ = this.createBehaviorSubject(initialDevicesState);
|
|
@@ -10794,13 +10856,13 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10794
10856
|
return this.cachedObservable("videoInputDevices$", () => this._devicesState$.pipe((0, import_cjs$29.map)((state) => state.videoinput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$)));
|
|
10795
10857
|
}
|
|
10796
10858
|
get selectedAudioInputDevice$() {
|
|
10797
|
-
return this.cachedObservable("selectedAudioInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.audioinput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$
|
|
10859
|
+
return this.cachedObservable("selectedAudioInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.audioinput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$30.debug("[DeviceController] Selected audio input device changed:", info))));
|
|
10798
10860
|
}
|
|
10799
10861
|
get selectedAudioOutputDevice$() {
|
|
10800
|
-
return this.cachedObservable("selectedAudioOutputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.audiooutput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$
|
|
10862
|
+
return this.cachedObservable("selectedAudioOutputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.audiooutput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$30.debug("[DeviceController] Selected audio output device changed:", info))));
|
|
10801
10863
|
}
|
|
10802
10864
|
get selectedVideoInputDevice$() {
|
|
10803
|
-
return this.cachedObservable("selectedVideoInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.videoinput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$
|
|
10865
|
+
return this.cachedObservable("selectedVideoInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.videoinput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$30.debug("[DeviceController] Selected video input device changed:", info))));
|
|
10804
10866
|
}
|
|
10805
10867
|
get selectedAudioInputDevice() {
|
|
10806
10868
|
if (this._audioInputDisabled$.value) return null;
|
|
@@ -10875,7 +10937,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10875
10937
|
if (device) this.persistDeviceSelection("audioinput", device);
|
|
10876
10938
|
}
|
|
10877
10939
|
selectVideoInputDevice(device) {
|
|
10878
|
-
logger$
|
|
10940
|
+
logger$30.debug("[DeviceController] Setting selected video input device:", device);
|
|
10879
10941
|
if (this._videoInputDisabled$.value && device) this._videoInputDisabled$.next(false);
|
|
10880
10942
|
const previous = this._selectedDevicesState$.value.videoinput;
|
|
10881
10943
|
if (previous && previous.deviceId !== device?.deviceId) this._deviceHistory.push("videoinput", previous);
|
|
@@ -10932,7 +10994,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10932
10994
|
}
|
|
10933
10995
|
const fromHistory = this._deviceHistory.findInHistory(kind, devices);
|
|
10934
10996
|
if (fromHistory) {
|
|
10935
|
-
logger$
|
|
10997
|
+
logger$30.debug(`[DeviceController] Device disappeared, falling back to history: ${fromHistory.label}`);
|
|
10936
10998
|
this.emitDeviceRecovered(kind, selected, fromHistory, "device_disconnected");
|
|
10937
10999
|
return fromHistory;
|
|
10938
11000
|
}
|
|
@@ -10985,7 +11047,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10985
11047
|
try {
|
|
10986
11048
|
await this._storageManager.setItem(DEVICE_STORAGE_KEYS[kind], stored, "local");
|
|
10987
11049
|
} catch (error) {
|
|
10988
|
-
logger$
|
|
11050
|
+
logger$30.error(`[DeviceController] Failed to persist device selection for ${kind}:`, error);
|
|
10989
11051
|
}
|
|
10990
11052
|
}
|
|
10991
11053
|
async loadPersistedDevices() {
|
|
@@ -11001,7 +11063,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
11001
11063
|
[kind]: stored
|
|
11002
11064
|
};
|
|
11003
11065
|
} catch (error) {
|
|
11004
|
-
logger$
|
|
11066
|
+
logger$30.error(`[DeviceController] Failed to load persisted device for ${kind}:`, error);
|
|
11005
11067
|
}
|
|
11006
11068
|
}
|
|
11007
11069
|
/** Clears device history, persisted selections, and re-enumerates devices. */
|
|
@@ -11019,7 +11081,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
11019
11081
|
this.disableDeviceMonitoring();
|
|
11020
11082
|
this.webRTCApiProvider.mediaDevices.addEventListener("devicechange", this.deviceChangeHandler);
|
|
11021
11083
|
if (PreferencesContainer.instance.devicePollingInterval > 0) this._devicesPoolingSubscription = (0, import_cjs$29.interval)(PreferencesContainer.instance.devicePollingInterval).subscribe(() => {
|
|
11022
|
-
logger$
|
|
11084
|
+
logger$30.debug("[DeviceController] Polling devices due to interval");
|
|
11023
11085
|
this.enumerateDevices();
|
|
11024
11086
|
});
|
|
11025
11087
|
this.enumerateDevices();
|
|
@@ -11045,13 +11107,13 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
11045
11107
|
videoinput: []
|
|
11046
11108
|
});
|
|
11047
11109
|
this._devicesState$.next(devicesByKind);
|
|
11048
|
-
logger$
|
|
11110
|
+
logger$30.debug("[DeviceController] Devices enumerated:", {
|
|
11049
11111
|
audioInputs: devicesByKind.audioinput.length,
|
|
11050
11112
|
audioOutputs: devicesByKind.audiooutput.length,
|
|
11051
11113
|
videoInputs: devicesByKind.videoinput.length
|
|
11052
11114
|
});
|
|
11053
11115
|
} catch (error) {
|
|
11054
|
-
logger$
|
|
11116
|
+
logger$30.error("[DeviceController] Failed to enumerate devices:", error);
|
|
11055
11117
|
this._errors$.next(toError(error));
|
|
11056
11118
|
}
|
|
11057
11119
|
}
|
|
@@ -11067,7 +11129,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
11067
11129
|
stream.getTracks().forEach((t) => t.stop());
|
|
11068
11130
|
return capabilities;
|
|
11069
11131
|
} catch (error) {
|
|
11070
|
-
logger$
|
|
11132
|
+
logger$30.error("[DeviceController] Failed to get device capabilities:", error);
|
|
11071
11133
|
this._errors$.next(toError(error));
|
|
11072
11134
|
throw error;
|
|
11073
11135
|
}
|
|
@@ -11318,7 +11380,7 @@ var DependencyContainer = class {
|
|
|
11318
11380
|
|
|
11319
11381
|
//#endregion
|
|
11320
11382
|
//#region src/controllers/CryptoController.ts
|
|
11321
|
-
const logger$
|
|
11383
|
+
const logger$29 = getLogger();
|
|
11322
11384
|
const DPOP_DB_NAME = "sw-dpop";
|
|
11323
11385
|
const DPOP_DB_VERSION = 1;
|
|
11324
11386
|
const DPOP_STORE_NAME = "keys";
|
|
@@ -11377,7 +11439,7 @@ async function loadKeyPairFromDB() {
|
|
|
11377
11439
|
tx.oncomplete = () => db.close();
|
|
11378
11440
|
});
|
|
11379
11441
|
} catch (error) {
|
|
11380
|
-
logger$
|
|
11442
|
+
logger$29.warn("[DPoP] Failed to load key pair from IndexedDB:", error);
|
|
11381
11443
|
return null;
|
|
11382
11444
|
}
|
|
11383
11445
|
}
|
|
@@ -11397,7 +11459,7 @@ async function saveKeyPairToDB(keyPair) {
|
|
|
11397
11459
|
};
|
|
11398
11460
|
});
|
|
11399
11461
|
} catch (error) {
|
|
11400
|
-
logger$
|
|
11462
|
+
logger$29.warn("[DPoP] Failed to save key pair to IndexedDB:", error);
|
|
11401
11463
|
}
|
|
11402
11464
|
}
|
|
11403
11465
|
async function deleteKeyPairFromDB() {
|
|
@@ -11416,7 +11478,7 @@ async function deleteKeyPairFromDB() {
|
|
|
11416
11478
|
};
|
|
11417
11479
|
});
|
|
11418
11480
|
} catch (error) {
|
|
11419
|
-
logger$
|
|
11481
|
+
logger$29.warn("[DPoP] Failed to delete key pair from IndexedDB:", error);
|
|
11420
11482
|
}
|
|
11421
11483
|
}
|
|
11422
11484
|
/**
|
|
@@ -11476,13 +11538,13 @@ var CryptoController = class {
|
|
|
11476
11538
|
this._publicJwk = await crypto.subtle.exportKey("jwk", stored.publicKey);
|
|
11477
11539
|
this._fingerprint = await computeJwkThumbprint(this._publicJwk);
|
|
11478
11540
|
this._initialized = true;
|
|
11479
|
-
logger$
|
|
11541
|
+
logger$29.debug("[DPoP] Key pair restored from IndexedDB, fingerprint:", this._fingerprint);
|
|
11480
11542
|
return this._fingerprint;
|
|
11481
11543
|
} catch (error) {
|
|
11482
|
-
logger$
|
|
11544
|
+
logger$29.warn("[DPoP] Stored key pair unusable, generating new one:", error);
|
|
11483
11545
|
await deleteKeyPairFromDB();
|
|
11484
11546
|
}
|
|
11485
|
-
logger$
|
|
11547
|
+
logger$29.debug("[DPoP] Generating RSA key pair");
|
|
11486
11548
|
this._keyPair = await crypto.subtle.generateKey({
|
|
11487
11549
|
name: "RSASSA-PKCS1-v1_5",
|
|
11488
11550
|
modulusLength: 2048,
|
|
@@ -11497,7 +11559,7 @@ var CryptoController = class {
|
|
|
11497
11559
|
this._fingerprint = await computeJwkThumbprint(this._publicJwk);
|
|
11498
11560
|
this._initialized = true;
|
|
11499
11561
|
await saveKeyPairToDB(this._keyPair);
|
|
11500
|
-
logger$
|
|
11562
|
+
logger$29.debug("[DPoP] Key pair generated and persisted, fingerprint:", this._fingerprint);
|
|
11501
11563
|
return this._fingerprint;
|
|
11502
11564
|
}
|
|
11503
11565
|
/**
|
|
@@ -11563,7 +11625,7 @@ var CryptoController = class {
|
|
|
11563
11625
|
this._fingerprint = null;
|
|
11564
11626
|
this._initialized = false;
|
|
11565
11627
|
deleteKeyPairFromDB();
|
|
11566
|
-
logger$
|
|
11628
|
+
logger$29.debug("[DPoP] Controller destroyed");
|
|
11567
11629
|
}
|
|
11568
11630
|
get publicJwk() {
|
|
11569
11631
|
if (!this._publicJwk) throw new DPoPInitError("CryptoController not initialized. Call init() first.");
|
|
@@ -11587,7 +11649,7 @@ var CryptoController = class {
|
|
|
11587
11649
|
//#endregion
|
|
11588
11650
|
//#region src/controllers/NetworkMonitor.ts
|
|
11589
11651
|
var import_cjs$28 = require_cjs();
|
|
11590
|
-
const logger$
|
|
11652
|
+
const logger$28 = getLogger();
|
|
11591
11653
|
/**
|
|
11592
11654
|
* Safely check whether we are running in a browser environment
|
|
11593
11655
|
* with `window` and the relevant event targets.
|
|
@@ -11644,7 +11706,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11644
11706
|
}
|
|
11645
11707
|
attachListeners() {
|
|
11646
11708
|
if (!hasBrowserNetworkEvents()) {
|
|
11647
|
-
logger$
|
|
11709
|
+
logger$28.debug("NetworkMonitor: no browser environment detected, skipping event listeners");
|
|
11648
11710
|
return;
|
|
11649
11711
|
}
|
|
11650
11712
|
window.addEventListener("online", this._onOnline);
|
|
@@ -11652,7 +11714,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11652
11714
|
const connection = getNetworkConnection();
|
|
11653
11715
|
if (connection) connection.addEventListener("change", this._onConnectionChange);
|
|
11654
11716
|
this._listenersAttached = true;
|
|
11655
|
-
logger$
|
|
11717
|
+
logger$28.debug("NetworkMonitor: event listeners attached");
|
|
11656
11718
|
}
|
|
11657
11719
|
removeListeners() {
|
|
11658
11720
|
if (!this._listenersAttached) return;
|
|
@@ -11663,10 +11725,10 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11663
11725
|
if (connection) connection.removeEventListener("change", this._onConnectionChange);
|
|
11664
11726
|
}
|
|
11665
11727
|
this._listenersAttached = false;
|
|
11666
|
-
logger$
|
|
11728
|
+
logger$28.debug("NetworkMonitor: event listeners removed");
|
|
11667
11729
|
}
|
|
11668
11730
|
handleOnline() {
|
|
11669
|
-
logger$
|
|
11731
|
+
logger$28.info("NetworkMonitor: browser went online");
|
|
11670
11732
|
this._isOnline$.next(true);
|
|
11671
11733
|
this._networkChange$.next({
|
|
11672
11734
|
type: "online",
|
|
@@ -11675,7 +11737,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11675
11737
|
});
|
|
11676
11738
|
}
|
|
11677
11739
|
handleOffline() {
|
|
11678
|
-
logger$
|
|
11740
|
+
logger$28.info("NetworkMonitor: browser went offline");
|
|
11679
11741
|
this._isOnline$.next(false);
|
|
11680
11742
|
this._networkChange$.next({
|
|
11681
11743
|
type: "offline",
|
|
@@ -11684,7 +11746,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11684
11746
|
}
|
|
11685
11747
|
handleConnectionChange() {
|
|
11686
11748
|
const networkType = getNetworkType();
|
|
11687
|
-
logger$
|
|
11749
|
+
logger$28.info(`NetworkMonitor: connection changed — effectiveType=${networkType ?? "unknown"}`);
|
|
11688
11750
|
this._networkChange$.next({
|
|
11689
11751
|
type: "connection_change",
|
|
11690
11752
|
timestamp: Date.now(),
|
|
@@ -11800,7 +11862,7 @@ function getNavigatorMediaDevices() {
|
|
|
11800
11862
|
//#endregion
|
|
11801
11863
|
//#region src/controllers/PreflightRunner.ts
|
|
11802
11864
|
var import_cjs$27 = require_cjs();
|
|
11803
|
-
const logger$
|
|
11865
|
+
const logger$27 = getLogger();
|
|
11804
11866
|
const DEFAULT_MEDIA_TEST_DURATION_S = 10;
|
|
11805
11867
|
const ICE_GATHERING_TIMEOUT_MS = 1e4;
|
|
11806
11868
|
const SIGNALING_RTT_TIMEOUT_MS = 5e3;
|
|
@@ -11849,7 +11911,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11849
11911
|
if (!this._options.skipMediaTest) try {
|
|
11850
11912
|
bandwidth = await this.testMediaBandwidth(destination);
|
|
11851
11913
|
} catch (error) {
|
|
11852
|
-
logger$
|
|
11914
|
+
logger$27.warn("[PreflightRunner] Media bandwidth test failed:", error);
|
|
11853
11915
|
warnings.push("Media bandwidth test failed");
|
|
11854
11916
|
}
|
|
11855
11917
|
return {
|
|
@@ -11861,7 +11923,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11861
11923
|
warnings
|
|
11862
11924
|
};
|
|
11863
11925
|
} catch (error) {
|
|
11864
|
-
logger$
|
|
11926
|
+
logger$27.error("[PreflightRunner] Preflight test failed:", error);
|
|
11865
11927
|
throw new PreflightError("preflight", error instanceof Error ? error : new Error(String(error)));
|
|
11866
11928
|
} finally {
|
|
11867
11929
|
this.destroy();
|
|
@@ -11892,7 +11954,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11892
11954
|
if (track.kind === "video" && track.readyState === "live") videoWorking = true;
|
|
11893
11955
|
}
|
|
11894
11956
|
} catch (error) {
|
|
11895
|
-
logger$
|
|
11957
|
+
logger$27.warn("[PreflightRunner] Device test failed:", error);
|
|
11896
11958
|
} finally {
|
|
11897
11959
|
if (audioStream) audioStream.getTracks().forEach((t) => t.stop());
|
|
11898
11960
|
}
|
|
@@ -11950,7 +12012,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11950
12012
|
rttMs
|
|
11951
12013
|
};
|
|
11952
12014
|
} catch (error) {
|
|
11953
|
-
logger$
|
|
12015
|
+
logger$27.warn("[PreflightRunner] ICE connectivity test failed:", error);
|
|
11954
12016
|
return {
|
|
11955
12017
|
type: "failed",
|
|
11956
12018
|
turnReachable: false,
|
|
@@ -11998,7 +12060,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11998
12060
|
//#endregion
|
|
11999
12061
|
//#region src/controllers/VisibilityController.ts
|
|
12000
12062
|
var import_cjs$26 = require_cjs();
|
|
12001
|
-
const logger$
|
|
12063
|
+
const logger$26 = getLogger();
|
|
12002
12064
|
/**
|
|
12003
12065
|
* Checks whether the document visibility API is available.
|
|
12004
12066
|
*/
|
|
@@ -12035,8 +12097,8 @@ var VisibilityController = class extends Destroyable {
|
|
|
12035
12097
|
this._boundHandler = this._handleVisibilityChange.bind(this);
|
|
12036
12098
|
if (this._hasVisibilityApi) {
|
|
12037
12099
|
document.addEventListener("visibilitychange", this._boundHandler);
|
|
12038
|
-
logger$
|
|
12039
|
-
} else logger$
|
|
12100
|
+
logger$26.debug("VisibilityController: listening for visibilitychange events");
|
|
12101
|
+
} else logger$26.debug("VisibilityController: document visibility API not available, defaulting to visible");
|
|
12040
12102
|
}
|
|
12041
12103
|
/**
|
|
12042
12104
|
* Observable of the current visibility state.
|
|
@@ -12061,7 +12123,7 @@ var VisibilityController = class extends Destroyable {
|
|
|
12061
12123
|
destroy() {
|
|
12062
12124
|
if (this._hasVisibilityApi) {
|
|
12063
12125
|
document.removeEventListener("visibilitychange", this._boundHandler);
|
|
12064
|
-
logger$
|
|
12126
|
+
logger$26.debug("VisibilityController: removed visibilitychange listener");
|
|
12065
12127
|
}
|
|
12066
12128
|
super.destroy();
|
|
12067
12129
|
}
|
|
@@ -12079,7 +12141,7 @@ var VisibilityController = class extends Destroyable {
|
|
|
12079
12141
|
timestamp: Date.now()
|
|
12080
12142
|
};
|
|
12081
12143
|
this._visibilityChange$.next(changeEvent);
|
|
12082
|
-
logger$
|
|
12144
|
+
logger$26.debug("VisibilityController: visibility changed", {
|
|
12083
12145
|
from: previousState,
|
|
12084
12146
|
to: newState
|
|
12085
12147
|
});
|
|
@@ -12154,22 +12216,17 @@ function unsafeStringify(arr, offset = 0) {
|
|
|
12154
12216
|
|
|
12155
12217
|
//#endregion
|
|
12156
12218
|
//#region ../../node_modules/uuid/dist-node/rng.js
|
|
12157
|
-
const
|
|
12158
|
-
let poolPtr = rnds8Pool.length;
|
|
12219
|
+
const rnds8 = new Uint8Array(16);
|
|
12159
12220
|
function rng() {
|
|
12160
|
-
|
|
12161
|
-
(0, node_crypto.randomFillSync)(rnds8Pool);
|
|
12162
|
-
poolPtr = 0;
|
|
12163
|
-
}
|
|
12164
|
-
return rnds8Pool.slice(poolPtr, poolPtr += 16);
|
|
12221
|
+
return crypto.getRandomValues(rnds8);
|
|
12165
12222
|
}
|
|
12166
12223
|
|
|
12167
|
-
//#endregion
|
|
12168
|
-
//#region ../../node_modules/uuid/dist-node/native.js
|
|
12169
|
-
var native_default = { randomUUID: node_crypto.randomUUID };
|
|
12170
|
-
|
|
12171
12224
|
//#endregion
|
|
12172
12225
|
//#region ../../node_modules/uuid/dist-node/v4.js
|
|
12226
|
+
function v4(options, buf, offset) {
|
|
12227
|
+
if (!buf && !options && crypto.randomUUID) return crypto.randomUUID();
|
|
12228
|
+
return _v4(options, buf, offset);
|
|
12229
|
+
}
|
|
12173
12230
|
function _v4(options, buf, offset) {
|
|
12174
12231
|
options = options || {};
|
|
12175
12232
|
const rnds = options.random ?? options.rng?.() ?? rng();
|
|
@@ -12184,10 +12241,6 @@ function _v4(options, buf, offset) {
|
|
|
12184
12241
|
}
|
|
12185
12242
|
return unsafeStringify(rnds);
|
|
12186
12243
|
}
|
|
12187
|
-
function v4(options, buf, offset) {
|
|
12188
|
-
if (native_default.randomUUID && !buf && !options) return native_default.randomUUID();
|
|
12189
|
-
return _v4(options, buf, offset);
|
|
12190
|
-
}
|
|
12191
12244
|
var v4_default = v4;
|
|
12192
12245
|
|
|
12193
12246
|
//#endregion
|
|
@@ -12329,7 +12382,7 @@ const RPCEventAckResponse = (id) => makeRPCResponse({
|
|
|
12329
12382
|
|
|
12330
12383
|
//#endregion
|
|
12331
12384
|
//#region src/managers/AttachManager.ts
|
|
12332
|
-
const logger$
|
|
12385
|
+
const logger$25 = getLogger();
|
|
12333
12386
|
var AttachManager = class {
|
|
12334
12387
|
constructor(storage, deviceController, reconnectCallsTimeout, attachKey) {
|
|
12335
12388
|
this.storage = storage;
|
|
@@ -12350,7 +12403,7 @@ var AttachManager = class {
|
|
|
12350
12403
|
try {
|
|
12351
12404
|
return await this.storage.getItem(this.attachKey) ?? {};
|
|
12352
12405
|
} catch (error) {
|
|
12353
|
-
logger$
|
|
12406
|
+
logger$25.warn("[AttachManager] Failed to retrieve attached calls from storage", error);
|
|
12354
12407
|
return {};
|
|
12355
12408
|
}
|
|
12356
12409
|
}
|
|
@@ -12358,7 +12411,7 @@ var AttachManager = class {
|
|
|
12358
12411
|
try {
|
|
12359
12412
|
await this.storage.setItem(this.attachKey, attached);
|
|
12360
12413
|
} catch (error) {
|
|
12361
|
-
logger$
|
|
12414
|
+
logger$25.warn("[AttachManager] Failed to write attached calls to storage", error);
|
|
12362
12415
|
}
|
|
12363
12416
|
}
|
|
12364
12417
|
/**
|
|
@@ -12377,7 +12430,7 @@ var AttachManager = class {
|
|
|
12377
12430
|
}
|
|
12378
12431
|
async attach(call) {
|
|
12379
12432
|
if (!call.to) {
|
|
12380
|
-
logger$
|
|
12433
|
+
logger$25.warn("[AttachManager] Skip attach for calls with no destination");
|
|
12381
12434
|
return;
|
|
12382
12435
|
}
|
|
12383
12436
|
const destination = call.to;
|
|
@@ -12430,15 +12483,15 @@ var AttachManager = class {
|
|
|
12430
12483
|
callId,
|
|
12431
12484
|
...options
|
|
12432
12485
|
});
|
|
12433
|
-
logger$
|
|
12486
|
+
logger$25.info(`[AttachManager] Reattached call ${callId} (attempt ${attempt})`);
|
|
12434
12487
|
succeeded = true;
|
|
12435
12488
|
break;
|
|
12436
12489
|
} catch (error) {
|
|
12437
|
-
logger$
|
|
12490
|
+
logger$25.warn(`[AttachManager] Reattach attempt ${attempt}/3 failed for call ${callId}:`, error);
|
|
12438
12491
|
if (attempt < 3) await new Promise((r) => setTimeout(r, (attempt + 1) * 1e3));
|
|
12439
12492
|
}
|
|
12440
12493
|
if (!succeeded) {
|
|
12441
|
-
logger$
|
|
12494
|
+
logger$25.warn(`[AttachManager] Reattach failed after 3 attempts for call ${callId}, removing reference`);
|
|
12442
12495
|
await this.detach({
|
|
12443
12496
|
id: callId,
|
|
12444
12497
|
mediaDirections: attachment.mediaDirections
|
|
@@ -13375,7 +13428,9 @@ const DEFAULT_MEMBER_CAPABILITIES = {
|
|
|
13375
13428
|
position: false,
|
|
13376
13429
|
meta: false,
|
|
13377
13430
|
remove: false,
|
|
13378
|
-
audioFlags: false
|
|
13431
|
+
audioFlags: false,
|
|
13432
|
+
denoise: false,
|
|
13433
|
+
lowbitrate: false
|
|
13379
13434
|
};
|
|
13380
13435
|
/**
|
|
13381
13436
|
* Default call capabilities with no permissions
|
|
@@ -13448,7 +13503,9 @@ function computeMemberCapabilities(flags, memberType) {
|
|
|
13448
13503
|
position: hasBooleanCapability(flags, memberType, null, "position"),
|
|
13449
13504
|
meta: hasBooleanCapability(flags, memberType, null, "meta"),
|
|
13450
13505
|
remove: hasBooleanCapability(flags, memberType, null, "remove"),
|
|
13451
|
-
audioFlags: hasBooleanCapability(flags, memberType, null, "audioflags")
|
|
13506
|
+
audioFlags: hasBooleanCapability(flags, memberType, null, "audioflags"),
|
|
13507
|
+
denoise: hasBooleanCapability(flags, memberType, null, "denoise"),
|
|
13508
|
+
lowbitrate: hasBooleanCapability(flags, memberType, null, "lowbitrate")
|
|
13452
13509
|
};
|
|
13453
13510
|
}
|
|
13454
13511
|
/**
|
|
@@ -13605,7 +13662,7 @@ function toggleHandraiseMethod(is) {
|
|
|
13605
13662
|
|
|
13606
13663
|
//#endregion
|
|
13607
13664
|
//#region src/core/entities/Participant.ts
|
|
13608
|
-
const logger$
|
|
13665
|
+
const logger$24 = getLogger();
|
|
13609
13666
|
const initialState = {};
|
|
13610
13667
|
/**
|
|
13611
13668
|
* Represents a participant in a call.
|
|
@@ -13615,9 +13672,9 @@ const initialState = {};
|
|
|
13615
13672
|
* the local participant with additional device control.
|
|
13616
13673
|
*/
|
|
13617
13674
|
var Participant = class extends Destroyable {
|
|
13618
|
-
constructor(id,
|
|
13675
|
+
constructor(id, callExecuteMethod, deviceController) {
|
|
13619
13676
|
super();
|
|
13620
|
-
this.
|
|
13677
|
+
this.callExecuteMethod = callExecuteMethod;
|
|
13621
13678
|
this.deviceController = deviceController;
|
|
13622
13679
|
this._state$ = this.createBehaviorSubject(initialState);
|
|
13623
13680
|
this.id = id;
|
|
@@ -13831,26 +13888,63 @@ var Participant = class extends Destroyable {
|
|
|
13831
13888
|
get nodeId() {
|
|
13832
13889
|
return this._state$.value.node_id;
|
|
13833
13890
|
}
|
|
13891
|
+
/** Call ID for this participant's leg, or `undefined` if not available. */
|
|
13892
|
+
get callId() {
|
|
13893
|
+
return this._state$.value.call_id;
|
|
13894
|
+
}
|
|
13834
13895
|
/** @internal */
|
|
13835
13896
|
get value() {
|
|
13836
13897
|
return this._state$.value;
|
|
13837
13898
|
}
|
|
13899
|
+
/**
|
|
13900
|
+
* Target triple for member RPCs, built from the participant's own state.
|
|
13901
|
+
* The backend locates the member's session by the target `call_id`/`node_id`,
|
|
13902
|
+
* so this must always be the participant's own call context — never the
|
|
13903
|
+
* local call's id (issue #19400).
|
|
13904
|
+
*
|
|
13905
|
+
* Reading it doubles as a readiness probe: it throws until the first full
|
|
13906
|
+
* member event (`member.joined`/`member.updated` or the `call.joined`
|
|
13907
|
+
* roster) arrives, and never regresses afterwards.
|
|
13908
|
+
*
|
|
13909
|
+
* @throws {ParticipantNotReadyError} If the member state has not been
|
|
13910
|
+
* received yet (e.g. a participant first seen via `member.talking`) — an
|
|
13911
|
+
* empty call context can never address the member, so fail fast instead of
|
|
13912
|
+
* sending a doomed RPC.
|
|
13913
|
+
*/
|
|
13914
|
+
get target() {
|
|
13915
|
+
const { call_id, node_id } = this._state$.value;
|
|
13916
|
+
if (!call_id || !node_id) throw new ParticipantNotReadyError(this.id);
|
|
13917
|
+
return {
|
|
13918
|
+
member_id: this.id,
|
|
13919
|
+
call_id,
|
|
13920
|
+
node_id
|
|
13921
|
+
};
|
|
13922
|
+
}
|
|
13923
|
+
/**
|
|
13924
|
+
* Executes a member RPC against this participant, injecting its own
|
|
13925
|
+
* {@link target} as the target.
|
|
13926
|
+
*
|
|
13927
|
+
* @throws {ParticipantNotReadyError} Via {@link target}, when the
|
|
13928
|
+
* member state has not been received yet.
|
|
13929
|
+
*/
|
|
13930
|
+
async executeMethod(method, args) {
|
|
13931
|
+
return this.callExecuteMethod(this.target, method, args);
|
|
13932
|
+
}
|
|
13838
13933
|
/** Toggles the deafened state (mutes/unmutes incoming audio). */
|
|
13839
13934
|
async toggleDeaf() {
|
|
13840
|
-
|
|
13841
|
-
await this.executeMethod(this.id, method, {});
|
|
13935
|
+
await this.executeMethod(toggleDeafMethod(this.deaf), {});
|
|
13842
13936
|
}
|
|
13843
13937
|
/** Toggles the hand-raised state. */
|
|
13844
13938
|
async toggleHandraise() {
|
|
13845
|
-
await this.executeMethod(
|
|
13939
|
+
await this.executeMethod(toggleHandraiseMethod(this.handraised), {});
|
|
13846
13940
|
}
|
|
13847
13941
|
/** Mutes the participant's audio. */
|
|
13848
13942
|
async mute() {
|
|
13849
|
-
await this.executeMethod(
|
|
13943
|
+
await this.executeMethod("call.mute", { channels: ["audio"] });
|
|
13850
13944
|
}
|
|
13851
13945
|
/** Unmutes the participant's audio. */
|
|
13852
13946
|
async unmute() {
|
|
13853
|
-
await this.executeMethod(
|
|
13947
|
+
await this.executeMethod("call.unmute", { channels: ["audio"] });
|
|
13854
13948
|
}
|
|
13855
13949
|
/** Toggles the participant's audio mute state. */
|
|
13856
13950
|
async toggleMute() {
|
|
@@ -13858,11 +13952,11 @@ var Participant = class extends Destroyable {
|
|
|
13858
13952
|
}
|
|
13859
13953
|
/** Mutes the participant's video. */
|
|
13860
13954
|
async muteVideo() {
|
|
13861
|
-
await this.executeMethod(
|
|
13955
|
+
await this.executeMethod("call.mute", { channels: ["video"] });
|
|
13862
13956
|
}
|
|
13863
13957
|
/** Unmutes the participant's video. */
|
|
13864
13958
|
async unmuteVideo() {
|
|
13865
|
-
await this.executeMethod(
|
|
13959
|
+
await this.executeMethod("call.unmute", { channels: ["video"] });
|
|
13866
13960
|
}
|
|
13867
13961
|
/** Toggles the participant's video mute state. */
|
|
13868
13962
|
async toggleMuteVideo() {
|
|
@@ -13870,7 +13964,7 @@ var Participant = class extends Destroyable {
|
|
|
13870
13964
|
}
|
|
13871
13965
|
/** Toggles echo cancellation on the audio input. */
|
|
13872
13966
|
async toggleEchoCancellation() {
|
|
13873
|
-
await this.executeMethod(
|
|
13967
|
+
await this.executeMethod("call.audioflags.set", {
|
|
13874
13968
|
echo_cancellation: !this.echoCancellation,
|
|
13875
13969
|
auto_gain: this.autoGain,
|
|
13876
13970
|
noise_suppression: this.noiseSuppression
|
|
@@ -13878,7 +13972,7 @@ var Participant = class extends Destroyable {
|
|
|
13878
13972
|
}
|
|
13879
13973
|
/** Toggles automatic gain control on the audio input. */
|
|
13880
13974
|
async toggleAudioInputAutoGain() {
|
|
13881
|
-
await this.executeMethod(
|
|
13975
|
+
await this.executeMethod("call.audioflags.set", {
|
|
13882
13976
|
echo_cancellation: this.echoCancellation,
|
|
13883
13977
|
auto_gain: !this.autoGain,
|
|
13884
13978
|
noise_suppression: this.noiseSuppression
|
|
@@ -13886,14 +13980,15 @@ var Participant = class extends Destroyable {
|
|
|
13886
13980
|
}
|
|
13887
13981
|
/** Toggles noise suppression on the audio input. */
|
|
13888
13982
|
async toggleNoiseSuppression() {
|
|
13889
|
-
await this.executeMethod(
|
|
13983
|
+
await this.executeMethod("call.audioflags.set", {
|
|
13890
13984
|
echo_cancellation: this.echoCancellation,
|
|
13891
13985
|
auto_gain: this.autoGain,
|
|
13892
13986
|
noise_suppression: !this.noiseSuppression
|
|
13893
13987
|
});
|
|
13894
13988
|
}
|
|
13989
|
+
/** Toggles low-bitrate mode for this participant's media. */
|
|
13895
13990
|
async toggleLowbitrate() {
|
|
13896
|
-
|
|
13991
|
+
await this.executeMethod("call.lowbitrate.set", { lowbitrate: !this.lowbitrate });
|
|
13897
13992
|
}
|
|
13898
13993
|
/**
|
|
13899
13994
|
* Adjusts the **conference-only** microphone energy gate / sensitivity level
|
|
@@ -13909,7 +14004,7 @@ var Participant = class extends Destroyable {
|
|
|
13909
14004
|
* (integer, larger values are more sensitive).
|
|
13910
14005
|
*/
|
|
13911
14006
|
async setAudioInputSensitivity(value) {
|
|
13912
|
-
await this.executeMethod(
|
|
14007
|
+
await this.executeMethod("call.microphone.sensitivity.set", { sensitivity: value });
|
|
13913
14008
|
}
|
|
13914
14009
|
/**
|
|
13915
14010
|
* Sets the **server-side** microphone volume on this participant's bridged
|
|
@@ -13922,7 +14017,7 @@ var Participant = class extends Destroyable {
|
|
|
13922
14017
|
* @param value - Volume level (0-100).
|
|
13923
14018
|
*/
|
|
13924
14019
|
async setAudioInputVolume(value) {
|
|
13925
|
-
await this.executeMethod(
|
|
14020
|
+
await this.executeMethod("call.microphone.volume.set", { volume: value });
|
|
13926
14021
|
}
|
|
13927
14022
|
/**
|
|
13928
14023
|
* Sets the **server-side** speaker volume on this participant's bridged call
|
|
@@ -13936,28 +14031,31 @@ var Participant = class extends Destroyable {
|
|
|
13936
14031
|
* @param value - Volume level (0-100).
|
|
13937
14032
|
*/
|
|
13938
14033
|
async setAudioOutputVolume(value) {
|
|
13939
|
-
await this.executeMethod(
|
|
14034
|
+
await this.executeMethod("call.speaker.volume.set", { volume: value });
|
|
13940
14035
|
}
|
|
13941
14036
|
/**
|
|
13942
14037
|
* Sets the participant's position in the video layout.
|
|
14038
|
+
*
|
|
14039
|
+
* Requires the `member.position` capability. The gateway requires a
|
|
14040
|
+
* `targets` array of `{ target, position }` entries (issue #19400). A
|
|
14041
|
+
* resolved promise does not guarantee a visible change: the backend silently
|
|
14042
|
+
* returns `200` (no-op) for non-conference targets.
|
|
14043
|
+
*
|
|
13943
14044
|
* @param value - The {@link VideoPosition} to assign (e.g. `'auto'`, `'reserved-0'`).
|
|
13944
14045
|
*/
|
|
13945
14046
|
async setPosition(value) {
|
|
13946
|
-
await this.executeMethod(
|
|
14047
|
+
await this.executeMethod("call.member.position.set", { targets: [{
|
|
14048
|
+
target: this.target,
|
|
14049
|
+
position: value
|
|
14050
|
+
}] });
|
|
13947
14051
|
}
|
|
13948
14052
|
/** Removes this participant from the call. */
|
|
13949
14053
|
async remove() {
|
|
13950
|
-
|
|
13951
|
-
const target = {
|
|
13952
|
-
member_id: this.id,
|
|
13953
|
-
call_id: state.call_id ?? "",
|
|
13954
|
-
node_id: state.node_id ?? ""
|
|
13955
|
-
};
|
|
13956
|
-
await this.executeMethod(target, "call.member.remove", {});
|
|
14054
|
+
await this.executeMethod("call.member.remove", { targets: [this.target] });
|
|
13957
14055
|
}
|
|
13958
14056
|
/** Ends the call for this participant. */
|
|
13959
14057
|
async end() {
|
|
13960
|
-
await this.executeMethod(
|
|
14058
|
+
await this.executeMethod("call.end", {});
|
|
13961
14059
|
}
|
|
13962
14060
|
/**
|
|
13963
14061
|
* Replaces custom metadata for this participant.
|
|
@@ -13977,7 +14075,7 @@ var Participant = class extends Destroyable {
|
|
|
13977
14075
|
}
|
|
13978
14076
|
/** Destroys the participant, releasing all subscriptions and references. */
|
|
13979
14077
|
destroy() {
|
|
13980
|
-
this.
|
|
14078
|
+
this.callExecuteMethod = void 0;
|
|
13981
14079
|
super.destroy();
|
|
13982
14080
|
}
|
|
13983
14081
|
};
|
|
@@ -13989,8 +14087,8 @@ var Participant = class extends Destroyable {
|
|
|
13989
14087
|
*/
|
|
13990
14088
|
var SelfParticipant = class extends Participant {
|
|
13991
14089
|
/** @internal */
|
|
13992
|
-
constructor(id,
|
|
13993
|
-
super(id,
|
|
14090
|
+
constructor(id, callExecuteMethod, vertoManager, deviceController) {
|
|
14091
|
+
super(id, callExecuteMethod, deviceController);
|
|
13994
14092
|
this.vertoManager = vertoManager;
|
|
13995
14093
|
this._studioAudio$ = this.createBehaviorSubject(false);
|
|
13996
14094
|
this.capabilities = new SelfCapabilities();
|
|
@@ -14014,7 +14112,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14014
14112
|
async enableStudioAudio() {
|
|
14015
14113
|
if (this._studioAudio$.value) return;
|
|
14016
14114
|
this._studioAudio$.next(true);
|
|
14017
|
-
await this.executeMethod(
|
|
14115
|
+
await this.executeMethod("call.audioflags.set", {
|
|
14018
14116
|
echo_cancellation: false,
|
|
14019
14117
|
auto_gain: false,
|
|
14020
14118
|
noise_suppression: false
|
|
@@ -14027,18 +14125,27 @@ var SelfParticipant = class extends Participant {
|
|
|
14027
14125
|
async disableStudioAudio() {
|
|
14028
14126
|
if (!this._studioAudio$.value) return;
|
|
14029
14127
|
this._studioAudio$.next(false);
|
|
14030
|
-
await this.executeMethod(
|
|
14128
|
+
await this.executeMethod("call.audioflags.set", {
|
|
14031
14129
|
echo_cancellation: true,
|
|
14032
14130
|
auto_gain: true,
|
|
14033
14131
|
noise_suppression: true
|
|
14034
14132
|
});
|
|
14035
14133
|
}
|
|
14036
|
-
/**
|
|
14134
|
+
/**
|
|
14135
|
+
* Starts sharing the local screen.
|
|
14136
|
+
*
|
|
14137
|
+
* The call is unaffected when acquisition fails.
|
|
14138
|
+
*
|
|
14139
|
+
* @throws The raw `getDisplayMedia` error. A dismissed picker or a
|
|
14140
|
+
* permission denial rejects with a `NotAllowedError` `DOMException` —
|
|
14141
|
+
* inspect `error.name` to tell benign cancels apart from real failures.
|
|
14142
|
+
*/
|
|
14037
14143
|
async startScreenShare() {
|
|
14038
14144
|
try {
|
|
14039
14145
|
await this.vertoManager.addScreenMedia();
|
|
14040
14146
|
} catch (error) {
|
|
14041
|
-
logger$
|
|
14147
|
+
logger$24.error("[Participant.startScreenShare] Screen share error:", error);
|
|
14148
|
+
throw error;
|
|
14042
14149
|
}
|
|
14043
14150
|
}
|
|
14044
14151
|
/** Observable of the current screen share status. */
|
|
@@ -14053,12 +14160,20 @@ var SelfParticipant = class extends Participant {
|
|
|
14053
14160
|
async stopScreenShare() {
|
|
14054
14161
|
return this.vertoManager.removeScreenMedia();
|
|
14055
14162
|
}
|
|
14056
|
-
/**
|
|
14163
|
+
/**
|
|
14164
|
+
* Adds an additional media input device to the call.
|
|
14165
|
+
*
|
|
14166
|
+
* The call is unaffected when acquisition fails.
|
|
14167
|
+
*
|
|
14168
|
+
* @throws The raw `getUserMedia` error (e.g. `NotAllowedError` on
|
|
14169
|
+
* permission denial) — inspect `error.name` to decide how to react.
|
|
14170
|
+
*/
|
|
14057
14171
|
async addAdditionalDevice(options) {
|
|
14058
14172
|
try {
|
|
14059
14173
|
await this.vertoManager.addInputDevice(options);
|
|
14060
14174
|
} catch (error) {
|
|
14061
|
-
logger$
|
|
14175
|
+
logger$24.error("[Participant.addAdditionalDevice] Additional device error:", error);
|
|
14176
|
+
throw error;
|
|
14062
14177
|
}
|
|
14063
14178
|
}
|
|
14064
14179
|
/** Removes an additional media input device by ID. */
|
|
@@ -14120,7 +14235,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14120
14235
|
*/
|
|
14121
14236
|
exitStudioModeIfActive() {
|
|
14122
14237
|
if (this._studioAudio$.value) {
|
|
14123
|
-
logger$
|
|
14238
|
+
logger$24.debug("[SelfParticipant] Exiting studio audio mode due to individual flag toggle");
|
|
14124
14239
|
this._studioAudio$.next(false);
|
|
14125
14240
|
}
|
|
14126
14241
|
}
|
|
@@ -14144,7 +14259,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14144
14259
|
try {
|
|
14145
14260
|
await super.mute();
|
|
14146
14261
|
} catch (error) {
|
|
14147
|
-
logger$
|
|
14262
|
+
logger$24.warn("[Participant.toggleAudioInput] Server Error while muting audio input, proceeding with local toggle anyway", error);
|
|
14148
14263
|
} finally {
|
|
14149
14264
|
this.vertoManager.muteMainAudioInputDevice();
|
|
14150
14265
|
}
|
|
@@ -14154,7 +14269,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14154
14269
|
try {
|
|
14155
14270
|
await super.unmute();
|
|
14156
14271
|
} catch (error) {
|
|
14157
|
-
logger$
|
|
14272
|
+
logger$24.warn("[Participant.toggleAudioInput] Server Error while unmuting audio input, proceeding with local toggle anyway", error);
|
|
14158
14273
|
} finally {
|
|
14159
14274
|
await this.vertoManager.unmuteMainAudioInputDevice();
|
|
14160
14275
|
}
|
|
@@ -14164,7 +14279,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14164
14279
|
try {
|
|
14165
14280
|
await super.muteVideo();
|
|
14166
14281
|
} catch (error) {
|
|
14167
|
-
logger$
|
|
14282
|
+
logger$24.warn("[Participant.toggleVideoInput] Server Error while muting video input, proceeding with local toggle anyway", error);
|
|
14168
14283
|
} finally {
|
|
14169
14284
|
this.vertoManager.muteMainVideoInputDevice();
|
|
14170
14285
|
}
|
|
@@ -14174,7 +14289,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14174
14289
|
try {
|
|
14175
14290
|
await super.unmuteVideo();
|
|
14176
14291
|
} catch (error) {
|
|
14177
|
-
logger$
|
|
14292
|
+
logger$24.warn("[Participant.toggleVideoInput] Server Error while unmuting video input, proceeding with local toggle anyway", error);
|
|
14178
14293
|
} finally {
|
|
14179
14294
|
await this.vertoManager.unmuteMainVideoInputDevice();
|
|
14180
14295
|
}
|
|
@@ -14379,7 +14494,7 @@ function filterAs(predicate, resultPath) {
|
|
|
14379
14494
|
//#endregion
|
|
14380
14495
|
//#region src/operators/throwOnRPCError.ts
|
|
14381
14496
|
var import_cjs$21 = require_cjs();
|
|
14382
|
-
const logger$
|
|
14497
|
+
const logger$23 = getLogger();
|
|
14383
14498
|
/**
|
|
14384
14499
|
* RxJS operator that throws a {@link JSONRPCError} when the RPC response contains an error.
|
|
14385
14500
|
* Passes successful responses through unchanged.
|
|
@@ -14387,14 +14502,14 @@ const logger$22 = getLogger();
|
|
|
14387
14502
|
function throwOnRPCError() {
|
|
14388
14503
|
return (0, import_cjs$21.map)((response) => {
|
|
14389
14504
|
if (response.error) {
|
|
14390
|
-
logger$
|
|
14505
|
+
logger$23.error("[throwOnRPCError] RPC error response:", {
|
|
14391
14506
|
code: response.error.code,
|
|
14392
14507
|
message: response.error.message,
|
|
14393
14508
|
data: response.error.data
|
|
14394
14509
|
});
|
|
14395
14510
|
throw new JSONRPCError(response.error.code, response.error.message, response.error.data);
|
|
14396
14511
|
}
|
|
14397
|
-
logger$
|
|
14512
|
+
logger$23.debug("[throwOnRPCError] RPC successful response:", response);
|
|
14398
14513
|
return response;
|
|
14399
14514
|
});
|
|
14400
14515
|
}
|
|
@@ -14402,7 +14517,7 @@ function throwOnRPCError() {
|
|
|
14402
14517
|
//#endregion
|
|
14403
14518
|
//#region src/managers/CallEventsManager.ts
|
|
14404
14519
|
var import_cjs$20 = require_cjs();
|
|
14405
|
-
const logger$
|
|
14520
|
+
const logger$22 = getLogger();
|
|
14406
14521
|
const initialSessionState = {};
|
|
14407
14522
|
/** @internal */
|
|
14408
14523
|
var CallEventsManager = class extends Destroyable {
|
|
@@ -14506,7 +14621,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14506
14621
|
}
|
|
14507
14622
|
initSubscriptions() {
|
|
14508
14623
|
this.subscribeTo(this.callJoinedEvent$, (callJoinedEvent) => {
|
|
14509
|
-
logger$
|
|
14624
|
+
logger$22.debug("[CallEventsManager] Handling call.joined event for call/session IDs:", {
|
|
14510
14625
|
callId: callJoinedEvent.call_id,
|
|
14511
14626
|
roomSessionId: callJoinedEvent.room_session_id
|
|
14512
14627
|
});
|
|
@@ -14533,19 +14648,19 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14533
14648
|
if (this._self$.value?.capabilities.setLayout) this.updateLayouts();
|
|
14534
14649
|
});
|
|
14535
14650
|
this.subscribeTo(this.memberUpdates$, (member) => {
|
|
14536
|
-
logger$
|
|
14651
|
+
logger$22.debug("[CallEventsManager] Handling member update event for member ID:", member);
|
|
14537
14652
|
this.upsertParticipant(member);
|
|
14538
14653
|
});
|
|
14539
14654
|
this.subscribeTo(this.webRtcCallSession.memberLeft$, (memberLeftEvent) => {
|
|
14540
|
-
logger$
|
|
14655
|
+
logger$22.debug("[CallEventsManager] Handling member.left event for member ID:", memberLeftEvent.member.member_id);
|
|
14541
14656
|
const participants = { ...this._participants$.value };
|
|
14542
14657
|
if (memberLeftEvent.member.member_id in participants) {
|
|
14543
14658
|
delete participants[memberLeftEvent.member.member_id];
|
|
14544
14659
|
this._participants$.next(participants);
|
|
14545
|
-
} else logger$
|
|
14660
|
+
} else logger$22.warn(`[CallEventsManager] Received member.left event for unknown member ID: ${memberLeftEvent.member.member_id}`);
|
|
14546
14661
|
});
|
|
14547
14662
|
this.subscribeTo(this.webRtcCallSession.callUpdated$, (callUpdatedEvent) => {
|
|
14548
|
-
logger$
|
|
14663
|
+
logger$22.debug("[CallEventsManager] Handling call.updated event:", callUpdatedEvent);
|
|
14549
14664
|
const roomSession = callUpdatedEvent.room_session;
|
|
14550
14665
|
this._sessionState$.next({
|
|
14551
14666
|
...this._sessionState$.value,
|
|
@@ -14560,7 +14675,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14560
14675
|
});
|
|
14561
14676
|
});
|
|
14562
14677
|
this.subscribeTo(this.layoutChangedEvent$, (layoutChangedEvent) => {
|
|
14563
|
-
logger$
|
|
14678
|
+
logger$22.debug("[CallEventsManager] Handling layout.changed event:", layoutChangedEvent);
|
|
14564
14679
|
this._sessionState$.next({
|
|
14565
14680
|
...this._sessionState$.value,
|
|
14566
14681
|
layout_name: layoutChangedEvent.id,
|
|
@@ -14570,10 +14685,10 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14570
14685
|
});
|
|
14571
14686
|
}
|
|
14572
14687
|
updateParticipantPositions(layoutChangedEvent) {
|
|
14573
|
-
if (Object.keys(this._participants$.value).length > 0 && !layoutChangedEvent.layers.some((layer) => !!layer.member_id)) logger$
|
|
14688
|
+
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.");
|
|
14574
14689
|
layoutChangedEvent.layers.filter((layer) => !!layer.member_id).filter((layer) => {
|
|
14575
14690
|
if (!(layer.member_id in this._participants$.value)) {
|
|
14576
|
-
logger$
|
|
14691
|
+
logger$22.warn(`[CallEventsManager] Skipping layout layer for unknown member_id: ${layer.member_id}`);
|
|
14577
14692
|
return false;
|
|
14578
14693
|
}
|
|
14579
14694
|
return true;
|
|
@@ -14596,7 +14711,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14596
14711
|
layouts: response.result.layouts
|
|
14597
14712
|
});
|
|
14598
14713
|
}).catch((error) => {
|
|
14599
|
-
logger$
|
|
14714
|
+
logger$22.error("[CallEventsManager] Error fetching layouts:", error);
|
|
14600
14715
|
});
|
|
14601
14716
|
}
|
|
14602
14717
|
updateParticipants(members) {
|
|
@@ -14612,7 +14727,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14612
14727
|
}
|
|
14613
14728
|
const participant = this._participants$.value[member.member_id];
|
|
14614
14729
|
const oldValue = participant.value;
|
|
14615
|
-
logger$
|
|
14730
|
+
logger$22.debug("[CallEventsManager] Updating participant:", member.member_id, {
|
|
14616
14731
|
oldValue,
|
|
14617
14732
|
newValue: member
|
|
14618
14733
|
});
|
|
@@ -14625,17 +14740,17 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14625
14740
|
}
|
|
14626
14741
|
get callJoinedEvent$() {
|
|
14627
14742
|
return this.cachedObservable("callJoinedEvent$", () => this.webRtcCallSession.callEvent$.pipe((0, import_cjs$20.filter)(isCallJoinedPayload), (0, import_cjs$20.tap)((event) => {
|
|
14628
|
-
logger$
|
|
14743
|
+
logger$22.debug("[CallEventsManager] Call joined event:", event);
|
|
14629
14744
|
})));
|
|
14630
14745
|
}
|
|
14631
14746
|
get layoutChangedEvent$() {
|
|
14632
14747
|
return this.cachedObservable("layoutChangedEvent$", () => this.webRtcCallSession.callEvent$.pipe(filterAs(isLayoutChangedPayload, "layout"), (0, import_cjs$20.tap)((event) => {
|
|
14633
|
-
logger$
|
|
14748
|
+
logger$22.debug("[CallEventsManager] Layout changed event:", event);
|
|
14634
14749
|
})));
|
|
14635
14750
|
}
|
|
14636
14751
|
get memberUpdates$() {
|
|
14637
14752
|
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) => {
|
|
14638
|
-
logger$
|
|
14753
|
+
logger$22.debug("[CallEventsManager] Member update event:", event);
|
|
14639
14754
|
})));
|
|
14640
14755
|
}
|
|
14641
14756
|
destroy() {
|
|
@@ -14892,7 +15007,7 @@ function appendStereoParams(fmtpLine, maxBitrate) {
|
|
|
14892
15007
|
//#endregion
|
|
14893
15008
|
//#region src/controllers/ICEGatheringController.ts
|
|
14894
15009
|
var import_cjs$19 = require_cjs();
|
|
14895
|
-
const logger$
|
|
15010
|
+
const logger$21 = getLogger();
|
|
14896
15011
|
var ICEGatheringController = class extends Destroyable {
|
|
14897
15012
|
constructor(peerConnection, peerConnectionControllerNegotiating$, options = {}) {
|
|
14898
15013
|
super();
|
|
@@ -14900,23 +15015,23 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14900
15015
|
this.peerConnectionControllerNegotiating$ = peerConnectionControllerNegotiating$;
|
|
14901
15016
|
this.onicegatheringstatechangeHandler = () => {
|
|
14902
15017
|
const { iceGatheringState } = this.peerConnection;
|
|
14903
|
-
logger$
|
|
15018
|
+
logger$21.debug(`[ICEGatheringController] ICE gathering state changed to: ${iceGatheringState}`);
|
|
14904
15019
|
if (iceGatheringState === "gathering") this._iceCandidatesState.next({
|
|
14905
15020
|
state: "gathering",
|
|
14906
15021
|
validSDP: false
|
|
14907
15022
|
});
|
|
14908
15023
|
};
|
|
14909
15024
|
this.onicecandidateHandler = (event) => {
|
|
14910
|
-
logger$
|
|
15025
|
+
logger$21.debug("[ICEGatheringController] ICE candidate event received:", event.candidate);
|
|
14911
15026
|
this.removeTimer("iceCandidateTimer");
|
|
14912
15027
|
if (event.candidate) this.iceCandidateTimer = setTimeout(() => {
|
|
14913
15028
|
if (this.peerConnection.iceGatheringState !== "complete") {
|
|
14914
|
-
logger$
|
|
15029
|
+
logger$21.warn("[ICEGatheringController] ICE candidate timeout, using current SDP");
|
|
14915
15030
|
this.handleICECandidateTimeout();
|
|
14916
15031
|
}
|
|
14917
15032
|
}, this.iceCandidateTimeout);
|
|
14918
15033
|
else {
|
|
14919
|
-
logger$
|
|
15034
|
+
logger$21.debug("[ICEGatheringController] ICE gathering completed: null candidate received");
|
|
14920
15035
|
this.removeTimer("iceGatheringTimer");
|
|
14921
15036
|
this.handleICEGatheringComplete();
|
|
14922
15037
|
}
|
|
@@ -14934,7 +15049,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14934
15049
|
this.setupEventListeners();
|
|
14935
15050
|
this.iceGatheringTimer = setTimeout(() => {
|
|
14936
15051
|
if (this.peerConnection.iceGatheringState !== "complete") {
|
|
14937
|
-
logger$
|
|
15052
|
+
logger$21.warn("[ICEGatheringController] ICE gathering timeout, using current SDP");
|
|
14938
15053
|
this.handleICEGatheringTimeout();
|
|
14939
15054
|
}
|
|
14940
15055
|
}, this.iceGatheringTimeout);
|
|
@@ -14961,9 +15076,9 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14961
15076
|
this.relayOnly = value;
|
|
14962
15077
|
}
|
|
14963
15078
|
handleICEGatheringComplete() {
|
|
14964
|
-
logger$
|
|
14965
|
-
logger$
|
|
14966
|
-
logger$
|
|
15079
|
+
logger$21.debug("[ICEGatheringController] Handling ICE gathering complete");
|
|
15080
|
+
logger$21.debug(`[ICEGatheringController] Checking ICE gathering state: ${this.peerConnection.iceGatheringState}`);
|
|
15081
|
+
logger$21.debug("[ICEGatheringController] ICE gathering complete");
|
|
14967
15082
|
this._iceCandidatesState.next({
|
|
14968
15083
|
state: "complete",
|
|
14969
15084
|
validSDP: this.hasValidLocalDescriptionSDP
|
|
@@ -14979,21 +15094,21 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14979
15094
|
this.removeTimer("iceGatheringTimer");
|
|
14980
15095
|
const validSDP = this.hasValidLocalDescriptionSDP;
|
|
14981
15096
|
if (validSDP) {
|
|
14982
|
-
logger$
|
|
15097
|
+
logger$21.debug("[ICEGatheringController] Local SDP is valid");
|
|
14983
15098
|
this._iceCandidatesState.next({
|
|
14984
15099
|
state: "timeout",
|
|
14985
15100
|
validSDP
|
|
14986
15101
|
});
|
|
14987
15102
|
this.stopGathering();
|
|
14988
|
-
} else logger$
|
|
15103
|
+
} else logger$21.debug("### ICE gathering timeout\n", this.peerConnection.localDescription?.sdp);
|
|
14989
15104
|
}
|
|
14990
15105
|
handleICECandidateTimeout() {
|
|
14991
15106
|
if (this.iceCandidateTimer) this.removeTimer("iceCandidateTimer");
|
|
14992
|
-
logger$
|
|
15107
|
+
logger$21.warn("[ICEGatheringController] ICE candidate timeout");
|
|
14993
15108
|
const validSDP = this.hasValidLocalDescriptionSDP;
|
|
14994
15109
|
if (!validSDP && !this.relayOnly) this.restartICEGatheringWithRelayOnly();
|
|
14995
15110
|
else {
|
|
14996
|
-
logger$
|
|
15111
|
+
logger$21.debug("[ICEGatheringController] Using current SDP due to ICE candidate timeout");
|
|
14997
15112
|
this._iceCandidatesState.next({
|
|
14998
15113
|
state: "timeout",
|
|
14999
15114
|
validSDP
|
|
@@ -15002,7 +15117,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
15002
15117
|
}
|
|
15003
15118
|
}
|
|
15004
15119
|
restartICEGatheringWithRelayOnly() {
|
|
15005
|
-
logger$
|
|
15120
|
+
logger$21.debug("[ICEGatheringController] Restarting ICE gathering with relay-only candidates");
|
|
15006
15121
|
this.relayOnly = true;
|
|
15007
15122
|
this.peerConnection.setConfiguration({
|
|
15008
15123
|
...this.peerConnection.getConfiguration(),
|
|
@@ -15017,7 +15132,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
15017
15132
|
}
|
|
15018
15133
|
}
|
|
15019
15134
|
clearAllTimers() {
|
|
15020
|
-
logger$
|
|
15135
|
+
logger$21.debug("[ICEGatheringController] Clearing all timers");
|
|
15021
15136
|
this.removeTimer("iceGatheringTimer");
|
|
15022
15137
|
this.removeTimer("iceCandidateTimer");
|
|
15023
15138
|
}
|
|
@@ -15026,7 +15141,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
15026
15141
|
this.peerConnection.removeEventListener("icecandidate", this.onicecandidateHandler);
|
|
15027
15142
|
}
|
|
15028
15143
|
destroy() {
|
|
15029
|
-
logger$
|
|
15144
|
+
logger$21.debug("[ICEGatheringController] Destroying ICEGatheringController");
|
|
15030
15145
|
this.clearAllTimers();
|
|
15031
15146
|
this.removeEventListeners();
|
|
15032
15147
|
super.destroy();
|
|
@@ -15036,7 +15151,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
15036
15151
|
//#endregion
|
|
15037
15152
|
//#region src/controllers/LocalAudioPipeline.ts
|
|
15038
15153
|
var import_cjs$18 = require_cjs();
|
|
15039
|
-
const logger$
|
|
15154
|
+
const logger$20 = getLogger();
|
|
15040
15155
|
/**
|
|
15041
15156
|
* Web Audio pipeline for the local microphone stream.
|
|
15042
15157
|
*
|
|
@@ -15148,7 +15263,7 @@ var LocalAudioPipeline = class extends Destroyable {
|
|
|
15148
15263
|
try {
|
|
15149
15264
|
this._inputSource.disconnect();
|
|
15150
15265
|
} catch (error) {
|
|
15151
|
-
logger$
|
|
15266
|
+
logger$20.debug("[LocalAudioPipeline] input disconnect warning:", error);
|
|
15152
15267
|
}
|
|
15153
15268
|
this._inputSource = null;
|
|
15154
15269
|
}
|
|
@@ -15158,7 +15273,7 @@ var LocalAudioPipeline = class extends Destroyable {
|
|
|
15158
15273
|
this._inputSource = this._audioContext.createMediaStreamSource(this._inputStream);
|
|
15159
15274
|
this._inputSource.connect(this._gainNode);
|
|
15160
15275
|
if (this._audioContext.state === "suspended") this._audioContext.resume().catch((error) => {
|
|
15161
|
-
logger$
|
|
15276
|
+
logger$20.warn("[LocalAudioPipeline] AudioContext resume failed:", error);
|
|
15162
15277
|
});
|
|
15163
15278
|
}
|
|
15164
15279
|
destroy() {
|
|
@@ -15173,7 +15288,7 @@ var LocalAudioPipeline = class extends Destroyable {
|
|
|
15173
15288
|
this._analyser.disconnect();
|
|
15174
15289
|
} catch {}
|
|
15175
15290
|
this._audioContext.close().catch((error) => {
|
|
15176
|
-
logger$
|
|
15291
|
+
logger$20.debug("[LocalAudioPipeline] audio context close warning:", error);
|
|
15177
15292
|
});
|
|
15178
15293
|
super.destroy();
|
|
15179
15294
|
}
|
|
@@ -15200,7 +15315,7 @@ var LocalAudioPipeline = class extends Destroyable {
|
|
|
15200
15315
|
//#endregion
|
|
15201
15316
|
//#region src/controllers/LocalStreamController.ts
|
|
15202
15317
|
var import_cjs$17 = require_cjs();
|
|
15203
|
-
const logger$
|
|
15318
|
+
const logger$19 = getLogger();
|
|
15204
15319
|
var LocalStreamController = class extends Destroyable {
|
|
15205
15320
|
constructor(options) {
|
|
15206
15321
|
super();
|
|
@@ -15238,28 +15353,30 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15238
15353
|
* Build the local media stream based on the provided options.
|
|
15239
15354
|
*/
|
|
15240
15355
|
async buildLocalStream() {
|
|
15241
|
-
logger$
|
|
15356
|
+
logger$19.debug("[LocalStreamController] Building local media stream.");
|
|
15242
15357
|
let stream;
|
|
15243
15358
|
if (this.options.inputAudioStream ?? this.options.inputVideoStream) {
|
|
15244
15359
|
const tracks = [...this.options.inputAudioStream?.getTracks() ?? [], ...this.options.inputVideoStream?.getTracks() ?? []];
|
|
15245
15360
|
stream = new MediaStream(tracks);
|
|
15246
15361
|
} else if (this.options.propose === "screenshare") {
|
|
15247
|
-
logger$
|
|
15362
|
+
logger$19.debug("[LocalStreamController] Requesting display media for screen sharing with audio:", Boolean(this.options.inputAudioDeviceConstraints));
|
|
15248
15363
|
stream = await this.options.getDisplayMedia({
|
|
15249
15364
|
video: true,
|
|
15250
15365
|
audio: Boolean(this.options.inputAudioDeviceConstraints)
|
|
15251
15366
|
});
|
|
15252
|
-
logger$
|
|
15367
|
+
logger$19.debug("[LocalStreamController] Screen share media obtained:", stream);
|
|
15253
15368
|
} else {
|
|
15254
15369
|
const constraints = {
|
|
15255
15370
|
audio: this.options.inputAudioDeviceConstraints,
|
|
15256
15371
|
video: this.options.inputVideoDeviceConstraints
|
|
15257
15372
|
};
|
|
15258
|
-
logger$
|
|
15373
|
+
logger$19.debug("[LocalStreamController] Requesting user media with constraints:", constraints);
|
|
15259
15374
|
stream = await this.options.getUserMedia(constraints);
|
|
15260
|
-
logger$
|
|
15375
|
+
logger$19.debug("[LocalStreamController] User media obtained:", stream);
|
|
15261
15376
|
}
|
|
15262
15377
|
this._localStream$.next(stream);
|
|
15378
|
+
this._localAudioTracks$.next(stream.getAudioTracks());
|
|
15379
|
+
this._localVideoTracks$.next(stream.getVideoTracks());
|
|
15263
15380
|
return stream;
|
|
15264
15381
|
}
|
|
15265
15382
|
/**
|
|
@@ -15274,7 +15391,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15274
15391
|
this._localStream$.next(localStream);
|
|
15275
15392
|
if (track.kind === "video") this._localVideoTracks$.next(localStream.getVideoTracks());
|
|
15276
15393
|
else this._localAudioTracks$.next(localStream.getAudioTracks());
|
|
15277
|
-
logger$
|
|
15394
|
+
logger$19.debug(`[LocalStreamController] ${track.kind} track added:`, track.id);
|
|
15278
15395
|
return localStream;
|
|
15279
15396
|
}
|
|
15280
15397
|
/**
|
|
@@ -15286,7 +15403,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15286
15403
|
const stream = this._localStream$.value;
|
|
15287
15404
|
const track = stream?.getTracks().find((t) => t.id === trackId);
|
|
15288
15405
|
if (!track) {
|
|
15289
|
-
logger$
|
|
15406
|
+
logger$19.debug(`[LocalStreamController] track not found: ${trackId}`);
|
|
15290
15407
|
return;
|
|
15291
15408
|
}
|
|
15292
15409
|
track.removeEventListener("ended", this.mediaTrackEndedHandler);
|
|
@@ -15295,7 +15412,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15295
15412
|
this._localStream$.next(stream);
|
|
15296
15413
|
if (track.kind === "video") this._localVideoTracks$.next(stream?.getVideoTracks() ?? []);
|
|
15297
15414
|
else this._localAudioTracks$.next(stream?.getAudioTracks() ?? []);
|
|
15298
|
-
logger$
|
|
15415
|
+
logger$19.debug(`[LocalStreamController] ${track.kind} track removed:`, trackId);
|
|
15299
15416
|
return track;
|
|
15300
15417
|
}
|
|
15301
15418
|
/**
|
|
@@ -15330,7 +15447,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15330
15447
|
*/
|
|
15331
15448
|
stopAllTracks() {
|
|
15332
15449
|
this._localStream$.value?.getTracks().forEach((track) => {
|
|
15333
|
-
logger$
|
|
15450
|
+
logger$19.debug(`[LocalStreamController] Stopping local track: ${track.kind}`);
|
|
15334
15451
|
track.removeEventListener("ended", this.mediaTrackEndedHandler);
|
|
15335
15452
|
track.stop();
|
|
15336
15453
|
});
|
|
@@ -15346,7 +15463,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15346
15463
|
|
|
15347
15464
|
//#endregion
|
|
15348
15465
|
//#region src/controllers/TransceiverController.ts
|
|
15349
|
-
const logger$
|
|
15466
|
+
const logger$18 = getLogger();
|
|
15350
15467
|
const getDirection = (send, recv) => {
|
|
15351
15468
|
if (send && recv) return "sendrecv";
|
|
15352
15469
|
else if (send && !recv) return "sendonly";
|
|
@@ -15427,6 +15544,12 @@ var TransceiverController = class extends Destroyable {
|
|
|
15427
15544
|
scaleResolutionDownBy: Number(rid) * 6 || 1
|
|
15428
15545
|
}));
|
|
15429
15546
|
}
|
|
15547
|
+
/**
|
|
15548
|
+
* Resolve the current MediaTrackConstraints for an input kind, normalising
|
|
15549
|
+
* boolean shorthand to an empty object. Public so the surrounding
|
|
15550
|
+
* RTCPeerConnectionController can drive its own pipeline-aware getUserMedia
|
|
15551
|
+
* call with the same effective constraints the transceiver would have used.
|
|
15552
|
+
*/
|
|
15430
15553
|
getConstraintsFor(kind) {
|
|
15431
15554
|
const constraints = kind === "audio" ? this.inputAudioDeviceConstraints : this.inputVideoDeviceConstraints;
|
|
15432
15555
|
return typeof constraints === "boolean" ? {} : constraints;
|
|
@@ -15448,7 +15571,7 @@ var TransceiverController = class extends Destroyable {
|
|
|
15448
15571
|
sendEncodings: isAudio ? void 0 : this.sendEncodings,
|
|
15449
15572
|
streams: direction === "recvonly" ? void 0 : [localStream]
|
|
15450
15573
|
};
|
|
15451
|
-
logger$
|
|
15574
|
+
logger$18.debug(`[TransceiverController] Setting up transceiver sender for local ${track.kind} track:`, {
|
|
15452
15575
|
transceiver,
|
|
15453
15576
|
transceiverParams
|
|
15454
15577
|
});
|
|
@@ -15456,11 +15579,11 @@ var TransceiverController = class extends Destroyable {
|
|
|
15456
15579
|
await transceiver.sender.replaceTrack(track);
|
|
15457
15580
|
transceiver.direction = transceiverParams.direction;
|
|
15458
15581
|
if (transceiverParams.streams?.some((stream) => Boolean(stream))) {
|
|
15459
|
-
logger$
|
|
15582
|
+
logger$18.debug(`[TransceiverController] Setting streams for transceiver sender for local ${track.kind} track:`, transceiverParams.streams);
|
|
15460
15583
|
transceiver.sender.setStreams(...transceiverParams.streams);
|
|
15461
15584
|
}
|
|
15462
15585
|
} else {
|
|
15463
|
-
logger$
|
|
15586
|
+
logger$18.debug(`[TransceiverController] Adding new transceiver for local ${track.kind} track:`, track.id);
|
|
15464
15587
|
this.peerConnection.addTransceiver(track, transceiverParams);
|
|
15465
15588
|
}
|
|
15466
15589
|
}
|
|
@@ -15474,13 +15597,13 @@ var TransceiverController = class extends Destroyable {
|
|
|
15474
15597
|
if (options.updateTransceiverDirection) transceiver.direction = "inactive";
|
|
15475
15598
|
}
|
|
15476
15599
|
} catch (error) {
|
|
15477
|
-
logger$
|
|
15600
|
+
logger$18.error("[TransceiverController] stopTrackSender error", kind, error);
|
|
15478
15601
|
this.options.onError?.(new MediaTrackError("stopTrackSender", kind, error));
|
|
15479
15602
|
}
|
|
15480
15603
|
}
|
|
15481
15604
|
async restoreTrackSender(kind) {
|
|
15482
15605
|
try {
|
|
15483
|
-
logger$
|
|
15606
|
+
logger$18.debug("[TransceiverController] restoreTrackSender called", kind);
|
|
15484
15607
|
const constraints = {};
|
|
15485
15608
|
const transceivers = this.transceiverByKind(kind);
|
|
15486
15609
|
for (const transceiver of transceivers) {
|
|
@@ -15490,23 +15613,23 @@ var TransceiverController = class extends Destroyable {
|
|
|
15490
15613
|
if (trackKind === "audio" || trackKind === "video") constraints[trackKind] = this.getConstraintsFor(trackKind);
|
|
15491
15614
|
}
|
|
15492
15615
|
}
|
|
15493
|
-
logger$
|
|
15616
|
+
logger$18.debug("[TransceiverController] restoreTrackSender constraints:", constraints);
|
|
15494
15617
|
if (Object.keys(constraints).length === 0) {
|
|
15495
|
-
logger$
|
|
15618
|
+
logger$18.warn("[TransceiverController] restoreTrackSender: no tracks need restoration", kind);
|
|
15496
15619
|
return;
|
|
15497
15620
|
}
|
|
15498
15621
|
const newTracks = (await this.options.getUserMedia(constraints)).getTracks();
|
|
15499
|
-
logger$
|
|
15622
|
+
logger$18.debug("[TransceiverController] restoreTrackSender new tracks:", newTracks);
|
|
15500
15623
|
for (const newTrack of newTracks) {
|
|
15501
15624
|
this.options.localStreamController.addTrack(newTrack);
|
|
15502
15625
|
const trackKind = newTrack.kind;
|
|
15503
15626
|
const transceiverOfKind = this.transceiverByKind(trackKind)[0];
|
|
15504
15627
|
transceiverOfKind.direction = trackKind === "audio" ? this.audioDirection : this.videoDirection;
|
|
15505
|
-
logger$
|
|
15628
|
+
logger$18.debug("[TransceiverController] restoreTrackSender setting direction for", trackKind, transceiverOfKind.direction);
|
|
15506
15629
|
await transceiverOfKind.sender.replaceTrack(newTrack);
|
|
15507
15630
|
}
|
|
15508
15631
|
} catch (error) {
|
|
15509
|
-
logger$
|
|
15632
|
+
logger$18.error("[TransceiverController] restoreTrackSender error", kind, error);
|
|
15510
15633
|
this.options.onError?.(new MediaTrackError("restoreTrackSender", kind, error));
|
|
15511
15634
|
}
|
|
15512
15635
|
}
|
|
@@ -15547,14 +15670,14 @@ var TransceiverController = class extends Destroyable {
|
|
|
15547
15670
|
};
|
|
15548
15671
|
try {
|
|
15549
15672
|
await track.applyConstraints(constraintsToApply);
|
|
15550
|
-
logger$
|
|
15551
|
-
logger$
|
|
15673
|
+
logger$18.debug(`[TransceiverController] Updated ${kind} sender constraints:`, constraintsToApply);
|
|
15674
|
+
logger$18.debug(`[TransceiverController] Updated ${kind} sender constraints:`, track.getConstraints());
|
|
15552
15675
|
} catch (error) {
|
|
15553
|
-
logger$
|
|
15676
|
+
logger$18.warn(`[TransceiverController] applyConstraints failed for ${kind} track ${track.id}, attempting track replacement fallback:`, error);
|
|
15554
15677
|
try {
|
|
15555
15678
|
await this.replaceTrackFallback(sender, track, kind, constraintsToApply);
|
|
15556
15679
|
} catch (fallbackError) {
|
|
15557
|
-
logger$
|
|
15680
|
+
logger$18.warn(`[TransceiverController] Track replacement fallback also failed for ${kind} track:`, fallbackError);
|
|
15558
15681
|
this.options.onError?.(new MediaTrackError("updateSendersConstraints", kind, fallbackError));
|
|
15559
15682
|
}
|
|
15560
15683
|
}
|
|
@@ -15582,7 +15705,7 @@ var TransceiverController = class extends Destroyable {
|
|
|
15582
15705
|
if (!newTrack) throw new MediaTrackError("replaceTrackFallback", kind, /* @__PURE__ */ new Error("getUserMedia returned no track of the requested kind"));
|
|
15583
15706
|
await sender.replaceTrack(newTrack);
|
|
15584
15707
|
this.options.localStreamController.addTrack(newTrack);
|
|
15585
|
-
logger$
|
|
15708
|
+
logger$18.debug(`[TransceiverController] Track replacement fallback succeeded for ${kind}. New track: ${newTrack.id}`);
|
|
15586
15709
|
}
|
|
15587
15710
|
getMediaDirections() {
|
|
15588
15711
|
if (this.peerConnection.connectionState === "connected") return this.peerConnection.getTransceivers().reduce((acc, transceiver) => {
|
|
@@ -15613,7 +15736,7 @@ var TransceiverController = class extends Destroyable {
|
|
|
15613
15736
|
//#endregion
|
|
15614
15737
|
//#region src/controllers/RTCPeerConnectionController.ts
|
|
15615
15738
|
var import_cjs$16 = require_cjs();
|
|
15616
|
-
const logger$
|
|
15739
|
+
const logger$17 = getLogger();
|
|
15617
15740
|
var RTCPeerConnectionController = class extends Destroyable {
|
|
15618
15741
|
constructor(options = {}, remoteSessionDescription, deviceController) {
|
|
15619
15742
|
super();
|
|
@@ -15629,43 +15752,43 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15629
15752
|
this.oniceconnectionstatechangeHandler = () => {
|
|
15630
15753
|
if (this.peerConnection) {
|
|
15631
15754
|
const { iceConnectionState } = this.peerConnection;
|
|
15632
|
-
logger$
|
|
15755
|
+
logger$17.debug(`[RTCPeerConnectionController] ICE connection state changed to: ${iceConnectionState}`);
|
|
15633
15756
|
this._iceConnectionState$.next(this.peerConnection.iceConnectionState);
|
|
15634
15757
|
}
|
|
15635
15758
|
};
|
|
15636
15759
|
this.onconnectionstatechangeHandler = () => {
|
|
15637
15760
|
if (this.peerConnection) {
|
|
15638
15761
|
const { connectionState } = this.peerConnection;
|
|
15639
|
-
logger$
|
|
15762
|
+
logger$17.debug(`[RTCPeerConnectionController] Connection state changed to: ${connectionState}`);
|
|
15640
15763
|
if (connectionState === "connected") this.removeConnectionTimer();
|
|
15641
15764
|
this._connectionState$.next(this.peerConnection.connectionState);
|
|
15642
15765
|
}
|
|
15643
15766
|
};
|
|
15644
15767
|
this.onsignalingstatechangeHandler = () => {
|
|
15645
|
-
logger$
|
|
15768
|
+
logger$17.debug(`[RTCPeerConnectionController] Signaling state changed to: ${this.peerConnection?.signalingState}`);
|
|
15646
15769
|
};
|
|
15647
15770
|
this.onicegatheringstatechangeHandler = () => {
|
|
15648
15771
|
if (this.peerConnection) this._iceGatheringState$.next(this.peerConnection.iceGatheringState);
|
|
15649
15772
|
};
|
|
15650
15773
|
this.onnegotiationneededHandler = (event) => {
|
|
15651
|
-
logger$
|
|
15774
|
+
logger$17.debug("[RTCPeerConnectionController] Negotiation needed event received.", event);
|
|
15652
15775
|
this.negotiationNeeded$.next();
|
|
15653
15776
|
};
|
|
15654
15777
|
this.updateSelectedInputDevice = async (kind, deviceInfo) => {
|
|
15655
15778
|
try {
|
|
15656
15779
|
const { localStream } = this;
|
|
15657
15780
|
if (!localStream) {
|
|
15658
|
-
logger$
|
|
15781
|
+
logger$17.warn("[RTCPeerConnectionController] No local stream available to update input device.");
|
|
15659
15782
|
return;
|
|
15660
15783
|
}
|
|
15661
|
-
logger$
|
|
15784
|
+
logger$17.debug(`[RTCPeerConnectionController] Updating selected ${kind} input device:`, localStream.getTracks());
|
|
15662
15785
|
const track = localStream.getTracks().find((track$1) => track$1.kind === kind);
|
|
15663
15786
|
if (track) {
|
|
15664
15787
|
this.transceiverController?.stopTrackSender(kind);
|
|
15665
15788
|
this.localStreamController.removeTrack(track.id);
|
|
15666
|
-
logger$
|
|
15789
|
+
logger$17.debug(`[RTCPeerConnectionController] Stopped existing ${kind} track: ${track.id}`, localStream.getTracks());
|
|
15667
15790
|
if (!deviceInfo) {
|
|
15668
|
-
logger$
|
|
15791
|
+
logger$17.debug(`[RTCPeerConnectionController] ${kind} input device selected: none`);
|
|
15669
15792
|
return;
|
|
15670
15793
|
}
|
|
15671
15794
|
const streamTrack = (await this.getUserMedia({ [kind]: {
|
|
@@ -15673,16 +15796,16 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15673
15796
|
...this.deviceController.deviceInfoToConstraints(deviceInfo)
|
|
15674
15797
|
} })).getTracks().find((t) => t.kind === kind);
|
|
15675
15798
|
if (streamTrack) {
|
|
15676
|
-
logger$
|
|
15799
|
+
logger$17.debug(`[RTCPeerConnectionController] Adding new ${kind} track: ${streamTrack.id}`);
|
|
15677
15800
|
this.localStreamController.addTrack(streamTrack);
|
|
15678
15801
|
await this.transceiverController?.replaceSenderTrack(kind, streamTrack);
|
|
15679
|
-
logger$
|
|
15802
|
+
logger$17.debug(`[RTCPeerConnectionController] Added new ${kind} track: ${streamTrack.id}`, this.localStream?.getTracks());
|
|
15680
15803
|
}
|
|
15681
15804
|
}
|
|
15682
|
-
logger$
|
|
15805
|
+
logger$17.debug(`[RTCPeerConnectionController] ${kind} input device selected:`, deviceInfo?.label);
|
|
15683
15806
|
} catch (error) {
|
|
15684
|
-
logger$
|
|
15685
|
-
this._errors$.next(
|
|
15807
|
+
logger$17.error(`[RTCPeerConnectionController] Failed to select ${kind} input device:`, error);
|
|
15808
|
+
this._errors$.next(new MediaTrackError("updateSelectedInputDevice", kind, error));
|
|
15686
15809
|
throw error;
|
|
15687
15810
|
}
|
|
15688
15811
|
};
|
|
@@ -15753,7 +15876,19 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15753
15876
|
return this._memberId;
|
|
15754
15877
|
}
|
|
15755
15878
|
stopTrackSender(kind, options = { updateTransceiverDirection: false }) {
|
|
15756
|
-
|
|
15879
|
+
const audioCovered = kind === "audio" || kind === "both";
|
|
15880
|
+
if (audioCovered && this._localAudioPipeline) this.stopRawAudioInputForPipeline();
|
|
15881
|
+
if (!audioCovered) this.transceiverController?.stopTrackSender(kind, options);
|
|
15882
|
+
else if (kind === "both") this.transceiverController?.stopTrackSender("video", options);
|
|
15883
|
+
else if (!this._localAudioPipeline) this.transceiverController?.stopTrackSender(kind, options);
|
|
15884
|
+
}
|
|
15885
|
+
stopRawAudioInputForPipeline() {
|
|
15886
|
+
const rawTracks = this.localStreamController.localAudioTracks;
|
|
15887
|
+
for (const track of rawTracks) if (track.readyState === "live") {
|
|
15888
|
+
track.stop();
|
|
15889
|
+
this.localStreamController.removeTrack(track.id);
|
|
15890
|
+
}
|
|
15891
|
+
this._localAudioPipeline?.setInputTrack(null);
|
|
15757
15892
|
}
|
|
15758
15893
|
get isNegotiating$() {
|
|
15759
15894
|
return this._isNegotiating$.asObservable();
|
|
@@ -15892,7 +16027,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15892
16027
|
case "main":
|
|
15893
16028
|
default: return {
|
|
15894
16029
|
...options,
|
|
15895
|
-
offerToReceiveAudio: true,
|
|
16030
|
+
offerToReceiveAudio: this.options.receiveAudio ?? true,
|
|
15896
16031
|
offerToReceiveVideo: this.options.receiveVideo ?? Boolean(this.inputVideoDeviceConstraints)
|
|
15897
16032
|
};
|
|
15898
16033
|
}
|
|
@@ -15919,15 +16054,15 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15919
16054
|
this.setupPeerConnection();
|
|
15920
16055
|
this.subscribeTo(this.negotiationNeeded$.pipe((0, import_cjs$16.auditTime)(0), (0, import_cjs$16.exhaustMap)(async () => this.startNegotiation())), {
|
|
15921
16056
|
next: () => {
|
|
15922
|
-
logger$
|
|
16057
|
+
logger$17.debug("[RTCPeerConnectionController] Start Negotiation completed successfully");
|
|
15923
16058
|
},
|
|
15924
16059
|
error: (error) => {
|
|
15925
|
-
logger$
|
|
16060
|
+
logger$17.error("[RTCPeerConnectionController] Start Negotiation error:", error);
|
|
15926
16061
|
this._errors$.next(toError(error));
|
|
15927
16062
|
}
|
|
15928
16063
|
});
|
|
15929
16064
|
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]) => {
|
|
15930
|
-
logger$
|
|
16065
|
+
logger$17.debug(`[RTCPeerConnectionController] Selected input device changed for:`, {
|
|
15931
16066
|
kind,
|
|
15932
16067
|
deviceInfo
|
|
15933
16068
|
});
|
|
@@ -15944,7 +16079,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15944
16079
|
this._initialized$.next(true);
|
|
15945
16080
|
}
|
|
15946
16081
|
} catch (error) {
|
|
15947
|
-
logger$
|
|
16082
|
+
logger$17.error("[RTCPeerConnectionController] Initialization error:", error);
|
|
15948
16083
|
this._errors$.next(toError(error));
|
|
15949
16084
|
this.destroy();
|
|
15950
16085
|
}
|
|
@@ -15976,22 +16111,22 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15976
16111
|
}
|
|
15977
16112
|
async startNegotiation() {
|
|
15978
16113
|
if (this.isNegotiating) {
|
|
15979
|
-
logger$
|
|
16114
|
+
logger$17.debug("[RTCPeerConnectionController] Negotiation already in progress, skipping.");
|
|
15980
16115
|
return;
|
|
15981
16116
|
}
|
|
15982
16117
|
this.setupEventListeners();
|
|
15983
16118
|
if (this.type === "answer") {
|
|
15984
|
-
logger$
|
|
16119
|
+
logger$17.debug("[RTCPeerConnectionController] This is an answer type still, skipping offer creation.");
|
|
15985
16120
|
return;
|
|
15986
16121
|
}
|
|
15987
16122
|
this._isNegotiating$.next(true);
|
|
15988
|
-
logger$
|
|
16123
|
+
logger$17.debug("[RTCPeerConnectionController] Starting negotiation.");
|
|
15989
16124
|
try {
|
|
15990
16125
|
const { offerOptions } = this;
|
|
15991
|
-
logger$
|
|
16126
|
+
logger$17.debug("[RTCPeerConnectionController] Creating offer with options:", offerOptions);
|
|
15992
16127
|
await this.createOffer(offerOptions);
|
|
15993
16128
|
} catch (error) {
|
|
15994
|
-
logger$
|
|
16129
|
+
logger$17.error("[RTCPeerConnectionController] Error during negotiation:", error);
|
|
15995
16130
|
this._errors$.next(toError(error));
|
|
15996
16131
|
}
|
|
15997
16132
|
}
|
|
@@ -16007,14 +16142,14 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16007
16142
|
let readyToConnect = status !== "failed";
|
|
16008
16143
|
try {
|
|
16009
16144
|
if (status === "received" && sdp) {
|
|
16010
|
-
logger$
|
|
16145
|
+
logger$17.debug("[RTCPeerConnectionController] Received answer SDP:", sdp);
|
|
16011
16146
|
await this._setRemoteDescription({
|
|
16012
16147
|
type: "answer",
|
|
16013
16148
|
sdp
|
|
16014
16149
|
});
|
|
16015
16150
|
}
|
|
16016
16151
|
} catch (error) {
|
|
16017
|
-
logger$
|
|
16152
|
+
logger$17.error("[RTCPeerConnectionController] Error updating answer status:", error);
|
|
16018
16153
|
this._errors$.next(toError(error));
|
|
16019
16154
|
readyToConnect = false;
|
|
16020
16155
|
} finally {
|
|
@@ -16033,7 +16168,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16033
16168
|
await this.handleOfferReceived();
|
|
16034
16169
|
break;
|
|
16035
16170
|
case "failed":
|
|
16036
|
-
logger$
|
|
16171
|
+
logger$17.error("[RTCPeerConnectionController] Offer failed to be processed by remote.");
|
|
16037
16172
|
break;
|
|
16038
16173
|
case "sent":
|
|
16039
16174
|
default:
|
|
@@ -16046,13 +16181,14 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16046
16181
|
*/
|
|
16047
16182
|
async acceptInbound(mediaOverrides) {
|
|
16048
16183
|
if (mediaOverrides) {
|
|
16049
|
-
const { audio, video, receiveAudio, receiveVideo } = mediaOverrides;
|
|
16184
|
+
const { audio, video, receiveAudio, receiveVideo, fallbackToReceiveOnly } = mediaOverrides;
|
|
16050
16185
|
this.options = {
|
|
16051
16186
|
...this.options,
|
|
16052
16187
|
...audio !== void 0 ? { audio } : {},
|
|
16053
16188
|
...video !== void 0 ? { video } : {},
|
|
16054
16189
|
...receiveAudio !== void 0 ? { receiveAudio } : {},
|
|
16055
|
-
...receiveVideo !== void 0 ? { receiveVideo } : {}
|
|
16190
|
+
...receiveVideo !== void 0 ? { receiveVideo } : {},
|
|
16191
|
+
...fallbackToReceiveOnly !== void 0 ? { fallbackToReceiveOnly } : {}
|
|
16056
16192
|
};
|
|
16057
16193
|
this.transceiverController?.updateOptions({
|
|
16058
16194
|
receiveAudio: this.receiveAudio,
|
|
@@ -16065,7 +16201,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16065
16201
|
}
|
|
16066
16202
|
await this.setupLocalTracks();
|
|
16067
16203
|
const { answerOptions } = this;
|
|
16068
|
-
logger$
|
|
16204
|
+
logger$17.debug("[RTCPeerConnectionController] Creating inbound answer with options:", answerOptions);
|
|
16069
16205
|
await this.createAnswer(answerOptions);
|
|
16070
16206
|
}
|
|
16071
16207
|
async handleOfferReceived() {
|
|
@@ -16073,7 +16209,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16073
16209
|
this._isNegotiating$.next(true);
|
|
16074
16210
|
await this._setRemoteDescription(this.sdpInit);
|
|
16075
16211
|
const { answerOptions } = this;
|
|
16076
|
-
logger$
|
|
16212
|
+
logger$17.debug("[RTCPeerConnectionController] Creating answer with options:", answerOptions);
|
|
16077
16213
|
await this.createAnswer(answerOptions);
|
|
16078
16214
|
}
|
|
16079
16215
|
readyToConnect() {
|
|
@@ -16081,7 +16217,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16081
16217
|
this.connectionTimer = setTimeout(() => {
|
|
16082
16218
|
this.removeConnectionTimer();
|
|
16083
16219
|
if (this.peerConnection?.connectionState !== "connected") {
|
|
16084
|
-
logger$
|
|
16220
|
+
logger$17.debug("[RTCPeerConnectionController] Connection timeout, restarting ICE gathering with relay only.");
|
|
16085
16221
|
this.iceGatheringController.restartICEGatheringWithRelayOnly();
|
|
16086
16222
|
}
|
|
16087
16223
|
}, this.connectionTimeout);
|
|
@@ -16103,14 +16239,14 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16103
16239
|
const stereo = this.options.stereo ?? PreferencesContainer.instance.stereoAudio;
|
|
16104
16240
|
if (preferredAudioCodecs.length > 0 || preferredVideoCodecs.length > 0) {
|
|
16105
16241
|
result = setCodecPreferences(result, preferredAudioCodecs, preferredVideoCodecs);
|
|
16106
|
-
logger$
|
|
16242
|
+
logger$17.debug("[RTCPeerConnectionController] Applied codec preferences to SDP", {
|
|
16107
16243
|
preferredAudioCodecs,
|
|
16108
16244
|
preferredVideoCodecs
|
|
16109
16245
|
});
|
|
16110
16246
|
}
|
|
16111
16247
|
if (stereo) {
|
|
16112
16248
|
result = enableStereoOpus(result);
|
|
16113
|
-
logger$
|
|
16249
|
+
logger$17.debug("[RTCPeerConnectionController] Applied stereo Opus to SDP");
|
|
16114
16250
|
}
|
|
16115
16251
|
return Promise.resolve(result);
|
|
16116
16252
|
}
|
|
@@ -16166,25 +16302,25 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16166
16302
|
...this.peerConnection.getConfiguration(),
|
|
16167
16303
|
iceTransportPolicy: "relay"
|
|
16168
16304
|
});
|
|
16169
|
-
logger$
|
|
16305
|
+
logger$17.debug("[RTCPeerConnectionController] ICE transport policy set to relay-only");
|
|
16170
16306
|
} catch (error) {
|
|
16171
|
-
logger$
|
|
16307
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to set relay-only policy:", error);
|
|
16172
16308
|
}
|
|
16173
16309
|
this.setupEventListeners();
|
|
16174
16310
|
this._isNegotiating$.next(true);
|
|
16175
|
-
logger$
|
|
16311
|
+
logger$17.debug(`[RTCPeerConnectionController] Triggering ICE restart${relayOnly ? " (relay-only)" : ""}.`);
|
|
16176
16312
|
try {
|
|
16177
16313
|
const offer = await this.peerConnection.createOffer({ iceRestart: true });
|
|
16178
16314
|
await this.setLocalDescription(offer);
|
|
16179
16315
|
} catch (error) {
|
|
16180
|
-
logger$
|
|
16316
|
+
logger$17.error("[RTCPeerConnectionController] ICE restart offer failed:", error);
|
|
16181
16317
|
this._errors$.next(toError(error));
|
|
16182
16318
|
this.negotiationEnded();
|
|
16183
16319
|
if (policyChanged) this.restoreIceTransportPolicy();
|
|
16184
16320
|
throw error;
|
|
16185
16321
|
}
|
|
16186
16322
|
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) => {
|
|
16187
|
-
logger$
|
|
16323
|
+
logger$17.warn("[RTCPeerConnectionController] Error waiting for ICE gathering to complete:", error);
|
|
16188
16324
|
this.restoreIceTransportPolicy();
|
|
16189
16325
|
});
|
|
16190
16326
|
}
|
|
@@ -16194,9 +16330,9 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16194
16330
|
...this.peerConnection.getConfiguration(),
|
|
16195
16331
|
iceTransportPolicy: this.options.relayOnly ? "relay" : "all"
|
|
16196
16332
|
});
|
|
16197
|
-
logger$
|
|
16333
|
+
logger$17.debug("[RTCPeerConnectionController] ICE transport policy restored");
|
|
16198
16334
|
} catch (error) {
|
|
16199
|
-
logger$
|
|
16335
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to restore ICE transport policy:", error);
|
|
16200
16336
|
}
|
|
16201
16337
|
}
|
|
16202
16338
|
/**
|
|
@@ -16208,13 +16344,25 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16208
16344
|
await this.setupRemoteTracks();
|
|
16209
16345
|
}
|
|
16210
16346
|
async setupLocalTracks() {
|
|
16211
|
-
logger$
|
|
16212
|
-
|
|
16347
|
+
logger$17.debug("[RTCPeerConnectionController] Setting up local tracks/transceivers.");
|
|
16348
|
+
if (this.hasNoLocalMediaToSend()) {
|
|
16349
|
+
if (!this.receiveAudio && !this.receiveVideo) throw new InvalidParams("Call requests no media: enable audio/video or receiveAudio/receiveVideo");
|
|
16350
|
+
logger$17.debug("[RTCPeerConnectionController] No local media requested; negotiating receive-only.");
|
|
16351
|
+
this.setupReceiveOnlyTransceivers();
|
|
16352
|
+
return;
|
|
16353
|
+
}
|
|
16354
|
+
let localStream;
|
|
16355
|
+
try {
|
|
16356
|
+
localStream = this.localStream ?? await this.localStreamController.buildLocalStream();
|
|
16357
|
+
} catch (error) {
|
|
16358
|
+
this.handleLocalMediaFailure(error);
|
|
16359
|
+
return;
|
|
16360
|
+
}
|
|
16213
16361
|
if (this.transceiverController?.useAddStream ?? false) {
|
|
16214
|
-
logger$
|
|
16362
|
+
logger$17.warn("[RTCPeerConnectionController] Using deprecated addStream API to add local stream.");
|
|
16215
16363
|
this.peerConnection?.addStream(localStream);
|
|
16216
16364
|
if (!this.isNegotiating) {
|
|
16217
|
-
logger$
|
|
16365
|
+
logger$17.debug("[RTCPeerConnectionController] Forcing negotiationneeded after local tracks setup.");
|
|
16218
16366
|
this.negotiationNeeded$.next();
|
|
16219
16367
|
}
|
|
16220
16368
|
return;
|
|
@@ -16230,12 +16378,54 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16230
16378
|
const transceivers = (kind === "audio" ? this.transceiverController?.audioTransceivers : this.transceiverController?.videoTransceivers) ?? [];
|
|
16231
16379
|
await this.transceiverController?.setupTransceiverSender(track, localStream, transceivers[index]);
|
|
16232
16380
|
} else {
|
|
16233
|
-
logger$
|
|
16381
|
+
logger$17.debug(`[RTCPeerConnectionController] Using addTrack for local ${kind} track:`, track.id);
|
|
16234
16382
|
this.peerConnection?.addTrack(track, localStream);
|
|
16235
16383
|
}
|
|
16236
16384
|
}
|
|
16237
16385
|
}
|
|
16238
16386
|
}
|
|
16387
|
+
/** True for a main connection with no local media to send. */
|
|
16388
|
+
hasNoLocalMediaToSend() {
|
|
16389
|
+
const hasInputStreams = Boolean(this.options.inputAudioStream ?? this.options.inputVideoStream);
|
|
16390
|
+
return this.propose === "main" && !this.localStream && !hasInputStreams && !this.inputAudioDeviceConstraints && !this.inputVideoDeviceConstraints;
|
|
16391
|
+
}
|
|
16392
|
+
/** The media kinds this connection wants to send: 'audiovideo' | 'video' | 'audio'. */
|
|
16393
|
+
get requestedMediaKinds() {
|
|
16394
|
+
const wantsAudio = Boolean(this.inputAudioDeviceConstraints);
|
|
16395
|
+
const wantsVideo = Boolean(this.inputVideoDeviceConstraints);
|
|
16396
|
+
if (wantsAudio && wantsVideo) return "audiovideo";
|
|
16397
|
+
return wantsVideo ? "video" : "audio";
|
|
16398
|
+
}
|
|
16399
|
+
/**
|
|
16400
|
+
* Handle a local media acquisition failure with a typed, semantically
|
|
16401
|
+
* accurate MediaAccessError created at the acquisition site:
|
|
16402
|
+
* - Auxiliary connections (screenshare / additional-device) throw a
|
|
16403
|
+
* non-fatal error — VertoManager surfaces it and the call is unaffected.
|
|
16404
|
+
* - The main connection degrades to receive-only when allowed (default),
|
|
16405
|
+
* otherwise fails with a fatal error.
|
|
16406
|
+
*/
|
|
16407
|
+
handleLocalMediaFailure(error) {
|
|
16408
|
+
if (this.propose === "screenshare") throw new MediaAccessError("startScreenShare", "screen", error, false);
|
|
16409
|
+
if (this.propose === "additional-device") throw new MediaAccessError("addInputDevice", this.requestedMediaKinds, error, false);
|
|
16410
|
+
const canReceive = this.receiveAudio || this.receiveVideo;
|
|
16411
|
+
if (!((this.options.fallbackToReceiveOnly ?? true) && canReceive)) throw new MediaAccessError("acquireLocalMedia", this.requestedMediaKinds, error, true);
|
|
16412
|
+
logger$17.warn("[RTCPeerConnectionController] Local media unavailable; continuing receive-only:", error);
|
|
16413
|
+
this._errors$.next(new MediaAccessError("acquireLocalMedia", this.requestedMediaKinds, error, false));
|
|
16414
|
+
this.setupReceiveOnlyTransceivers();
|
|
16415
|
+
}
|
|
16416
|
+
/**
|
|
16417
|
+
* Negotiate receive-only m-lines when there are no local tracks to send.
|
|
16418
|
+
* Only offer-type connections add transceivers — answer-type connections
|
|
16419
|
+
* reuse the transceivers created from the remote offer.
|
|
16420
|
+
*/
|
|
16421
|
+
setupReceiveOnlyTransceivers() {
|
|
16422
|
+
if (this.type !== "offer") return;
|
|
16423
|
+
if (this.transceiverController?.useAddTransceivers ?? false) {
|
|
16424
|
+
this.peerConnection?.addTransceiver("audio", { direction: this.receiveAudio ? "recvonly" : "inactive" });
|
|
16425
|
+
this.peerConnection?.addTransceiver("video", { direction: this.receiveVideo ? "recvonly" : "inactive" });
|
|
16426
|
+
}
|
|
16427
|
+
if (!this.isNegotiating) this.negotiationNeeded$.next();
|
|
16428
|
+
}
|
|
16239
16429
|
async getUserMedia(constraints) {
|
|
16240
16430
|
return (this.options.webRTCApiProvider?.mediaDevices ?? navigator.mediaDevices).getUserMedia(constraints);
|
|
16241
16431
|
}
|
|
@@ -16247,7 +16437,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16247
16437
|
async setupRemoteTracks() {
|
|
16248
16438
|
if (!this.peerConnection) throw new DependencyError("RTCPeerConnection is not initialized");
|
|
16249
16439
|
this.peerConnection.ontrack = (event) => {
|
|
16250
|
-
logger$
|
|
16440
|
+
logger$17.debug("[RTCPeerConnectionController] Remote track received:", event.track.kind);
|
|
16251
16441
|
if (event.streams[0]) this._remoteStream$.next(event.streams[0]);
|
|
16252
16442
|
else {
|
|
16253
16443
|
const existingTracks = this._remoteStream$.value?.getTracks() ?? [];
|
|
@@ -16258,8 +16448,27 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16258
16448
|
await this.transceiverController?.setupRemoteTransceivers(this.type);
|
|
16259
16449
|
}
|
|
16260
16450
|
async restoreTrackSender(kind) {
|
|
16261
|
-
|
|
16262
|
-
if (
|
|
16451
|
+
const audioCovered = kind === "audio" || kind === "both";
|
|
16452
|
+
if (audioCovered && this._localAudioPipeline) await this.restoreRawAudioInputForPipeline();
|
|
16453
|
+
if (!audioCovered) await this.transceiverController?.restoreTrackSender(kind);
|
|
16454
|
+
else if (kind === "both") await this.transceiverController?.restoreTrackSender("video");
|
|
16455
|
+
else if (!this._localAudioPipeline) await this.transceiverController?.restoreTrackSender(kind);
|
|
16456
|
+
}
|
|
16457
|
+
async restoreRawAudioInputForPipeline() {
|
|
16458
|
+
if (!this._localAudioPipeline) return;
|
|
16459
|
+
const constraints = this.transceiverController?.getConstraintsFor("audio") ?? {};
|
|
16460
|
+
let stream;
|
|
16461
|
+
try {
|
|
16462
|
+
stream = await this.getUserMedia({ audio: constraints });
|
|
16463
|
+
} catch (error) {
|
|
16464
|
+
logger$17.error("[RTCPeerConnectionController] Failed to re-acquire mic for pipeline restore:", error);
|
|
16465
|
+
this._errors$.next(new MediaTrackError("restoreAudioPipelineInput", "audio", error));
|
|
16466
|
+
return;
|
|
16467
|
+
}
|
|
16468
|
+
const newTrack = stream.getAudioTracks().at(0);
|
|
16469
|
+
if (!newTrack) return;
|
|
16470
|
+
this.localStreamController.addTrack(newTrack);
|
|
16471
|
+
this._localAudioPipeline.setInputTrack(newTrack);
|
|
16263
16472
|
}
|
|
16264
16473
|
/**
|
|
16265
16474
|
* Return the lazily-created {@link LocalAudioPipeline}, constructing it on
|
|
@@ -16274,7 +16483,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16274
16483
|
try {
|
|
16275
16484
|
this._localAudioPipeline = new LocalAudioPipeline();
|
|
16276
16485
|
} catch (error) {
|
|
16277
|
-
logger$
|
|
16486
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to create LocalAudioPipeline:", error);
|
|
16278
16487
|
return null;
|
|
16279
16488
|
}
|
|
16280
16489
|
this.subscribeTo(this.localStreamController.localAudioTracks$, () => {
|
|
@@ -16296,7 +16505,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16296
16505
|
try {
|
|
16297
16506
|
await sender.replaceTrack(this._localAudioPipeline.outputTrack);
|
|
16298
16507
|
} catch (error) {
|
|
16299
|
-
logger$
|
|
16508
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to route audio sender through pipeline:", error);
|
|
16300
16509
|
}
|
|
16301
16510
|
}
|
|
16302
16511
|
/**
|
|
@@ -16312,10 +16521,10 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16312
16521
|
try {
|
|
16313
16522
|
const localStream = this.localStreamController.addTrack(track);
|
|
16314
16523
|
this.peerConnection.addTrack(track, localStream);
|
|
16315
|
-
logger$
|
|
16524
|
+
logger$17.debug(`[RTCPeerConnectionController] ${track.kind} track added:`, track.id);
|
|
16316
16525
|
} catch (error) {
|
|
16317
|
-
logger$
|
|
16318
|
-
this._errors$.next(
|
|
16526
|
+
logger$17.error(`[RTCPeerConnectionController] Failed to add ${track.kind} track:`, error);
|
|
16527
|
+
this._errors$.next(new MediaTrackError("addLocalTrack", track.kind, error));
|
|
16319
16528
|
throw error;
|
|
16320
16529
|
}
|
|
16321
16530
|
}
|
|
@@ -16331,16 +16540,16 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16331
16540
|
}
|
|
16332
16541
|
const sender = this.peerConnection.getSenders().find((sender$1) => sender$1.track?.id === trackId);
|
|
16333
16542
|
if (!sender) {
|
|
16334
|
-
logger$
|
|
16543
|
+
logger$17.debug(`[RTCPeerConnectionController] track not found: ${trackId}`);
|
|
16335
16544
|
return;
|
|
16336
16545
|
}
|
|
16337
16546
|
try {
|
|
16338
16547
|
this.peerConnection.removeTrack(sender);
|
|
16339
16548
|
this.localStreamController.removeTrack(trackId);
|
|
16340
|
-
logger$
|
|
16549
|
+
logger$17.debug(`[RTCPeerConnectionController] ${sender.track?.kind} track removed:`, trackId);
|
|
16341
16550
|
} catch (error) {
|
|
16342
|
-
logger$
|
|
16343
|
-
this._errors$.next(
|
|
16551
|
+
logger$17.error(`[RTCPeerConnectionController] Failed to remove ${sender.track?.kind} track:`, error);
|
|
16552
|
+
this._errors$.next(new MediaTrackError("removeLocalTrack", sender.track?.kind ?? "unknown", error));
|
|
16344
16553
|
throw error;
|
|
16345
16554
|
}
|
|
16346
16555
|
}
|
|
@@ -16366,7 +16575,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16366
16575
|
async replaceAudioTrackWithConstraints(constraints) {
|
|
16367
16576
|
const senders = this.peerConnection?.getSenders().filter((s) => s.track?.kind === "audio" && s.track.readyState === "live");
|
|
16368
16577
|
if (!senders || senders.length === 0) {
|
|
16369
|
-
logger$
|
|
16578
|
+
logger$17.warn("[RTCPeerConnectionController] No live audio sender to replace");
|
|
16370
16579
|
return;
|
|
16371
16580
|
}
|
|
16372
16581
|
for (const sender of senders) {
|
|
@@ -16384,7 +16593,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16384
16593
|
const newTrack = (await this.getUserMedia({ audio: mergedConstraints })).getAudioTracks()[0];
|
|
16385
16594
|
await sender.replaceTrack(newTrack);
|
|
16386
16595
|
this.localStreamController.addTrack(newTrack);
|
|
16387
|
-
logger$
|
|
16596
|
+
logger$17.debug(`[RTCPeerConnectionController] Audio track replaced for server-pushed params. New track: ${newTrack.id}`);
|
|
16388
16597
|
}
|
|
16389
16598
|
}
|
|
16390
16599
|
/**
|
|
@@ -16392,7 +16601,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16392
16601
|
* Completes all observables to prevent memory leaks.
|
|
16393
16602
|
*/
|
|
16394
16603
|
destroy() {
|
|
16395
|
-
logger$
|
|
16604
|
+
logger$17.debug(`[RTCPeerConnectionController] Destroying RTCPeerConnectionController. ${this.propose}`);
|
|
16396
16605
|
this.removeConnectionTimer();
|
|
16397
16606
|
this._iceGatheringController?.destroy();
|
|
16398
16607
|
this._localAudioPipeline?.destroy();
|
|
@@ -16418,7 +16627,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16418
16627
|
}
|
|
16419
16628
|
stopRemoteTracks() {
|
|
16420
16629
|
this._remoteStream$.value?.getTracks().forEach((track) => {
|
|
16421
|
-
logger$
|
|
16630
|
+
logger$17.debug(`[RTCPeerConnectionController] Stopping remote track: ${track.kind}`);
|
|
16422
16631
|
track.stop();
|
|
16423
16632
|
});
|
|
16424
16633
|
}
|
|
@@ -16435,7 +16644,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16435
16644
|
...params,
|
|
16436
16645
|
sdp: finalRemote
|
|
16437
16646
|
};
|
|
16438
|
-
logger$
|
|
16647
|
+
logger$17.debug("[RTCPeerConnectionController] Setting remote description:", answer);
|
|
16439
16648
|
return this.peerConnection.setRemoteDescription(answer);
|
|
16440
16649
|
}
|
|
16441
16650
|
};
|
|
@@ -16450,7 +16659,11 @@ function isVertoInviteMessage(value) {
|
|
|
16450
16659
|
const msg = value;
|
|
16451
16660
|
return msg.method === "verto.invite" && isObject(msg.params) && hasProperty(msg.params, "sdp") && hasProperty(msg.params, "callID");
|
|
16452
16661
|
}
|
|
16453
|
-
|
|
16662
|
+
/**
|
|
16663
|
+
* Guards inbound `verto.bye` frames (server → client). Outbound bye messages are
|
|
16664
|
+
* built locally and never type-guarded, so only the inbound shape is asserted.
|
|
16665
|
+
*/
|
|
16666
|
+
function isVertoByeInboundMessage(value) {
|
|
16454
16667
|
if (!isVertoMethodMessage(value)) return false;
|
|
16455
16668
|
return value.method === "verto.bye";
|
|
16456
16669
|
}
|
|
@@ -16470,11 +16683,19 @@ function isVertoMediaParamsInnerParams(value) {
|
|
|
16470
16683
|
function isVertoPingInnerParams(value) {
|
|
16471
16684
|
return isObject(value) && hasProperty(value, "jsonrpc") && value.jsonrpc === "2.0" && hasProperty(value, "method") && value.method === "verto.ping";
|
|
16472
16685
|
}
|
|
16686
|
+
/**
|
|
16687
|
+
* Guards the params of an inbound `verto.bye` frame (top-level `callID`). Use
|
|
16688
|
+
* this to recognise the bye payload extracted by `vertoBye$`, e.g. when racing
|
|
16689
|
+
* it against a boolean answer/reject signal.
|
|
16690
|
+
*/
|
|
16691
|
+
function isVertoByeInboundParamsGuard(value) {
|
|
16692
|
+
return isObject(value) && hasProperty(value, "callID") && typeof value.callID === "string";
|
|
16693
|
+
}
|
|
16473
16694
|
|
|
16474
16695
|
//#endregion
|
|
16475
16696
|
//#region src/managers/VertoManager.ts
|
|
16476
16697
|
var import_cjs$15 = require_cjs();
|
|
16477
|
-
const logger$
|
|
16698
|
+
const logger$16 = getLogger();
|
|
16478
16699
|
/**
|
|
16479
16700
|
* Decide what value goes on the `node_id` field of a `webrtc.verto` envelope.
|
|
16480
16701
|
*
|
|
@@ -16530,7 +16751,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16530
16751
|
try {
|
|
16531
16752
|
await this.executeVerto(vertoModifyMessage);
|
|
16532
16753
|
} catch (error) {
|
|
16533
|
-
logger$
|
|
16754
|
+
logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto hold:", error);
|
|
16534
16755
|
throw error;
|
|
16535
16756
|
}
|
|
16536
16757
|
}
|
|
@@ -16543,7 +16764,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16543
16764
|
try {
|
|
16544
16765
|
await this.executeVerto(vertoModifyMessage);
|
|
16545
16766
|
} catch (error) {
|
|
16546
|
-
logger$
|
|
16767
|
+
logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto unhold:", error);
|
|
16547
16768
|
throw error;
|
|
16548
16769
|
}
|
|
16549
16770
|
}
|
|
@@ -16596,25 +16817,25 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16596
16817
|
if (event.member_id) this.setSelfIdIfNull(event.member_id);
|
|
16597
16818
|
});
|
|
16598
16819
|
this.subscribeTo(this.vertoMedia$, (event) => {
|
|
16599
|
-
logger$
|
|
16600
|
-
this._signalingStatus$.next("ringing");
|
|
16820
|
+
logger$16.debug("[WebRTCManager] Received Verto media event (early media SDP):", event);
|
|
16601
16821
|
const { sdp, callID } = event;
|
|
16822
|
+
this.emitMainSignalingStatus(callID, "ringing");
|
|
16602
16823
|
this._rtcPeerConnectionsMap.get(callID)?.updateAnswerStatus({
|
|
16603
16824
|
status: "received",
|
|
16604
16825
|
sdp
|
|
16605
16826
|
});
|
|
16606
16827
|
});
|
|
16607
16828
|
this.subscribeTo(this.vertoAnswer$, (event) => {
|
|
16608
|
-
logger$
|
|
16609
|
-
this._signalingStatus$.next("connecting");
|
|
16829
|
+
logger$16.debug("[WebRTCManager] Received Verto answer event:", event);
|
|
16610
16830
|
const { sdp, callID } = event;
|
|
16831
|
+
this.emitMainSignalingStatus(callID, "connecting");
|
|
16611
16832
|
this._rtcPeerConnectionsMap.get(callID)?.updateAnswerStatus({
|
|
16612
16833
|
status: "received",
|
|
16613
16834
|
sdp
|
|
16614
16835
|
});
|
|
16615
16836
|
});
|
|
16616
16837
|
this.subscribeTo(this.vertoMediaParams$, (event) => {
|
|
16617
|
-
logger$
|
|
16838
|
+
logger$16.debug("[WebRTCManager] Received Verto mediaParams event:", event);
|
|
16618
16839
|
const { mediaParams, callID } = event;
|
|
16619
16840
|
const rtcPeerConnController = this._rtcPeerConnectionsMap.get(callID);
|
|
16620
16841
|
const { audio, video } = mediaParams;
|
|
@@ -16628,7 +16849,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16628
16849
|
timestamp: Date.now()
|
|
16629
16850
|
});
|
|
16630
16851
|
} catch (error) {
|
|
16631
|
-
logger$
|
|
16852
|
+
logger$16.warn("[WebRTCManager] Error applying server-pushed media params:", error);
|
|
16632
16853
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
16633
16854
|
}
|
|
16634
16855
|
})();
|
|
@@ -16650,13 +16871,13 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16650
16871
|
*/
|
|
16651
16872
|
setNodeIdIfNull(nodeId) {
|
|
16652
16873
|
if (!this._nodeId$.value && nodeId) {
|
|
16653
|
-
logger$
|
|
16874
|
+
logger$16.debug(`[WebRTCManager] Early node_id set: ${nodeId}`);
|
|
16654
16875
|
this._nodeId$.next(nodeId);
|
|
16655
16876
|
}
|
|
16656
16877
|
}
|
|
16657
16878
|
setSelfIdIfNull(selfId) {
|
|
16658
16879
|
if (!this._selfId$.value && selfId) {
|
|
16659
|
-
logger$
|
|
16880
|
+
logger$16.debug(`[WebRTCManager] Early selfId set: ${selfId}`);
|
|
16660
16881
|
this._selfId$.next(selfId);
|
|
16661
16882
|
}
|
|
16662
16883
|
}
|
|
@@ -16665,7 +16886,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16665
16886
|
const vertoPongMessage = VertoPong({ ...vertoPing });
|
|
16666
16887
|
await this.executeVerto(vertoPongMessage);
|
|
16667
16888
|
} catch (error) {
|
|
16668
|
-
logger$
|
|
16889
|
+
logger$16.warn("[WebRTCManager] Call might disconnect, error sending Verto pong:", error);
|
|
16669
16890
|
this.onError?.(new VertoPongError(error));
|
|
16670
16891
|
}
|
|
16671
16892
|
}
|
|
@@ -16675,7 +16896,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16675
16896
|
if (audio) await this.mainPeerConnection.updateSendersConstraints("audio", audio);
|
|
16676
16897
|
if (video) await this.mainPeerConnection.updateSendersConstraints("video", video);
|
|
16677
16898
|
} catch (error) {
|
|
16678
|
-
logger$
|
|
16899
|
+
logger$16.warn("[WebRTCManager] Error updating media constraints:", error);
|
|
16679
16900
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
16680
16901
|
throw error;
|
|
16681
16902
|
}
|
|
@@ -16705,20 +16926,20 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16705
16926
|
try {
|
|
16706
16927
|
const pc = this.mainPeerConnection.peerConnection;
|
|
16707
16928
|
if (!pc) {
|
|
16708
|
-
logger$
|
|
16929
|
+
logger$16.warn("[WebRTCManager] No peer connection for keyframe request");
|
|
16709
16930
|
return;
|
|
16710
16931
|
}
|
|
16711
16932
|
const videoReceiver = pc.getReceivers().find((r) => r.track.kind === "video");
|
|
16712
16933
|
if (!videoReceiver) {
|
|
16713
|
-
logger$
|
|
16934
|
+
logger$16.warn("[WebRTCManager] No video receiver for keyframe request");
|
|
16714
16935
|
return;
|
|
16715
16936
|
}
|
|
16716
16937
|
if (typeof videoReceiver.requestKeyFrame === "function") {
|
|
16717
16938
|
videoReceiver.requestKeyFrame();
|
|
16718
|
-
logger$
|
|
16719
|
-
} else logger$
|
|
16939
|
+
logger$16.debug("[WebRTCManager] Keyframe requested via RTCRtpReceiver.requestKeyFrame()");
|
|
16940
|
+
} else logger$16.debug("[WebRTCManager] requestKeyFrame() not supported, skipping");
|
|
16720
16941
|
} catch (error) {
|
|
16721
|
-
logger$
|
|
16942
|
+
logger$16.warn("[WebRTCManager] Keyframe request failed (non-fatal):", error);
|
|
16722
16943
|
}
|
|
16723
16944
|
}
|
|
16724
16945
|
/**
|
|
@@ -16736,13 +16957,13 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16736
16957
|
try {
|
|
16737
16958
|
const controller = this.mainPeerConnection;
|
|
16738
16959
|
if (!controller.peerConnection) {
|
|
16739
|
-
logger$
|
|
16960
|
+
logger$16.warn("[WebRTCManager] No peer connection for ICE restart");
|
|
16740
16961
|
return;
|
|
16741
16962
|
}
|
|
16742
16963
|
await controller.triggerIceRestart(relayOnly);
|
|
16743
|
-
logger$
|
|
16964
|
+
logger$16.info(`[WebRTCManager] ICE restart initiated${relayOnly ? " (relay-only)" : ""}`);
|
|
16744
16965
|
} catch (error) {
|
|
16745
|
-
logger$
|
|
16966
|
+
logger$16.error("[WebRTCManager] ICE restart failed:", error);
|
|
16746
16967
|
throw error;
|
|
16747
16968
|
}
|
|
16748
16969
|
}
|
|
@@ -16760,13 +16981,13 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16760
16981
|
const entries = Array.from(this._rtcPeerConnectionsMap.entries());
|
|
16761
16982
|
for (const [id, controller] of entries) try {
|
|
16762
16983
|
if (!controller.peerConnection) {
|
|
16763
|
-
logger$
|
|
16984
|
+
logger$16.debug(`[WebRTCManager] No peer connection for leg ${id}, skipping ICE restart`);
|
|
16764
16985
|
continue;
|
|
16765
16986
|
}
|
|
16766
16987
|
await controller.triggerIceRestart(relayOnly);
|
|
16767
|
-
logger$
|
|
16988
|
+
logger$16.info(`[WebRTCManager] ICE restart initiated for leg ${id}${relayOnly ? " (relay-only)" : ""}`);
|
|
16768
16989
|
} catch (error) {
|
|
16769
|
-
logger$
|
|
16990
|
+
logger$16.warn(`[WebRTCManager] ICE restart failed for leg ${id}:`, error);
|
|
16770
16991
|
}
|
|
16771
16992
|
}
|
|
16772
16993
|
/**
|
|
@@ -16778,7 +16999,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16778
16999
|
requestKeyframeAll() {
|
|
16779
17000
|
for (const [id, controller] of this._rtcPeerConnectionsMap) {
|
|
16780
17001
|
if (controller.isScreenShare) {
|
|
16781
|
-
logger$
|
|
17002
|
+
logger$16.debug(`[WebRTCManager] Skipping keyframe for send-only screen share leg ${id}`);
|
|
16782
17003
|
continue;
|
|
16783
17004
|
}
|
|
16784
17005
|
try {
|
|
@@ -16788,10 +17009,10 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16788
17009
|
if (!videoReceiver) continue;
|
|
16789
17010
|
if (typeof videoReceiver.requestKeyFrame === "function") {
|
|
16790
17011
|
videoReceiver.requestKeyFrame();
|
|
16791
|
-
logger$
|
|
17012
|
+
logger$16.debug(`[WebRTCManager] Keyframe requested for leg ${id}`);
|
|
16792
17013
|
}
|
|
16793
17014
|
} catch (error) {
|
|
16794
|
-
logger$
|
|
17015
|
+
logger$16.warn(`[WebRTCManager] Keyframe request failed for leg ${id} (non-fatal):`, error);
|
|
16795
17016
|
}
|
|
16796
17017
|
}
|
|
16797
17018
|
}
|
|
@@ -16808,7 +17029,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16808
17029
|
return this.cachedObservable("vertoMediaParams$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoMediaParamsInnerParams, "params"), (0, import_cjs$15.takeUntil)(this.destroyed$)));
|
|
16809
17030
|
}
|
|
16810
17031
|
get vertoBye$() {
|
|
16811
|
-
return this.cachedObservable("vertoBye$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(
|
|
17032
|
+
return this.cachedObservable("vertoBye$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoByeInboundMessage, "params"), (0, import_cjs$15.takeUntil)(this.destroyed$)));
|
|
16812
17033
|
}
|
|
16813
17034
|
get vertoAttach$() {
|
|
16814
17035
|
return this.cachedObservable("vertoAttach$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoAttachMessage, "params"), (0, import_cjs$15.takeUntil)(this.destroyed$)));
|
|
@@ -16852,7 +17073,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16852
17073
|
default:
|
|
16853
17074
|
}
|
|
16854
17075
|
} catch (error) {
|
|
16855
|
-
logger$
|
|
17076
|
+
logger$16.error(`[WebRTCManager] Error sending Verto ${vertoMethod}:`, error);
|
|
16856
17077
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
16857
17078
|
if (vertoMethod === "verto.modify") this.onModifyFailed?.();
|
|
16858
17079
|
}
|
|
@@ -16867,19 +17088,33 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16867
17088
|
sdp
|
|
16868
17089
|
});
|
|
16869
17090
|
} catch (error) {
|
|
16870
|
-
logger$
|
|
17091
|
+
logger$16.warn("[WebRTCManager] Error processing modify response:", error);
|
|
16871
17092
|
const modifyError = error instanceof Error ? error : new Error(String(error), { cause: error });
|
|
16872
17093
|
this.onError?.(modifyError);
|
|
16873
17094
|
}
|
|
16874
17095
|
}
|
|
16875
17096
|
}
|
|
17097
|
+
emitMainSignalingStatus(callId, status) {
|
|
17098
|
+
const rtcPeerConnController = this._rtcPeerConnectionsMap.get(callId);
|
|
17099
|
+
if (!rtcPeerConnController) {
|
|
17100
|
+
const signalingError = new DependencyError(`Cannot emit signaling status, RTCPeerConnectionController not found for callID: ${callId}`);
|
|
17101
|
+
logger$16.error("[WebRTCManager] Failed to emit signaling status:", {
|
|
17102
|
+
callId,
|
|
17103
|
+
status,
|
|
17104
|
+
signalingError
|
|
17105
|
+
});
|
|
17106
|
+
this.onError?.(signalingError);
|
|
17107
|
+
return;
|
|
17108
|
+
}
|
|
17109
|
+
if (rtcPeerConnController.isMainDevice) this._signalingStatus$.next(status);
|
|
17110
|
+
}
|
|
16876
17111
|
processInviteResponse(response, rtcPeerConnController) {
|
|
16877
17112
|
if (!response.error && getValueFrom(response, "result.result.result.message") === "CALL CREATED") {
|
|
16878
|
-
this.
|
|
17113
|
+
this.emitMainSignalingStatus(rtcPeerConnController.id, "trying");
|
|
16879
17114
|
this._nodeId$.next(getValueFrom(response, "result.node_id") ?? null);
|
|
16880
17115
|
const memberId = getValueFrom(response, "result.result.result.memberID") ?? null;
|
|
16881
17116
|
const callId = getValueFrom(response, "result.result.result.callID") ?? null;
|
|
16882
|
-
logger$
|
|
17117
|
+
logger$16.debug("[WebRTCManager] Verto invite response:", {
|
|
16883
17118
|
callId,
|
|
16884
17119
|
memberId,
|
|
16885
17120
|
response
|
|
@@ -16889,14 +17124,14 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16889
17124
|
if (callId) {
|
|
16890
17125
|
this.webRtcCallSession.addCallId(callId);
|
|
16891
17126
|
this.attachManager.attach(this.buildAttachableCall(callId));
|
|
16892
|
-
} else logger$
|
|
17127
|
+
} else logger$16.warn("[WebRTCManager] Cannot attach call, missing callId:", {
|
|
16893
17128
|
nodeId: this.nodeId,
|
|
16894
17129
|
callId
|
|
16895
17130
|
});
|
|
16896
|
-
logger$
|
|
16897
|
-
logger$
|
|
17131
|
+
logger$16.info("[WebRTCManager] Verto invite successful");
|
|
17132
|
+
logger$16.debug(`[WebRTCManager] nodeid: ${this._nodeId$.value}, selfId: ${this._selfId$.value}`);
|
|
16898
17133
|
} else {
|
|
16899
|
-
logger$
|
|
17134
|
+
logger$16.error("[WebRTCManager] Verto invite failed:", response);
|
|
16900
17135
|
const inviteError = response.error ? new JSONRPCError(response.error.code, response.error.message, response.error.data) : /* @__PURE__ */ new Error("Verto invite failed: unexpected response");
|
|
16901
17136
|
this.onError?.(inviteError);
|
|
16902
17137
|
}
|
|
@@ -16923,6 +17158,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16923
17158
|
inputVideoStream: options.inputVideoStream,
|
|
16924
17159
|
receiveAudio: options.receiveAudio,
|
|
16925
17160
|
receiveVideo: options.receiveVideo,
|
|
17161
|
+
fallbackToReceiveOnly: options.fallbackToReceiveOnly,
|
|
16926
17162
|
webRTCApiProvider: this.webRTCApiProvider,
|
|
16927
17163
|
preferredVideoCodecs: options.preferredVideoCodecs,
|
|
16928
17164
|
preferredAudioCodecs: options.preferredAudioCodecs,
|
|
@@ -16941,17 +17177,17 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16941
17177
|
if (options.initOffer) this.handleInboundAnswer(rtcPeerConnController);
|
|
16942
17178
|
}
|
|
16943
17179
|
async handleInboundAnswer(rtcPeerConnController) {
|
|
16944
|
-
logger$
|
|
17180
|
+
logger$16.debug("[WebRTCManager] Waiting for inbound call to be accepted or rejected");
|
|
16945
17181
|
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);
|
|
16946
17182
|
if (vertoByeOrAccepted === null) {
|
|
16947
|
-
logger$
|
|
17183
|
+
logger$16.debug("[WebRTCManager] Inbound answer handler aborted (destroyed).");
|
|
16948
17184
|
return;
|
|
16949
17185
|
}
|
|
16950
|
-
if (
|
|
16951
|
-
logger$
|
|
17186
|
+
if (isVertoByeInboundParamsGuard(vertoByeOrAccepted)) {
|
|
17187
|
+
logger$16.info("[WebRTCManager] Inbound call ended by remote before answer.");
|
|
16952
17188
|
this.callSession?.destroy();
|
|
16953
17189
|
} else if (!vertoByeOrAccepted) {
|
|
16954
|
-
logger$
|
|
17190
|
+
logger$16.info("[WebRTCManager] Inbound call rejected by user.");
|
|
16955
17191
|
try {
|
|
16956
17192
|
await this.bye("USER_BUSY");
|
|
16957
17193
|
} finally {
|
|
@@ -16959,19 +17195,19 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16959
17195
|
this.callSession?.destroy();
|
|
16960
17196
|
}
|
|
16961
17197
|
} else {
|
|
16962
|
-
logger$
|
|
17198
|
+
logger$16.debug("[WebRTCManager] Inbound call accepted, creating SDP answer");
|
|
16963
17199
|
const answerOptions = this.webRtcCallSession.answerMediaOptions;
|
|
16964
17200
|
try {
|
|
16965
17201
|
await rtcPeerConnController.acceptInbound(answerOptions);
|
|
16966
17202
|
} catch (error) {
|
|
16967
|
-
logger$
|
|
17203
|
+
logger$16.error("[WebRTCManager] Error creating inbound answer:", error);
|
|
16968
17204
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
16969
17205
|
}
|
|
16970
17206
|
}
|
|
16971
17207
|
}
|
|
16972
17208
|
setupVertoAttachHandler() {
|
|
16973
17209
|
this.subscribeTo(this.vertoAttach$, async (vertoAttach) => {
|
|
16974
|
-
logger$
|
|
17210
|
+
logger$16.debug("[WebRTCManager] Received Verto attach event for existing call:", vertoAttach);
|
|
16975
17211
|
const { callID } = vertoAttach;
|
|
16976
17212
|
await this.attachManager.attach({
|
|
16977
17213
|
nodeId: this.nodeId ?? void 0,
|
|
@@ -17043,17 +17279,17 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17043
17279
|
};
|
|
17044
17280
|
}
|
|
17045
17281
|
async sendLocalDescriptionOnceAccepted(vertoMessageRequest, rtcPeerConnectionController) {
|
|
17046
|
-
logger$
|
|
17282
|
+
logger$16.debug("[WebRTCManager] Waiting for call to be accepted or ended before sending answer");
|
|
17047
17283
|
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);
|
|
17048
17284
|
if (vertoByeOrAccepted === null) {
|
|
17049
|
-
logger$
|
|
17285
|
+
logger$16.debug("[WebRTCManager] Destroyed while waiting for call acceptance");
|
|
17050
17286
|
return;
|
|
17051
17287
|
}
|
|
17052
|
-
if (
|
|
17053
|
-
logger$
|
|
17288
|
+
if (isVertoByeInboundParamsGuard(vertoByeOrAccepted)) {
|
|
17289
|
+
logger$16.info("[WebRTCManager] Call ended before answer was sent.");
|
|
17054
17290
|
this.callSession?.destroy();
|
|
17055
17291
|
} else if (!vertoByeOrAccepted) {
|
|
17056
|
-
logger$
|
|
17292
|
+
logger$16.info("[WebRTCManager] Call was not accepted, sending verto.bye.");
|
|
17057
17293
|
try {
|
|
17058
17294
|
await this.bye("USER_BUSY");
|
|
17059
17295
|
} finally {
|
|
@@ -17061,14 +17297,14 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17061
17297
|
this.callSession?.destroy();
|
|
17062
17298
|
}
|
|
17063
17299
|
} else {
|
|
17064
|
-
logger$
|
|
17300
|
+
logger$16.debug("[WebRTCManager] Call accepted, sending answer");
|
|
17065
17301
|
try {
|
|
17066
|
-
this.
|
|
17302
|
+
this.emitMainSignalingStatus(rtcPeerConnectionController.id, "connecting");
|
|
17067
17303
|
await this.sendLocalDescription(vertoMessageRequest, rtcPeerConnectionController);
|
|
17068
17304
|
await rtcPeerConnectionController.updateAnswerStatus({ status: "sent" });
|
|
17069
17305
|
await this.attachManager.attach(this.buildAttachableCall());
|
|
17070
17306
|
} catch (error) {
|
|
17071
|
-
logger$
|
|
17307
|
+
logger$16.error("[WebRTCManager] Error sending Verto answer:", error);
|
|
17072
17308
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
17073
17309
|
await rtcPeerConnectionController.updateAnswerStatus({ status: "failed" });
|
|
17074
17310
|
}
|
|
@@ -17149,9 +17385,11 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17149
17385
|
await this.initAdditionalPeerConnection("screenshare", options);
|
|
17150
17386
|
}
|
|
17151
17387
|
async initAdditionalPeerConnection(propose, options) {
|
|
17388
|
+
const isScreenShare = propose === "screenshare";
|
|
17389
|
+
let firstPeerConnectionError;
|
|
17152
17390
|
let rtcPeerConnController = null;
|
|
17153
17391
|
try {
|
|
17154
|
-
this._screenShareStatus$.next("starting");
|
|
17392
|
+
if (isScreenShare) this._screenShareStatus$.next("starting");
|
|
17155
17393
|
rtcPeerConnController = new RTCPeerConnectionController({
|
|
17156
17394
|
...options,
|
|
17157
17395
|
...this.RTCPeerConnectionConfig,
|
|
@@ -17159,21 +17397,27 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17159
17397
|
webRTCApiProvider: this.webRTCApiProvider
|
|
17160
17398
|
}, void 0, this.deviceController);
|
|
17161
17399
|
this.setupLocalDescriptionHandler(rtcPeerConnController);
|
|
17162
|
-
if (
|
|
17400
|
+
if (isScreenShare) this._screenShareId = rtcPeerConnController.id;
|
|
17163
17401
|
this._rtcPeerConnectionsMap.set(rtcPeerConnController.id, rtcPeerConnController);
|
|
17164
17402
|
this._rtcPeerConnections$.next(Array.from(this._rtcPeerConnectionsMap.values()));
|
|
17165
17403
|
this.subscribeTo(rtcPeerConnController.errors$, (error) => {
|
|
17166
|
-
|
|
17404
|
+
firstPeerConnectionError ??= error;
|
|
17405
|
+
this.onError?.(error, { fatal: false });
|
|
17167
17406
|
});
|
|
17168
17407
|
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$)));
|
|
17169
|
-
this._screenShareStatus$.next("started");
|
|
17170
|
-
logger$
|
|
17408
|
+
if (isScreenShare) this._screenShareStatus$.next("started");
|
|
17409
|
+
logger$16.info(`[WebRTCManager] Additional peer connection connected (${propose}).`);
|
|
17171
17410
|
return rtcPeerConnController.id;
|
|
17172
17411
|
} catch (error) {
|
|
17173
|
-
logger$
|
|
17174
|
-
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
17412
|
+
logger$16.warn("[WebRTCManager] Error initializing additional peer connection:", error);
|
|
17175
17413
|
if (rtcPeerConnController) rtcPeerConnController.destroy();
|
|
17176
|
-
this._screenShareStatus$.next("none");
|
|
17414
|
+
if (isScreenShare) this._screenShareStatus$.next("none");
|
|
17415
|
+
if (firstPeerConnectionError) throw firstPeerConnectionError instanceof MediaAccessError && firstPeerConnectionError.originalError instanceof Error ? firstPeerConnectionError.originalError : firstPeerConnectionError;
|
|
17416
|
+
if (error instanceof import_cjs$15.EmptyError) {
|
|
17417
|
+
logger$16.debug("[WebRTCManager] Additional peer connection aborted before connecting.");
|
|
17418
|
+
return;
|
|
17419
|
+
}
|
|
17420
|
+
throw error instanceof Error ? error : new Error(String(error), { cause: error });
|
|
17177
17421
|
}
|
|
17178
17422
|
}
|
|
17179
17423
|
async removeInputDevices(id) {
|
|
@@ -17189,9 +17433,9 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17189
17433
|
if (removeTrack) return this.mainPeerConnection.stopTrackSender(removeTrack, { updateTransceiverDirection: true });
|
|
17190
17434
|
}
|
|
17191
17435
|
async removeScreenMedia() {
|
|
17192
|
-
if (!["starting", "started"].includes(this._screenShareStatus$.value)) logger$
|
|
17436
|
+
if (!["starting", "started"].includes(this._screenShareStatus$.value)) logger$16.warn("[WebRTCManager] No active screen share to stop.");
|
|
17193
17437
|
if (!this._screenShareId) {
|
|
17194
|
-
logger$
|
|
17438
|
+
logger$16.debug("[WebRTCManager] No screen share peer connection found.");
|
|
17195
17439
|
return;
|
|
17196
17440
|
}
|
|
17197
17441
|
this._screenShareStatus$.next("stopping");
|
|
@@ -17220,7 +17464,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17220
17464
|
dialogParams: this.dialogParams(rtcPeerConnController)
|
|
17221
17465
|
}));
|
|
17222
17466
|
} catch (error) {
|
|
17223
|
-
logger$
|
|
17467
|
+
logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto bye:", error);
|
|
17224
17468
|
throw error;
|
|
17225
17469
|
}
|
|
17226
17470
|
}
|
|
@@ -17238,7 +17482,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17238
17482
|
try {
|
|
17239
17483
|
await this.executeVerto(vertoInfoMessage);
|
|
17240
17484
|
} catch (error) {
|
|
17241
|
-
logger$
|
|
17485
|
+
logger$16.warn("[WebRTCManager] Error sending DTMF digits:", error);
|
|
17242
17486
|
throw error;
|
|
17243
17487
|
}
|
|
17244
17488
|
}
|
|
@@ -17249,10 +17493,10 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17249
17493
|
action: "transfer"
|
|
17250
17494
|
});
|
|
17251
17495
|
try {
|
|
17252
|
-
logger$
|
|
17496
|
+
logger$16.debug("[WebRTCManager] Transferring call with options:", options);
|
|
17253
17497
|
await this.executeVerto(message);
|
|
17254
17498
|
} catch (error) {
|
|
17255
|
-
logger$
|
|
17499
|
+
logger$16.error("[WebRTCManager] Error transferring call:", error);
|
|
17256
17500
|
throw error;
|
|
17257
17501
|
}
|
|
17258
17502
|
}
|
|
@@ -17269,7 +17513,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17269
17513
|
//#endregion
|
|
17270
17514
|
//#region src/controllers/RemoteAudioMeter.ts
|
|
17271
17515
|
var import_cjs$14 = require_cjs();
|
|
17272
|
-
const logger$
|
|
17516
|
+
const logger$15 = getLogger();
|
|
17273
17517
|
/**
|
|
17274
17518
|
* Read-only audio level meter for a remote MediaStream. Attaches an
|
|
17275
17519
|
* AnalyserNode to a MediaStreamAudioSourceNode so it observes the stream
|
|
@@ -17304,7 +17548,7 @@ var RemoteAudioMeter = class extends Destroyable {
|
|
|
17304
17548
|
try {
|
|
17305
17549
|
this._source.disconnect();
|
|
17306
17550
|
} catch (error) {
|
|
17307
|
-
logger$
|
|
17551
|
+
logger$15.debug("[RemoteAudioMeter] source disconnect warning:", error);
|
|
17308
17552
|
}
|
|
17309
17553
|
this._source = null;
|
|
17310
17554
|
this._stream = null;
|
|
@@ -17321,7 +17565,7 @@ var RemoteAudioMeter = class extends Destroyable {
|
|
|
17321
17565
|
this._source = null;
|
|
17322
17566
|
}
|
|
17323
17567
|
this._audioContext.close().catch((error) => {
|
|
17324
|
-
logger$
|
|
17568
|
+
logger$15.debug("[RemoteAudioMeter] audio context close warning:", error);
|
|
17325
17569
|
});
|
|
17326
17570
|
super.destroy();
|
|
17327
17571
|
}
|
|
@@ -17340,7 +17584,7 @@ var RemoteAudioMeter = class extends Destroyable {
|
|
|
17340
17584
|
//#endregion
|
|
17341
17585
|
//#region src/controllers/RTCStatsMonitor.ts
|
|
17342
17586
|
var import_cjs$13 = require_cjs();
|
|
17343
|
-
const logger$
|
|
17587
|
+
const logger$14 = getLogger();
|
|
17344
17588
|
const DEFAULT_POLLING_INTERVAL_MS = 1e3;
|
|
17345
17589
|
const DEFAULT_BASELINE_SAMPLES = 10;
|
|
17346
17590
|
const DEFAULT_NO_AUDIO_PACKET_THRESHOLD_MS = 2e3;
|
|
@@ -17430,9 +17674,9 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
17430
17674
|
const now = Date.now();
|
|
17431
17675
|
this.lastAudioPacketChangeTime = now;
|
|
17432
17676
|
this.lastVideoPacketChangeTime = now;
|
|
17433
|
-
logger$
|
|
17677
|
+
logger$14.debug("[RTCStatsMonitor] Starting stats monitoring");
|
|
17434
17678
|
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) => {
|
|
17435
|
-
logger$
|
|
17679
|
+
logger$14.warn("[RTCStatsMonitor] Failed to get stats:", err);
|
|
17436
17680
|
return import_cjs$13.EMPTY;
|
|
17437
17681
|
}))), (0, import_cjs$13.filter)(() => this.running), (0, import_cjs$13.map)((report) => this.extractSample(report))), (sample$1) => this._sample$.next(sample$1));
|
|
17438
17682
|
this.subscribeTo(this._sample$.pipe((0, import_cjs$13.take)(this.baselineSampleCount), (0, import_cjs$13.toArray)(), (0, import_cjs$13.map)((samples) => ({
|
|
@@ -17440,7 +17684,7 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
17440
17684
|
jitter: samples.reduce((s, b) => s + b.audioJitter, 0) / samples.length,
|
|
17441
17685
|
ready: true
|
|
17442
17686
|
}))), (baseline) => {
|
|
17443
|
-
logger$
|
|
17687
|
+
logger$14.debug(`[RTCStatsMonitor] Baseline established: rtt=${baseline.rtt.toFixed(1)}ms, jitter=${baseline.jitter.toFixed(1)}ms`);
|
|
17444
17688
|
this._baseline$.next(baseline);
|
|
17445
17689
|
});
|
|
17446
17690
|
this.subscribeTo(this._sample$.pipe((0, import_cjs$13.scan)((acc, sample$1) => ({
|
|
@@ -17477,10 +17721,10 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
17477
17721
|
stop() {
|
|
17478
17722
|
if (!this.running) return;
|
|
17479
17723
|
this.running = false;
|
|
17480
|
-
logger$
|
|
17724
|
+
logger$14.debug("[RTCStatsMonitor] Stopping stats monitoring");
|
|
17481
17725
|
}
|
|
17482
17726
|
destroy() {
|
|
17483
|
-
logger$
|
|
17727
|
+
logger$14.debug("[RTCStatsMonitor] Destroying RTCStatsMonitor");
|
|
17484
17728
|
this.stop();
|
|
17485
17729
|
super.destroy();
|
|
17486
17730
|
}
|
|
@@ -17609,7 +17853,7 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
17609
17853
|
//#endregion
|
|
17610
17854
|
//#region src/managers/CallRecoveryManager.ts
|
|
17611
17855
|
var import_cjs$12 = require_cjs();
|
|
17612
|
-
const logger$
|
|
17856
|
+
const logger$13 = getLogger();
|
|
17613
17857
|
const DEFAULT_DEBOUNCE_TIME_MS = 2e3;
|
|
17614
17858
|
const DEFAULT_COOLDOWN_MS = 1e4;
|
|
17615
17859
|
const DEFAULT_ICE_GRACE_PERIOD_MS = 3e3;
|
|
@@ -17706,10 +17950,10 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17706
17950
|
*/
|
|
17707
17951
|
async requestIceRestart() {
|
|
17708
17952
|
if (this._recoveryState$.value === "recovering") {
|
|
17709
|
-
logger$
|
|
17953
|
+
logger$13.info("CallRecoveryManager: manual ICE restart skipped — recovery already in progress");
|
|
17710
17954
|
return;
|
|
17711
17955
|
}
|
|
17712
|
-
logger$
|
|
17956
|
+
logger$13.info("CallRecoveryManager: manual ICE restart requested");
|
|
17713
17957
|
this.transitionTo("recovering");
|
|
17714
17958
|
await this.executeIceRestart(false);
|
|
17715
17959
|
this.startCooldown();
|
|
@@ -17725,7 +17969,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17725
17969
|
* WebSocket reconnect or call state recovers to 'connected'.
|
|
17726
17970
|
*/
|
|
17727
17971
|
reset() {
|
|
17728
|
-
logger$
|
|
17972
|
+
logger$13.info("CallRecoveryManager: resetting counters");
|
|
17729
17973
|
this._attemptCount = 0;
|
|
17730
17974
|
this._keyframeBurstCount = 0;
|
|
17731
17975
|
this._keyframeBurstStart = 0;
|
|
@@ -17740,7 +17984,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17740
17984
|
*/
|
|
17741
17985
|
notifyModifyFailed() {
|
|
17742
17986
|
if (this._recoveryState$.value === "cooldown" || this._recoveryState$.value === "idle") {
|
|
17743
|
-
logger$
|
|
17987
|
+
logger$13.info("CallRecoveryManager: verto.modify failed — re-entering recovery");
|
|
17744
17988
|
this._cooldownUntil = 0;
|
|
17745
17989
|
this.transitionTo("idle");
|
|
17746
17990
|
this.pushTrigger({
|
|
@@ -17764,7 +18008,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17764
18008
|
reason: `bandwidth ${bitrateKbps}kbps below threshold ${this._config.degradationBitrateThreshold}kbps`,
|
|
17765
18009
|
timestamp: Date.now()
|
|
17766
18010
|
});
|
|
17767
|
-
logger$
|
|
18011
|
+
logger$13.warn(`CallRecoveryManager: disabling video — bandwidth ${bitrateKbps}kbps < ${this._config.degradationBitrateThreshold}kbps`);
|
|
17768
18012
|
} else if (wasConstrained && bitrateKbps >= this._config.degradationRecoveryThreshold) {
|
|
17769
18013
|
this._bandwidthConstrained$.next(false);
|
|
17770
18014
|
this._callbacks.enableVideo();
|
|
@@ -17773,7 +18017,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17773
18017
|
reason: `bandwidth ${bitrateKbps}kbps recovered above ${this._config.degradationRecoveryThreshold}kbps`,
|
|
17774
18018
|
timestamp: Date.now()
|
|
17775
18019
|
});
|
|
17776
|
-
logger$
|
|
18020
|
+
logger$13.info(`CallRecoveryManager: restoring video — bandwidth ${bitrateKbps}kbps >= ${this._config.degradationRecoveryThreshold}kbps`);
|
|
17777
18021
|
}
|
|
17778
18022
|
}
|
|
17779
18023
|
/**
|
|
@@ -17792,14 +18036,14 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17792
18036
|
handleWebSocketReconnect() {
|
|
17793
18037
|
const pcState = this._callbacks.getPeerConnectionState();
|
|
17794
18038
|
if (pcState === "connected" || pcState === "completed") {
|
|
17795
|
-
logger$
|
|
18039
|
+
logger$13.info("CallRecoveryManager: signal-only reconnect — peer connection still alive");
|
|
17796
18040
|
this.emitEvent({
|
|
17797
18041
|
action: "signal_reconnect",
|
|
17798
18042
|
reason: "WebSocket reconnected, peer connection still connected",
|
|
17799
18043
|
timestamp: Date.now()
|
|
17800
18044
|
});
|
|
17801
18045
|
} else {
|
|
17802
|
-
logger$
|
|
18046
|
+
logger$13.info("CallRecoveryManager: full reconnect — peer connection also down");
|
|
17803
18047
|
this.emitEvent({
|
|
17804
18048
|
action: "full_reconnect",
|
|
17805
18049
|
reason: "WebSocket reconnected, peer connection not connected — ICE restart needed",
|
|
@@ -17823,7 +18067,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17823
18067
|
}), (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$))), {
|
|
17824
18068
|
next: () => {},
|
|
17825
18069
|
error: (err) => {
|
|
17826
|
-
logger$
|
|
18070
|
+
logger$13.error("CallRecoveryManager: pipeline error", err);
|
|
17827
18071
|
this.transitionTo("idle");
|
|
17828
18072
|
}
|
|
17829
18073
|
});
|
|
@@ -17850,27 +18094,27 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17850
18094
|
reason: `no packet loss for ${this._config.packetLossRecoveryDelaySec}s — restoring video`,
|
|
17851
18095
|
timestamp: Date.now()
|
|
17852
18096
|
});
|
|
17853
|
-
logger$
|
|
18097
|
+
logger$13.info(`CallRecoveryManager: restoring video — no packet loss for ${this._config.packetLossRecoveryDelaySec}s`);
|
|
17854
18098
|
});
|
|
17855
18099
|
}
|
|
17856
18100
|
passGateChecks(signalingReady) {
|
|
17857
18101
|
if (this._callbacks.isNegotiating()) {
|
|
17858
|
-
logger$
|
|
18102
|
+
logger$13.debug("CallRecoveryManager: gate blocked — negotiation in progress");
|
|
17859
18103
|
this.transitionTo("idle");
|
|
17860
18104
|
return false;
|
|
17861
18105
|
}
|
|
17862
18106
|
if (!signalingReady) {
|
|
17863
|
-
logger$
|
|
18107
|
+
logger$13.debug("CallRecoveryManager: gate blocked — signaling not ready");
|
|
17864
18108
|
this.transitionTo("idle");
|
|
17865
18109
|
return false;
|
|
17866
18110
|
}
|
|
17867
18111
|
if (!this._callbacks.isCallConnected()) {
|
|
17868
|
-
logger$
|
|
18112
|
+
logger$13.debug("CallRecoveryManager: gate blocked — call not connected");
|
|
17869
18113
|
this.transitionTo("idle");
|
|
17870
18114
|
return false;
|
|
17871
18115
|
}
|
|
17872
18116
|
if (this.isCooldownActive()) {
|
|
17873
|
-
logger$
|
|
18117
|
+
logger$13.debug("CallRecoveryManager: gate blocked — cooldown active");
|
|
17874
18118
|
this.transitionTo("cooldown");
|
|
17875
18119
|
return false;
|
|
17876
18120
|
}
|
|
@@ -17881,9 +18125,9 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17881
18125
|
}
|
|
17882
18126
|
executeTieredRecovery(trigger) {
|
|
17883
18127
|
this.transitionTo("recovering");
|
|
17884
|
-
logger$
|
|
18128
|
+
logger$13.info(`CallRecoveryManager: starting tiered recovery — source=${trigger.source} detail=${trigger.detail}`);
|
|
17885
18129
|
return (0, import_cjs$12.from)(this.runTiers(trigger)).pipe((0, import_cjs$12.tap)(() => this.startCooldown()), (0, import_cjs$12.catchError)((err) => {
|
|
17886
|
-
logger$
|
|
18130
|
+
logger$13.error("CallRecoveryManager: tiered recovery failed", err);
|
|
17887
18131
|
this.startCooldown();
|
|
17888
18132
|
return import_cjs$12.EMPTY;
|
|
17889
18133
|
}));
|
|
@@ -17891,7 +18135,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17891
18135
|
async runTiers(trigger) {
|
|
17892
18136
|
this.executeKeyframe(trigger.detail);
|
|
17893
18137
|
if (trigger.issueType && DEGRADATION_ONLY_ISSUES.has(trigger.issueType)) {
|
|
17894
|
-
logger$
|
|
18138
|
+
logger$13.debug(`CallRecoveryManager: degradation-only issue (${trigger.issueType}) — Tier 1 only, skipping ICE restart`);
|
|
17895
18139
|
return;
|
|
17896
18140
|
}
|
|
17897
18141
|
if (this._attemptCount < this._config.maxAttempts) {
|
|
@@ -17908,13 +18152,13 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17908
18152
|
maxAttempts: this._config.maxAttempts,
|
|
17909
18153
|
timestamp: Date.now()
|
|
17910
18154
|
});
|
|
17911
|
-
logger$
|
|
18155
|
+
logger$13.warn("CallRecoveryManager: max recovery attempts reached");
|
|
17912
18156
|
}
|
|
17913
18157
|
}
|
|
17914
18158
|
executeKeyframe(reason) {
|
|
17915
18159
|
const now = Date.now();
|
|
17916
18160
|
if (now < this._keyframeCooldownUntil) {
|
|
17917
|
-
logger$
|
|
18161
|
+
logger$13.debug("CallRecoveryManager: keyframe request skipped — cooldown active");
|
|
17918
18162
|
return;
|
|
17919
18163
|
}
|
|
17920
18164
|
if (now - this._keyframeBurstStart > this._config.keyframeBurstWindowMs) {
|
|
@@ -17923,7 +18167,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17923
18167
|
}
|
|
17924
18168
|
if (this._keyframeBurstCount >= this._config.keyframeMaxBurst) {
|
|
17925
18169
|
this._keyframeCooldownUntil = now + this._config.keyframeCooldownMs;
|
|
17926
|
-
logger$
|
|
18170
|
+
logger$13.debug(`CallRecoveryManager: keyframe burst limit reached (${this._config.keyframeMaxBurst}), cooldown until ${this._keyframeCooldownUntil}`);
|
|
17927
18171
|
return;
|
|
17928
18172
|
}
|
|
17929
18173
|
this._keyframeBurstCount += 1;
|
|
@@ -17933,12 +18177,12 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17933
18177
|
reason,
|
|
17934
18178
|
timestamp: now
|
|
17935
18179
|
});
|
|
17936
|
-
logger$
|
|
18180
|
+
logger$13.debug(`CallRecoveryManager: keyframe requested (burst ${this._keyframeBurstCount}/${this._config.keyframeMaxBurst})`);
|
|
17937
18181
|
}
|
|
17938
18182
|
async executeIceRestart(relayOnly) {
|
|
17939
18183
|
this._attemptCount += 1;
|
|
17940
18184
|
const tier = relayOnly ? "Tier 3 (relay-only)" : "Tier 2 (standard)";
|
|
17941
|
-
logger$
|
|
18185
|
+
logger$13.info(`CallRecoveryManager: ${tier} ICE restart — attempt ${this._attemptCount}/${this._config.maxAttempts}`);
|
|
17942
18186
|
this.emitEvent({
|
|
17943
18187
|
action: "reinvite_started",
|
|
17944
18188
|
reason: `${tier} ICE restart`,
|
|
@@ -17955,7 +18199,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17955
18199
|
maxAttempts: this._config.maxAttempts,
|
|
17956
18200
|
timestamp: Date.now()
|
|
17957
18201
|
});
|
|
17958
|
-
logger$
|
|
18202
|
+
logger$13.info(`CallRecoveryManager: ${tier} ICE restart succeeded`);
|
|
17959
18203
|
this._attemptCount = 0;
|
|
17960
18204
|
return true;
|
|
17961
18205
|
}
|
|
@@ -17966,7 +18210,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17966
18210
|
maxAttempts: this._config.maxAttempts,
|
|
17967
18211
|
timestamp: Date.now()
|
|
17968
18212
|
});
|
|
17969
|
-
logger$
|
|
18213
|
+
logger$13.warn(`CallRecoveryManager: ${tier} ICE restart failed`);
|
|
17970
18214
|
return false;
|
|
17971
18215
|
} catch {
|
|
17972
18216
|
this.emitEvent({
|
|
@@ -17976,7 +18220,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17976
18220
|
maxAttempts: this._config.maxAttempts,
|
|
17977
18221
|
timestamp: Date.now()
|
|
17978
18222
|
});
|
|
17979
|
-
logger$
|
|
18223
|
+
logger$13.warn(`CallRecoveryManager: ${tier} ICE restart timed out`);
|
|
17980
18224
|
return false;
|
|
17981
18225
|
}
|
|
17982
18226
|
}
|
|
@@ -17997,7 +18241,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17997
18241
|
transitionTo(state) {
|
|
17998
18242
|
const prev = this._recoveryState$.value;
|
|
17999
18243
|
if (prev !== state) {
|
|
18000
|
-
logger$
|
|
18244
|
+
logger$13.debug(`CallRecoveryManager: state ${prev} -> ${state}`);
|
|
18001
18245
|
this._recoveryState$.next(state);
|
|
18002
18246
|
}
|
|
18003
18247
|
}
|
|
@@ -18100,7 +18344,7 @@ function mosToQualityLevel(mos) {
|
|
|
18100
18344
|
//#endregion
|
|
18101
18345
|
//#region src/core/entities/Call.ts
|
|
18102
18346
|
var import_cjs$11 = require_cjs();
|
|
18103
|
-
const logger$
|
|
18347
|
+
const logger$12 = getLogger();
|
|
18104
18348
|
/**
|
|
18105
18349
|
* Ratio between the critical and warning RTT spike multipliers.
|
|
18106
18350
|
* Warning threshold = baseline * warningMultiplier (default 3x)
|
|
@@ -18118,7 +18362,7 @@ const fromDestinationParams = (destination) => {
|
|
|
18118
18362
|
});
|
|
18119
18363
|
return params;
|
|
18120
18364
|
} catch (error) {
|
|
18121
|
-
logger$
|
|
18365
|
+
logger$12.warn(`Failed to parse destination URI: ${destination}`, error);
|
|
18122
18366
|
return {};
|
|
18123
18367
|
}
|
|
18124
18368
|
};
|
|
@@ -18252,7 +18496,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18252
18496
|
/** Toggles the call lock state, preventing or allowing new participants from joining. */
|
|
18253
18497
|
async toggleLock() {
|
|
18254
18498
|
const method = this.locked ? "call.unlock" : "call.lock";
|
|
18255
|
-
await this.executeMethod(this.
|
|
18499
|
+
await this.executeMethod(this.callSelf, method, {});
|
|
18256
18500
|
}
|
|
18257
18501
|
/**
|
|
18258
18502
|
* Toggles the hold state of the call (pauses/resumes local media transmission).
|
|
@@ -18301,14 +18545,24 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18301
18545
|
*
|
|
18302
18546
|
* Constructs call context (node_id, call_id, member_id) and sends the RPC request.
|
|
18303
18547
|
*
|
|
18304
|
-
* @param target - Target
|
|
18548
|
+
* @param target - Target {@link MemberTarget} triple, or the local member's
|
|
18549
|
+
* ID string for self-operations (any other string is rejected — a bare
|
|
18550
|
+
* member id cannot carry the remote member's own call context).
|
|
18305
18551
|
* @param method - Verto method name (e.g. `'call.mute'`, `'call.member.remove'`).
|
|
18306
18552
|
* @param args - Parameters for the RPC method.
|
|
18307
18553
|
* @returns The RPC response.
|
|
18554
|
+
* @throws {CallNotReadyError} If the call has no self member context yet.
|
|
18555
|
+
* @throws {InvalidParams} If a string target is not the local member's ID.
|
|
18308
18556
|
* @throws {JSONRPCError} If the RPC call returns an error.
|
|
18309
18557
|
*/
|
|
18310
18558
|
async executeMethod(target, method, args) {
|
|
18311
|
-
const
|
|
18559
|
+
const self = this.callSelf;
|
|
18560
|
+
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}`);
|
|
18561
|
+
const params = {
|
|
18562
|
+
...args,
|
|
18563
|
+
self,
|
|
18564
|
+
target: typeof target === "string" ? self : target
|
|
18565
|
+
};
|
|
18312
18566
|
const request = buildRPCRequest({
|
|
18313
18567
|
method,
|
|
18314
18568
|
params
|
|
@@ -18318,29 +18572,27 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18318
18572
|
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);
|
|
18319
18573
|
return response;
|
|
18320
18574
|
} catch (error) {
|
|
18321
|
-
logger$
|
|
18575
|
+
logger$12.error(`[Call] Error executing method ${method} with params`, params, error);
|
|
18322
18576
|
throw error;
|
|
18323
18577
|
}
|
|
18324
18578
|
}
|
|
18325
|
-
|
|
18326
|
-
|
|
18327
|
-
|
|
18328
|
-
|
|
18329
|
-
|
|
18330
|
-
|
|
18331
|
-
|
|
18332
|
-
|
|
18333
|
-
|
|
18334
|
-
|
|
18335
|
-
|
|
18579
|
+
/**
|
|
18580
|
+
* The local leg's member triple — sent as `self` in every member RPC
|
|
18581
|
+
* envelope, and as the `target` of call-scoped self-operations (e.g. lock,
|
|
18582
|
+
* layout).
|
|
18583
|
+
*
|
|
18584
|
+
* @throws {CallNotReadyError} Before `call.joined` delivers the self member
|
|
18585
|
+
* context (`selfId`/`nodeId`) — an RPC without it cannot be routed, so fail
|
|
18586
|
+
* fast instead of sending a doomed request.
|
|
18587
|
+
*/
|
|
18588
|
+
get callSelf() {
|
|
18589
|
+
const node_id = this.nodeId;
|
|
18590
|
+
const member_id = this.vertoManager.selfId;
|
|
18591
|
+
if (!node_id || !member_id) throw new CallNotReadyError(this.id);
|
|
18336
18592
|
return {
|
|
18337
|
-
|
|
18338
|
-
|
|
18339
|
-
|
|
18340
|
-
node_id: this.nodeId ?? "",
|
|
18341
|
-
call_id: this.id,
|
|
18342
|
-
member_id: target
|
|
18343
|
-
}
|
|
18593
|
+
node_id,
|
|
18594
|
+
call_id: this.id,
|
|
18595
|
+
member_id
|
|
18344
18596
|
};
|
|
18345
18597
|
}
|
|
18346
18598
|
/** Observable of the current call status (e.g. `'ringing'`, `'connected'`). */
|
|
@@ -18521,9 +18773,9 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18521
18773
|
*/
|
|
18522
18774
|
initResilienceSubsystems() {
|
|
18523
18775
|
const pc = this.rtcPeerConnection;
|
|
18524
|
-
logger$
|
|
18776
|
+
logger$12.debug(`[Call] initResilienceSubsystems: pc=${pc ? "exists" : "undefined"}, connectionState=${pc?.connectionState}`);
|
|
18525
18777
|
if (!pc) {
|
|
18526
|
-
logger$
|
|
18778
|
+
logger$12.warn("[Call] No peer connection available, skipping resilience init");
|
|
18527
18779
|
return;
|
|
18528
18780
|
}
|
|
18529
18781
|
try {
|
|
@@ -18558,14 +18810,14 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18558
18810
|
disableVideo: () => {
|
|
18559
18811
|
try {
|
|
18560
18812
|
this.vertoManager.muteMainVideoInputDevice();
|
|
18561
|
-
logger$
|
|
18813
|
+
logger$12.debug("[Call] Recovery manager disabled video");
|
|
18562
18814
|
} catch {
|
|
18563
|
-
logger$
|
|
18815
|
+
logger$12.debug("[Call] Recovery manager failed to disable video");
|
|
18564
18816
|
}
|
|
18565
18817
|
},
|
|
18566
18818
|
enableVideo: () => {
|
|
18567
18819
|
this.vertoManager.unmuteMainVideoInputDevice().catch(() => {
|
|
18568
|
-
logger$
|
|
18820
|
+
logger$12.debug("[Call] Recovery manager failed to enable video");
|
|
18569
18821
|
});
|
|
18570
18822
|
},
|
|
18571
18823
|
isNegotiating: () => this.vertoManager.mainPeerConnection.isNegotiating,
|
|
@@ -18615,7 +18867,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18615
18867
|
this.subscribeTo(this._recoveryManager.recoveryEvent$, (event) => {
|
|
18616
18868
|
this._recoveryEvent$.next(event);
|
|
18617
18869
|
if (event.action === "max_attempts_reached") {
|
|
18618
|
-
logger$
|
|
18870
|
+
logger$12.warn("[Call] All recovery attempts exhausted, terminating call");
|
|
18619
18871
|
this.emitError({
|
|
18620
18872
|
kind: "network",
|
|
18621
18873
|
fatal: true,
|
|
@@ -18635,13 +18887,13 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18635
18887
|
else if (event.type === "online") this._recoveryManager?.handleWebSocketReconnect();
|
|
18636
18888
|
});
|
|
18637
18889
|
this.subscribeTo(this.clientSession.authenticated$.pipe((0, import_cjs$11.skip)(1), (0, import_cjs$11.filter)(Boolean)), () => {
|
|
18638
|
-
logger$
|
|
18890
|
+
logger$12.debug("[Call] WebSocket reconnected — notifying recovery manager");
|
|
18639
18891
|
this._recoveryManager?.handleWebSocketReconnect();
|
|
18640
18892
|
});
|
|
18641
18893
|
this._statsMonitor.start();
|
|
18642
|
-
logger$
|
|
18894
|
+
logger$12.debug("[Call] Resilience subsystems initialized for call", this.id);
|
|
18643
18895
|
} catch (error) {
|
|
18644
|
-
logger$
|
|
18896
|
+
logger$12.warn("[Call] Failed to initialize resilience subsystems:", error);
|
|
18645
18897
|
}
|
|
18646
18898
|
}
|
|
18647
18899
|
/**
|
|
@@ -18724,19 +18976,19 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18724
18976
|
}
|
|
18725
18977
|
isCallSessionEvent(event) {
|
|
18726
18978
|
try {
|
|
18727
|
-
logger$
|
|
18979
|
+
logger$12.debug("[Call] Checking if event is for this call session:", event);
|
|
18728
18980
|
const callId = getValueFrom(event, "params.params.callID") ?? getValueFrom(event, "params.call_id");
|
|
18729
18981
|
const roomSessionId = getValueFrom(event, "params.room_session_id");
|
|
18730
|
-
logger$
|
|
18982
|
+
logger$12.debug(`[Call] Extracted session identifiers callID: ${callId} and roomSessionID: ${roomSessionId} from event:`);
|
|
18731
18983
|
return callId === this.id || !!callId && this.callEventsManager.isCallIdValid(callId) || !!roomSessionId && this.callEventsManager.isRoomSessionIdValid(roomSessionId);
|
|
18732
18984
|
} catch (error) {
|
|
18733
|
-
logger$
|
|
18985
|
+
logger$12.error("[Call] Error checking if event is for this call session:", error);
|
|
18734
18986
|
return false;
|
|
18735
18987
|
}
|
|
18736
18988
|
}
|
|
18737
18989
|
get callSessionEvents$() {
|
|
18738
18990
|
return this.cachedObservable("callSessionEvents$", () => this.clientSession.signalingEvent$.pipe((0, import_cjs$11.filter)((event) => this.isCallSessionEvent(event)), (0, import_cjs$11.tap)((event) => {
|
|
18739
|
-
logger$
|
|
18991
|
+
logger$12.debug("[Call] Received call session event:", event);
|
|
18740
18992
|
}), (0, import_cjs$11.takeUntil)(this.destroyed$), (0, import_cjs$11.share)()));
|
|
18741
18993
|
}
|
|
18742
18994
|
/** Observable of call-updated events. */
|
|
@@ -18806,16 +19058,16 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18806
19058
|
this._customSubscriptions.set(eventType, filtered$);
|
|
18807
19059
|
}, (error) => {
|
|
18808
19060
|
this._customSubscriptions.delete(eventType);
|
|
18809
|
-
logger$
|
|
19061
|
+
logger$12.warn(`[Call] verto.subscribe for '${eventType}' failed, not caching:`, error);
|
|
18810
19062
|
});
|
|
18811
19063
|
this._customSubscriptions.set(eventType, filtered$);
|
|
18812
19064
|
return filtered$;
|
|
18813
19065
|
}
|
|
18814
19066
|
get webrtcMessages$() {
|
|
18815
|
-
return this.cachedObservable("webrtcMessages$", () => this.callSessionEvents$.pipe(filterAs(isWebrtcMessageMetadata, "params"), (0, import_cjs$11.tap)((event) => logger$
|
|
19067
|
+
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)()));
|
|
18816
19068
|
}
|
|
18817
19069
|
get callEvent$() {
|
|
18818
|
-
return this.cachedObservable("callEvent$", () => this.callSessionEvents$.pipe(filterAs(isSignalwireCallMetadata, "params"), (0, import_cjs$11.tap)((event) => logger$
|
|
19070
|
+
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)()));
|
|
18819
19071
|
}
|
|
18820
19072
|
get layoutEvent$() {
|
|
18821
19073
|
return this.cachedObservable("layoutEvent$", () => this.callEvent$.pipe(filterAs(isLayoutChangedMetadata, "params")));
|
|
@@ -18893,11 +19145,27 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18893
19145
|
return this.deferEmission(this._answered$.asObservable());
|
|
18894
19146
|
}
|
|
18895
19147
|
/**
|
|
18896
|
-
* Sets the call layout and participant positions.
|
|
19148
|
+
* Sets the call layout and, optionally, individual participant positions.
|
|
19149
|
+
*
|
|
19150
|
+
* The gateway `call.layout.set` DTO has **no** `positions` member, so when
|
|
19151
|
+
* `positions` is provided this method issues a `call.member.position.set`
|
|
19152
|
+
* request per member (via {@link Participant.setPosition}, which keys each
|
|
19153
|
+
* position by that member's own call context) alongside `call.layout.set`
|
|
19154
|
+
* (issue #19400, Flag #6).
|
|
19155
|
+
*
|
|
19156
|
+
* **These operations are NOT atomic.** The layout is applied first, then each
|
|
19157
|
+
* member position sequentially, so members may briefly flash into their
|
|
19158
|
+
* default slots before being moved to the requested positions. Targeted
|
|
19159
|
+
* members are validated upfront, though: when any of them has no
|
|
19160
|
+
* {@link Participant.target | member call context} yet, the whole call
|
|
19161
|
+
* rejects before any request is sent and the layout is left unchanged.
|
|
18897
19162
|
*
|
|
18898
19163
|
* @param layout - Layout name (must be one of {@link layouts}).
|
|
18899
|
-
* @param positions -
|
|
19164
|
+
* @param positions - Optional map of member IDs to {@link VideoPosition} values.
|
|
19165
|
+
* When omitted or empty, only the layout is changed.
|
|
18900
19166
|
* @throws {InvalidParams} If the layout is not in the available {@link layouts}.
|
|
19167
|
+
* @throws {ParticipantNotReadyError} If a targeted member's call context has
|
|
19168
|
+
* not been received yet — thrown before any request is sent.
|
|
18901
19169
|
*
|
|
18902
19170
|
* @example
|
|
18903
19171
|
* ```ts
|
|
@@ -18908,11 +19176,19 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18908
19176
|
*/
|
|
18909
19177
|
async setLayout(layout, positions) {
|
|
18910
19178
|
if (!this.layouts.includes(layout)) throw new InvalidParams(`Layout ${layout} is not available in the current call layouts: ${this.layouts.join(", ")}`);
|
|
19179
|
+
const targets = [];
|
|
19180
|
+
for (const [memberId, position] of Object.entries(positions ?? {})) {
|
|
19181
|
+
const participant = this.participants.find((p) => p.id === memberId);
|
|
19182
|
+
if (!participant) {
|
|
19183
|
+
logger$12.warn(`[Call] setLayout: member ${memberId} not found in participants; skipping position ${position}`);
|
|
19184
|
+
continue;
|
|
19185
|
+
}
|
|
19186
|
+
participant.target;
|
|
19187
|
+
targets.push([participant, position]);
|
|
19188
|
+
}
|
|
18911
19189
|
const selfId = await (0, import_cjs$11.firstValueFrom)(this.selfId$.pipe((0, import_cjs$11.filter)((id) => id !== null)));
|
|
18912
|
-
await this.executeMethod(selfId, "call.layout.set", {
|
|
18913
|
-
|
|
18914
|
-
positions
|
|
18915
|
-
});
|
|
19190
|
+
await this.executeMethod(selfId, "call.layout.set", { layout });
|
|
19191
|
+
for (const [participant, position] of targets) await participant.setPosition(position);
|
|
18916
19192
|
}
|
|
18917
19193
|
/**
|
|
18918
19194
|
* Transfers the call to another destination.
|
|
@@ -18944,7 +19220,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18944
19220
|
setLocalMicrophoneGain(value) {
|
|
18945
19221
|
const pipeline = this.vertoManager.ensureLocalAudioPipeline();
|
|
18946
19222
|
if (!pipeline) {
|
|
18947
|
-
logger$
|
|
19223
|
+
logger$12.warn("[Call] setLocalMicrophoneGain: audio pipeline unavailable");
|
|
18948
19224
|
return;
|
|
18949
19225
|
}
|
|
18950
19226
|
const percent = Math.max(0, Math.min(200, value));
|
|
@@ -18989,7 +19265,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18989
19265
|
enablePushToTalk() {
|
|
18990
19266
|
const pipeline = this.vertoManager.ensureLocalAudioPipeline();
|
|
18991
19267
|
if (!pipeline) {
|
|
18992
|
-
logger$
|
|
19268
|
+
logger$12.warn("[Call] enablePushToTalk: audio pipeline unavailable");
|
|
18993
19269
|
return;
|
|
18994
19270
|
}
|
|
18995
19271
|
pipeline.setPTTActive(false);
|
|
@@ -19086,6 +19362,7 @@ function inferCallErrorKind(error) {
|
|
|
19086
19362
|
if (error instanceof RPCTimeoutError) return "timeout";
|
|
19087
19363
|
if (error instanceof JSONRPCError) return "signaling";
|
|
19088
19364
|
if (error instanceof MediaTrackError) return "media";
|
|
19365
|
+
if (error instanceof MediaAccessError) return "media";
|
|
19089
19366
|
if (error instanceof WebSocketConnectionError || error instanceof TransportConnectionError) return "network";
|
|
19090
19367
|
return "internal";
|
|
19091
19368
|
}
|
|
@@ -19102,6 +19379,7 @@ const RECOVERABLE_RPC_CODES = new Set([
|
|
|
19102
19379
|
function isFatalError(error) {
|
|
19103
19380
|
if (error instanceof VertoPongError) return false;
|
|
19104
19381
|
if (error instanceof MediaTrackError) return false;
|
|
19382
|
+
if (error instanceof MediaAccessError) return error.fatal;
|
|
19105
19383
|
if (error instanceof RPCTimeoutError) return false;
|
|
19106
19384
|
if (error instanceof JSONRPCError && RECOVERABLE_RPC_CODES.has(error.code)) return false;
|
|
19107
19385
|
return true;
|
|
@@ -19127,10 +19405,10 @@ var CallFactory = class {
|
|
|
19127
19405
|
return {
|
|
19128
19406
|
vertoManager: new WebRTCVertoManager(callInstance, this.attachManager, this.deviceController, this.webRTCApiProvider, {
|
|
19129
19407
|
nodeId: options.nodeId,
|
|
19130
|
-
onError: (error) => {
|
|
19408
|
+
onError: (error, options$1) => {
|
|
19131
19409
|
const callError = {
|
|
19132
19410
|
kind: inferCallErrorKind(error),
|
|
19133
|
-
fatal: isFatalError(error),
|
|
19411
|
+
fatal: options$1?.fatal ?? isFatalError(error),
|
|
19134
19412
|
error,
|
|
19135
19413
|
callId: callInstance.id
|
|
19136
19414
|
};
|
|
@@ -19152,7 +19430,7 @@ var CallFactory = class {
|
|
|
19152
19430
|
//#endregion
|
|
19153
19431
|
//#region src/behaviors/Collection.ts
|
|
19154
19432
|
var import_cjs$10 = require_cjs();
|
|
19155
|
-
const logger$
|
|
19433
|
+
const logger$11 = getLogger();
|
|
19156
19434
|
var Fetcher = class {
|
|
19157
19435
|
constructor(endpoint, params, http) {
|
|
19158
19436
|
this.endpoint = endpoint;
|
|
@@ -19176,7 +19454,7 @@ var Fetcher = class {
|
|
|
19176
19454
|
this.hasMore = !!this.nextUrl;
|
|
19177
19455
|
return result.data.filter(this.filter).map(this.mapper);
|
|
19178
19456
|
}
|
|
19179
|
-
logger$
|
|
19457
|
+
logger$11.error("Failed to fetch entity");
|
|
19180
19458
|
return [];
|
|
19181
19459
|
}
|
|
19182
19460
|
async id(v) {
|
|
@@ -19252,7 +19530,7 @@ var EntityCollection = class extends Destroyable {
|
|
|
19252
19530
|
this._hasMore$.next(this.fetchController.hasMore ?? false);
|
|
19253
19531
|
this._loading$.next(false);
|
|
19254
19532
|
} catch (error) {
|
|
19255
|
-
logger$
|
|
19533
|
+
logger$11.error(`Failed to fetch initial collection data`, error);
|
|
19256
19534
|
this._hasMore$.next(this.fetchController.hasMore ?? false);
|
|
19257
19535
|
this._loading$.next(false);
|
|
19258
19536
|
this.onError?.(new CollectionFetchError("fetchMore", error));
|
|
@@ -19266,7 +19544,7 @@ var EntityCollection = class extends Destroyable {
|
|
|
19266
19544
|
if (data) this.upsertData(data);
|
|
19267
19545
|
return data;
|
|
19268
19546
|
} catch (error) {
|
|
19269
|
-
logger$
|
|
19547
|
+
logger$11.error(`Failed to fetch data for (${String(key)}:${String(value)}) :`, error);
|
|
19270
19548
|
this._loading$.next(false);
|
|
19271
19549
|
this.onError?.(new CollectionFetchError(`tryFetch(${String(key)})`, error));
|
|
19272
19550
|
}
|
|
@@ -19515,13 +19793,13 @@ var Address = class extends Destroyable {
|
|
|
19515
19793
|
//#endregion
|
|
19516
19794
|
//#region src/core/utils.ts
|
|
19517
19795
|
var import_cjs$8 = require_cjs();
|
|
19518
|
-
const logger$
|
|
19796
|
+
const logger$10 = getLogger();
|
|
19519
19797
|
const isRPCConnectResult = (e) => {
|
|
19520
|
-
logger$
|
|
19798
|
+
logger$10.debug("isRPCConnectResult check:", e);
|
|
19521
19799
|
if (!e || typeof e !== "object") return false;
|
|
19522
19800
|
const result = e;
|
|
19523
19801
|
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";
|
|
19524
|
-
logger$
|
|
19802
|
+
logger$10.debug("isRPCConnectResult check result:", is);
|
|
19525
19803
|
return is;
|
|
19526
19804
|
};
|
|
19527
19805
|
var PendingRPC = class PendingRPC {
|
|
@@ -19530,7 +19808,7 @@ var PendingRPC = class PendingRPC {
|
|
|
19530
19808
|
}
|
|
19531
19809
|
constructor(request, responses$, options) {
|
|
19532
19810
|
this.id = v4_default();
|
|
19533
|
-
logger$
|
|
19811
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}: method:${request.method}] Creating PendingRPC`);
|
|
19534
19812
|
this.request = request;
|
|
19535
19813
|
const timeoutMs = options?.timeoutMs ?? PendingRPC.defaultTimeoutMs;
|
|
19536
19814
|
const signal = options?.signal;
|
|
@@ -19556,22 +19834,22 @@ var PendingRPC = class PendingRPC {
|
|
|
19556
19834
|
isSettled = true;
|
|
19557
19835
|
if (response.error) {
|
|
19558
19836
|
const rpcError = new JSONRPCError(response.error.code, response.error.message, response.error.data, void 0, request.id);
|
|
19559
|
-
logger$
|
|
19837
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with RPC error:`, rpcError);
|
|
19560
19838
|
reject(rpcError);
|
|
19561
19839
|
} else {
|
|
19562
|
-
logger$
|
|
19840
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Resolving promise with response:`, response);
|
|
19563
19841
|
resolve(response);
|
|
19564
19842
|
}
|
|
19565
19843
|
subscription.unsubscribe();
|
|
19566
19844
|
},
|
|
19567
19845
|
error: (error) => {
|
|
19568
|
-
logger$
|
|
19846
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with error:`, error);
|
|
19569
19847
|
isSettled = true;
|
|
19570
19848
|
reject(error);
|
|
19571
19849
|
subscription.unsubscribe();
|
|
19572
19850
|
},
|
|
19573
19851
|
complete: () => {
|
|
19574
|
-
logger$
|
|
19852
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Observable completed`);
|
|
19575
19853
|
if (!isSettled) reject(new RPCTimeoutError(request.id, timeoutMs));
|
|
19576
19854
|
subscription.unsubscribe();
|
|
19577
19855
|
}
|
|
@@ -19592,7 +19870,18 @@ var PendingRPC = class PendingRPC {
|
|
|
19592
19870
|
//#endregion
|
|
19593
19871
|
//#region src/managers/ClientSessionManager.ts
|
|
19594
19872
|
var import_cjs$7 = require_cjs();
|
|
19595
|
-
const logger$
|
|
19873
|
+
const logger$9 = getLogger();
|
|
19874
|
+
/**
|
|
19875
|
+
* Decide whether an error emitted on `call.errors$` during dial should
|
|
19876
|
+
* abort the dial. A non-fatal MediaAccessError means the call degraded to
|
|
19877
|
+
* receive-only and still connects — everything else rejects `dial()` with
|
|
19878
|
+
* the real cause.
|
|
19879
|
+
*
|
|
19880
|
+
* Pure function — exported for unit testing.
|
|
19881
|
+
*/
|
|
19882
|
+
function shouldAbortDial(callError) {
|
|
19883
|
+
return callError.fatal || !(callError.error instanceof MediaAccessError);
|
|
19884
|
+
}
|
|
19596
19885
|
const getAddressSearchURI = (options) => {
|
|
19597
19886
|
const to = options.to?.split("?")[0];
|
|
19598
19887
|
const from$9 = options.from?.startsWith("subscriber://") ? options.from.replace("subscriber://", "") : options.from;
|
|
@@ -19690,7 +19979,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19690
19979
|
try {
|
|
19691
19980
|
return await this.transport.execute(request, options);
|
|
19692
19981
|
} catch (error) {
|
|
19693
|
-
logger$
|
|
19982
|
+
logger$9.debug("[Session] Execute Error", error);
|
|
19694
19983
|
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
19695
19984
|
throw error;
|
|
19696
19985
|
}
|
|
@@ -19704,13 +19993,13 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19704
19993
|
return true;
|
|
19705
19994
|
}
|
|
19706
19995
|
setupMessageHandlers() {
|
|
19707
|
-
logger$
|
|
19996
|
+
logger$9.debug("[Session] Setting up message handlers");
|
|
19708
19997
|
this.subscribeTo(this.authStateEvent$, async (authStateEvent) => {
|
|
19709
|
-
logger$
|
|
19998
|
+
logger$9.debug("[Session] Authorization state event received:", authStateEvent);
|
|
19710
19999
|
try {
|
|
19711
20000
|
await this.updateAuthorizationStateInStorage(authStateEvent.authorization_state);
|
|
19712
20001
|
} catch (error) {
|
|
19713
|
-
logger$
|
|
20002
|
+
logger$9.error("[Session] Failed to handle authorization state update:", error);
|
|
19714
20003
|
this._errors$.next(new AuthStateHandlerError(error));
|
|
19715
20004
|
}
|
|
19716
20005
|
});
|
|
@@ -19718,29 +20007,29 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19718
20007
|
if (this._authState$.value.kind === "authenticated") this._authState$.next({ kind: "unauthenticated" });
|
|
19719
20008
|
});
|
|
19720
20009
|
this.subscribeTo(this.transport.connectionStatus$.pipe((0, import_cjs$7.filter)((status) => status === "connected"), (0, import_cjs$7.exhaustMap)(() => {
|
|
19721
|
-
logger$
|
|
20010
|
+
logger$9.debug("[Session] Connection established, initiating authentication");
|
|
19722
20011
|
return (0, import_cjs$7.from)(this.authenticate()).pipe((0, import_cjs$7.catchError)((error) => {
|
|
19723
20012
|
this.handleAuthenticationError(error).catch((err) => {
|
|
19724
|
-
logger$
|
|
20013
|
+
logger$9.error("[Session] Error handling authentication failure:", err);
|
|
19725
20014
|
});
|
|
19726
20015
|
return import_cjs$7.EMPTY;
|
|
19727
20016
|
}));
|
|
19728
20017
|
})), void 0);
|
|
19729
20018
|
this.subscribeTo(this.vertoInvite$, async (invite) => {
|
|
19730
|
-
logger$
|
|
20019
|
+
logger$9.debug("[Session] Verto invite received:", invite);
|
|
19731
20020
|
try {
|
|
19732
20021
|
await this.createInboundCall(invite);
|
|
19733
20022
|
} catch (error) {
|
|
19734
|
-
logger$
|
|
20023
|
+
logger$9.error("[Session] Error handling Verto invite:", error);
|
|
19735
20024
|
this._errors$.next(new VertoInviteHandlerError(error));
|
|
19736
20025
|
}
|
|
19737
20026
|
});
|
|
19738
20027
|
this.subscribeTo(this.vertoAttach$, async (attach) => {
|
|
19739
|
-
logger$
|
|
20028
|
+
logger$9.debug("[Session] Verto attach received:", attach);
|
|
19740
20029
|
try {
|
|
19741
20030
|
await this.handleVertoAttach(attach);
|
|
19742
20031
|
} catch (error) {
|
|
19743
|
-
logger$
|
|
20032
|
+
logger$9.error("[Session] Error handling Verto attach:", error);
|
|
19744
20033
|
this._errors$.next(new VertoAttachHandlerError(error));
|
|
19745
20034
|
}
|
|
19746
20035
|
});
|
|
@@ -19750,36 +20039,36 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19750
20039
|
const storedState = await this.storage.getItem(this.authorizationStateKey);
|
|
19751
20040
|
this.authorizationState$.next(storedState ?? void 0);
|
|
19752
20041
|
} catch (error) {
|
|
19753
|
-
logger$
|
|
20042
|
+
logger$9.error("Failed to retrieve authorization state from storage:", error);
|
|
19754
20043
|
this.authorizationState$.next(void 0);
|
|
19755
20044
|
}
|
|
19756
20045
|
}
|
|
19757
20046
|
async updateAuthorizationStateInStorage(authorizationState) {
|
|
19758
20047
|
if (!authorizationState) {
|
|
19759
|
-
logger$
|
|
20048
|
+
logger$9.debug("[Session] Removing authorization state from storage");
|
|
19760
20049
|
try {
|
|
19761
20050
|
await this.storage.removeItem(this.authorizationStateKey);
|
|
19762
20051
|
this.authorizationState$.next(void 0);
|
|
19763
20052
|
} catch (error) {
|
|
19764
|
-
logger$
|
|
20053
|
+
logger$9.error("Failed to remove authorization state from storage:", error);
|
|
19765
20054
|
throw error;
|
|
19766
20055
|
}
|
|
19767
20056
|
return;
|
|
19768
20057
|
}
|
|
19769
20058
|
try {
|
|
19770
|
-
logger$
|
|
20059
|
+
logger$9.debug("[Session] Updating authorization state in storage");
|
|
19771
20060
|
await this.storage.setItem(this.authorizationStateKey, authorizationState);
|
|
19772
20061
|
this.authorizationState$.next(authorizationState);
|
|
19773
20062
|
} catch (error) {
|
|
19774
|
-
logger$
|
|
20063
|
+
logger$9.error("Failed to retrieve authorization state from storage:", error);
|
|
19775
20064
|
throw error;
|
|
19776
20065
|
}
|
|
19777
20066
|
}
|
|
19778
20067
|
get authStateEvent$() {
|
|
19779
20068
|
return this.cachedObservable("authStateEvent$", () => this.signalingEvent$.pipe((0, import_cjs$7.tap)((msg) => {
|
|
19780
|
-
logger$
|
|
20069
|
+
logger$9.debug("[Session] Received incoming message:", msg);
|
|
19781
20070
|
}), filterAs(isSignalwireAuthorizationStateMetadata, "params"), (0, import_cjs$7.tap)((event) => {
|
|
19782
|
-
logger$
|
|
20071
|
+
logger$9.debug("[Session] Authorization state event received:", event.authorization_state);
|
|
19783
20072
|
})));
|
|
19784
20073
|
}
|
|
19785
20074
|
get signalingEvent$() {
|
|
@@ -19817,42 +20106,72 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19817
20106
|
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 })));
|
|
19818
20107
|
}
|
|
19819
20108
|
async handleAuthenticationError(error) {
|
|
19820
|
-
logger$
|
|
19821
|
-
const isRecoverableAuthError = error instanceof JSONRPCError && (error.code === RPC_ERROR_REQUESTER_VALIDATION_FAILED || error.code === RPC_ERROR_INVALID_PARAMS || error.code === RPC_ERROR_AUTHENTICATION_FAILED);
|
|
20109
|
+
logger$9.error("Authentication error:", error);
|
|
20110
|
+
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);
|
|
19822
20111
|
const hasStoredState = await (0, import_cjs$7.firstValueFrom)(this.authorizationState$.pipe((0, import_cjs$7.take)(1))) !== void 0;
|
|
19823
|
-
if (isRecoverableAuthError && hasStoredState) {
|
|
19824
|
-
logger$
|
|
20112
|
+
if (isRecoverableAuthError$1 && hasStoredState) {
|
|
20113
|
+
logger$9.debug("[Session] Recoverable auth error — cleaning up stored state and reconnecting fresh");
|
|
19825
20114
|
try {
|
|
19826
20115
|
await this.cleanupStoredConnectionParams();
|
|
19827
20116
|
} catch (cleanupError) {
|
|
19828
|
-
logger$
|
|
20117
|
+
logger$9.error("Failed to cleanup stored connection params:", cleanupError);
|
|
19829
20118
|
} finally {
|
|
19830
20119
|
this.transport.reconnect();
|
|
19831
20120
|
}
|
|
19832
20121
|
} else this._errors$.next(error);
|
|
19833
20122
|
}
|
|
20123
|
+
/**
|
|
20124
|
+
* Clear the resume state (authorization_state + protocol) only.
|
|
20125
|
+
*
|
|
20126
|
+
* This is the stale-auth-state recovery helper used by handleAuthError:
|
|
20127
|
+
* the server rejected a reconnect, so the resume state is discarded and a
|
|
20128
|
+
* fresh connect follows. Attach records are deliberately preserved — the
|
|
20129
|
+
* session lives on through the reconnect and reattachCalls() needs the
|
|
20130
|
+
* stored call references afterwards. Do NOT add detachAll() here.
|
|
20131
|
+
*
|
|
20132
|
+
* For public teardown (disconnect/destroy), use {@link teardownSessionState}
|
|
20133
|
+
* instead, which clears the attach records as well.
|
|
20134
|
+
*/
|
|
19834
20135
|
async cleanupStoredConnectionParams() {
|
|
19835
20136
|
await this.transport.setProtocol(void 0);
|
|
19836
20137
|
await this.updateAuthorizationStateInStorage(void 0);
|
|
19837
20138
|
this._authorization$.next(void 0);
|
|
19838
20139
|
}
|
|
20140
|
+
/**
|
|
20141
|
+
* Public-teardown helper for disconnect()/destroy(). Clears the resume
|
|
20142
|
+
* state (authorization_state + protocol) AND the attach records as one
|
|
20143
|
+
* atomic unit.
|
|
20144
|
+
*
|
|
20145
|
+
* The two stores are coupled: the backend only honors attach records
|
|
20146
|
+
* within the session identified by the resume state, so ending the
|
|
20147
|
+
* session must clear both. Clearing one without the other strands records
|
|
20148
|
+
* no future session can honor (disconnect) or revives a session the
|
|
20149
|
+
* developer explicitly ended (destroy).
|
|
20150
|
+
*
|
|
20151
|
+
* Distinct from {@link cleanupStoredConnectionParams}, which keeps the
|
|
20152
|
+
* attach records for the stale-auth-state recovery path.
|
|
20153
|
+
*/
|
|
20154
|
+
async teardownSessionState() {
|
|
20155
|
+
await this.cleanupStoredConnectionParams();
|
|
20156
|
+
await this.attachManager.detachAll();
|
|
20157
|
+
}
|
|
19839
20158
|
async updateAuthState(authorization_state) {
|
|
19840
20159
|
try {
|
|
19841
20160
|
await this.storage.setItem(this.authorizationStateKey, authorization_state);
|
|
19842
20161
|
} catch (error) {
|
|
19843
|
-
logger$
|
|
20162
|
+
logger$9.error("Failed to update authorization state in storage:", error);
|
|
19844
20163
|
this._errors$.next(new AuthStateHandlerError(error));
|
|
19845
20164
|
}
|
|
19846
20165
|
}
|
|
19847
20166
|
async reauthenticate(token, dpopToken, options) {
|
|
19848
|
-
logger$
|
|
20167
|
+
logger$9.debug("[Session] Re-authenticating session");
|
|
19849
20168
|
try {
|
|
19850
20169
|
let resolvedDpopToken = dpopToken;
|
|
19851
20170
|
if (!resolvedDpopToken && this.dpopManager?.initialized) try {
|
|
19852
20171
|
resolvedDpopToken = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
|
|
19853
20172
|
} catch (error) {
|
|
19854
20173
|
if (this.clientBound) throw error;
|
|
19855
|
-
logger$
|
|
20174
|
+
logger$9.warn("[Session] Failed to create DPoP proof for reauthenticate:", error);
|
|
19856
20175
|
}
|
|
19857
20176
|
const request = RPCReauthenticate({
|
|
19858
20177
|
project: this._authorization$.value?.project_id ?? "",
|
|
@@ -19860,24 +20179,24 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19860
20179
|
...resolvedDpopToken ? { dpop_token: resolvedDpopToken } : {}
|
|
19861
20180
|
});
|
|
19862
20181
|
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) => {
|
|
19863
|
-
logger$
|
|
20182
|
+
logger$9.error("[Session] Re-authentication RPC failed:", err);
|
|
19864
20183
|
throw err;
|
|
19865
20184
|
})));
|
|
19866
20185
|
if (options?.clientBound) this._wasClientBound = true;
|
|
19867
|
-
logger$
|
|
20186
|
+
logger$9.debug("[Session] Re-authentication successful, updating stored auth state");
|
|
19868
20187
|
} catch (error) {
|
|
19869
|
-
logger$
|
|
20188
|
+
logger$9.error("[Session] Re-authentication failed:", error);
|
|
19870
20189
|
this._errors$.next(new AuthStateHandlerError(error));
|
|
19871
20190
|
throw error;
|
|
19872
20191
|
}
|
|
19873
20192
|
}
|
|
19874
20193
|
async authenticate() {
|
|
19875
|
-
logger$
|
|
20194
|
+
logger$9.debug("[Session] Starting authentication process");
|
|
19876
20195
|
const persistedParams = await (0, import_cjs$7.firstValueFrom)((0, import_cjs$7.combineLatest)({
|
|
19877
20196
|
protocol: this.transport.protocol$,
|
|
19878
20197
|
authorization_state: this.authorizationState$
|
|
19879
20198
|
}).pipe((0, import_cjs$7.take)(1)));
|
|
19880
|
-
logger$
|
|
20199
|
+
logger$9.debug("[Session] Persisted params:\n", {
|
|
19881
20200
|
protocol: persistedParams.protocol,
|
|
19882
20201
|
authStateLength: persistedParams.authorization_state?.length
|
|
19883
20202
|
});
|
|
@@ -19885,16 +20204,20 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19885
20204
|
const storedToken = this.getCredential().token;
|
|
19886
20205
|
const isReconnect = hasReconnectState && storedToken;
|
|
19887
20206
|
let dpopToken;
|
|
19888
|
-
if (isReconnect) logger$
|
|
19889
|
-
else
|
|
19890
|
-
|
|
19891
|
-
|
|
20207
|
+
if (isReconnect) logger$9.debug("[Session] Reconnecting with stored jwt_token + authorization_state");
|
|
20208
|
+
else {
|
|
20209
|
+
const credential = this.getCredential();
|
|
20210
|
+
const credentialExpired = credential.expiry_at !== void 0 && credential.expiry_at <= Date.now() + CREDENTIAL_EXPIRY_SKEW_MS;
|
|
20211
|
+
if (this.onBeforeReconnect && (this.clientBound || credentialExpired)) {
|
|
20212
|
+
logger$9.debug("[Session] Refreshing credentials before fresh connect");
|
|
20213
|
+
await this.onBeforeReconnect();
|
|
20214
|
+
}
|
|
19892
20215
|
}
|
|
19893
|
-
if (
|
|
20216
|
+
if (this.dpopManager?.initialized) try {
|
|
19894
20217
|
dpopToken = await this.dpopManager.createRpcProof({ method: "signalwire.connect" });
|
|
19895
20218
|
} catch (error) {
|
|
19896
20219
|
if (this.clientBound) throw error;
|
|
19897
|
-
logger$
|
|
20220
|
+
logger$9.warn("[Session] Failed to create DPoP proof for connect, proceeding without:", error);
|
|
19898
20221
|
}
|
|
19899
20222
|
const rpcConnectRequest = RPCConnect({
|
|
19900
20223
|
authentication: isReconnect ? { jwt_token: storedToken } : this.authentication,
|
|
@@ -19911,26 +20234,27 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19911
20234
|
} : {}
|
|
19912
20235
|
});
|
|
19913
20236
|
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)(() => {
|
|
19914
|
-
logger$
|
|
20237
|
+
logger$9.debug("[Session] Response passed filter, processing authentication result");
|
|
19915
20238
|
}), (0, import_cjs$7.take)(1), (0, import_cjs$7.catchError)((err) => {
|
|
19916
|
-
logger$
|
|
20239
|
+
logger$9.error("[Session] Authentication RPC failed:", err);
|
|
19917
20240
|
throw err;
|
|
19918
20241
|
})));
|
|
19919
|
-
logger$
|
|
20242
|
+
logger$9.debug("[Session] Processing authentication result:", {
|
|
19920
20243
|
hasProtocol: !!response.protocol,
|
|
19921
20244
|
hasAuthorization: !!response.authorization,
|
|
19922
20245
|
hasIceServers: !!response.ice_servers
|
|
19923
20246
|
});
|
|
19924
20247
|
if (response.protocol) await this.transport.setProtocol(response.protocol);
|
|
19925
20248
|
this._authorization$.next(response.authorization);
|
|
20249
|
+
if (response.authorization.cnf?.jkt) this._wasClientBound = true;
|
|
19926
20250
|
this._iceServers$.next(response.ice_servers ?? []);
|
|
19927
20251
|
this._authState$.next({ kind: "authenticated" });
|
|
19928
|
-
logger$
|
|
20252
|
+
logger$9.debug("[Session] Authentication completed successfully");
|
|
19929
20253
|
}
|
|
19930
20254
|
async disconnect() {
|
|
19931
20255
|
this.transport.disconnect();
|
|
19932
20256
|
this._authState$.next({ kind: "unauthenticated" });
|
|
19933
|
-
await this.
|
|
20257
|
+
await this.teardownSessionState();
|
|
19934
20258
|
}
|
|
19935
20259
|
async createInboundCall(invite) {
|
|
19936
20260
|
const callSession = await this.createCall({
|
|
@@ -19961,11 +20285,11 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19961
20285
|
async handleVertoAttach(attach) {
|
|
19962
20286
|
const { callID } = attach;
|
|
19963
20287
|
if (callID in this._calls$.value) {
|
|
19964
|
-
logger$
|
|
20288
|
+
logger$9.debug(`[Session] Verto attach for existing call ${callID}, deferring to per-call handler`);
|
|
19965
20289
|
return;
|
|
19966
20290
|
}
|
|
19967
20291
|
const storedOptions = await this.attachManager.consumePendingAttachment(callID);
|
|
19968
|
-
logger$
|
|
20292
|
+
logger$9.debug(`[Session] Creating reattached call for callID: ${callID}`);
|
|
19969
20293
|
const callSession = await this.createCall({
|
|
19970
20294
|
nodeId: attach.node_id,
|
|
19971
20295
|
callId: callID,
|
|
@@ -19989,14 +20313,14 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19989
20313
|
to: destinationURI,
|
|
19990
20314
|
...options
|
|
19991
20315
|
});
|
|
19992
|
-
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)))));
|
|
20316
|
+
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)))));
|
|
19993
20317
|
this._calls$.next({
|
|
19994
20318
|
[`${callSession.id}`]: callSession,
|
|
19995
20319
|
...this._calls$.value
|
|
19996
20320
|
});
|
|
19997
20321
|
return callSession;
|
|
19998
20322
|
} catch (error) {
|
|
19999
|
-
logger$
|
|
20323
|
+
logger$9.error("[Session] Error creating outbound call:", error);
|
|
20000
20324
|
callSession?.destroy();
|
|
20001
20325
|
const callError = new CallCreateError(error instanceof import_cjs$7.TimeoutError ? "Call create timeout" : "Call creation failed", error, "outbound");
|
|
20002
20326
|
this._errors$.next(callError);
|
|
@@ -20014,7 +20338,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
20014
20338
|
address = this._directory.get(addressId);
|
|
20015
20339
|
if (!address) throw new DependencyError(`Address ID: ${addressId} not found`);
|
|
20016
20340
|
} catch {
|
|
20017
|
-
logger$
|
|
20341
|
+
logger$9.warn(`[Session] Directory lookup failed for ${addressURI}, proceeding with raw URI`);
|
|
20018
20342
|
}
|
|
20019
20343
|
const callSession = this.callFactory.createCall(address, { ...options });
|
|
20020
20344
|
this.subscribeTo(callSession.status$.pipe((0, import_cjs$7.filter)((status) => status === "destroyed"), (0, import_cjs$7.take)(1)), () => {
|
|
@@ -20023,7 +20347,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
20023
20347
|
});
|
|
20024
20348
|
return callSession;
|
|
20025
20349
|
} catch (error) {
|
|
20026
|
-
logger$
|
|
20350
|
+
logger$9.error("[Session] Error creating call session:", error);
|
|
20027
20351
|
throw new CallCreateError("Call create error", error, options.initOffer ? "inbound" : "outbound");
|
|
20028
20352
|
}
|
|
20029
20353
|
}
|
|
@@ -20042,6 +20366,14 @@ var ClientSessionWrapper = class {
|
|
|
20042
20366
|
get authenticated() {
|
|
20043
20367
|
return this.clientSessionManager.authenticated;
|
|
20044
20368
|
}
|
|
20369
|
+
/**
|
|
20370
|
+
* Whether the session is using a Client Bound SAT (DPoP). Sticky — set
|
|
20371
|
+
* when the binding is established or restored from a resumed session's
|
|
20372
|
+
* server authorization.
|
|
20373
|
+
*/
|
|
20374
|
+
get clientBound() {
|
|
20375
|
+
return this.clientSessionManager.clientBound;
|
|
20376
|
+
}
|
|
20045
20377
|
get signalingEvent$() {
|
|
20046
20378
|
return this.clientSessionManager.signalingEvent$;
|
|
20047
20379
|
}
|
|
@@ -20072,7 +20404,7 @@ const isString = (obj) => typeof obj === "string";
|
|
|
20072
20404
|
//#endregion
|
|
20073
20405
|
//#region src/managers/ConversationsManager.ts
|
|
20074
20406
|
var import_cjs$6 = require_cjs();
|
|
20075
|
-
const logger$
|
|
20407
|
+
const logger$8 = getLogger();
|
|
20076
20408
|
var ConversationMessagesFetcher = class extends Fetcher {
|
|
20077
20409
|
constructor(groupId, http) {
|
|
20078
20410
|
super(`/api/fabric/conversations/${groupId}/messages`, "page_size=100", http);
|
|
@@ -20112,13 +20444,13 @@ var ConversationsManager = class {
|
|
|
20112
20444
|
}
|
|
20113
20445
|
throw new ConversationError("Join Failed - Unexpected response");
|
|
20114
20446
|
} catch (error) {
|
|
20115
|
-
logger$
|
|
20447
|
+
logger$8.error("[ConversationsManager] Failed to join conversation:", error);
|
|
20116
20448
|
throw error;
|
|
20117
20449
|
}
|
|
20118
20450
|
}
|
|
20119
20451
|
async getConversationMessageCollection(addressId) {
|
|
20120
20452
|
const groupId = this.groupIds.get(addressId) ?? await this.join(addressId);
|
|
20121
|
-
return Promise.resolve(new ConversationMessageCollection(groupId, this.clientSession.signalingEvent$.pipe(filterAs(isConversationMessageMetadata, "params"), (0, import_cjs$6.tap)((event) => logger$
|
|
20453
|
+
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));
|
|
20122
20454
|
}
|
|
20123
20455
|
async sendText(text, destinationAddressId) {
|
|
20124
20456
|
const groupId = this.groupIds.get(destinationAddressId) ?? await this.join(destinationAddressId);
|
|
@@ -20135,7 +20467,7 @@ var ConversationsManager = class {
|
|
|
20135
20467
|
})).ok) return;
|
|
20136
20468
|
throw new ConversationError("Send Text Failed - Unexpected response");
|
|
20137
20469
|
} catch (error) {
|
|
20138
|
-
logger$
|
|
20470
|
+
logger$8.error("[ConversationsManager] Failed to send text message:", error);
|
|
20139
20471
|
throw error;
|
|
20140
20472
|
}
|
|
20141
20473
|
}
|
|
@@ -20144,7 +20476,7 @@ var ConversationsManager = class {
|
|
|
20144
20476
|
//#endregion
|
|
20145
20477
|
//#region src/managers/DeviceTokenManager.ts
|
|
20146
20478
|
var import_cjs$5 = require_cjs();
|
|
20147
|
-
const logger$
|
|
20479
|
+
const logger$7 = getLogger();
|
|
20148
20480
|
/**
|
|
20149
20481
|
* Resolves the token expiry timestamp (epoch seconds) using a 3-tier priority chain:
|
|
20150
20482
|
* 1. `data.expires_at` — server-provided absolute timestamp
|
|
@@ -20154,7 +20486,7 @@ const logger$6 = getLogger();
|
|
|
20154
20486
|
function resolveExpiresAt(data) {
|
|
20155
20487
|
if (data.expires_at) return data.expires_at;
|
|
20156
20488
|
if (data.expires_in) return Math.floor(Date.now() / 1e3) + data.expires_in;
|
|
20157
|
-
logger$
|
|
20489
|
+
logger$7.warn("[DeviceToken] Could not determine token expiry, using default");
|
|
20158
20490
|
return Math.floor(Date.now() / 1e3) + DEVICE_TOKEN_DEFAULT_EXPIRE_IN;
|
|
20159
20491
|
}
|
|
20160
20492
|
/**
|
|
@@ -20187,11 +20519,12 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20187
20519
|
this.getCredential = getCredential;
|
|
20188
20520
|
this._currentToken$ = this.createBehaviorSubject(null);
|
|
20189
20521
|
this._refreshInProgress = false;
|
|
20522
|
+
this._paused = false;
|
|
20190
20523
|
this._effectiveExpireIn = DEVICE_TOKEN_DEFAULT_EXPIRE_IN;
|
|
20191
20524
|
this.subscribeTo(this._currentToken$.pipe((0, import_cjs$5.filter)(Boolean), (0, import_cjs$5.switchMap)((tokenData) => {
|
|
20192
20525
|
const expiresAt = resolveExpiresAt(tokenData);
|
|
20193
20526
|
const refreshIn = Math.max(expiresAt * 1e3 - Date.now() - DEVICE_TOKEN_REFRESH_BUFFER_MS, 1e3);
|
|
20194
|
-
logger$
|
|
20527
|
+
logger$7.debug(`[DeviceToken] Scheduling Client Bound SAT refresh in ${refreshIn}ms`);
|
|
20195
20528
|
return (0, import_cjs$5.timer)(refreshIn);
|
|
20196
20529
|
})), () => {
|
|
20197
20530
|
this.executeRefresh();
|
|
@@ -20205,7 +20538,12 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20205
20538
|
* Activates the Client Bound SAT flow when the user's token has
|
|
20206
20539
|
* `sat:refresh` scope.
|
|
20207
20540
|
*
|
|
20208
|
-
*
|
|
20541
|
+
* Returns an {@link ActivationResult} indicating whether the manager
|
|
20542
|
+
* took ownership of refresh duties. The caller must use the `activated`
|
|
20543
|
+
* boolean to decide whether to keep its own refresh path armed — when
|
|
20544
|
+
* `activated` is `false`, the caller is responsible for refresh.
|
|
20545
|
+
*
|
|
20546
|
+
* Steps on success:
|
|
20209
20547
|
* 1. Check user's `sat_claims` for `sat:refresh` scope
|
|
20210
20548
|
* 2. Call `/api/fabric/subscriber/devices/token` with a DPoP proof
|
|
20211
20549
|
* 3. Reauthenticate the session with the Client Bound SAT + DPoP proof
|
|
@@ -20214,32 +20552,66 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20214
20552
|
async activate(user, session, updateCredential) {
|
|
20215
20553
|
const { satClaims } = user;
|
|
20216
20554
|
if (!satClaims?.scope?.includes(SAT_REFRESH_SCOPE)) {
|
|
20217
|
-
logger$
|
|
20218
|
-
return
|
|
20555
|
+
logger$7.debug("[DeviceToken] No sat:refresh scope, skipping Client Bound SAT activation");
|
|
20556
|
+
return {
|
|
20557
|
+
activated: false,
|
|
20558
|
+
reason: "no-scope"
|
|
20559
|
+
};
|
|
20219
20560
|
}
|
|
20220
20561
|
this._session = session;
|
|
20221
20562
|
this._updateCredential = updateCredential;
|
|
20222
20563
|
try {
|
|
20564
|
+
const cached = this._currentToken$.value;
|
|
20565
|
+
if (cached && this.isTokenFresh(cached)) {
|
|
20566
|
+
logger$7.debug("[DeviceToken] Reusing cached Client Bound SAT — skipping /devices/token");
|
|
20567
|
+
const rpcProof$1 = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
|
|
20568
|
+
await session.reauthenticate(cached.token, rpcProof$1, { clientBound: true });
|
|
20569
|
+
updateCredential({ token: cached.token });
|
|
20570
|
+
return { activated: true };
|
|
20571
|
+
}
|
|
20223
20572
|
const tokenData = await this.obtainToken();
|
|
20224
20573
|
if (!tokenData.expires_at && !tokenData.expires_in && satClaims.expires_at) tokenData.expires_at = satClaims.expires_at;
|
|
20225
20574
|
this._effectiveExpireIn = resolveExpireIn(tokenData);
|
|
20226
20575
|
const rpcProof = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
|
|
20227
20576
|
await session.reauthenticate(tokenData.token, rpcProof, { clientBound: true });
|
|
20228
20577
|
updateCredential({ token: tokenData.token });
|
|
20229
|
-
logger$
|
|
20230
|
-
this.
|
|
20578
|
+
logger$7.info("[DeviceToken] Client Bound SAT activated successfully");
|
|
20579
|
+
this.emitCurrentToken(tokenData);
|
|
20580
|
+
return { activated: true };
|
|
20231
20581
|
} catch (error) {
|
|
20232
|
-
logger$
|
|
20582
|
+
logger$7.error("[DeviceToken] Failed to activate Client Bound SAT:", error);
|
|
20233
20583
|
this.errorHandler(new DPoPInitError(error, "Failed to activate Client Bound SAT"));
|
|
20234
|
-
|
|
20235
|
-
|
|
20236
|
-
|
|
20237
|
-
|
|
20238
|
-
expires_at: expiresAt
|
|
20239
|
-
});
|
|
20584
|
+
return {
|
|
20585
|
+
activated: false,
|
|
20586
|
+
reason: "endpoint-failed"
|
|
20587
|
+
};
|
|
20240
20588
|
}
|
|
20241
20589
|
}
|
|
20242
20590
|
/**
|
|
20591
|
+
* Emit a freshly received token to the reactive pipeline, stamping an
|
|
20592
|
+
* absolute `expires_at` when the response carried only `expires_in`.
|
|
20593
|
+
* Resolving the expiry at RECEIVE time (not at read time) is what lets
|
|
20594
|
+
* {@link refreshNowIfDue} detect due-ness on resume: a bare `expires_in`
|
|
20595
|
+
* re-resolved later would always compute a full TTL from "now" and never
|
|
20596
|
+
* cross the refresh buffer.
|
|
20597
|
+
*/
|
|
20598
|
+
emitCurrentToken(token) {
|
|
20599
|
+
const stamped = token.expires_at ? token : {
|
|
20600
|
+
...token,
|
|
20601
|
+
expires_at: resolveExpiresAt(token)
|
|
20602
|
+
};
|
|
20603
|
+
this._currentToken$.next(stamped);
|
|
20604
|
+
}
|
|
20605
|
+
/**
|
|
20606
|
+
* Returns true when the cached token has enough headroom before expiry to
|
|
20607
|
+
* be safely reused on reactivation. The headroom matches the refresh
|
|
20608
|
+
* buffer, so a token within the refresh window is treated as stale (the
|
|
20609
|
+
* reactive pipeline is about to refresh it anyway).
|
|
20610
|
+
*/
|
|
20611
|
+
isTokenFresh(token) {
|
|
20612
|
+
return resolveExpiresAt(token) * 1e3 - Date.now() > DEVICE_TOKEN_REFRESH_BUFFER_MS;
|
|
20613
|
+
}
|
|
20614
|
+
/**
|
|
20243
20615
|
* Obtains a Client Bound SAT from `/api/fabric/subscriber/devices/token`.
|
|
20244
20616
|
* Returns the full {@link DeviceTokenResponse} including expiry metadata.
|
|
20245
20617
|
*/
|
|
@@ -20269,7 +20641,7 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20269
20641
|
* handled by the reactive pipeline).
|
|
20270
20642
|
*/
|
|
20271
20643
|
async refreshToken(session, currentToken, updateCredential) {
|
|
20272
|
-
logger$
|
|
20644
|
+
logger$7.debug("[DeviceToken] Refreshing Client Bound SAT");
|
|
20273
20645
|
const dpopProof = await this.dpopManager.createHttpProof({
|
|
20274
20646
|
method: "POST",
|
|
20275
20647
|
uri: DEVICE_REFRESH_ENDPOINT,
|
|
@@ -20291,7 +20663,7 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20291
20663
|
const rpcProof = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
|
|
20292
20664
|
await session.reauthenticate(data.token, rpcProof);
|
|
20293
20665
|
updateCredential({ token: data.token });
|
|
20294
|
-
logger$
|
|
20666
|
+
logger$7.info("[DeviceToken] Client Bound SAT refreshed successfully");
|
|
20295
20667
|
return data;
|
|
20296
20668
|
}
|
|
20297
20669
|
/**
|
|
@@ -20300,18 +20672,22 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20300
20672
|
* On all retries exhausted, emits to `errorHandler`.
|
|
20301
20673
|
*/
|
|
20302
20674
|
async executeRefresh() {
|
|
20675
|
+
if (this._paused) {
|
|
20676
|
+
logger$7.debug("[DeviceToken] Manager paused, skipping refresh");
|
|
20677
|
+
return;
|
|
20678
|
+
}
|
|
20303
20679
|
if (this._refreshInProgress) {
|
|
20304
|
-
logger$
|
|
20680
|
+
logger$7.debug("[DeviceToken] Refresh already in progress, skipping");
|
|
20305
20681
|
return;
|
|
20306
20682
|
}
|
|
20307
20683
|
const session = this._session;
|
|
20308
20684
|
const updateCredential = this._updateCredential;
|
|
20309
20685
|
if (!session || !updateCredential) {
|
|
20310
|
-
logger$
|
|
20686
|
+
logger$7.warn("[DeviceToken] Cannot refresh: session or updateCredential not set");
|
|
20311
20687
|
return;
|
|
20312
20688
|
}
|
|
20313
20689
|
if (!session.authenticated) {
|
|
20314
|
-
logger$
|
|
20690
|
+
logger$7.debug("[DeviceToken] Session not authenticated, deferring refresh");
|
|
20315
20691
|
return;
|
|
20316
20692
|
}
|
|
20317
20693
|
this._refreshInProgress = true;
|
|
@@ -20319,9 +20695,9 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20319
20695
|
const currentToken = this.getCredential().token;
|
|
20320
20696
|
if (!currentToken) throw new TokenRefreshError("No current token available for refresh");
|
|
20321
20697
|
const newTokenData = await this.retryRefresh(session, currentToken, updateCredential);
|
|
20322
|
-
this.
|
|
20698
|
+
this.emitCurrentToken(newTokenData);
|
|
20323
20699
|
} catch (error) {
|
|
20324
|
-
logger$
|
|
20700
|
+
logger$7.error("[DeviceToken] Automatic Client Bound SAT refresh failed:", error);
|
|
20325
20701
|
this.errorHandler(error instanceof TokenRefreshError ? error : new TokenRefreshError("Automatic token refresh failed", error));
|
|
20326
20702
|
} finally {
|
|
20327
20703
|
this._refreshInProgress = false;
|
|
@@ -20339,18 +20715,324 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20339
20715
|
lastError = error;
|
|
20340
20716
|
if (attempt < DEVICE_TOKEN_REFRESH_MAX_RETRIES - 1) {
|
|
20341
20717
|
const delay$1 = DEVICE_TOKEN_REFRESH_RETRY_BASE_MS * Math.pow(2, attempt);
|
|
20342
|
-
logger$
|
|
20718
|
+
logger$7.warn(`[DeviceToken] Refresh attempt ${attempt + 1} failed, retrying in ${delay$1}ms`);
|
|
20343
20719
|
await new Promise((resolve) => setTimeout(resolve, delay$1));
|
|
20344
20720
|
}
|
|
20345
20721
|
}
|
|
20346
20722
|
throw lastError instanceof Error ? lastError : new TokenRefreshError("All refresh retries exhausted", lastError);
|
|
20347
20723
|
}
|
|
20724
|
+
/**
|
|
20725
|
+
* Force an immediate refresh when the cached Client Bound SAT is already
|
|
20726
|
+
* past its refresh window. Called on resume from suspension where
|
|
20727
|
+
* background-tab throttling can delay the reactive timer past the buffer.
|
|
20728
|
+
* A no-op when no token is cached or it still has headroom; the normal
|
|
20729
|
+
* {@link executeRefresh} guards (paused / in-progress / unauthenticated)
|
|
20730
|
+
* still apply.
|
|
20731
|
+
*/
|
|
20732
|
+
refreshNowIfDue() {
|
|
20733
|
+
const token = this._currentToken$.value;
|
|
20734
|
+
if (!token) return;
|
|
20735
|
+
if (resolveExpiresAt(token) * 1e3 - Date.now() <= DEVICE_TOKEN_REFRESH_BUFFER_MS) {
|
|
20736
|
+
logger$7.debug("[DeviceToken] Resume: cached SAT past refresh window; refreshing now");
|
|
20737
|
+
this.executeRefresh();
|
|
20738
|
+
}
|
|
20739
|
+
}
|
|
20740
|
+
/**
|
|
20741
|
+
* Stops the reactive refresh pipeline from firing. Use when the underlying
|
|
20742
|
+
* session is being torn down (e.g., during {@link SignalWire.disconnect})
|
|
20743
|
+
* so a scheduled refresh cannot fire against a destroyed session.
|
|
20744
|
+
*
|
|
20745
|
+
* The manager's state (cached token, effective TTL, subscriptions) is
|
|
20746
|
+
* preserved — call {@link resume} to re-enable firing after reconnect.
|
|
20747
|
+
*/
|
|
20748
|
+
pause() {
|
|
20749
|
+
this._paused = true;
|
|
20750
|
+
}
|
|
20751
|
+
/** Re-enables the reactive refresh pipeline after a previous {@link pause}. */
|
|
20752
|
+
resume() {
|
|
20753
|
+
this._paused = false;
|
|
20754
|
+
}
|
|
20348
20755
|
/** Cleans up the manager, cancelling the reactive pipeline and all subscriptions. */
|
|
20349
20756
|
destroy() {
|
|
20350
20757
|
super.destroy();
|
|
20351
20758
|
}
|
|
20352
20759
|
};
|
|
20353
20760
|
|
|
20761
|
+
//#endregion
|
|
20762
|
+
//#region src/managers/CredentialRefreshCoordinator.ts
|
|
20763
|
+
const logger$6 = getLogger();
|
|
20764
|
+
const defaultDeviceTokenManagerFactory = (dpopManager, http, errorHandler, getCredential) => new DeviceTokenManager(dpopManager, http, errorHandler, getCredential);
|
|
20765
|
+
/**
|
|
20766
|
+
* Centralizes credential-refresh ownership across the two competing
|
|
20767
|
+
* mechanisms — developer-provided `CredentialProvider.refresh()` and the
|
|
20768
|
+
* Client Bound SAT path via {@link DeviceTokenManager}.
|
|
20769
|
+
*
|
|
20770
|
+
* Maintains the invariant: **at most one refresh mechanism is armed at a
|
|
20771
|
+
* time, and at least one is armed whenever the current credential has an
|
|
20772
|
+
* `expiry_at`**.
|
|
20773
|
+
*
|
|
20774
|
+
* Replaces the previous design where refresh state was distributed across
|
|
20775
|
+
* `SignalWire` (timer field, scheduler method, activation helper) and
|
|
20776
|
+
* `DeviceTokenManager` (reactive pipeline). Centralizing the invariant in
|
|
20777
|
+
* one component eliminates the bug class that produced issue #19074.
|
|
20778
|
+
*
|
|
20779
|
+
* Race-safety:
|
|
20780
|
+
* - `_activating` flag prevents overlapping `activate()` calls from racing.
|
|
20781
|
+
* - `_activationGeneration` lets late resolutions detect they've been
|
|
20782
|
+
* preempted by a newer activation (e.g., reconnect during in-flight
|
|
20783
|
+
* `obtainToken`).
|
|
20784
|
+
*/
|
|
20785
|
+
var CredentialRefreshCoordinator = class extends Destroyable {
|
|
20786
|
+
constructor(dpopManager, deps) {
|
|
20787
|
+
super();
|
|
20788
|
+
this.deps = deps;
|
|
20789
|
+
this._activating = false;
|
|
20790
|
+
this._activationGeneration = 0;
|
|
20791
|
+
this._developerRefreshInProgress = false;
|
|
20792
|
+
if (dpopManager?.initialized) this._deviceTokenManager = (deps.deviceTokenManagerFactory ?? defaultDeviceTokenManagerFactory)(dpopManager, deps.http, (error) => deps.notifier.onError(error), () => deps.store.read());
|
|
20793
|
+
}
|
|
20794
|
+
/** True when the Client Bound SAT path is available (DPoP initialized). */
|
|
20795
|
+
get clientBoundSATAvailable() {
|
|
20796
|
+
return this._deviceTokenManager !== void 0;
|
|
20797
|
+
}
|
|
20798
|
+
/** True when the developer-provided refresh timer is currently armed. */
|
|
20799
|
+
get developerRefreshArmed() {
|
|
20800
|
+
return this._developerTimerId !== void 0;
|
|
20801
|
+
}
|
|
20802
|
+
/**
|
|
20803
|
+
* Arms the developer-provided refresh timer to fire shortly before
|
|
20804
|
+
* `expiresAt`. Replaces any previously scheduled developer refresh.
|
|
20805
|
+
*
|
|
20806
|
+
* Idempotent — multiple calls just reschedule. On retry exhaustion,
|
|
20807
|
+
* invokes `deps.onRefreshExhausted` so the orchestrator can disconnect.
|
|
20808
|
+
*/
|
|
20809
|
+
scheduleDeveloperRefresh(provider, expiresAt, attempt = 0) {
|
|
20810
|
+
this._activeProvider = provider;
|
|
20811
|
+
if (this._developerTimerId !== void 0) clearTimeout(this._developerTimerId);
|
|
20812
|
+
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);
|
|
20813
|
+
this._developerTimerId = setTimeout(() => {
|
|
20814
|
+
this._developerTimerId = void 0;
|
|
20815
|
+
this.executeDeveloperRefresh(provider, expiresAt, attempt);
|
|
20816
|
+
}, refreshInterval);
|
|
20817
|
+
}
|
|
20818
|
+
/**
|
|
20819
|
+
* Runs the developer-provided refresh once: mints a new credential, stores
|
|
20820
|
+
* and persists it, reauthenticates the live session (via the notifier), and
|
|
20821
|
+
* reschedules against the new expiry. On failure retries with backoff up to
|
|
20822
|
+
* {@link CREDENTIAL_REFRESH_MAX_RETRIES}, then signals exhaustion.
|
|
20823
|
+
*
|
|
20824
|
+
* Shared by the scheduled timer tick and {@link forceRefreshIfDue}. The
|
|
20825
|
+
* `_developerRefreshInProgress` guard prevents the two from overlapping.
|
|
20826
|
+
*/
|
|
20827
|
+
async executeDeveloperRefresh(provider, expiresAt, attempt) {
|
|
20828
|
+
if (this._developerRefreshInProgress) {
|
|
20829
|
+
logger$6.debug("[Coordinator] Developer refresh already in progress; skipping");
|
|
20830
|
+
return;
|
|
20831
|
+
}
|
|
20832
|
+
this._developerRefreshInProgress = true;
|
|
20833
|
+
try {
|
|
20834
|
+
const newCredentials = await this.refreshCredential(provider);
|
|
20835
|
+
this.deps.store.write(newCredentials);
|
|
20836
|
+
this.deps.store.persist(newCredentials);
|
|
20837
|
+
try {
|
|
20838
|
+
await this.deps.notifier.onCredentialRefreshed(newCredentials);
|
|
20839
|
+
} catch (reauthError) {
|
|
20840
|
+
logger$6.warn("[Coordinator] onCredentialRefreshed rejected (non-fatal):", reauthError);
|
|
20841
|
+
}
|
|
20842
|
+
logger$6.info("[Coordinator] Credentials refreshed successfully.");
|
|
20843
|
+
if (newCredentials.expiry_at) this.scheduleDeveloperRefresh(provider, newCredentials.expiry_at, 0);
|
|
20844
|
+
} catch (error) {
|
|
20845
|
+
const nextAttempt = attempt + 1;
|
|
20846
|
+
logger$6.error(`[Coordinator] Credential refresh failed (attempt ${nextAttempt}/${CREDENTIAL_REFRESH_MAX_RETRIES}):`, error);
|
|
20847
|
+
this.deps.notifier.onError(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
20848
|
+
if (nextAttempt < CREDENTIAL_REFRESH_MAX_RETRIES) this.scheduleDeveloperRefresh(provider, expiresAt, nextAttempt);
|
|
20849
|
+
else {
|
|
20850
|
+
logger$6.error("[Coordinator] Credential refresh exhausted all retries. Disconnecting.");
|
|
20851
|
+
this.deps.notifier.onError(new TokenRefreshError("Credential refresh failed after max retries"));
|
|
20852
|
+
this.deps.notifier.onRefreshExhausted();
|
|
20853
|
+
}
|
|
20854
|
+
} finally {
|
|
20855
|
+
this._developerRefreshInProgress = false;
|
|
20856
|
+
}
|
|
20857
|
+
}
|
|
20858
|
+
/**
|
|
20859
|
+
* Force an immediate refresh when the current credential is already past its
|
|
20860
|
+
* scheduled refresh window. Called on resume from suspension, where
|
|
20861
|
+
* background-tab timer throttling can delay the armed refresh well past
|
|
20862
|
+
* expiry, leaving the live session stale.
|
|
20863
|
+
*
|
|
20864
|
+
* Routes to whichever mechanism is armed: the developer timer if armed,
|
|
20865
|
+
* otherwise the Client Bound SAT pipeline. A no-op when nothing is due.
|
|
20866
|
+
*/
|
|
20867
|
+
forceRefreshIfDue() {
|
|
20868
|
+
if (this._developerTimerId !== void 0 && this._activeProvider) {
|
|
20869
|
+
const expiry = this.deps.store.read().expiry_at;
|
|
20870
|
+
if (expiry !== void 0 && Date.now() >= expiry - CREDENTIAL_REFRESH_BUFFER_MS) {
|
|
20871
|
+
logger$6.debug("[Coordinator] Resume: credential past refresh window; forcing refresh");
|
|
20872
|
+
clearTimeout(this._developerTimerId);
|
|
20873
|
+
this._developerTimerId = void 0;
|
|
20874
|
+
this.executeDeveloperRefresh(this._activeProvider, expiry, 0);
|
|
20875
|
+
}
|
|
20876
|
+
return;
|
|
20877
|
+
}
|
|
20878
|
+
this._deviceTokenManager?.refreshNowIfDue();
|
|
20879
|
+
}
|
|
20880
|
+
/**
|
|
20881
|
+
* Sync the credential's expiry from the server-provided authorization (the
|
|
20882
|
+
* `signalwire.connect` result). SATs are opaque JWE, so
|
|
20883
|
+
* `fabric_subscriber.expires_at` is the authoritative expiry of the token
|
|
20884
|
+
* the session actually connected with — the provider-reported `expiry_at`
|
|
20885
|
+
* is only a hint (and may be wrong or absent). Corrects the stored
|
|
20886
|
+
* credential and re-arms the developer refresh timer against the real
|
|
20887
|
+
* deadline when the provider supports `refresh()`.
|
|
20888
|
+
*/
|
|
20889
|
+
syncExpiryFromAuthorization(authorization, provider) {
|
|
20890
|
+
const expiresAtSec = authorization?.fabric_subscriber?.expires_at;
|
|
20891
|
+
if (!expiresAtSec) return;
|
|
20892
|
+
const expiryAt = expiresAtSec * 1e3;
|
|
20893
|
+
const credential = this.deps.store.read();
|
|
20894
|
+
if (credential.expiry_at === expiryAt) return;
|
|
20895
|
+
logger$6.debug(`[Coordinator] Correcting credential expiry from server authorization: ${new Date(expiryAt).toISOString()}`);
|
|
20896
|
+
const updated = {
|
|
20897
|
+
...credential,
|
|
20898
|
+
expiry_at: expiryAt
|
|
20899
|
+
};
|
|
20900
|
+
this.deps.store.write(updated);
|
|
20901
|
+
this.deps.store.persist(updated);
|
|
20902
|
+
if (provider?.refresh) this.scheduleDeveloperRefresh(provider, expiryAt);
|
|
20903
|
+
}
|
|
20904
|
+
/**
|
|
20905
|
+
* Invoke `provider.refresh()` deduped against any concurrent developer
|
|
20906
|
+
* refresh. Concurrent callers — the scheduled tick, a resume-forced refresh,
|
|
20907
|
+
* and the orchestrator's -32003 recovery / reconnect re-mint — share one
|
|
20908
|
+
* in-flight promise, so a provider backed by one-time-use rotating refresh
|
|
20909
|
+
* tokens is never invoked twice in parallel.
|
|
20910
|
+
*
|
|
20911
|
+
* The caller owns applying the returned credential (store write, session
|
|
20912
|
+
* reauth, rescheduling); this method only serializes the network call.
|
|
20913
|
+
*/
|
|
20914
|
+
async refreshCredential(provider) {
|
|
20915
|
+
if (this._refreshInFlight) return this._refreshInFlight;
|
|
20916
|
+
if (!provider.refresh) throw new InvalidCredentialsError("Credential provider does not support refresh");
|
|
20917
|
+
const run = provider.refresh();
|
|
20918
|
+
this._refreshInFlight = run;
|
|
20919
|
+
const clear = () => {
|
|
20920
|
+
if (this._refreshInFlight === run) this._refreshInFlight = void 0;
|
|
20921
|
+
};
|
|
20922
|
+
run.then(clear, clear);
|
|
20923
|
+
return run;
|
|
20924
|
+
}
|
|
20925
|
+
/**
|
|
20926
|
+
* Cancels any scheduled developer-provided refresh. Idempotent.
|
|
20927
|
+
*
|
|
20928
|
+
* @internal Used by the coordinator's own activation flow. External
|
|
20929
|
+
* callers should use {@link suspend} for disconnect-time quiescence —
|
|
20930
|
+
* `suspend()` also pauses the internal Client Bound SAT pipeline.
|
|
20931
|
+
*/
|
|
20932
|
+
cancelDeveloperRefresh() {
|
|
20933
|
+
if (this._developerTimerId !== void 0) {
|
|
20934
|
+
clearTimeout(this._developerTimerId);
|
|
20935
|
+
this._developerTimerId = void 0;
|
|
20936
|
+
}
|
|
20937
|
+
}
|
|
20938
|
+
/**
|
|
20939
|
+
* Suspends both refresh paths — cancels the developer timer and pauses
|
|
20940
|
+
* the internal reactive pipeline. Use when the underlying session is
|
|
20941
|
+
* being torn down (e.g., {@link SignalWire.disconnect}). The next
|
|
20942
|
+
* {@link activate} call re-enables the internal pipeline.
|
|
20943
|
+
*
|
|
20944
|
+
* The internal manager's cached token survives — see
|
|
20945
|
+
* {@link DeviceTokenManager.pause} — so a subsequent reconnect can
|
|
20946
|
+
* skip the `/devices/token` exchange entirely.
|
|
20947
|
+
*/
|
|
20948
|
+
suspend() {
|
|
20949
|
+
this.cancelDeveloperRefresh();
|
|
20950
|
+
this._deviceTokenManager?.pause();
|
|
20951
|
+
}
|
|
20952
|
+
/**
|
|
20953
|
+
* Asks the Client Bound SAT path to take over refresh. If it accepts,
|
|
20954
|
+
* the developer-provided timer (if any) is cancelled. If it declines, the
|
|
20955
|
+
* developer timer remains armed and a `credential_refresh_fallback`
|
|
20956
|
+
* warning is emitted.
|
|
20957
|
+
*
|
|
20958
|
+
* **Idempotent** — re-entrant calls during an in-flight activation are
|
|
20959
|
+
* dropped. Use `_activationGeneration` to detect stale resolutions
|
|
20960
|
+
* (e.g., a reconnect-triggered activate() that races with an earlier one).
|
|
20961
|
+
*/
|
|
20962
|
+
async activate(user, session) {
|
|
20963
|
+
if (this._activating) {
|
|
20964
|
+
logger$6.debug("[Coordinator] activate() in flight; ignoring re-entrant call");
|
|
20965
|
+
return;
|
|
20966
|
+
}
|
|
20967
|
+
if (!this._deviceTokenManager) return;
|
|
20968
|
+
this._deviceTokenManager.resume();
|
|
20969
|
+
const generation = ++this._activationGeneration;
|
|
20970
|
+
this._activating = true;
|
|
20971
|
+
try {
|
|
20972
|
+
const result = await this.withActivationTimeout(this._deviceTokenManager.activate(user, session, (cred) => this.deps.store.merge(cred)));
|
|
20973
|
+
if (generation !== this._activationGeneration) {
|
|
20974
|
+
logger$6.debug("[Coordinator] activate() result discarded (preempted by newer activation)");
|
|
20975
|
+
return;
|
|
20976
|
+
}
|
|
20977
|
+
if (result.activated) {
|
|
20978
|
+
this.cancelDeveloperRefresh();
|
|
20979
|
+
logger$6.debug("[Coordinator] Developer refresh disabled — Client Bound SAT owns refresh");
|
|
20980
|
+
return;
|
|
20981
|
+
}
|
|
20982
|
+
logger$6.warn(`[SignalWire] [SW-REFRESH-FALLBACK] Client Bound SAT declined (reason=${result.reason}); using developer-provided refresh handler.`);
|
|
20983
|
+
this.deps.notifier.onWarning({
|
|
20984
|
+
code: "credential_refresh_fallback",
|
|
20985
|
+
source: "CredentialProvider",
|
|
20986
|
+
reason: result.reason,
|
|
20987
|
+
message: `Client Bound SAT activation declined (${result.reason}); using developer-provided refresh.`
|
|
20988
|
+
});
|
|
20989
|
+
} finally {
|
|
20990
|
+
this._activating = false;
|
|
20991
|
+
}
|
|
20992
|
+
}
|
|
20993
|
+
destroy() {
|
|
20994
|
+
this.cancelDeveloperRefresh();
|
|
20995
|
+
this._deviceTokenManager?.destroy();
|
|
20996
|
+
this._deviceTokenManager = void 0;
|
|
20997
|
+
super.destroy();
|
|
20998
|
+
}
|
|
20999
|
+
/**
|
|
21000
|
+
* Races the manager's `activate()` against a hard timeout. A wedged HTTP
|
|
21001
|
+
* layer (e.g., proxy issues) could otherwise hang the activation
|
|
21002
|
+
* indefinitely, leaving the session with no refresh mechanism while the
|
|
21003
|
+
* `_activating` guard blocks subsequent retries.
|
|
21004
|
+
*
|
|
21005
|
+
* On timeout, the manager is paused immediately. The inner `activate()`
|
|
21006
|
+
* may still complete in the background and emit to `_currentToken$`,
|
|
21007
|
+
* which would normally arm the reactive refresh pipeline; pausing
|
|
21008
|
+
* prevents that pipeline from firing while the developer refresh path
|
|
21009
|
+
* is the active mechanism — preserving the "at most one mechanism
|
|
21010
|
+
* armed" invariant. The next `activate()` call resumes the manager.
|
|
21011
|
+
*/
|
|
21012
|
+
async withActivationTimeout(inner) {
|
|
21013
|
+
return new Promise((resolve) => {
|
|
21014
|
+
const timer$4 = setTimeout(() => {
|
|
21015
|
+
this._deviceTokenManager?.pause();
|
|
21016
|
+
resolve({
|
|
21017
|
+
activated: false,
|
|
21018
|
+
reason: "activation-timeout"
|
|
21019
|
+
});
|
|
21020
|
+
}, CREDENTIAL_ACTIVATE_TIMEOUT_MS);
|
|
21021
|
+
inner.then((result) => {
|
|
21022
|
+
clearTimeout(timer$4);
|
|
21023
|
+
resolve(result);
|
|
21024
|
+
}, (error) => {
|
|
21025
|
+
clearTimeout(timer$4);
|
|
21026
|
+
this.deps.notifier.onError(error instanceof Error ? error : new Error(String(error)));
|
|
21027
|
+
resolve({
|
|
21028
|
+
activated: false,
|
|
21029
|
+
reason: "endpoint-failed"
|
|
21030
|
+
});
|
|
21031
|
+
});
|
|
21032
|
+
});
|
|
21033
|
+
}
|
|
21034
|
+
};
|
|
21035
|
+
|
|
20354
21036
|
//#endregion
|
|
20355
21037
|
//#region src/managers/DiagnosticsCollector.ts
|
|
20356
21038
|
const logger$5 = getLogger();
|
|
@@ -20871,6 +21553,10 @@ var TransportManager = class extends Destroyable {
|
|
|
20871
21553
|
if (!isSignalwireRequest(message)) return true;
|
|
20872
21554
|
const eventChannel = message.params.event_channel;
|
|
20873
21555
|
if (!eventChannel) return true;
|
|
21556
|
+
if (message.params.event_type.startsWith("conversation.")) {
|
|
21557
|
+
logger$2.debug(`[Transport] Received conversation event: ${message.params.event_type} (event_channel: ${eventChannel})`);
|
|
21558
|
+
return true;
|
|
21559
|
+
}
|
|
20874
21560
|
const currentProtocol = this._currentProtocol;
|
|
20875
21561
|
if (!currentProtocol) return true;
|
|
20876
21562
|
if (!eventChannel.includes(currentProtocol)) {
|
|
@@ -21014,6 +21700,33 @@ var TransportManager = class extends Destroyable {
|
|
|
21014
21700
|
}
|
|
21015
21701
|
};
|
|
21016
21702
|
|
|
21703
|
+
//#endregion
|
|
21704
|
+
//#region src/utils/authRecovery.ts
|
|
21705
|
+
/**
|
|
21706
|
+
* Walk an error's `error`/`cause` chain looking for a {@link JSONRPCError}.
|
|
21707
|
+
* Errors thrown by call creation are wrapped (e.g. `CallCreateError`), so the
|
|
21708
|
+
* underlying signaling error is nested. Bounded by a visited set to guard
|
|
21709
|
+
* against cyclic causes.
|
|
21710
|
+
*/
|
|
21711
|
+
function findJSONRPCError(error) {
|
|
21712
|
+
const seen = /* @__PURE__ */ new Set();
|
|
21713
|
+
let current = error;
|
|
21714
|
+
while (current instanceof Error && !seen.has(current)) {
|
|
21715
|
+
seen.add(current);
|
|
21716
|
+
if (current instanceof JSONRPCError) return current;
|
|
21717
|
+
current = current.error ?? current.cause;
|
|
21718
|
+
}
|
|
21719
|
+
}
|
|
21720
|
+
/**
|
|
21721
|
+
* Whether an error is a session-recoverable authentication failure
|
|
21722
|
+
* (`-32002` authentication failed or `-32003` requester validation failed)
|
|
21723
|
+
* that a credential re-mint + retry can heal.
|
|
21724
|
+
*/
|
|
21725
|
+
function isRecoverableAuthError(error) {
|
|
21726
|
+
const rpcError = findJSONRPCError(error);
|
|
21727
|
+
return rpcError !== void 0 && (rpcError.code === RPC_ERROR_REQUESTER_VALIDATION_FAILED || rpcError.code === RPC_ERROR_AUTHENTICATION_FAILED);
|
|
21728
|
+
}
|
|
21729
|
+
|
|
21017
21730
|
//#endregion
|
|
21018
21731
|
//#region src/clients/SignalWire.ts
|
|
21019
21732
|
var import_cjs$1 = require_cjs();
|
|
@@ -21064,6 +21777,7 @@ var SignalWire = class extends Destroyable {
|
|
|
21064
21777
|
this._isConnected$ = this.createBehaviorSubject(false);
|
|
21065
21778
|
this._isRegistered$ = this.createBehaviorSubject(false);
|
|
21066
21779
|
this._errors$ = this.createReplaySubject(1);
|
|
21780
|
+
this._warnings$ = this.createReplaySubject(10);
|
|
21067
21781
|
this._options = {};
|
|
21068
21782
|
this._deps = new DependencyContainer();
|
|
21069
21783
|
this._credentialProvider = credentialProvider;
|
|
@@ -21123,6 +21837,29 @@ var SignalWire = class extends Destroyable {
|
|
|
21123
21837
|
*/
|
|
21124
21838
|
async resolveCredentials() {
|
|
21125
21839
|
const fingerprint = await this.initDPoP();
|
|
21840
|
+
this._refreshCoordinator = new CredentialRefreshCoordinator(this._dpopManager, {
|
|
21841
|
+
http: this._deps.http,
|
|
21842
|
+
notifier: {
|
|
21843
|
+
onError: (error) => this._errors$.next(error),
|
|
21844
|
+
onWarning: (warning) => this._warnings$.next(warning),
|
|
21845
|
+
onRefreshExhausted: () => void this.disconnect(),
|
|
21846
|
+
onCredentialRefreshed: async (credential) => this.reauthenticateLiveSession(credential)
|
|
21847
|
+
},
|
|
21848
|
+
store: {
|
|
21849
|
+
read: () => this._deps.credential,
|
|
21850
|
+
write: (credential) => {
|
|
21851
|
+
this._deps.credential = credential;
|
|
21852
|
+
},
|
|
21853
|
+
merge: (partial) => {
|
|
21854
|
+
this._deps.credential = {
|
|
21855
|
+
...this._deps.credential,
|
|
21856
|
+
...partial
|
|
21857
|
+
};
|
|
21858
|
+
this.persistCredential(this._deps.credential);
|
|
21859
|
+
},
|
|
21860
|
+
persist: (credential) => this.persistCredential(credential)
|
|
21861
|
+
}
|
|
21862
|
+
});
|
|
21126
21863
|
if (this._credentialProvider) return this.validateCredentials(this._credentialProvider, void 0, fingerprint);
|
|
21127
21864
|
for (const scope of this._deps.persistSession ? ["local", "session"] : ["session"]) try {
|
|
21128
21865
|
const cached = await this._deps.storage.getItem("sw:cached_credential", scope);
|
|
@@ -21152,11 +21889,32 @@ var SignalWire = class extends Destroyable {
|
|
|
21152
21889
|
logger$1.error("[SignalWire] Provided credentials have expired.");
|
|
21153
21890
|
throw new InvalidCredentialsError("Provided credentials have expired.");
|
|
21154
21891
|
}
|
|
21155
|
-
if (_credentials.expiry_at && credentialProvider?.refresh) this.
|
|
21892
|
+
if (_credentials.expiry_at && credentialProvider?.refresh) this._refreshCoordinator?.scheduleDeveloperRefresh(credentialProvider, _credentials.expiry_at);
|
|
21893
|
+
else if (_credentials.expiry_at && !credentialProvider?.refresh) {
|
|
21894
|
+
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.`);
|
|
21895
|
+
this._warnings$.next({
|
|
21896
|
+
code: "credential_no_refresh_handler",
|
|
21897
|
+
source: "CredentialProvider",
|
|
21898
|
+
message: "Credential has expiry_at but no refresh handler. Session will terminate at expiry unless the SAT carries 'sat:refresh' scope.",
|
|
21899
|
+
expiresAt: _credentials.expiry_at
|
|
21900
|
+
});
|
|
21901
|
+
}
|
|
21156
21902
|
this._deps.credential = _credentials;
|
|
21157
21903
|
this.persistCredential(_credentials);
|
|
21158
|
-
|
|
21159
|
-
|
|
21904
|
+
await this.reauthenticateLiveSession(_credentials);
|
|
21905
|
+
}
|
|
21906
|
+
/**
|
|
21907
|
+
* Reauthenticate the currently-open session with a freshly obtained
|
|
21908
|
+
* credential so the new token takes effect on the live socket immediately —
|
|
21909
|
+
* not just on the next reconnect. No-op when the session is not
|
|
21910
|
+
* connected/authenticated or the credential carries no token (e.g. an
|
|
21911
|
+
* authorization-state-only refresh). Non-fatal: reauth failures surface on
|
|
21912
|
+
* `errors$` without aborting the refresh that triggered this.
|
|
21913
|
+
*/
|
|
21914
|
+
async reauthenticateLiveSession(credential) {
|
|
21915
|
+
if (!this.isConnected || !this._clientSession.authenticated || !credential.token) return;
|
|
21916
|
+
try {
|
|
21917
|
+
await this._clientSession.reauthenticate(credential.token);
|
|
21160
21918
|
logger$1.info("[SignalWire] Session refreshed with new credentials.");
|
|
21161
21919
|
} catch (error) {
|
|
21162
21920
|
logger$1.error("[SignalWire] Failed to refresh session with new credentials:", error);
|
|
@@ -21164,33 +21922,101 @@ var SignalWire = class extends Destroyable {
|
|
|
21164
21922
|
}
|
|
21165
21923
|
}
|
|
21166
21924
|
/**
|
|
21167
|
-
*
|
|
21168
|
-
*
|
|
21169
|
-
*
|
|
21925
|
+
* One-shot recovery for a live session that failed with a recoverable auth
|
|
21926
|
+
* error (`-32002`/`-32003`) because its token went stale — e.g. a refresh
|
|
21927
|
+
* timer that was throttled while the tab was backgrounded.
|
|
21928
|
+
*
|
|
21929
|
+
* Two steps, first that succeeds wins:
|
|
21930
|
+
* 1. Reauthenticate with the in-memory token (a fresh DPoP proof is
|
|
21931
|
+
* generated automatically). Heals a session whose token was already
|
|
21932
|
+
* refreshed by the coordinator but never applied to the open socket,
|
|
21933
|
+
* and a client-bound session whose server-side auth merely drifted.
|
|
21934
|
+
* 2. Re-mint via the developer provider's `refresh()` and reauthenticate.
|
|
21935
|
+
* Skipped for client-bound sessions (the DeviceTokenManager owns their
|
|
21936
|
+
* refresh; re-minting a base SAT here would drop the DPoP binding).
|
|
21937
|
+
*
|
|
21938
|
+
* @param allowRemint - Whether step 2 (provider re-mint) may run. Callers
|
|
21939
|
+
* pass `false` for non-auth failures so a transient network error never
|
|
21940
|
+
* escalates to a token re-mint; step 1 (in-memory reauth) always runs.
|
|
21941
|
+
* @returns `true` if the session was reauthenticated, `false` otherwise.
|
|
21170
21942
|
*/
|
|
21171
|
-
|
|
21172
|
-
|
|
21173
|
-
|
|
21174
|
-
|
|
21175
|
-
|
|
21176
|
-
|
|
21177
|
-
|
|
21178
|
-
|
|
21179
|
-
|
|
21180
|
-
|
|
21181
|
-
|
|
21182
|
-
|
|
21183
|
-
|
|
21184
|
-
|
|
21185
|
-
|
|
21186
|
-
|
|
21187
|
-
|
|
21188
|
-
|
|
21189
|
-
|
|
21190
|
-
|
|
21191
|
-
|
|
21943
|
+
async recoverStaleCredential(allowRemint = true) {
|
|
21944
|
+
const currentToken = this._deps.credential.token;
|
|
21945
|
+
if (currentToken) try {
|
|
21946
|
+
await this._clientSession.reauthenticate(currentToken);
|
|
21947
|
+
return true;
|
|
21948
|
+
} catch (error) {
|
|
21949
|
+
logger$1.debug("[SignalWire] In-memory reauth failed during recovery:", error);
|
|
21950
|
+
}
|
|
21951
|
+
if (!allowRemint || this._clientSession.clientBound || !this._credentialProvider?.refresh) return false;
|
|
21952
|
+
try {
|
|
21953
|
+
const newCredentials = await this.remintCredential(this._credentialProvider);
|
|
21954
|
+
if (!newCredentials.token) return false;
|
|
21955
|
+
this._deps.credential = newCredentials;
|
|
21956
|
+
this.persistCredential(newCredentials);
|
|
21957
|
+
await this._clientSession.reauthenticate(newCredentials.token);
|
|
21958
|
+
if (newCredentials.expiry_at) this._refreshCoordinator?.scheduleDeveloperRefresh(this._credentialProvider, newCredentials.expiry_at);
|
|
21959
|
+
return true;
|
|
21960
|
+
} catch (error) {
|
|
21961
|
+
logger$1.error("[SignalWire] Credential recovery failed:", error);
|
|
21962
|
+
return false;
|
|
21963
|
+
}
|
|
21964
|
+
}
|
|
21965
|
+
/**
|
|
21966
|
+
* Re-mint a credential via `provider.refresh()`, routed through the
|
|
21967
|
+
* coordinator's shared in-flight guard so concurrent re-mint paths (a
|
|
21968
|
+
* scheduled/resume refresh, -32003 recovery, and reconnect) never fire a
|
|
21969
|
+
* second `provider.refresh()` in parallel — which rotating one-time-use
|
|
21970
|
+
* refresh tokens reject. Falls back to a direct call only if the coordinator
|
|
21971
|
+
* has not been constructed yet.
|
|
21972
|
+
*/
|
|
21973
|
+
async remintCredential(provider) {
|
|
21974
|
+
if (this._refreshCoordinator) return this._refreshCoordinator.refreshCredential(provider);
|
|
21975
|
+
if (!provider.refresh) throw new InvalidCredentialsError("Credential provider does not support refresh");
|
|
21976
|
+
return provider.refresh();
|
|
21977
|
+
}
|
|
21978
|
+
/**
|
|
21979
|
+
* Re-mint credentials before a fresh (re)connect (`onBeforeReconnect` hook).
|
|
21980
|
+
* The session invokes this only when it is client-bound OR the in-memory
|
|
21981
|
+
* token is expired. The re-mint mechanism depends on the binding:
|
|
21982
|
+
* - Client-bound: `authenticate()` with the DPoP fingerprint to obtain a
|
|
21983
|
+
* fresh base SAT the upcoming reconnect can re-bind (the
|
|
21984
|
+
* DeviceTokenManager re-activates afterwards).
|
|
21985
|
+
* - Unbound: the developer's non-interactive `refresh()` handler.
|
|
21986
|
+
* `authenticate()` is deliberately NOT used here — it may be interactive
|
|
21987
|
+
* (a login prompt) and must not fire on a background reconnect.
|
|
21988
|
+
*
|
|
21989
|
+
* Rejects on failure so the session aborts the reconnect rather than
|
|
21990
|
+
* replaying a stale token.
|
|
21991
|
+
*/
|
|
21992
|
+
async refreshCredentialForReconnect() {
|
|
21993
|
+
if (!this._credentialProvider) return;
|
|
21994
|
+
try {
|
|
21995
|
+
let newCredentials;
|
|
21996
|
+
if (this._clientSession.clientBound) {
|
|
21997
|
+
const fingerprint = this._dpopManager?.initialized ? this._dpopManager.fingerprint : void 0;
|
|
21998
|
+
logger$1.debug("[SignalWire] Re-minting client-bound base SAT before reconnect");
|
|
21999
|
+
newCredentials = await this._credentialProvider.authenticate(fingerprint ? { fingerprint } : void 0);
|
|
22000
|
+
} else if (this._credentialProvider.refresh) {
|
|
22001
|
+
logger$1.debug("[SignalWire] Refreshing unbound credential before reconnect");
|
|
22002
|
+
newCredentials = await this.remintCredential(this._credentialProvider);
|
|
22003
|
+
} else {
|
|
22004
|
+
logger$1.warn("[SignalWire] [SW-NO-REFRESH-HANDLER] Token expired on reconnect but no refresh handler; reconnecting with the existing token.");
|
|
22005
|
+
return;
|
|
21192
22006
|
}
|
|
21193
|
-
|
|
22007
|
+
if (!newCredentials.token) {
|
|
22008
|
+
logger$1.warn("[SignalWire] Re-minted credential has no token; keeping the existing credential for reconnect.");
|
|
22009
|
+
return;
|
|
22010
|
+
}
|
|
22011
|
+
this._deps.credential = newCredentials;
|
|
22012
|
+
this.persistCredential(newCredentials);
|
|
22013
|
+
if (newCredentials.expiry_at && this._credentialProvider.refresh) this._refreshCoordinator?.scheduleDeveloperRefresh(this._credentialProvider, newCredentials.expiry_at);
|
|
22014
|
+
logger$1.debug("[SignalWire] Credential refreshed successfully for reconnect");
|
|
22015
|
+
} catch (error) {
|
|
22016
|
+
logger$1.error("[SignalWire] Failed to refresh credentials for reconnect:", error);
|
|
22017
|
+
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
22018
|
+
throw error;
|
|
22019
|
+
}
|
|
21194
22020
|
}
|
|
21195
22021
|
/** Persist credential to localStorage when persistSession is enabled. */
|
|
21196
22022
|
persistCredential(credential) {
|
|
@@ -21278,51 +22104,20 @@ var SignalWire = class extends Destroyable {
|
|
|
21278
22104
|
this._attachManager = new AttachManager(this._deps.storage, this._deps.deviceController, PreferencesContainer.instance.reconnectCallsTimeout, this._deps.attachedCallsKey);
|
|
21279
22105
|
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$);
|
|
21280
22106
|
this._publicSession = new ClientSessionWrapper(this._clientSession);
|
|
21281
|
-
this._clientSession.onBeforeReconnect = async () =>
|
|
21282
|
-
if (!this._credentialProvider) return;
|
|
21283
|
-
try {
|
|
21284
|
-
const fingerprint = this._dpopManager?.initialized ? this._dpopManager.fingerprint : void 0;
|
|
21285
|
-
logger$1.debug("[SignalWire] Credential expired, refreshing before reconnect");
|
|
21286
|
-
const newCredentials = await this._credentialProvider.authenticate(fingerprint ? { fingerprint } : void 0);
|
|
21287
|
-
this._deps.credential = newCredentials;
|
|
21288
|
-
logger$1.debug("[SignalWire] Credential refreshed successfully for reconnect");
|
|
21289
|
-
} catch (error) {
|
|
21290
|
-
logger$1.error("[SignalWire] Failed to refresh credentials for reconnect:", error);
|
|
21291
|
-
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
21292
|
-
throw error;
|
|
21293
|
-
}
|
|
21294
|
-
};
|
|
22107
|
+
this._clientSession.onBeforeReconnect = async () => this.refreshCredentialForReconnect();
|
|
21295
22108
|
this.subscribeTo(this._clientSession.errors$, (error) => {
|
|
21296
22109
|
this._errors$.next(error);
|
|
21297
22110
|
});
|
|
22111
|
+
this.subscribeTo(this._clientSession.authorization$, (authorization) => {
|
|
22112
|
+
this._refreshCoordinator?.syncExpiryFromAuthorization(authorization, this._credentialProvider);
|
|
22113
|
+
});
|
|
21298
22114
|
await this._clientSession.connect();
|
|
21299
|
-
|
|
21300
|
-
if (this._refreshTimerId) {
|
|
21301
|
-
clearTimeout(this._refreshTimerId);
|
|
21302
|
-
this._refreshTimerId = void 0;
|
|
21303
|
-
logger$1.debug("[SignalWire] Developer refresh disabled — Client Bound SAT activation starting");
|
|
21304
|
-
}
|
|
21305
|
-
this._deviceTokenManager = new DeviceTokenManager(this._dpopManager, this._deps.http, (error) => this._errors$.next(error), () => this._deps.credential);
|
|
21306
|
-
await this._deviceTokenManager.activate(this._deps.user, this._clientSession, (cred) => {
|
|
21307
|
-
this._deps.credential = {
|
|
21308
|
-
...this._deps.credential,
|
|
21309
|
-
...cred
|
|
21310
|
-
};
|
|
21311
|
-
});
|
|
21312
|
-
}
|
|
22115
|
+
await this._refreshCoordinator?.activate(this._deps.user, this._clientSession);
|
|
21313
22116
|
this.subscribeTo(this._clientSession.authenticated$.pipe((0, import_cjs$1.skip)(1), (0, import_cjs$1.filter)(Boolean)), async () => {
|
|
21314
22117
|
try {
|
|
21315
|
-
|
|
21316
|
-
await this._deviceTokenManager.activate(this._deps.user, this._clientSession, (cred) => {
|
|
21317
|
-
this._deps.credential = {
|
|
21318
|
-
...this._deps.credential,
|
|
21319
|
-
...cred
|
|
21320
|
-
};
|
|
21321
|
-
});
|
|
21322
|
-
logger$1.debug("[SignalWire] Client Bound SAT re-activated after reconnect");
|
|
21323
|
-
}
|
|
22118
|
+
await this._refreshCoordinator?.activate(this._deps.user, this._clientSession);
|
|
21324
22119
|
} catch (error) {
|
|
21325
|
-
logger$1.error("[SignalWire]
|
|
22120
|
+
logger$1.error("[SignalWire] Refresh re-arm after reconnect failed (non-fatal):", error);
|
|
21326
22121
|
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
21327
22122
|
}
|
|
21328
22123
|
try {
|
|
@@ -21408,6 +22203,19 @@ var SignalWire = class extends Destroyable {
|
|
|
21408
22203
|
get errors$() {
|
|
21409
22204
|
return this.deferEmission(this._errors$.asObservable());
|
|
21410
22205
|
}
|
|
22206
|
+
/**
|
|
22207
|
+
* Observable stream of non-fatal SDK warnings.
|
|
22208
|
+
*
|
|
22209
|
+
* Subscribe to detect SDK behaviors that affect session liveness or developer-facing
|
|
22210
|
+
* contracts but do not warrant disconnection — e.g., a fallback from Client Bound SAT
|
|
22211
|
+
* refresh to the developer-provided `refresh()` because the SAT lacks `sat:refresh`
|
|
22212
|
+
* scope. Discriminated by `code`.
|
|
22213
|
+
*
|
|
22214
|
+
* Independent from {@link errors$}: existing error consumers are not notified.
|
|
22215
|
+
*/
|
|
22216
|
+
get warnings$() {
|
|
22217
|
+
return this.deferEmission(this._warnings$.asObservable());
|
|
22218
|
+
}
|
|
21411
22219
|
/** Platform WebRTC capabilities detected at construction time. */
|
|
21412
22220
|
get platformCapabilities() {
|
|
21413
22221
|
this._platformCapabilities ??= detectPlatformCapabilities(this._options.webRTCApiProvider);
|
|
@@ -21465,6 +22273,13 @@ var SignalWire = class extends Destroyable {
|
|
|
21465
22273
|
}
|
|
21466
22274
|
try {
|
|
21467
22275
|
this._visibilityController = new VisibilityController();
|
|
22276
|
+
this.subscribeTo(this._visibilityController.visibilityChange$.pipe((0, import_cjs$1.filter)((event) => event.to === "visible")), () => {
|
|
22277
|
+
try {
|
|
22278
|
+
this._refreshCoordinator?.forceRefreshIfDue();
|
|
22279
|
+
} catch (error) {
|
|
22280
|
+
logger$1.warn("[SignalWire] Resume credential revalidation failed (non-fatal):", error);
|
|
22281
|
+
}
|
|
22282
|
+
});
|
|
21468
22283
|
this.subscribeTo(this._visibilityController.visibilityChange$.pipe((0, import_cjs$1.filter)((event) => event.to === "visible" && PreferencesContainer.instance.refreshDevicesOnVisible)), () => {
|
|
21469
22284
|
logger$1.debug("[SignalWire] Page visible, re-enumerating devices");
|
|
21470
22285
|
try {
|
|
@@ -21476,7 +22291,7 @@ var SignalWire = class extends Destroyable {
|
|
|
21476
22291
|
logger$1.warn("[SignalWire] Failed to initialize VisibilityController:", error);
|
|
21477
22292
|
}
|
|
21478
22293
|
try {
|
|
21479
|
-
this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "
|
|
22294
|
+
this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "4.0.0-rc.2" });
|
|
21480
22295
|
} catch (error) {
|
|
21481
22296
|
logger$1.warn("[SignalWire] Failed to initialize DiagnosticsCollector:", error);
|
|
21482
22297
|
}
|
|
@@ -21484,14 +22299,19 @@ var SignalWire = class extends Destroyable {
|
|
|
21484
22299
|
/**
|
|
21485
22300
|
* Disconnects the WebSocket and tears down the current session.
|
|
21486
22301
|
*
|
|
22302
|
+
* Ends the session identified by the protocol and clears its persisted
|
|
22303
|
+
* resume state (`authorization_state` + protocol) and attach records
|
|
22304
|
+
* together — a later {@link connect} with the same credentials starts a
|
|
22305
|
+
* fresh session and cannot reattach to the ended session's calls.
|
|
22306
|
+
* Credentials and device preferences are preserved. To temporarily stop
|
|
22307
|
+
* receiving inbound calls while keeping the session alive, use
|
|
22308
|
+
* `unregister()` instead.
|
|
22309
|
+
*
|
|
21487
22310
|
* The client can be reconnected by calling {@link connect} again,
|
|
21488
22311
|
* which creates a fresh transport and session.
|
|
21489
22312
|
*/
|
|
21490
22313
|
async disconnect() {
|
|
21491
|
-
|
|
21492
|
-
clearTimeout(this._refreshTimerId);
|
|
21493
|
-
this._refreshTimerId = void 0;
|
|
21494
|
-
}
|
|
22314
|
+
this._refreshCoordinator?.suspend();
|
|
21495
22315
|
this._diagnosticsCollector?.record("connection", "disconnected");
|
|
21496
22316
|
await this.teardownTransportAndSession();
|
|
21497
22317
|
this._isConnected$.next(false);
|
|
@@ -21542,21 +22362,23 @@ var SignalWire = class extends Destroyable {
|
|
|
21542
22362
|
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
21543
22363
|
throw error;
|
|
21544
22364
|
}
|
|
21545
|
-
logger$1.debug("[SignalWire] Failed to register user,
|
|
21546
|
-
|
|
21547
|
-
|
|
21548
|
-
logger$1.debug("[SignalWire]
|
|
22365
|
+
logger$1.debug("[SignalWire] Failed to register user, attempting credential recovery...");
|
|
22366
|
+
let failureCause = error;
|
|
22367
|
+
if (await this.recoverStaleCredential(isRecoverableAuthError(error))) try {
|
|
22368
|
+
logger$1.debug("[SignalWire] Recovery succeeded, retrying register()");
|
|
21549
22369
|
await this._transport.execute(RPCExecute({
|
|
21550
22370
|
method: "subscriber.online",
|
|
21551
22371
|
params: {}
|
|
21552
22372
|
}));
|
|
21553
22373
|
this._isRegistered$.next(true);
|
|
21554
|
-
|
|
21555
|
-
|
|
21556
|
-
|
|
21557
|
-
|
|
21558
|
-
throw registerError;
|
|
22374
|
+
return;
|
|
22375
|
+
} catch (retryError) {
|
|
22376
|
+
logger$1.error("[SignalWire] Register retry after recovery failed:", retryError);
|
|
22377
|
+
failureCause = retryError;
|
|
21559
22378
|
}
|
|
22379
|
+
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 }) });
|
|
22380
|
+
this._errors$.next(registerError);
|
|
22381
|
+
throw registerError;
|
|
21560
22382
|
}
|
|
21561
22383
|
}
|
|
21562
22384
|
/**
|
|
@@ -21609,7 +22431,15 @@ var SignalWire = class extends Destroyable {
|
|
|
21609
22431
|
};
|
|
21610
22432
|
await this.waitAuthentication();
|
|
21611
22433
|
logger$1.debug("[SignalWire] Dialing with options:", computed_options);
|
|
21612
|
-
|
|
22434
|
+
try {
|
|
22435
|
+
return await this._clientSession.createOutboundCall(destination, computed_options);
|
|
22436
|
+
} catch (error) {
|
|
22437
|
+
if (!isRecoverableAuthError(error)) throw error;
|
|
22438
|
+
logger$1.debug("[SignalWire] Dial hit a recoverable auth error; recovering credential and retrying once");
|
|
22439
|
+
if (!await this.recoverStaleCredential()) throw error;
|
|
22440
|
+
await this.waitAuthentication();
|
|
22441
|
+
return this._clientSession.createOutboundCall(destination, computed_options);
|
|
22442
|
+
}
|
|
21613
22443
|
}
|
|
21614
22444
|
/**
|
|
21615
22445
|
* Runs a multi-phase connectivity test against the given destination.
|
|
@@ -21878,17 +22708,23 @@ var SignalWire = class extends Destroyable {
|
|
|
21878
22708
|
prefs.preferredVideoInput = null;
|
|
21879
22709
|
await this._deviceController.clearDeviceState();
|
|
21880
22710
|
}
|
|
21881
|
-
/**
|
|
22711
|
+
/**
|
|
22712
|
+
* Destroys the client, clearing timers and releasing all resources.
|
|
22713
|
+
*
|
|
22714
|
+
* Intentionally destroying the client ends its session: the resume state
|
|
22715
|
+
* (`authorization_state` + protocol) and the attach records are both
|
|
22716
|
+
* cleared. Credentials and device preferences are preserved — use
|
|
22717
|
+
* {@link resetToDefaults} for a full wipe. To temporarily stop receiving
|
|
22718
|
+
* inbound calls while keeping the session alive, use `unregister()`.
|
|
22719
|
+
*/
|
|
21882
22720
|
destroy() {
|
|
21883
|
-
|
|
21884
|
-
|
|
21885
|
-
this._refreshTimerId = void 0;
|
|
21886
|
-
}
|
|
21887
|
-
this._deviceTokenManager?.destroy();
|
|
22721
|
+
this._refreshCoordinator?.destroy();
|
|
22722
|
+
this._refreshCoordinator = void 0;
|
|
21888
22723
|
this._dpopManager?.destroy();
|
|
21889
|
-
|
|
21890
|
-
|
|
21891
|
-
this.
|
|
22724
|
+
const session = this._clientSession;
|
|
22725
|
+
session?.teardownSessionState();
|
|
22726
|
+
this._transport?.destroy();
|
|
22727
|
+
session?.destroy();
|
|
21892
22728
|
try {
|
|
21893
22729
|
this._networkMonitor?.destroy();
|
|
21894
22730
|
} catch {}
|
|
@@ -22001,7 +22837,7 @@ var StaticCredentialProvider = class {
|
|
|
22001
22837
|
/**
|
|
22002
22838
|
* Library version from package.json, injected at build time.
|
|
22003
22839
|
*/
|
|
22004
|
-
const version = "
|
|
22840
|
+
const version = "4.0.0-rc.2";
|
|
22005
22841
|
/**
|
|
22006
22842
|
* Flag indicating the library has been loaded and is ready to use.
|
|
22007
22843
|
* For UMD builds: `window.SignalWire.ready`
|
|
@@ -22023,7 +22859,7 @@ const ready = true;
|
|
|
22023
22859
|
*/
|
|
22024
22860
|
const emitReadyEvent = () => {
|
|
22025
22861
|
if (typeof window !== "undefined") {
|
|
22026
|
-
const event = new CustomEvent("signalwire:js:ready", { detail: { version: "
|
|
22862
|
+
const event = new CustomEvent("signalwire:js:ready", { detail: { version: "4.0.0-rc.2" } });
|
|
22027
22863
|
window.dispatchEvent(event);
|
|
22028
22864
|
}
|
|
22029
22865
|
};
|
|
@@ -22049,16 +22885,19 @@ if (typeof process === "undefined") globalThis.process = { env: { NODE_ENV: "pro
|
|
|
22049
22885
|
//#endregion
|
|
22050
22886
|
exports.Address = Address;
|
|
22051
22887
|
exports.CallCreateError = CallCreateError;
|
|
22888
|
+
exports.CallNotReadyError = CallNotReadyError;
|
|
22052
22889
|
exports.ClientPreferences = ClientPreferences;
|
|
22053
22890
|
exports.CollectionFetchError = CollectionFetchError;
|
|
22054
22891
|
exports.DPoPInitError = DPoPInitError;
|
|
22055
22892
|
exports.DeviceTokenError = DeviceTokenError;
|
|
22056
22893
|
exports.EmbedTokenCredentialProvider = EmbedTokenCredentialProvider;
|
|
22057
22894
|
exports.InvalidCredentialsError = InvalidCredentialsError;
|
|
22895
|
+
exports.MediaAccessError = MediaAccessError;
|
|
22058
22896
|
exports.MediaTrackError = MediaTrackError;
|
|
22059
22897
|
exports.MessageParseError = MessageParseError;
|
|
22060
22898
|
exports.OverconstrainedFallbackError = OverconstrainedFallbackError;
|
|
22061
22899
|
exports.Participant = Participant;
|
|
22900
|
+
exports.ParticipantNotReadyError = ParticipantNotReadyError;
|
|
22062
22901
|
exports.PreflightError = PreflightError;
|
|
22063
22902
|
exports.RecoveryError = RecoveryError;
|
|
22064
22903
|
exports.SelfCapabilities = SelfCapabilities;
|