@signalwire/js 4.0.0-dev-20260515133934 → 4.0.0-dev-20260717142935
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 +1156 -654
- package/dist/browser.mjs.map +1 -1
- package/dist/browser.umd.js +1158 -653
- package/dist/browser.umd.js.map +1 -1
- package/dist/index.cjs +1042 -715
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +322 -39
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +322 -39
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +970 -646
- package/dist/index.mjs.map +1 -1
- package/dist/operators/index.cjs +1 -1
- package/dist/operators/index.mjs +1 -1
- package/dist/{operators-CX_lCCJm.mjs → operators-Cgz8swPu.mjs} +180 -2
- package/dist/operators-Cgz8swPu.mjs.map +1 -0
- package/dist/{operators-D6a2J1KA.cjs → operators-D3_VhSb1.cjs} +587 -1
- package/dist/operators-D3_VhSb1.cjs.map +1 -0
- package/package.json +1 -1
- package/dist/operators-CX_lCCJm.mjs.map +0 -1
- package/dist/operators-D6a2J1KA.cjs.map +0 -1
package/dist/browser.mjs
CHANGED
|
@@ -9015,6 +9015,143 @@ var Destroyable = class {
|
|
|
9015
9015
|
}
|
|
9016
9016
|
};
|
|
9017
9017
|
|
|
9018
|
+
//#endregion
|
|
9019
|
+
//#region src/core/constants.ts
|
|
9020
|
+
const INVITE_VERSION = 1e3;
|
|
9021
|
+
const DEFAULT_ICE_CANDIDATE_TIMEOUT_MS = 600;
|
|
9022
|
+
const DEFAULT_ICE_GATHERING_TIMEOUT_MS = 6e3;
|
|
9023
|
+
const DEFAULT_RECONNECT_CALLS_TIMEOUT_MS = 300 * 1e3;
|
|
9024
|
+
const DEFAULT_CONNECTION_TIMEOUT_MS = 1e4;
|
|
9025
|
+
const DEFAULT_RECONNECT_DELAY_MIN_MS = 100;
|
|
9026
|
+
const DEFAULT_RECONNECT_DELAY_MAX_MS = 3e3;
|
|
9027
|
+
const DEFAULT_DEVICE_DEBOUNCE_TIME_MS = 1500;
|
|
9028
|
+
const DEFAULT_DEVICE_POLLING_INTERVAL_MS = 0;
|
|
9029
|
+
const PREFERENCES_STORAGE_KEY = "sw:preferences";
|
|
9030
|
+
/** Scope value that enables automatic token refresh. */
|
|
9031
|
+
const SAT_REFRESH_SCOPE = "sat:refresh";
|
|
9032
|
+
/** API endpoints for device token operations. */
|
|
9033
|
+
const DEVICE_TOKEN_ENDPOINT = "/api/fabric/subscriber/devices/token";
|
|
9034
|
+
const DEVICE_REFRESH_ENDPOINT = "/api/fabric/subscriber/devices/refresh";
|
|
9035
|
+
/** Default device token TTL in seconds (15 minutes). */
|
|
9036
|
+
const DEVICE_TOKEN_DEFAULT_EXPIRE_IN = 900;
|
|
9037
|
+
/** Buffer time in milliseconds before expiry to trigger refresh. */
|
|
9038
|
+
const DEVICE_TOKEN_REFRESH_BUFFER_MS = 3e4;
|
|
9039
|
+
/** Maximum retry attempts for device token refresh on transient failure. */
|
|
9040
|
+
const DEVICE_TOKEN_REFRESH_MAX_RETRIES = 3;
|
|
9041
|
+
/** Base delay in milliseconds for exponential backoff on refresh retry. */
|
|
9042
|
+
const DEVICE_TOKEN_REFRESH_RETRY_BASE_MS = 1e3;
|
|
9043
|
+
/** Maximum retry attempts for developer credential refresh on transient failure. */
|
|
9044
|
+
const CREDENTIAL_REFRESH_MAX_RETRIES = 5;
|
|
9045
|
+
/** Base delay in milliseconds for exponential backoff on credential refresh retry. */
|
|
9046
|
+
const CREDENTIAL_REFRESH_RETRY_BASE_MS = 1e3;
|
|
9047
|
+
/** Maximum delay in milliseconds for credential refresh backoff. */
|
|
9048
|
+
const CREDENTIAL_REFRESH_MAX_DELAY_MS = 3e4;
|
|
9049
|
+
/** Buffer in milliseconds before token expiry to trigger refresh. */
|
|
9050
|
+
const CREDENTIAL_REFRESH_BUFFER_MS = 5e3;
|
|
9051
|
+
/**
|
|
9052
|
+
* Maximum time the coordinator will wait for `DeviceTokenManager.activate()`
|
|
9053
|
+
* to resolve before treating the activation as failed and falling back to
|
|
9054
|
+
* the developer-provided refresh path. Prevents a wedged HTTP layer from
|
|
9055
|
+
* leaving the session with no active refresh mechanism.
|
|
9056
|
+
*/
|
|
9057
|
+
const CREDENTIAL_ACTIVATE_TIMEOUT_MS = 3e4;
|
|
9058
|
+
/** JSON-RPC error code for requester validation failure (corrupted auth state). */
|
|
9059
|
+
const RPC_ERROR_REQUESTER_VALIDATION_FAILED = -32003;
|
|
9060
|
+
/** JSON-RPC error code for invalid params (e.g., missing authentication block). */
|
|
9061
|
+
const RPC_ERROR_INVALID_PARAMS = -32602;
|
|
9062
|
+
/** JSON-RPC error code for authentication failure (invalid token, missing DPoP, etc.). */
|
|
9063
|
+
const RPC_ERROR_AUTHENTICATION_FAILED = -32002;
|
|
9064
|
+
/** Error names browsers use for a media permission denial (user or policy). */
|
|
9065
|
+
const MEDIA_ACCESS_DENIAL_NAMES = [
|
|
9066
|
+
"NotAllowedError",
|
|
9067
|
+
"SecurityError",
|
|
9068
|
+
"PermissionDeniedError"
|
|
9069
|
+
];
|
|
9070
|
+
/** Default polling interval for RTCPeerConnection.getStats() in milliseconds. */
|
|
9071
|
+
const DEFAULT_STATS_POLLING_INTERVAL_MS = 1e3;
|
|
9072
|
+
/** Number of initial samples used to build a baseline for spike detection. */
|
|
9073
|
+
const DEFAULT_STATS_BASELINE_SAMPLES = 10;
|
|
9074
|
+
/** Duration in ms with no inbound audio packets before emitting a critical issue. */
|
|
9075
|
+
const DEFAULT_STATS_NO_PACKET_THRESHOLD_MS = 2e3;
|
|
9076
|
+
/** Multiplier applied to baseline RTT to detect a warning-level RTT spike. */
|
|
9077
|
+
const DEFAULT_STATS_RTT_SPIKE_MULTIPLIER = 3;
|
|
9078
|
+
/** Packet loss fraction (0-1) above which a warning is emitted. */
|
|
9079
|
+
const DEFAULT_STATS_PACKET_LOSS_THRESHOLD = .05;
|
|
9080
|
+
/** Multiplier applied to baseline jitter to detect a jitter spike. */
|
|
9081
|
+
const DEFAULT_STATS_JITTER_SPIKE_MULTIPLIER = 4;
|
|
9082
|
+
/** Number of seconds of metrics history to retain. */
|
|
9083
|
+
const DEFAULT_STATS_HISTORY_SIZE = 30;
|
|
9084
|
+
/** Maximum keyframe requests allowed within a single burst window. */
|
|
9085
|
+
const DEFAULT_KEYFRAME_MAX_BURST$1 = 3;
|
|
9086
|
+
/** Duration of the keyframe burst window in milliseconds. */
|
|
9087
|
+
const DEFAULT_KEYFRAME_BURST_WINDOW_MS$1 = 3e3;
|
|
9088
|
+
/** Cooldown period in ms after burst limit is reached before allowing more keyframes. */
|
|
9089
|
+
const DEFAULT_KEYFRAME_COOLDOWN_MS$1 = 1e4;
|
|
9090
|
+
/** Minimum time between re-INVITE attempts in milliseconds. */
|
|
9091
|
+
const DEFAULT_REINVITE_DEBOUNCE_TIME_MS = 1e4;
|
|
9092
|
+
/** Maximum number of re-INVITE attempts per call. */
|
|
9093
|
+
const DEFAULT_REINVITE_MAX_ATTEMPTS = 3;
|
|
9094
|
+
/** Timeout for a single re-INVITE attempt in milliseconds. */
|
|
9095
|
+
const DEFAULT_REINVITE_TIMEOUT_MS = 5e3;
|
|
9096
|
+
/** Debounce window in ms to collapse multiple detection signals into one trigger. */
|
|
9097
|
+
const DEFAULT_RECOVERY_DEBOUNCE_TIME_MS = 2e3;
|
|
9098
|
+
/** Cooldown period in ms between recovery attempts. */
|
|
9099
|
+
const DEFAULT_RECOVERY_COOLDOWN_MS = 1e4;
|
|
9100
|
+
/** Grace period in ms before treating ICE 'disconnected' as a failure. */
|
|
9101
|
+
const DEFAULT_ICE_DISCONNECTED_GRACE_PERIOD_MS = 3e3;
|
|
9102
|
+
/** Timeout for a single ICE restart attempt in milliseconds. */
|
|
9103
|
+
const DEFAULT_ICE_RESTART_TIMEOUT_MS$1 = 5e3;
|
|
9104
|
+
/** Maximum recovery attempts before emitting 'max_attempts_reached'. */
|
|
9105
|
+
const DEFAULT_MAX_RECOVERY_ATTEMPTS = 3;
|
|
9106
|
+
/** Upper bound in ms for waiting on iceGatheringState === 'complete' after an ICE restart. */
|
|
9107
|
+
const ICE_GATHERING_COMPLETE_TIMEOUT_MS = 1e4;
|
|
9108
|
+
/** Upper bound in ms for waiting on RTCPeerConnection.connectionState === 'connected' after a recovery ICE restart. */
|
|
9109
|
+
const PEER_CONNECTION_RECOVERY_WAIT_MS = 5e3;
|
|
9110
|
+
/** Polling interval in ms while waiting for RTCPeerConnection.connectionState to transition. */
|
|
9111
|
+
const PEER_CONNECTION_RECOVERY_POLL_MS = 100;
|
|
9112
|
+
/** Polling interval for LocalAudioPipeline.level$ (ms). ~30fps is smooth for meters. */
|
|
9113
|
+
const AUDIO_LEVEL_POLL_INTERVAL_MS = 33;
|
|
9114
|
+
/** RMS level threshold (0..1) above which the local participant is considered speaking. */
|
|
9115
|
+
const VAD_THRESHOLD = .03;
|
|
9116
|
+
/** Hold window in ms below the threshold before speaking$ flips back to false. */
|
|
9117
|
+
const VAD_HOLD_MS = 250;
|
|
9118
|
+
/** Whether to persist device selections to storage by default. */
|
|
9119
|
+
const DEFAULT_PERSIST_DEVICE_SELECTION = true;
|
|
9120
|
+
/** Whether to auto-apply device changes to active calls by default. */
|
|
9121
|
+
const DEFAULT_SYNC_DEVICES_TO_ACTIVE_CALLS = true;
|
|
9122
|
+
/** Storage keys for persisted device selections. */
|
|
9123
|
+
const DEVICE_STORAGE_KEY_AUDIO_INPUT = "sw:device:audioinput";
|
|
9124
|
+
const DEVICE_STORAGE_KEY_AUDIO_OUTPUT = "sw:device:audiooutput";
|
|
9125
|
+
const DEVICE_STORAGE_KEY_VIDEO_INPUT = "sw:device:videoinput";
|
|
9126
|
+
/** Whether to auto-mute video when the tab becomes hidden. */
|
|
9127
|
+
const DEFAULT_AUTO_MUTE_VIDEO_ON_HIDDEN = false;
|
|
9128
|
+
/** Whether to re-enumerate devices when the page becomes visible. */
|
|
9129
|
+
const DEFAULT_REFRESH_DEVICES_ON_VISIBLE = true;
|
|
9130
|
+
/** Whether to check peer connection health when the page becomes visible. */
|
|
9131
|
+
const DEFAULT_CHECK_CONNECTION_ON_VISIBLE = true;
|
|
9132
|
+
/** Whether automatic video degradation on low bandwidth is enabled. */
|
|
9133
|
+
const DEFAULT_ENABLE_AUTO_DEGRADATION = true;
|
|
9134
|
+
/** Bitrate in kbps below which video is automatically disabled. */
|
|
9135
|
+
const DEFAULT_DEGRADATION_BITRATE_THRESHOLD_KBPS = 150;
|
|
9136
|
+
/** Bitrate in kbps above which video is automatically re-enabled (hysteresis). */
|
|
9137
|
+
const DEFAULT_DEGRADATION_RECOVERY_THRESHOLD_KBPS = 300;
|
|
9138
|
+
/** Whether relay-only escalation is enabled as a last-resort recovery tier. */
|
|
9139
|
+
const DEFAULT_ENABLE_RELAY_FALLBACK = true;
|
|
9140
|
+
/** Whether to listen for browser online/offline/connection events. */
|
|
9141
|
+
const DEFAULT_ENABLE_NETWORK_CHANGE_DETECTION = true;
|
|
9142
|
+
/** Whether to intercept server-sent media-timeout hangups and attempt recovery. */
|
|
9143
|
+
const DEFAULT_ENABLE_SERVER_HANGUP_INTERCEPTION = true;
|
|
9144
|
+
/** Default video track constraints applied when video is enabled without explicit constraints. */
|
|
9145
|
+
const DEFAULT_VIDEO_CONSTRAINTS = {
|
|
9146
|
+
width: { ideal: 1280 },
|
|
9147
|
+
height: { ideal: 720 },
|
|
9148
|
+
aspectRatio: 16 / 9
|
|
9149
|
+
};
|
|
9150
|
+
/** Whether stereo Opus is enabled by default. */
|
|
9151
|
+
const DEFAULT_STEREO_AUDIO = false;
|
|
9152
|
+
/** Max average bitrate for stereo Opus in bits per second. */
|
|
9153
|
+
const DEFAULT_STEREO_MAX_AVERAGE_BITRATE = 51e4;
|
|
9154
|
+
|
|
9018
9155
|
//#endregion
|
|
9019
9156
|
//#region src/core/errors.ts
|
|
9020
9157
|
var UnexpectedError = class extends Error {
|
|
@@ -9144,6 +9281,20 @@ var CallCreateError = class extends Error {
|
|
|
9144
9281
|
this.name = "CallCreateError";
|
|
9145
9282
|
}
|
|
9146
9283
|
};
|
|
9284
|
+
var CallNotReadyError = class extends Error {
|
|
9285
|
+
constructor(callId, options) {
|
|
9286
|
+
super(`Call "${callId}" has no self member context yet: selfId/nodeId have not been received from the server`, options);
|
|
9287
|
+
this.callId = callId;
|
|
9288
|
+
this.name = "CallNotReadyError";
|
|
9289
|
+
}
|
|
9290
|
+
};
|
|
9291
|
+
var ParticipantNotReadyError = class extends Error {
|
|
9292
|
+
constructor(memberId, options) {
|
|
9293
|
+
super(`Participant "${memberId}" has no call context yet: its member state (call_id/node_id) has not been received from the server`, options);
|
|
9294
|
+
this.memberId = memberId;
|
|
9295
|
+
this.name = "ParticipantNotReadyError";
|
|
9296
|
+
}
|
|
9297
|
+
};
|
|
9147
9298
|
var JSONRPCError = class extends Error {
|
|
9148
9299
|
constructor(code, message, data, options, requestId) {
|
|
9149
9300
|
super(message, options);
|
|
@@ -9224,6 +9375,33 @@ var MediaTrackError = class extends Error {
|
|
|
9224
9375
|
this.name = "MediaTrackError";
|
|
9225
9376
|
}
|
|
9226
9377
|
};
|
|
9378
|
+
/** True when a `getUserMedia`/`getDisplayMedia` rejection is a permission denial. */
|
|
9379
|
+
function isMediaAccessDenial(originalError) {
|
|
9380
|
+
return originalError instanceof Error && MEDIA_ACCESS_DENIAL_NAMES.includes(originalError.name);
|
|
9381
|
+
}
|
|
9382
|
+
/**
|
|
9383
|
+
* Failure to acquire local media (camera, microphone, or screen capture)
|
|
9384
|
+
* via `getUserMedia`/`getDisplayMedia`.
|
|
9385
|
+
*
|
|
9386
|
+
* Non-fatal by default: screenshare and additional-device failures never
|
|
9387
|
+
* end the call, and main-connection failures degrade to receive-only when
|
|
9388
|
+
* possible. The wrapping site sets `fatal` to `true` only when the call
|
|
9389
|
+
* cannot continue (receive-only fallback disabled or no receive intent).
|
|
9390
|
+
*/
|
|
9391
|
+
var MediaAccessError = class extends Error {
|
|
9392
|
+
constructor(operation, media, originalError, fatal = false) {
|
|
9393
|
+
super(`Media access ${isMediaAccessDenial(originalError) ? "denied" : "failed"} for ${operation} (${media})`, { cause: originalError instanceof Error ? originalError : void 0 });
|
|
9394
|
+
this.operation = operation;
|
|
9395
|
+
this.media = media;
|
|
9396
|
+
this.originalError = originalError;
|
|
9397
|
+
this.fatal = fatal;
|
|
9398
|
+
this.name = "MediaAccessError";
|
|
9399
|
+
}
|
|
9400
|
+
/** True when the underlying failure is a permission denial (user or policy). */
|
|
9401
|
+
get denied() {
|
|
9402
|
+
return isMediaAccessDenial(this.originalError);
|
|
9403
|
+
}
|
|
9404
|
+
};
|
|
9227
9405
|
var DPoPInitError = class extends Error {
|
|
9228
9406
|
constructor(originalError, message = "Failed to initialize DPoP key pair") {
|
|
9229
9407
|
super(message, { cause: originalError instanceof Error ? originalError : void 0 });
|
|
@@ -9464,9 +9642,9 @@ var require_loglevel = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
9464
9642
|
defaultLogger$1 = new Logger();
|
|
9465
9643
|
defaultLogger$1.getLogger = function getLogger$1(name) {
|
|
9466
9644
|
if (typeof name !== "symbol" && typeof name !== "string" || name === "") throw new TypeError("You must supply a name when creating a logger.");
|
|
9467
|
-
var logger$
|
|
9468
|
-
if (!logger$
|
|
9469
|
-
return logger$
|
|
9645
|
+
var logger$33 = _loggersByName[name];
|
|
9646
|
+
if (!logger$33) logger$33 = _loggersByName[name] = new Logger(name, defaultLogger$1.methodFactory);
|
|
9647
|
+
return logger$33;
|
|
9470
9648
|
};
|
|
9471
9649
|
var _log = typeof window !== undefinedType ? window.log : void 0;
|
|
9472
9650
|
defaultLogger$1.noConflict = function() {
|
|
@@ -9502,8 +9680,8 @@ const defaultLoggerLevel = defaultLogger.levels.WARN;
|
|
|
9502
9680
|
defaultLogger.setLevel(defaultLoggerLevel);
|
|
9503
9681
|
let userLogger = null;
|
|
9504
9682
|
/** Replace the built-in logger with a custom implementation. Pass `null` to restore defaults. */
|
|
9505
|
-
const setLogger = (logger$
|
|
9506
|
-
userLogger = logger$
|
|
9683
|
+
const setLogger = (logger$33) => {
|
|
9684
|
+
userLogger = logger$33;
|
|
9507
9685
|
};
|
|
9508
9686
|
let debugOptions = {};
|
|
9509
9687
|
/** Configure debug options (e.g., `{ logWsTraffic: true }`). */
|
|
@@ -9547,8 +9725,8 @@ const wsTraffic = (options) => {
|
|
|
9547
9725
|
loggerInstance.debug(`${options.type.toUpperCase()}: \n`, msg, "\n");
|
|
9548
9726
|
};
|
|
9549
9727
|
const getLogger = () => {
|
|
9550
|
-
const logger$
|
|
9551
|
-
return new Proxy(logger$
|
|
9728
|
+
const logger$33 = getLoggerInstance();
|
|
9729
|
+
return new Proxy(logger$33, { get(_target, prop, _receiver) {
|
|
9552
9730
|
if (prop === "wsTraffic") return wsTraffic;
|
|
9553
9731
|
const instance = getLoggerInstance();
|
|
9554
9732
|
const value = Reflect.get(instance, prop);
|
|
@@ -9600,7 +9778,7 @@ const asyncRetry = async ({ asyncCallable, maxRetries: retries = DEFAULT_MAX_RET
|
|
|
9600
9778
|
|
|
9601
9779
|
//#endregion
|
|
9602
9780
|
//#region src/controllers/HTTPRequestController.ts
|
|
9603
|
-
const logger$
|
|
9781
|
+
const logger$32 = getLogger();
|
|
9604
9782
|
const GET_PARAMS = {
|
|
9605
9783
|
method: "GET",
|
|
9606
9784
|
headers: { Accept: "application/json" }
|
|
@@ -9664,7 +9842,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9664
9842
|
this._responses$.next(response);
|
|
9665
9843
|
return response;
|
|
9666
9844
|
} catch (error) {
|
|
9667
|
-
logger$
|
|
9845
|
+
logger$32.error("[HTTPRequestController] Request error:", error);
|
|
9668
9846
|
this._status$.next("error");
|
|
9669
9847
|
const err = error instanceof Error ? error : new Error("HTTP request failed", { cause: error });
|
|
9670
9848
|
this._errors$.next(err);
|
|
@@ -9691,7 +9869,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9691
9869
|
const url = this.buildURL(request.url);
|
|
9692
9870
|
const headers = this.buildHeaders(request.headers);
|
|
9693
9871
|
const timeout$5 = request.timeout ?? this.requestTimeout;
|
|
9694
|
-
logger$
|
|
9872
|
+
logger$32.debug("[HTTPRequestController] Executing request:", {
|
|
9695
9873
|
method: request.method,
|
|
9696
9874
|
url,
|
|
9697
9875
|
headers: Object.keys(headers).reduce((acc, key) => {
|
|
@@ -9711,7 +9889,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9711
9889
|
});
|
|
9712
9890
|
clearTimeout(timeoutId);
|
|
9713
9891
|
const httpResponse = await this.convertResponse(response);
|
|
9714
|
-
logger$
|
|
9892
|
+
logger$32.debug("[HTTPRequestController] Response received:", {
|
|
9715
9893
|
status: response.status,
|
|
9716
9894
|
statusText: response.statusText,
|
|
9717
9895
|
headers: [...response.headers.entries()],
|
|
@@ -9721,7 +9899,7 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9721
9899
|
} catch (error) {
|
|
9722
9900
|
clearTimeout(timeoutId);
|
|
9723
9901
|
if (error instanceof Error && error.name === "AbortError") throw new RequestTimeoutError(`Request timeout after ${timeout$5}ms`, { cause: error });
|
|
9724
|
-
logger$
|
|
9902
|
+
logger$32.error("[HTTPRequestController] Request failed:", error);
|
|
9725
9903
|
throw error;
|
|
9726
9904
|
}
|
|
9727
9905
|
}
|
|
@@ -9735,8 +9913,8 @@ var HTTPRequestController = class HTTPRequestController extends Destroyable {
|
|
|
9735
9913
|
const credential = this.getCredential();
|
|
9736
9914
|
if (credential.token) {
|
|
9737
9915
|
headers.Authorization = `Bearer ${credential.token}`;
|
|
9738
|
-
logger$
|
|
9739
|
-
} else logger$
|
|
9916
|
+
logger$32.debug("[HTTPRequestController] Using Bearer token auth, token length:", credential.token.length);
|
|
9917
|
+
} else logger$32.warn("[HTTPRequestController] No credentials available for authentication");
|
|
9740
9918
|
return headers;
|
|
9741
9919
|
}
|
|
9742
9920
|
/**
|
|
@@ -9873,130 +10051,6 @@ var DeviceHistoryManager = class {
|
|
|
9873
10051
|
}
|
|
9874
10052
|
};
|
|
9875
10053
|
|
|
9876
|
-
//#endregion
|
|
9877
|
-
//#region src/core/constants.ts
|
|
9878
|
-
const INVITE_VERSION = 1e3;
|
|
9879
|
-
const DEFAULT_ICE_CANDIDATE_TIMEOUT_MS = 600;
|
|
9880
|
-
const DEFAULT_ICE_GATHERING_TIMEOUT_MS = 6e3;
|
|
9881
|
-
const DEFAULT_RECONNECT_CALLS_TIMEOUT_MS = 300 * 1e3;
|
|
9882
|
-
const DEFAULT_CONNECTION_TIMEOUT_MS = 1e4;
|
|
9883
|
-
const DEFAULT_RECONNECT_DELAY_MIN_MS = 100;
|
|
9884
|
-
const DEFAULT_RECONNECT_DELAY_MAX_MS = 3e3;
|
|
9885
|
-
const DEFAULT_DEVICE_DEBOUNCE_TIME_MS = 1500;
|
|
9886
|
-
const DEFAULT_DEVICE_POLLING_INTERVAL_MS = 0;
|
|
9887
|
-
const PREFERENCES_STORAGE_KEY = "sw:preferences";
|
|
9888
|
-
/** Scope value that enables automatic token refresh. */
|
|
9889
|
-
const SAT_REFRESH_SCOPE = "sat:refresh";
|
|
9890
|
-
/** API endpoints for device token operations. */
|
|
9891
|
-
const DEVICE_TOKEN_ENDPOINT = "/api/fabric/subscriber/devices/token";
|
|
9892
|
-
const DEVICE_REFRESH_ENDPOINT = "/api/fabric/subscriber/devices/refresh";
|
|
9893
|
-
/** Default device token TTL in seconds (15 minutes). */
|
|
9894
|
-
const DEVICE_TOKEN_DEFAULT_EXPIRE_IN = 900;
|
|
9895
|
-
/** Buffer time in milliseconds before expiry to trigger refresh. */
|
|
9896
|
-
const DEVICE_TOKEN_REFRESH_BUFFER_MS = 3e4;
|
|
9897
|
-
/** Maximum retry attempts for device token refresh on transient failure. */
|
|
9898
|
-
const DEVICE_TOKEN_REFRESH_MAX_RETRIES = 3;
|
|
9899
|
-
/** Base delay in milliseconds for exponential backoff on refresh retry. */
|
|
9900
|
-
const DEVICE_TOKEN_REFRESH_RETRY_BASE_MS = 1e3;
|
|
9901
|
-
/** Maximum retry attempts for developer credential refresh on transient failure. */
|
|
9902
|
-
const CREDENTIAL_REFRESH_MAX_RETRIES = 5;
|
|
9903
|
-
/** Base delay in milliseconds for exponential backoff on credential refresh retry. */
|
|
9904
|
-
const CREDENTIAL_REFRESH_RETRY_BASE_MS = 1e3;
|
|
9905
|
-
/** Maximum delay in milliseconds for credential refresh backoff. */
|
|
9906
|
-
const CREDENTIAL_REFRESH_MAX_DELAY_MS = 3e4;
|
|
9907
|
-
/** Buffer in milliseconds before token expiry to trigger refresh. */
|
|
9908
|
-
const CREDENTIAL_REFRESH_BUFFER_MS = 5e3;
|
|
9909
|
-
/** JSON-RPC error code for requester validation failure (corrupted auth state). */
|
|
9910
|
-
const RPC_ERROR_REQUESTER_VALIDATION_FAILED = -32003;
|
|
9911
|
-
/** JSON-RPC error code for invalid params (e.g., missing authentication block). */
|
|
9912
|
-
const RPC_ERROR_INVALID_PARAMS = -32602;
|
|
9913
|
-
/** JSON-RPC error code for authentication failure (invalid token, missing DPoP, etc.). */
|
|
9914
|
-
const RPC_ERROR_AUTHENTICATION_FAILED = -32002;
|
|
9915
|
-
/** Default polling interval for RTCPeerConnection.getStats() in milliseconds. */
|
|
9916
|
-
const DEFAULT_STATS_POLLING_INTERVAL_MS = 1e3;
|
|
9917
|
-
/** Number of initial samples used to build a baseline for spike detection. */
|
|
9918
|
-
const DEFAULT_STATS_BASELINE_SAMPLES = 10;
|
|
9919
|
-
/** Duration in ms with no inbound audio packets before emitting a critical issue. */
|
|
9920
|
-
const DEFAULT_STATS_NO_PACKET_THRESHOLD_MS = 2e3;
|
|
9921
|
-
/** Multiplier applied to baseline RTT to detect a warning-level RTT spike. */
|
|
9922
|
-
const DEFAULT_STATS_RTT_SPIKE_MULTIPLIER = 3;
|
|
9923
|
-
/** Packet loss fraction (0-1) above which a warning is emitted. */
|
|
9924
|
-
const DEFAULT_STATS_PACKET_LOSS_THRESHOLD = .05;
|
|
9925
|
-
/** Multiplier applied to baseline jitter to detect a jitter spike. */
|
|
9926
|
-
const DEFAULT_STATS_JITTER_SPIKE_MULTIPLIER = 4;
|
|
9927
|
-
/** Number of seconds of metrics history to retain. */
|
|
9928
|
-
const DEFAULT_STATS_HISTORY_SIZE = 30;
|
|
9929
|
-
/** Maximum keyframe requests allowed within a single burst window. */
|
|
9930
|
-
const DEFAULT_KEYFRAME_MAX_BURST$1 = 3;
|
|
9931
|
-
/** Duration of the keyframe burst window in milliseconds. */
|
|
9932
|
-
const DEFAULT_KEYFRAME_BURST_WINDOW_MS$1 = 3e3;
|
|
9933
|
-
/** Cooldown period in ms after burst limit is reached before allowing more keyframes. */
|
|
9934
|
-
const DEFAULT_KEYFRAME_COOLDOWN_MS$1 = 1e4;
|
|
9935
|
-
/** Minimum time between re-INVITE attempts in milliseconds. */
|
|
9936
|
-
const DEFAULT_REINVITE_DEBOUNCE_TIME_MS = 1e4;
|
|
9937
|
-
/** Maximum number of re-INVITE attempts per call. */
|
|
9938
|
-
const DEFAULT_REINVITE_MAX_ATTEMPTS = 3;
|
|
9939
|
-
/** Timeout for a single re-INVITE attempt in milliseconds. */
|
|
9940
|
-
const DEFAULT_REINVITE_TIMEOUT_MS = 5e3;
|
|
9941
|
-
/** Debounce window in ms to collapse multiple detection signals into one trigger. */
|
|
9942
|
-
const DEFAULT_RECOVERY_DEBOUNCE_TIME_MS = 2e3;
|
|
9943
|
-
/** Cooldown period in ms between recovery attempts. */
|
|
9944
|
-
const DEFAULT_RECOVERY_COOLDOWN_MS = 1e4;
|
|
9945
|
-
/** Grace period in ms before treating ICE 'disconnected' as a failure. */
|
|
9946
|
-
const DEFAULT_ICE_DISCONNECTED_GRACE_PERIOD_MS = 3e3;
|
|
9947
|
-
/** Timeout for a single ICE restart attempt in milliseconds. */
|
|
9948
|
-
const DEFAULT_ICE_RESTART_TIMEOUT_MS$1 = 5e3;
|
|
9949
|
-
/** Maximum recovery attempts before emitting 'max_attempts_reached'. */
|
|
9950
|
-
const DEFAULT_MAX_RECOVERY_ATTEMPTS = 3;
|
|
9951
|
-
/** Upper bound in ms for waiting on iceGatheringState === 'complete' after an ICE restart. */
|
|
9952
|
-
const ICE_GATHERING_COMPLETE_TIMEOUT_MS = 1e4;
|
|
9953
|
-
/** Upper bound in ms for waiting on RTCPeerConnection.connectionState === 'connected' after a recovery ICE restart. */
|
|
9954
|
-
const PEER_CONNECTION_RECOVERY_WAIT_MS = 5e3;
|
|
9955
|
-
/** Polling interval in ms while waiting for RTCPeerConnection.connectionState to transition. */
|
|
9956
|
-
const PEER_CONNECTION_RECOVERY_POLL_MS = 100;
|
|
9957
|
-
/** Polling interval for LocalAudioPipeline.level$ (ms). ~30fps is smooth for meters. */
|
|
9958
|
-
const AUDIO_LEVEL_POLL_INTERVAL_MS = 33;
|
|
9959
|
-
/** RMS level threshold (0..1) above which the local participant is considered speaking. */
|
|
9960
|
-
const VAD_THRESHOLD = .03;
|
|
9961
|
-
/** Hold window in ms below the threshold before speaking$ flips back to false. */
|
|
9962
|
-
const VAD_HOLD_MS = 250;
|
|
9963
|
-
/** Whether to persist device selections to storage by default. */
|
|
9964
|
-
const DEFAULT_PERSIST_DEVICE_SELECTION = true;
|
|
9965
|
-
/** Whether to auto-apply device changes to active calls by default. */
|
|
9966
|
-
const DEFAULT_SYNC_DEVICES_TO_ACTIVE_CALLS = true;
|
|
9967
|
-
/** Storage keys for persisted device selections. */
|
|
9968
|
-
const DEVICE_STORAGE_KEY_AUDIO_INPUT = "sw:device:audioinput";
|
|
9969
|
-
const DEVICE_STORAGE_KEY_AUDIO_OUTPUT = "sw:device:audiooutput";
|
|
9970
|
-
const DEVICE_STORAGE_KEY_VIDEO_INPUT = "sw:device:videoinput";
|
|
9971
|
-
/** Whether to auto-mute video when the tab becomes hidden. */
|
|
9972
|
-
const DEFAULT_AUTO_MUTE_VIDEO_ON_HIDDEN = false;
|
|
9973
|
-
/** Whether to re-enumerate devices when the page becomes visible. */
|
|
9974
|
-
const DEFAULT_REFRESH_DEVICES_ON_VISIBLE = true;
|
|
9975
|
-
/** Whether to check peer connection health when the page becomes visible. */
|
|
9976
|
-
const DEFAULT_CHECK_CONNECTION_ON_VISIBLE = true;
|
|
9977
|
-
/** Whether automatic video degradation on low bandwidth is enabled. */
|
|
9978
|
-
const DEFAULT_ENABLE_AUTO_DEGRADATION = true;
|
|
9979
|
-
/** Bitrate in kbps below which video is automatically disabled. */
|
|
9980
|
-
const DEFAULT_DEGRADATION_BITRATE_THRESHOLD_KBPS = 150;
|
|
9981
|
-
/** Bitrate in kbps above which video is automatically re-enabled (hysteresis). */
|
|
9982
|
-
const DEFAULT_DEGRADATION_RECOVERY_THRESHOLD_KBPS = 300;
|
|
9983
|
-
/** Whether relay-only escalation is enabled as a last-resort recovery tier. */
|
|
9984
|
-
const DEFAULT_ENABLE_RELAY_FALLBACK = true;
|
|
9985
|
-
/** Whether to listen for browser online/offline/connection events. */
|
|
9986
|
-
const DEFAULT_ENABLE_NETWORK_CHANGE_DETECTION = true;
|
|
9987
|
-
/** Whether to intercept server-sent media-timeout hangups and attempt recovery. */
|
|
9988
|
-
const DEFAULT_ENABLE_SERVER_HANGUP_INTERCEPTION = true;
|
|
9989
|
-
/** Default video track constraints applied when video is enabled without explicit constraints. */
|
|
9990
|
-
const DEFAULT_VIDEO_CONSTRAINTS = {
|
|
9991
|
-
width: { ideal: 1280 },
|
|
9992
|
-
height: { ideal: 720 },
|
|
9993
|
-
aspectRatio: 16 / 9
|
|
9994
|
-
};
|
|
9995
|
-
/** Whether stereo Opus is enabled by default. */
|
|
9996
|
-
const DEFAULT_STEREO_AUDIO = false;
|
|
9997
|
-
/** Max average bitrate for stereo Opus in bits per second. */
|
|
9998
|
-
const DEFAULT_STEREO_MAX_AVERAGE_BITRATE = 51e4;
|
|
9999
|
-
|
|
10000
10054
|
//#endregion
|
|
10001
10055
|
//#region src/utils/time.ts
|
|
10002
10056
|
function fromSecToMs(seconds) {
|
|
@@ -10008,7 +10062,7 @@ function fromMsToSec(milliseconds) {
|
|
|
10008
10062
|
|
|
10009
10063
|
//#endregion
|
|
10010
10064
|
//#region src/containers/PreferencesContainer.ts
|
|
10011
|
-
const logger$
|
|
10065
|
+
const logger$31 = getLogger();
|
|
10012
10066
|
var PreferencesContainer = class PreferencesContainer {
|
|
10013
10067
|
static get instance() {
|
|
10014
10068
|
this._instance ??= new PreferencesContainer();
|
|
@@ -10670,7 +10724,7 @@ var ClientPreferences = class {
|
|
|
10670
10724
|
if (!this._storage) return;
|
|
10671
10725
|
const data = collectStoredPreferences();
|
|
10672
10726
|
this._storage.setItem(PREFERENCES_STORAGE_KEY, data, "local").catch((error) => {
|
|
10673
|
-
logger$
|
|
10727
|
+
logger$31.error(`[ClientPreferences] Failed to save preferences: ${String(error)}`);
|
|
10674
10728
|
});
|
|
10675
10729
|
}
|
|
10676
10730
|
/** Loads preferences from storage and applies them to the container. */
|
|
@@ -10679,7 +10733,7 @@ var ClientPreferences = class {
|
|
|
10679
10733
|
this._storage.getItem(PREFERENCES_STORAGE_KEY, "local").then((stored) => {
|
|
10680
10734
|
if (stored) applyStoredPreferences(stored);
|
|
10681
10735
|
}).catch((error) => {
|
|
10682
|
-
logger$
|
|
10736
|
+
logger$31.error(`[ClientPreferences] Failed to load preferences: ${String(error)}`);
|
|
10683
10737
|
});
|
|
10684
10738
|
}
|
|
10685
10739
|
};
|
|
@@ -10701,7 +10755,7 @@ function toError(value) {
|
|
|
10701
10755
|
//#endregion
|
|
10702
10756
|
//#region src/controllers/NavigatorDeviceController.ts
|
|
10703
10757
|
var import_cjs$29 = require_cjs();
|
|
10704
|
-
const logger$
|
|
10758
|
+
const logger$30 = getLogger();
|
|
10705
10759
|
/** Maps a device kind to its storage key. */
|
|
10706
10760
|
const DEVICE_STORAGE_KEYS = {
|
|
10707
10761
|
audioinput: DEVICE_STORAGE_KEY_AUDIO_INPUT,
|
|
@@ -10723,7 +10777,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10723
10777
|
super();
|
|
10724
10778
|
this.webRTCApiProvider = webRTCApiProvider;
|
|
10725
10779
|
this.deviceChangeHandler = () => {
|
|
10726
|
-
logger$
|
|
10780
|
+
logger$30.debug("[DeviceController] Device change detected");
|
|
10727
10781
|
this.enumerateDevices();
|
|
10728
10782
|
};
|
|
10729
10783
|
this._devicesState$ = this.createBehaviorSubject(initialDevicesState);
|
|
@@ -10788,13 +10842,13 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10788
10842
|
return this.cachedObservable("videoInputDevices$", () => this._devicesState$.pipe((0, import_cjs$29.map)((state) => state.videoinput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$)));
|
|
10789
10843
|
}
|
|
10790
10844
|
get selectedAudioInputDevice$() {
|
|
10791
|
-
return this.cachedObservable("selectedAudioInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.audioinput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$
|
|
10845
|
+
return this.cachedObservable("selectedAudioInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.audioinput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$30.debug("[DeviceController] Selected audio input device changed:", info))));
|
|
10792
10846
|
}
|
|
10793
10847
|
get selectedAudioOutputDevice$() {
|
|
10794
|
-
return this.cachedObservable("selectedAudioOutputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.audiooutput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$
|
|
10848
|
+
return this.cachedObservable("selectedAudioOutputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.audiooutput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$30.debug("[DeviceController] Selected audio output device changed:", info))));
|
|
10795
10849
|
}
|
|
10796
10850
|
get selectedVideoInputDevice$() {
|
|
10797
|
-
return this.cachedObservable("selectedVideoInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.videoinput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$
|
|
10851
|
+
return this.cachedObservable("selectedVideoInputDevice$", () => this._selectedDevicesState$.asObservable().pipe((0, import_cjs$29.map)((state) => state.videoinput), (0, import_cjs$29.distinctUntilChanged)(), (0, import_cjs$29.takeUntil)(this.destroyed$), (0, import_cjs$29.tap)((info) => logger$30.debug("[DeviceController] Selected video input device changed:", info))));
|
|
10798
10852
|
}
|
|
10799
10853
|
get selectedAudioInputDevice() {
|
|
10800
10854
|
if (this._audioInputDisabled$.value) return null;
|
|
@@ -10869,7 +10923,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10869
10923
|
if (device) this.persistDeviceSelection("audioinput", device);
|
|
10870
10924
|
}
|
|
10871
10925
|
selectVideoInputDevice(device) {
|
|
10872
|
-
logger$
|
|
10926
|
+
logger$30.debug("[DeviceController] Setting selected video input device:", device);
|
|
10873
10927
|
if (this._videoInputDisabled$.value && device) this._videoInputDisabled$.next(false);
|
|
10874
10928
|
const previous = this._selectedDevicesState$.value.videoinput;
|
|
10875
10929
|
if (previous && previous.deviceId !== device?.deviceId) this._deviceHistory.push("videoinput", previous);
|
|
@@ -10926,7 +10980,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10926
10980
|
}
|
|
10927
10981
|
const fromHistory = this._deviceHistory.findInHistory(kind, devices);
|
|
10928
10982
|
if (fromHistory) {
|
|
10929
|
-
logger$
|
|
10983
|
+
logger$30.debug(`[DeviceController] Device disappeared, falling back to history: ${fromHistory.label}`);
|
|
10930
10984
|
this.emitDeviceRecovered(kind, selected, fromHistory, "device_disconnected");
|
|
10931
10985
|
return fromHistory;
|
|
10932
10986
|
}
|
|
@@ -10979,7 +11033,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10979
11033
|
try {
|
|
10980
11034
|
await this._storageManager.setItem(DEVICE_STORAGE_KEYS[kind], stored, "local");
|
|
10981
11035
|
} catch (error) {
|
|
10982
|
-
logger$
|
|
11036
|
+
logger$30.error(`[DeviceController] Failed to persist device selection for ${kind}:`, error);
|
|
10983
11037
|
}
|
|
10984
11038
|
}
|
|
10985
11039
|
async loadPersistedDevices() {
|
|
@@ -10995,7 +11049,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
10995
11049
|
[kind]: stored
|
|
10996
11050
|
};
|
|
10997
11051
|
} catch (error) {
|
|
10998
|
-
logger$
|
|
11052
|
+
logger$30.error(`[DeviceController] Failed to load persisted device for ${kind}:`, error);
|
|
10999
11053
|
}
|
|
11000
11054
|
}
|
|
11001
11055
|
/** Clears device history, persisted selections, and re-enumerates devices. */
|
|
@@ -11013,7 +11067,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
11013
11067
|
this.disableDeviceMonitoring();
|
|
11014
11068
|
this.webRTCApiProvider.mediaDevices.addEventListener("devicechange", this.deviceChangeHandler);
|
|
11015
11069
|
if (PreferencesContainer.instance.devicePollingInterval > 0) this._devicesPoolingSubscription = (0, import_cjs$29.interval)(PreferencesContainer.instance.devicePollingInterval).subscribe(() => {
|
|
11016
|
-
logger$
|
|
11070
|
+
logger$30.debug("[DeviceController] Polling devices due to interval");
|
|
11017
11071
|
this.enumerateDevices();
|
|
11018
11072
|
});
|
|
11019
11073
|
this.enumerateDevices();
|
|
@@ -11039,13 +11093,13 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
11039
11093
|
videoinput: []
|
|
11040
11094
|
});
|
|
11041
11095
|
this._devicesState$.next(devicesByKind);
|
|
11042
|
-
logger$
|
|
11096
|
+
logger$30.debug("[DeviceController] Devices enumerated:", {
|
|
11043
11097
|
audioInputs: devicesByKind.audioinput.length,
|
|
11044
11098
|
audioOutputs: devicesByKind.audiooutput.length,
|
|
11045
11099
|
videoInputs: devicesByKind.videoinput.length
|
|
11046
11100
|
});
|
|
11047
11101
|
} catch (error) {
|
|
11048
|
-
logger$
|
|
11102
|
+
logger$30.error("[DeviceController] Failed to enumerate devices:", error);
|
|
11049
11103
|
this._errors$.next(toError(error));
|
|
11050
11104
|
}
|
|
11051
11105
|
}
|
|
@@ -11061,7 +11115,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
11061
11115
|
stream.getTracks().forEach((t) => t.stop());
|
|
11062
11116
|
return capabilities;
|
|
11063
11117
|
} catch (error) {
|
|
11064
|
-
logger$
|
|
11118
|
+
logger$30.error("[DeviceController] Failed to get device capabilities:", error);
|
|
11065
11119
|
this._errors$.next(toError(error));
|
|
11066
11120
|
throw error;
|
|
11067
11121
|
}
|
|
@@ -11312,7 +11366,7 @@ var DependencyContainer = class {
|
|
|
11312
11366
|
|
|
11313
11367
|
//#endregion
|
|
11314
11368
|
//#region src/controllers/CryptoController.ts
|
|
11315
|
-
const logger$
|
|
11369
|
+
const logger$29 = getLogger();
|
|
11316
11370
|
const DPOP_DB_NAME = "sw-dpop";
|
|
11317
11371
|
const DPOP_DB_VERSION = 1;
|
|
11318
11372
|
const DPOP_STORE_NAME = "keys";
|
|
@@ -11371,7 +11425,7 @@ async function loadKeyPairFromDB() {
|
|
|
11371
11425
|
tx.oncomplete = () => db.close();
|
|
11372
11426
|
});
|
|
11373
11427
|
} catch (error) {
|
|
11374
|
-
logger$
|
|
11428
|
+
logger$29.warn("[DPoP] Failed to load key pair from IndexedDB:", error);
|
|
11375
11429
|
return null;
|
|
11376
11430
|
}
|
|
11377
11431
|
}
|
|
@@ -11391,7 +11445,7 @@ async function saveKeyPairToDB(keyPair) {
|
|
|
11391
11445
|
};
|
|
11392
11446
|
});
|
|
11393
11447
|
} catch (error) {
|
|
11394
|
-
logger$
|
|
11448
|
+
logger$29.warn("[DPoP] Failed to save key pair to IndexedDB:", error);
|
|
11395
11449
|
}
|
|
11396
11450
|
}
|
|
11397
11451
|
async function deleteKeyPairFromDB() {
|
|
@@ -11410,7 +11464,7 @@ async function deleteKeyPairFromDB() {
|
|
|
11410
11464
|
};
|
|
11411
11465
|
});
|
|
11412
11466
|
} catch (error) {
|
|
11413
|
-
logger$
|
|
11467
|
+
logger$29.warn("[DPoP] Failed to delete key pair from IndexedDB:", error);
|
|
11414
11468
|
}
|
|
11415
11469
|
}
|
|
11416
11470
|
/**
|
|
@@ -11470,13 +11524,13 @@ var CryptoController = class {
|
|
|
11470
11524
|
this._publicJwk = await crypto.subtle.exportKey("jwk", stored.publicKey);
|
|
11471
11525
|
this._fingerprint = await computeJwkThumbprint(this._publicJwk);
|
|
11472
11526
|
this._initialized = true;
|
|
11473
|
-
logger$
|
|
11527
|
+
logger$29.debug("[DPoP] Key pair restored from IndexedDB, fingerprint:", this._fingerprint);
|
|
11474
11528
|
return this._fingerprint;
|
|
11475
11529
|
} catch (error) {
|
|
11476
|
-
logger$
|
|
11530
|
+
logger$29.warn("[DPoP] Stored key pair unusable, generating new one:", error);
|
|
11477
11531
|
await deleteKeyPairFromDB();
|
|
11478
11532
|
}
|
|
11479
|
-
logger$
|
|
11533
|
+
logger$29.debug("[DPoP] Generating RSA key pair");
|
|
11480
11534
|
this._keyPair = await crypto.subtle.generateKey({
|
|
11481
11535
|
name: "RSASSA-PKCS1-v1_5",
|
|
11482
11536
|
modulusLength: 2048,
|
|
@@ -11491,7 +11545,7 @@ var CryptoController = class {
|
|
|
11491
11545
|
this._fingerprint = await computeJwkThumbprint(this._publicJwk);
|
|
11492
11546
|
this._initialized = true;
|
|
11493
11547
|
await saveKeyPairToDB(this._keyPair);
|
|
11494
|
-
logger$
|
|
11548
|
+
logger$29.debug("[DPoP] Key pair generated and persisted, fingerprint:", this._fingerprint);
|
|
11495
11549
|
return this._fingerprint;
|
|
11496
11550
|
}
|
|
11497
11551
|
/**
|
|
@@ -11557,7 +11611,7 @@ var CryptoController = class {
|
|
|
11557
11611
|
this._fingerprint = null;
|
|
11558
11612
|
this._initialized = false;
|
|
11559
11613
|
deleteKeyPairFromDB();
|
|
11560
|
-
logger$
|
|
11614
|
+
logger$29.debug("[DPoP] Controller destroyed");
|
|
11561
11615
|
}
|
|
11562
11616
|
get publicJwk() {
|
|
11563
11617
|
if (!this._publicJwk) throw new DPoPInitError("CryptoController not initialized. Call init() first.");
|
|
@@ -11581,7 +11635,7 @@ var CryptoController = class {
|
|
|
11581
11635
|
//#endregion
|
|
11582
11636
|
//#region src/controllers/NetworkMonitor.ts
|
|
11583
11637
|
var import_cjs$28 = require_cjs();
|
|
11584
|
-
const logger$
|
|
11638
|
+
const logger$28 = getLogger();
|
|
11585
11639
|
/**
|
|
11586
11640
|
* Safely check whether we are running in a browser environment
|
|
11587
11641
|
* with `window` and the relevant event targets.
|
|
@@ -11638,7 +11692,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11638
11692
|
}
|
|
11639
11693
|
attachListeners() {
|
|
11640
11694
|
if (!hasBrowserNetworkEvents()) {
|
|
11641
|
-
logger$
|
|
11695
|
+
logger$28.debug("NetworkMonitor: no browser environment detected, skipping event listeners");
|
|
11642
11696
|
return;
|
|
11643
11697
|
}
|
|
11644
11698
|
window.addEventListener("online", this._onOnline);
|
|
@@ -11646,7 +11700,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11646
11700
|
const connection = getNetworkConnection();
|
|
11647
11701
|
if (connection) connection.addEventListener("change", this._onConnectionChange);
|
|
11648
11702
|
this._listenersAttached = true;
|
|
11649
|
-
logger$
|
|
11703
|
+
logger$28.debug("NetworkMonitor: event listeners attached");
|
|
11650
11704
|
}
|
|
11651
11705
|
removeListeners() {
|
|
11652
11706
|
if (!this._listenersAttached) return;
|
|
@@ -11657,10 +11711,10 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11657
11711
|
if (connection) connection.removeEventListener("change", this._onConnectionChange);
|
|
11658
11712
|
}
|
|
11659
11713
|
this._listenersAttached = false;
|
|
11660
|
-
logger$
|
|
11714
|
+
logger$28.debug("NetworkMonitor: event listeners removed");
|
|
11661
11715
|
}
|
|
11662
11716
|
handleOnline() {
|
|
11663
|
-
logger$
|
|
11717
|
+
logger$28.info("NetworkMonitor: browser went online");
|
|
11664
11718
|
this._isOnline$.next(true);
|
|
11665
11719
|
this._networkChange$.next({
|
|
11666
11720
|
type: "online",
|
|
@@ -11669,7 +11723,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11669
11723
|
});
|
|
11670
11724
|
}
|
|
11671
11725
|
handleOffline() {
|
|
11672
|
-
logger$
|
|
11726
|
+
logger$28.info("NetworkMonitor: browser went offline");
|
|
11673
11727
|
this._isOnline$.next(false);
|
|
11674
11728
|
this._networkChange$.next({
|
|
11675
11729
|
type: "offline",
|
|
@@ -11678,7 +11732,7 @@ var NetworkMonitor = class extends Destroyable {
|
|
|
11678
11732
|
}
|
|
11679
11733
|
handleConnectionChange() {
|
|
11680
11734
|
const networkType = getNetworkType();
|
|
11681
|
-
logger$
|
|
11735
|
+
logger$28.info(`NetworkMonitor: connection changed — effectiveType=${networkType ?? "unknown"}`);
|
|
11682
11736
|
this._networkChange$.next({
|
|
11683
11737
|
type: "connection_change",
|
|
11684
11738
|
timestamp: Date.now(),
|
|
@@ -11794,7 +11848,7 @@ function getNavigatorMediaDevices() {
|
|
|
11794
11848
|
//#endregion
|
|
11795
11849
|
//#region src/controllers/PreflightRunner.ts
|
|
11796
11850
|
var import_cjs$27 = require_cjs();
|
|
11797
|
-
const logger$
|
|
11851
|
+
const logger$27 = getLogger();
|
|
11798
11852
|
const DEFAULT_MEDIA_TEST_DURATION_S = 10;
|
|
11799
11853
|
const ICE_GATHERING_TIMEOUT_MS = 1e4;
|
|
11800
11854
|
const SIGNALING_RTT_TIMEOUT_MS = 5e3;
|
|
@@ -11843,7 +11897,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11843
11897
|
if (!this._options.skipMediaTest) try {
|
|
11844
11898
|
bandwidth = await this.testMediaBandwidth(destination);
|
|
11845
11899
|
} catch (error) {
|
|
11846
|
-
logger$
|
|
11900
|
+
logger$27.warn("[PreflightRunner] Media bandwidth test failed:", error);
|
|
11847
11901
|
warnings.push("Media bandwidth test failed");
|
|
11848
11902
|
}
|
|
11849
11903
|
return {
|
|
@@ -11855,7 +11909,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11855
11909
|
warnings
|
|
11856
11910
|
};
|
|
11857
11911
|
} catch (error) {
|
|
11858
|
-
logger$
|
|
11912
|
+
logger$27.error("[PreflightRunner] Preflight test failed:", error);
|
|
11859
11913
|
throw new PreflightError("preflight", error instanceof Error ? error : new Error(String(error)));
|
|
11860
11914
|
} finally {
|
|
11861
11915
|
this.destroy();
|
|
@@ -11886,7 +11940,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11886
11940
|
if (track.kind === "video" && track.readyState === "live") videoWorking = true;
|
|
11887
11941
|
}
|
|
11888
11942
|
} catch (error) {
|
|
11889
|
-
logger$
|
|
11943
|
+
logger$27.warn("[PreflightRunner] Device test failed:", error);
|
|
11890
11944
|
} finally {
|
|
11891
11945
|
if (audioStream) audioStream.getTracks().forEach((t) => t.stop());
|
|
11892
11946
|
}
|
|
@@ -11944,7 +11998,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11944
11998
|
rttMs
|
|
11945
11999
|
};
|
|
11946
12000
|
} catch (error) {
|
|
11947
|
-
logger$
|
|
12001
|
+
logger$27.warn("[PreflightRunner] ICE connectivity test failed:", error);
|
|
11948
12002
|
return {
|
|
11949
12003
|
type: "failed",
|
|
11950
12004
|
turnReachable: false,
|
|
@@ -11992,7 +12046,7 @@ var PreflightRunner = class extends Destroyable {
|
|
|
11992
12046
|
//#endregion
|
|
11993
12047
|
//#region src/controllers/VisibilityController.ts
|
|
11994
12048
|
var import_cjs$26 = require_cjs();
|
|
11995
|
-
const logger$
|
|
12049
|
+
const logger$26 = getLogger();
|
|
11996
12050
|
/**
|
|
11997
12051
|
* Checks whether the document visibility API is available.
|
|
11998
12052
|
*/
|
|
@@ -12029,8 +12083,8 @@ var VisibilityController = class extends Destroyable {
|
|
|
12029
12083
|
this._boundHandler = this._handleVisibilityChange.bind(this);
|
|
12030
12084
|
if (this._hasVisibilityApi) {
|
|
12031
12085
|
document.addEventListener("visibilitychange", this._boundHandler);
|
|
12032
|
-
logger$
|
|
12033
|
-
} else logger$
|
|
12086
|
+
logger$26.debug("VisibilityController: listening for visibilitychange events");
|
|
12087
|
+
} else logger$26.debug("VisibilityController: document visibility API not available, defaulting to visible");
|
|
12034
12088
|
}
|
|
12035
12089
|
/**
|
|
12036
12090
|
* Observable of the current visibility state.
|
|
@@ -12055,7 +12109,7 @@ var VisibilityController = class extends Destroyable {
|
|
|
12055
12109
|
destroy() {
|
|
12056
12110
|
if (this._hasVisibilityApi) {
|
|
12057
12111
|
document.removeEventListener("visibilitychange", this._boundHandler);
|
|
12058
|
-
logger$
|
|
12112
|
+
logger$26.debug("VisibilityController: removed visibilitychange listener");
|
|
12059
12113
|
}
|
|
12060
12114
|
super.destroy();
|
|
12061
12115
|
}
|
|
@@ -12073,7 +12127,7 @@ var VisibilityController = class extends Destroyable {
|
|
|
12073
12127
|
timestamp: Date.now()
|
|
12074
12128
|
};
|
|
12075
12129
|
this._visibilityChange$.next(changeEvent);
|
|
12076
|
-
logger$
|
|
12130
|
+
logger$26.debug("VisibilityController: visibility changed", {
|
|
12077
12131
|
from: previousState,
|
|
12078
12132
|
to: newState
|
|
12079
12133
|
});
|
|
@@ -12314,7 +12368,7 @@ const RPCEventAckResponse = (id) => makeRPCResponse({
|
|
|
12314
12368
|
|
|
12315
12369
|
//#endregion
|
|
12316
12370
|
//#region src/managers/AttachManager.ts
|
|
12317
|
-
const logger$
|
|
12371
|
+
const logger$25 = getLogger();
|
|
12318
12372
|
var AttachManager = class {
|
|
12319
12373
|
constructor(storage, deviceController, reconnectCallsTimeout, attachKey) {
|
|
12320
12374
|
this.storage = storage;
|
|
@@ -12335,7 +12389,7 @@ var AttachManager = class {
|
|
|
12335
12389
|
try {
|
|
12336
12390
|
return await this.storage.getItem(this.attachKey) ?? {};
|
|
12337
12391
|
} catch (error) {
|
|
12338
|
-
logger$
|
|
12392
|
+
logger$25.warn("[AttachManager] Failed to retrieve attached calls from storage", error);
|
|
12339
12393
|
return {};
|
|
12340
12394
|
}
|
|
12341
12395
|
}
|
|
@@ -12343,7 +12397,7 @@ var AttachManager = class {
|
|
|
12343
12397
|
try {
|
|
12344
12398
|
await this.storage.setItem(this.attachKey, attached);
|
|
12345
12399
|
} catch (error) {
|
|
12346
|
-
logger$
|
|
12400
|
+
logger$25.warn("[AttachManager] Failed to write attached calls to storage", error);
|
|
12347
12401
|
}
|
|
12348
12402
|
}
|
|
12349
12403
|
/**
|
|
@@ -12362,7 +12416,7 @@ var AttachManager = class {
|
|
|
12362
12416
|
}
|
|
12363
12417
|
async attach(call) {
|
|
12364
12418
|
if (!call.to) {
|
|
12365
|
-
logger$
|
|
12419
|
+
logger$25.warn("[AttachManager] Skip attach for calls with no destination");
|
|
12366
12420
|
return;
|
|
12367
12421
|
}
|
|
12368
12422
|
const destination = call.to;
|
|
@@ -12415,15 +12469,15 @@ var AttachManager = class {
|
|
|
12415
12469
|
callId,
|
|
12416
12470
|
...options
|
|
12417
12471
|
});
|
|
12418
|
-
logger$
|
|
12472
|
+
logger$25.info(`[AttachManager] Reattached call ${callId} (attempt ${attempt})`);
|
|
12419
12473
|
succeeded = true;
|
|
12420
12474
|
break;
|
|
12421
12475
|
} catch (error) {
|
|
12422
|
-
logger$
|
|
12476
|
+
logger$25.warn(`[AttachManager] Reattach attempt ${attempt}/3 failed for call ${callId}:`, error);
|
|
12423
12477
|
if (attempt < 3) await new Promise((r) => setTimeout(r, (attempt + 1) * 1e3));
|
|
12424
12478
|
}
|
|
12425
12479
|
if (!succeeded) {
|
|
12426
|
-
logger$
|
|
12480
|
+
logger$25.warn(`[AttachManager] Reattach failed after 3 attempts for call ${callId}, removing reference`);
|
|
12427
12481
|
await this.detach({
|
|
12428
12482
|
id: callId,
|
|
12429
12483
|
mediaDirections: attachment.mediaDirections
|
|
@@ -13360,7 +13414,9 @@ const DEFAULT_MEMBER_CAPABILITIES = {
|
|
|
13360
13414
|
position: false,
|
|
13361
13415
|
meta: false,
|
|
13362
13416
|
remove: false,
|
|
13363
|
-
audioFlags: false
|
|
13417
|
+
audioFlags: false,
|
|
13418
|
+
denoise: false,
|
|
13419
|
+
lowbitrate: false
|
|
13364
13420
|
};
|
|
13365
13421
|
/**
|
|
13366
13422
|
* Default call capabilities with no permissions
|
|
@@ -13433,7 +13489,9 @@ function computeMemberCapabilities(flags, memberType) {
|
|
|
13433
13489
|
position: hasBooleanCapability(flags, memberType, null, "position"),
|
|
13434
13490
|
meta: hasBooleanCapability(flags, memberType, null, "meta"),
|
|
13435
13491
|
remove: hasBooleanCapability(flags, memberType, null, "remove"),
|
|
13436
|
-
audioFlags: hasBooleanCapability(flags, memberType, null, "audioflags")
|
|
13492
|
+
audioFlags: hasBooleanCapability(flags, memberType, null, "audioflags"),
|
|
13493
|
+
denoise: hasBooleanCapability(flags, memberType, null, "denoise"),
|
|
13494
|
+
lowbitrate: hasBooleanCapability(flags, memberType, null, "lowbitrate")
|
|
13437
13495
|
};
|
|
13438
13496
|
}
|
|
13439
13497
|
/**
|
|
@@ -13590,7 +13648,7 @@ function toggleHandraiseMethod(is) {
|
|
|
13590
13648
|
|
|
13591
13649
|
//#endregion
|
|
13592
13650
|
//#region src/core/entities/Participant.ts
|
|
13593
|
-
const logger$
|
|
13651
|
+
const logger$24 = getLogger();
|
|
13594
13652
|
const initialState = {};
|
|
13595
13653
|
/**
|
|
13596
13654
|
* Represents a participant in a call.
|
|
@@ -13600,9 +13658,9 @@ const initialState = {};
|
|
|
13600
13658
|
* the local participant with additional device control.
|
|
13601
13659
|
*/
|
|
13602
13660
|
var Participant = class extends Destroyable {
|
|
13603
|
-
constructor(id,
|
|
13661
|
+
constructor(id, callExecuteMethod, deviceController) {
|
|
13604
13662
|
super();
|
|
13605
|
-
this.
|
|
13663
|
+
this.callExecuteMethod = callExecuteMethod;
|
|
13606
13664
|
this.deviceController = deviceController;
|
|
13607
13665
|
this._state$ = this.createBehaviorSubject(initialState);
|
|
13608
13666
|
this.id = id;
|
|
@@ -13816,26 +13874,63 @@ var Participant = class extends Destroyable {
|
|
|
13816
13874
|
get nodeId() {
|
|
13817
13875
|
return this._state$.value.node_id;
|
|
13818
13876
|
}
|
|
13877
|
+
/** Call ID for this participant's leg, or `undefined` if not available. */
|
|
13878
|
+
get callId() {
|
|
13879
|
+
return this._state$.value.call_id;
|
|
13880
|
+
}
|
|
13819
13881
|
/** @internal */
|
|
13820
13882
|
get value() {
|
|
13821
13883
|
return this._state$.value;
|
|
13822
13884
|
}
|
|
13885
|
+
/**
|
|
13886
|
+
* Target triple for member RPCs, built from the participant's own state.
|
|
13887
|
+
* The backend locates the member's session by the target `call_id`/`node_id`,
|
|
13888
|
+
* so this must always be the participant's own call context — never the
|
|
13889
|
+
* local call's id (issue #19400).
|
|
13890
|
+
*
|
|
13891
|
+
* Reading it doubles as a readiness probe: it throws until the first full
|
|
13892
|
+
* member event (`member.joined`/`member.updated` or the `call.joined`
|
|
13893
|
+
* roster) arrives, and never regresses afterwards.
|
|
13894
|
+
*
|
|
13895
|
+
* @throws {ParticipantNotReadyError} If the member state has not been
|
|
13896
|
+
* received yet (e.g. a participant first seen via `member.talking`) — an
|
|
13897
|
+
* empty call context can never address the member, so fail fast instead of
|
|
13898
|
+
* sending a doomed RPC.
|
|
13899
|
+
*/
|
|
13900
|
+
get target() {
|
|
13901
|
+
const { call_id, node_id } = this._state$.value;
|
|
13902
|
+
if (!call_id || !node_id) throw new ParticipantNotReadyError(this.id);
|
|
13903
|
+
return {
|
|
13904
|
+
member_id: this.id,
|
|
13905
|
+
call_id,
|
|
13906
|
+
node_id
|
|
13907
|
+
};
|
|
13908
|
+
}
|
|
13909
|
+
/**
|
|
13910
|
+
* Executes a member RPC against this participant, injecting its own
|
|
13911
|
+
* {@link target} as the target.
|
|
13912
|
+
*
|
|
13913
|
+
* @throws {ParticipantNotReadyError} Via {@link target}, when the
|
|
13914
|
+
* member state has not been received yet.
|
|
13915
|
+
*/
|
|
13916
|
+
async executeMethod(method, args) {
|
|
13917
|
+
return this.callExecuteMethod(this.target, method, args);
|
|
13918
|
+
}
|
|
13823
13919
|
/** Toggles the deafened state (mutes/unmutes incoming audio). */
|
|
13824
13920
|
async toggleDeaf() {
|
|
13825
|
-
|
|
13826
|
-
await this.executeMethod(this.id, method, {});
|
|
13921
|
+
await this.executeMethod(toggleDeafMethod(this.deaf), {});
|
|
13827
13922
|
}
|
|
13828
13923
|
/** Toggles the hand-raised state. */
|
|
13829
13924
|
async toggleHandraise() {
|
|
13830
|
-
await this.executeMethod(
|
|
13925
|
+
await this.executeMethod(toggleHandraiseMethod(this.handraised), {});
|
|
13831
13926
|
}
|
|
13832
13927
|
/** Mutes the participant's audio. */
|
|
13833
13928
|
async mute() {
|
|
13834
|
-
await this.executeMethod(
|
|
13929
|
+
await this.executeMethod("call.mute", { channels: ["audio"] });
|
|
13835
13930
|
}
|
|
13836
13931
|
/** Unmutes the participant's audio. */
|
|
13837
13932
|
async unmute() {
|
|
13838
|
-
await this.executeMethod(
|
|
13933
|
+
await this.executeMethod("call.unmute", { channels: ["audio"] });
|
|
13839
13934
|
}
|
|
13840
13935
|
/** Toggles the participant's audio mute state. */
|
|
13841
13936
|
async toggleMute() {
|
|
@@ -13843,11 +13938,11 @@ var Participant = class extends Destroyable {
|
|
|
13843
13938
|
}
|
|
13844
13939
|
/** Mutes the participant's video. */
|
|
13845
13940
|
async muteVideo() {
|
|
13846
|
-
await this.executeMethod(
|
|
13941
|
+
await this.executeMethod("call.mute", { channels: ["video"] });
|
|
13847
13942
|
}
|
|
13848
13943
|
/** Unmutes the participant's video. */
|
|
13849
13944
|
async unmuteVideo() {
|
|
13850
|
-
await this.executeMethod(
|
|
13945
|
+
await this.executeMethod("call.unmute", { channels: ["video"] });
|
|
13851
13946
|
}
|
|
13852
13947
|
/** Toggles the participant's video mute state. */
|
|
13853
13948
|
async toggleMuteVideo() {
|
|
@@ -13855,7 +13950,7 @@ var Participant = class extends Destroyable {
|
|
|
13855
13950
|
}
|
|
13856
13951
|
/** Toggles echo cancellation on the audio input. */
|
|
13857
13952
|
async toggleEchoCancellation() {
|
|
13858
|
-
await this.executeMethod(
|
|
13953
|
+
await this.executeMethod("call.audioflags.set", {
|
|
13859
13954
|
echo_cancellation: !this.echoCancellation,
|
|
13860
13955
|
auto_gain: this.autoGain,
|
|
13861
13956
|
noise_suppression: this.noiseSuppression
|
|
@@ -13863,7 +13958,7 @@ var Participant = class extends Destroyable {
|
|
|
13863
13958
|
}
|
|
13864
13959
|
/** Toggles automatic gain control on the audio input. */
|
|
13865
13960
|
async toggleAudioInputAutoGain() {
|
|
13866
|
-
await this.executeMethod(
|
|
13961
|
+
await this.executeMethod("call.audioflags.set", {
|
|
13867
13962
|
echo_cancellation: this.echoCancellation,
|
|
13868
13963
|
auto_gain: !this.autoGain,
|
|
13869
13964
|
noise_suppression: this.noiseSuppression
|
|
@@ -13871,14 +13966,15 @@ var Participant = class extends Destroyable {
|
|
|
13871
13966
|
}
|
|
13872
13967
|
/** Toggles noise suppression on the audio input. */
|
|
13873
13968
|
async toggleNoiseSuppression() {
|
|
13874
|
-
await this.executeMethod(
|
|
13969
|
+
await this.executeMethod("call.audioflags.set", {
|
|
13875
13970
|
echo_cancellation: this.echoCancellation,
|
|
13876
13971
|
auto_gain: this.autoGain,
|
|
13877
13972
|
noise_suppression: !this.noiseSuppression
|
|
13878
13973
|
});
|
|
13879
13974
|
}
|
|
13975
|
+
/** Toggles low-bitrate mode for this participant's media. */
|
|
13880
13976
|
async toggleLowbitrate() {
|
|
13881
|
-
|
|
13977
|
+
await this.executeMethod("call.lowbitrate.set", { lowbitrate: !this.lowbitrate });
|
|
13882
13978
|
}
|
|
13883
13979
|
/**
|
|
13884
13980
|
* Adjusts the **conference-only** microphone energy gate / sensitivity level
|
|
@@ -13894,7 +13990,7 @@ var Participant = class extends Destroyable {
|
|
|
13894
13990
|
* (integer, larger values are more sensitive).
|
|
13895
13991
|
*/
|
|
13896
13992
|
async setAudioInputSensitivity(value) {
|
|
13897
|
-
await this.executeMethod(
|
|
13993
|
+
await this.executeMethod("call.microphone.sensitivity.set", { sensitivity: value });
|
|
13898
13994
|
}
|
|
13899
13995
|
/**
|
|
13900
13996
|
* Sets the **server-side** microphone volume on this participant's bridged
|
|
@@ -13907,7 +14003,7 @@ var Participant = class extends Destroyable {
|
|
|
13907
14003
|
* @param value - Volume level (0-100).
|
|
13908
14004
|
*/
|
|
13909
14005
|
async setAudioInputVolume(value) {
|
|
13910
|
-
await this.executeMethod(
|
|
14006
|
+
await this.executeMethod("call.microphone.volume.set", { volume: value });
|
|
13911
14007
|
}
|
|
13912
14008
|
/**
|
|
13913
14009
|
* Sets the **server-side** speaker volume on this participant's bridged call
|
|
@@ -13921,28 +14017,31 @@ var Participant = class extends Destroyable {
|
|
|
13921
14017
|
* @param value - Volume level (0-100).
|
|
13922
14018
|
*/
|
|
13923
14019
|
async setAudioOutputVolume(value) {
|
|
13924
|
-
await this.executeMethod(
|
|
14020
|
+
await this.executeMethod("call.speaker.volume.set", { volume: value });
|
|
13925
14021
|
}
|
|
13926
14022
|
/**
|
|
13927
14023
|
* Sets the participant's position in the video layout.
|
|
14024
|
+
*
|
|
14025
|
+
* Requires the `member.position` capability. The gateway requires a
|
|
14026
|
+
* `targets` array of `{ target, position }` entries (issue #19400). A
|
|
14027
|
+
* resolved promise does not guarantee a visible change: the backend silently
|
|
14028
|
+
* returns `200` (no-op) for non-conference targets.
|
|
14029
|
+
*
|
|
13928
14030
|
* @param value - The {@link VideoPosition} to assign (e.g. `'auto'`, `'reserved-0'`).
|
|
13929
14031
|
*/
|
|
13930
14032
|
async setPosition(value) {
|
|
13931
|
-
await this.executeMethod(
|
|
14033
|
+
await this.executeMethod("call.member.position.set", { targets: [{
|
|
14034
|
+
target: this.target,
|
|
14035
|
+
position: value
|
|
14036
|
+
}] });
|
|
13932
14037
|
}
|
|
13933
14038
|
/** Removes this participant from the call. */
|
|
13934
14039
|
async remove() {
|
|
13935
|
-
|
|
13936
|
-
const target = {
|
|
13937
|
-
member_id: this.id,
|
|
13938
|
-
call_id: state.call_id ?? "",
|
|
13939
|
-
node_id: state.node_id ?? ""
|
|
13940
|
-
};
|
|
13941
|
-
await this.executeMethod(target, "call.member.remove", {});
|
|
14040
|
+
await this.executeMethod("call.member.remove", { targets: [this.target] });
|
|
13942
14041
|
}
|
|
13943
14042
|
/** Ends the call for this participant. */
|
|
13944
14043
|
async end() {
|
|
13945
|
-
await this.executeMethod(
|
|
14044
|
+
await this.executeMethod("call.end", {});
|
|
13946
14045
|
}
|
|
13947
14046
|
/**
|
|
13948
14047
|
* Replaces custom metadata for this participant.
|
|
@@ -13962,7 +14061,7 @@ var Participant = class extends Destroyable {
|
|
|
13962
14061
|
}
|
|
13963
14062
|
/** Destroys the participant, releasing all subscriptions and references. */
|
|
13964
14063
|
destroy() {
|
|
13965
|
-
this.
|
|
14064
|
+
this.callExecuteMethod = void 0;
|
|
13966
14065
|
super.destroy();
|
|
13967
14066
|
}
|
|
13968
14067
|
};
|
|
@@ -13974,8 +14073,8 @@ var Participant = class extends Destroyable {
|
|
|
13974
14073
|
*/
|
|
13975
14074
|
var SelfParticipant = class extends Participant {
|
|
13976
14075
|
/** @internal */
|
|
13977
|
-
constructor(id,
|
|
13978
|
-
super(id,
|
|
14076
|
+
constructor(id, callExecuteMethod, vertoManager, deviceController) {
|
|
14077
|
+
super(id, callExecuteMethod, deviceController);
|
|
13979
14078
|
this.vertoManager = vertoManager;
|
|
13980
14079
|
this._studioAudio$ = this.createBehaviorSubject(false);
|
|
13981
14080
|
this.capabilities = new SelfCapabilities();
|
|
@@ -13999,7 +14098,7 @@ var SelfParticipant = class extends Participant {
|
|
|
13999
14098
|
async enableStudioAudio() {
|
|
14000
14099
|
if (this._studioAudio$.value) return;
|
|
14001
14100
|
this._studioAudio$.next(true);
|
|
14002
|
-
await this.executeMethod(
|
|
14101
|
+
await this.executeMethod("call.audioflags.set", {
|
|
14003
14102
|
echo_cancellation: false,
|
|
14004
14103
|
auto_gain: false,
|
|
14005
14104
|
noise_suppression: false
|
|
@@ -14012,18 +14111,27 @@ var SelfParticipant = class extends Participant {
|
|
|
14012
14111
|
async disableStudioAudio() {
|
|
14013
14112
|
if (!this._studioAudio$.value) return;
|
|
14014
14113
|
this._studioAudio$.next(false);
|
|
14015
|
-
await this.executeMethod(
|
|
14114
|
+
await this.executeMethod("call.audioflags.set", {
|
|
14016
14115
|
echo_cancellation: true,
|
|
14017
14116
|
auto_gain: true,
|
|
14018
14117
|
noise_suppression: true
|
|
14019
14118
|
});
|
|
14020
14119
|
}
|
|
14021
|
-
/**
|
|
14120
|
+
/**
|
|
14121
|
+
* Starts sharing the local screen.
|
|
14122
|
+
*
|
|
14123
|
+
* The call is unaffected when acquisition fails.
|
|
14124
|
+
*
|
|
14125
|
+
* @throws The raw `getDisplayMedia` error. A dismissed picker or a
|
|
14126
|
+
* permission denial rejects with a `NotAllowedError` `DOMException` —
|
|
14127
|
+
* inspect `error.name` to tell benign cancels apart from real failures.
|
|
14128
|
+
*/
|
|
14022
14129
|
async startScreenShare() {
|
|
14023
14130
|
try {
|
|
14024
14131
|
await this.vertoManager.addScreenMedia();
|
|
14025
14132
|
} catch (error) {
|
|
14026
|
-
logger$
|
|
14133
|
+
logger$24.error("[Participant.startScreenShare] Screen share error:", error);
|
|
14134
|
+
throw error;
|
|
14027
14135
|
}
|
|
14028
14136
|
}
|
|
14029
14137
|
/** Observable of the current screen share status. */
|
|
@@ -14038,12 +14146,20 @@ var SelfParticipant = class extends Participant {
|
|
|
14038
14146
|
async stopScreenShare() {
|
|
14039
14147
|
return this.vertoManager.removeScreenMedia();
|
|
14040
14148
|
}
|
|
14041
|
-
/**
|
|
14149
|
+
/**
|
|
14150
|
+
* Adds an additional media input device to the call.
|
|
14151
|
+
*
|
|
14152
|
+
* The call is unaffected when acquisition fails.
|
|
14153
|
+
*
|
|
14154
|
+
* @throws The raw `getUserMedia` error (e.g. `NotAllowedError` on
|
|
14155
|
+
* permission denial) — inspect `error.name` to decide how to react.
|
|
14156
|
+
*/
|
|
14042
14157
|
async addAdditionalDevice(options) {
|
|
14043
14158
|
try {
|
|
14044
14159
|
await this.vertoManager.addInputDevice(options);
|
|
14045
14160
|
} catch (error) {
|
|
14046
|
-
logger$
|
|
14161
|
+
logger$24.error("[Participant.addAdditionalDevice] Additional device error:", error);
|
|
14162
|
+
throw error;
|
|
14047
14163
|
}
|
|
14048
14164
|
}
|
|
14049
14165
|
/** Removes an additional media input device by ID. */
|
|
@@ -14105,7 +14221,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14105
14221
|
*/
|
|
14106
14222
|
exitStudioModeIfActive() {
|
|
14107
14223
|
if (this._studioAudio$.value) {
|
|
14108
|
-
logger$
|
|
14224
|
+
logger$24.debug("[SelfParticipant] Exiting studio audio mode due to individual flag toggle");
|
|
14109
14225
|
this._studioAudio$.next(false);
|
|
14110
14226
|
}
|
|
14111
14227
|
}
|
|
@@ -14129,7 +14245,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14129
14245
|
try {
|
|
14130
14246
|
await super.mute();
|
|
14131
14247
|
} catch (error) {
|
|
14132
|
-
logger$
|
|
14248
|
+
logger$24.warn("[Participant.toggleAudioInput] Server Error while muting audio input, proceeding with local toggle anyway", error);
|
|
14133
14249
|
} finally {
|
|
14134
14250
|
this.vertoManager.muteMainAudioInputDevice();
|
|
14135
14251
|
}
|
|
@@ -14139,7 +14255,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14139
14255
|
try {
|
|
14140
14256
|
await super.unmute();
|
|
14141
14257
|
} catch (error) {
|
|
14142
|
-
logger$
|
|
14258
|
+
logger$24.warn("[Participant.toggleAudioInput] Server Error while unmuting audio input, proceeding with local toggle anyway", error);
|
|
14143
14259
|
} finally {
|
|
14144
14260
|
await this.vertoManager.unmuteMainAudioInputDevice();
|
|
14145
14261
|
}
|
|
@@ -14149,7 +14265,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14149
14265
|
try {
|
|
14150
14266
|
await super.muteVideo();
|
|
14151
14267
|
} catch (error) {
|
|
14152
|
-
logger$
|
|
14268
|
+
logger$24.warn("[Participant.toggleVideoInput] Server Error while muting video input, proceeding with local toggle anyway", error);
|
|
14153
14269
|
} finally {
|
|
14154
14270
|
this.vertoManager.muteMainVideoInputDevice();
|
|
14155
14271
|
}
|
|
@@ -14159,7 +14275,7 @@ var SelfParticipant = class extends Participant {
|
|
|
14159
14275
|
try {
|
|
14160
14276
|
await super.unmuteVideo();
|
|
14161
14277
|
} catch (error) {
|
|
14162
|
-
logger$
|
|
14278
|
+
logger$24.warn("[Participant.toggleVideoInput] Server Error while unmuting video input, proceeding with local toggle anyway", error);
|
|
14163
14279
|
} finally {
|
|
14164
14280
|
await this.vertoManager.unmuteMainVideoInputDevice();
|
|
14165
14281
|
}
|
|
@@ -14364,7 +14480,7 @@ function filterAs(predicate, resultPath) {
|
|
|
14364
14480
|
//#endregion
|
|
14365
14481
|
//#region src/operators/throwOnRPCError.ts
|
|
14366
14482
|
var import_cjs$21 = require_cjs();
|
|
14367
|
-
const logger$
|
|
14483
|
+
const logger$23 = getLogger();
|
|
14368
14484
|
/**
|
|
14369
14485
|
* RxJS operator that throws a {@link JSONRPCError} when the RPC response contains an error.
|
|
14370
14486
|
* Passes successful responses through unchanged.
|
|
@@ -14372,14 +14488,14 @@ const logger$22 = getLogger();
|
|
|
14372
14488
|
function throwOnRPCError() {
|
|
14373
14489
|
return (0, import_cjs$21.map)((response) => {
|
|
14374
14490
|
if (response.error) {
|
|
14375
|
-
logger$
|
|
14491
|
+
logger$23.error("[throwOnRPCError] RPC error response:", {
|
|
14376
14492
|
code: response.error.code,
|
|
14377
14493
|
message: response.error.message,
|
|
14378
14494
|
data: response.error.data
|
|
14379
14495
|
});
|
|
14380
14496
|
throw new JSONRPCError(response.error.code, response.error.message, response.error.data);
|
|
14381
14497
|
}
|
|
14382
|
-
logger$
|
|
14498
|
+
logger$23.debug("[throwOnRPCError] RPC successful response:", response);
|
|
14383
14499
|
return response;
|
|
14384
14500
|
});
|
|
14385
14501
|
}
|
|
@@ -14387,7 +14503,7 @@ function throwOnRPCError() {
|
|
|
14387
14503
|
//#endregion
|
|
14388
14504
|
//#region src/managers/CallEventsManager.ts
|
|
14389
14505
|
var import_cjs$20 = require_cjs();
|
|
14390
|
-
const logger$
|
|
14506
|
+
const logger$22 = getLogger();
|
|
14391
14507
|
const initialSessionState = {};
|
|
14392
14508
|
/** @internal */
|
|
14393
14509
|
var CallEventsManager = class extends Destroyable {
|
|
@@ -14491,7 +14607,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14491
14607
|
}
|
|
14492
14608
|
initSubscriptions() {
|
|
14493
14609
|
this.subscribeTo(this.callJoinedEvent$, (callJoinedEvent) => {
|
|
14494
|
-
logger$
|
|
14610
|
+
logger$22.debug("[CallEventsManager] Handling call.joined event for call/session IDs:", {
|
|
14495
14611
|
callId: callJoinedEvent.call_id,
|
|
14496
14612
|
roomSessionId: callJoinedEvent.room_session_id
|
|
14497
14613
|
});
|
|
@@ -14518,19 +14634,19 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14518
14634
|
if (this._self$.value?.capabilities.setLayout) this.updateLayouts();
|
|
14519
14635
|
});
|
|
14520
14636
|
this.subscribeTo(this.memberUpdates$, (member) => {
|
|
14521
|
-
logger$
|
|
14637
|
+
logger$22.debug("[CallEventsManager] Handling member update event for member ID:", member);
|
|
14522
14638
|
this.upsertParticipant(member);
|
|
14523
14639
|
});
|
|
14524
14640
|
this.subscribeTo(this.webRtcCallSession.memberLeft$, (memberLeftEvent) => {
|
|
14525
|
-
logger$
|
|
14641
|
+
logger$22.debug("[CallEventsManager] Handling member.left event for member ID:", memberLeftEvent.member.member_id);
|
|
14526
14642
|
const participants = { ...this._participants$.value };
|
|
14527
14643
|
if (memberLeftEvent.member.member_id in participants) {
|
|
14528
14644
|
delete participants[memberLeftEvent.member.member_id];
|
|
14529
14645
|
this._participants$.next(participants);
|
|
14530
|
-
} else logger$
|
|
14646
|
+
} else logger$22.warn(`[CallEventsManager] Received member.left event for unknown member ID: ${memberLeftEvent.member.member_id}`);
|
|
14531
14647
|
});
|
|
14532
14648
|
this.subscribeTo(this.webRtcCallSession.callUpdated$, (callUpdatedEvent) => {
|
|
14533
|
-
logger$
|
|
14649
|
+
logger$22.debug("[CallEventsManager] Handling call.updated event:", callUpdatedEvent);
|
|
14534
14650
|
const roomSession = callUpdatedEvent.room_session;
|
|
14535
14651
|
this._sessionState$.next({
|
|
14536
14652
|
...this._sessionState$.value,
|
|
@@ -14545,7 +14661,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14545
14661
|
});
|
|
14546
14662
|
});
|
|
14547
14663
|
this.subscribeTo(this.layoutChangedEvent$, (layoutChangedEvent) => {
|
|
14548
|
-
logger$
|
|
14664
|
+
logger$22.debug("[CallEventsManager] Handling layout.changed event:", layoutChangedEvent);
|
|
14549
14665
|
this._sessionState$.next({
|
|
14550
14666
|
...this._sessionState$.value,
|
|
14551
14667
|
layout_name: layoutChangedEvent.id,
|
|
@@ -14555,10 +14671,10 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14555
14671
|
});
|
|
14556
14672
|
}
|
|
14557
14673
|
updateParticipantPositions(layoutChangedEvent) {
|
|
14558
|
-
if (Object.keys(this._participants$.value).length > 0 && !layoutChangedEvent.layers.some((layer) => !!layer.member_id)) logger$
|
|
14674
|
+
if (Object.keys(this._participants$.value).length > 0 && !layoutChangedEvent.layers.some((layer) => !!layer.member_id)) logger$22.warn("[CallEventsManager] No layers with member_id found in layout.changed event. Nothing to update.");
|
|
14559
14675
|
layoutChangedEvent.layers.filter((layer) => !!layer.member_id).filter((layer) => {
|
|
14560
14676
|
if (!(layer.member_id in this._participants$.value)) {
|
|
14561
|
-
logger$
|
|
14677
|
+
logger$22.warn(`[CallEventsManager] Skipping layout layer for unknown member_id: ${layer.member_id}`);
|
|
14562
14678
|
return false;
|
|
14563
14679
|
}
|
|
14564
14680
|
return true;
|
|
@@ -14581,7 +14697,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14581
14697
|
layouts: response.result.layouts
|
|
14582
14698
|
});
|
|
14583
14699
|
}).catch((error) => {
|
|
14584
|
-
logger$
|
|
14700
|
+
logger$22.error("[CallEventsManager] Error fetching layouts:", error);
|
|
14585
14701
|
});
|
|
14586
14702
|
}
|
|
14587
14703
|
updateParticipants(members) {
|
|
@@ -14597,7 +14713,7 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14597
14713
|
}
|
|
14598
14714
|
const participant = this._participants$.value[member.member_id];
|
|
14599
14715
|
const oldValue = participant.value;
|
|
14600
|
-
logger$
|
|
14716
|
+
logger$22.debug("[CallEventsManager] Updating participant:", member.member_id, {
|
|
14601
14717
|
oldValue,
|
|
14602
14718
|
newValue: member
|
|
14603
14719
|
});
|
|
@@ -14610,17 +14726,17 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14610
14726
|
}
|
|
14611
14727
|
get callJoinedEvent$() {
|
|
14612
14728
|
return this.cachedObservable("callJoinedEvent$", () => this.webRtcCallSession.callEvent$.pipe((0, import_cjs$20.filter)(isCallJoinedPayload), (0, import_cjs$20.tap)((event) => {
|
|
14613
|
-
logger$
|
|
14729
|
+
logger$22.debug("[CallEventsManager] Call joined event:", event);
|
|
14614
14730
|
})));
|
|
14615
14731
|
}
|
|
14616
14732
|
get layoutChangedEvent$() {
|
|
14617
14733
|
return this.cachedObservable("layoutChangedEvent$", () => this.webRtcCallSession.callEvent$.pipe(filterAs(isLayoutChangedPayload, "layout"), (0, import_cjs$20.tap)((event) => {
|
|
14618
|
-
logger$
|
|
14734
|
+
logger$22.debug("[CallEventsManager] Layout changed event:", event);
|
|
14619
14735
|
})));
|
|
14620
14736
|
}
|
|
14621
14737
|
get memberUpdates$() {
|
|
14622
14738
|
return this.cachedObservable("memberUpdates$", () => (0, import_cjs$20.merge)(this.webRtcCallSession.memberJoined$, this.webRtcCallSession.memberUpdated$, this.webRtcCallSession.memberTalking$).pipe((0, import_cjs$20.map)((event) => event.member), (0, import_cjs$20.tap)((event) => {
|
|
14623
|
-
logger$
|
|
14739
|
+
logger$22.debug("[CallEventsManager] Member update event:", event);
|
|
14624
14740
|
})));
|
|
14625
14741
|
}
|
|
14626
14742
|
destroy() {
|
|
@@ -14877,7 +14993,7 @@ function appendStereoParams(fmtpLine, maxBitrate) {
|
|
|
14877
14993
|
//#endregion
|
|
14878
14994
|
//#region src/controllers/ICEGatheringController.ts
|
|
14879
14995
|
var import_cjs$19 = require_cjs();
|
|
14880
|
-
const logger$
|
|
14996
|
+
const logger$21 = getLogger();
|
|
14881
14997
|
var ICEGatheringController = class extends Destroyable {
|
|
14882
14998
|
constructor(peerConnection, peerConnectionControllerNegotiating$, options = {}) {
|
|
14883
14999
|
super();
|
|
@@ -14885,23 +15001,23 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14885
15001
|
this.peerConnectionControllerNegotiating$ = peerConnectionControllerNegotiating$;
|
|
14886
15002
|
this.onicegatheringstatechangeHandler = () => {
|
|
14887
15003
|
const { iceGatheringState } = this.peerConnection;
|
|
14888
|
-
logger$
|
|
15004
|
+
logger$21.debug(`[ICEGatheringController] ICE gathering state changed to: ${iceGatheringState}`);
|
|
14889
15005
|
if (iceGatheringState === "gathering") this._iceCandidatesState.next({
|
|
14890
15006
|
state: "gathering",
|
|
14891
15007
|
validSDP: false
|
|
14892
15008
|
});
|
|
14893
15009
|
};
|
|
14894
15010
|
this.onicecandidateHandler = (event) => {
|
|
14895
|
-
logger$
|
|
15011
|
+
logger$21.debug("[ICEGatheringController] ICE candidate event received:", event.candidate);
|
|
14896
15012
|
this.removeTimer("iceCandidateTimer");
|
|
14897
15013
|
if (event.candidate) this.iceCandidateTimer = setTimeout(() => {
|
|
14898
15014
|
if (this.peerConnection.iceGatheringState !== "complete") {
|
|
14899
|
-
logger$
|
|
15015
|
+
logger$21.warn("[ICEGatheringController] ICE candidate timeout, using current SDP");
|
|
14900
15016
|
this.handleICECandidateTimeout();
|
|
14901
15017
|
}
|
|
14902
15018
|
}, this.iceCandidateTimeout);
|
|
14903
15019
|
else {
|
|
14904
|
-
logger$
|
|
15020
|
+
logger$21.debug("[ICEGatheringController] ICE gathering completed: null candidate received");
|
|
14905
15021
|
this.removeTimer("iceGatheringTimer");
|
|
14906
15022
|
this.handleICEGatheringComplete();
|
|
14907
15023
|
}
|
|
@@ -14919,7 +15035,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14919
15035
|
this.setupEventListeners();
|
|
14920
15036
|
this.iceGatheringTimer = setTimeout(() => {
|
|
14921
15037
|
if (this.peerConnection.iceGatheringState !== "complete") {
|
|
14922
|
-
logger$
|
|
15038
|
+
logger$21.warn("[ICEGatheringController] ICE gathering timeout, using current SDP");
|
|
14923
15039
|
this.handleICEGatheringTimeout();
|
|
14924
15040
|
}
|
|
14925
15041
|
}, this.iceGatheringTimeout);
|
|
@@ -14946,9 +15062,9 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14946
15062
|
this.relayOnly = value;
|
|
14947
15063
|
}
|
|
14948
15064
|
handleICEGatheringComplete() {
|
|
14949
|
-
logger$
|
|
14950
|
-
logger$
|
|
14951
|
-
logger$
|
|
15065
|
+
logger$21.debug("[ICEGatheringController] Handling ICE gathering complete");
|
|
15066
|
+
logger$21.debug(`[ICEGatheringController] Checking ICE gathering state: ${this.peerConnection.iceGatheringState}`);
|
|
15067
|
+
logger$21.debug("[ICEGatheringController] ICE gathering complete");
|
|
14952
15068
|
this._iceCandidatesState.next({
|
|
14953
15069
|
state: "complete",
|
|
14954
15070
|
validSDP: this.hasValidLocalDescriptionSDP
|
|
@@ -14964,21 +15080,21 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14964
15080
|
this.removeTimer("iceGatheringTimer");
|
|
14965
15081
|
const validSDP = this.hasValidLocalDescriptionSDP;
|
|
14966
15082
|
if (validSDP) {
|
|
14967
|
-
logger$
|
|
15083
|
+
logger$21.debug("[ICEGatheringController] Local SDP is valid");
|
|
14968
15084
|
this._iceCandidatesState.next({
|
|
14969
15085
|
state: "timeout",
|
|
14970
15086
|
validSDP
|
|
14971
15087
|
});
|
|
14972
15088
|
this.stopGathering();
|
|
14973
|
-
} else logger$
|
|
15089
|
+
} else logger$21.debug("### ICE gathering timeout\n", this.peerConnection.localDescription?.sdp);
|
|
14974
15090
|
}
|
|
14975
15091
|
handleICECandidateTimeout() {
|
|
14976
15092
|
if (this.iceCandidateTimer) this.removeTimer("iceCandidateTimer");
|
|
14977
|
-
logger$
|
|
15093
|
+
logger$21.warn("[ICEGatheringController] ICE candidate timeout");
|
|
14978
15094
|
const validSDP = this.hasValidLocalDescriptionSDP;
|
|
14979
15095
|
if (!validSDP && !this.relayOnly) this.restartICEGatheringWithRelayOnly();
|
|
14980
15096
|
else {
|
|
14981
|
-
logger$
|
|
15097
|
+
logger$21.debug("[ICEGatheringController] Using current SDP due to ICE candidate timeout");
|
|
14982
15098
|
this._iceCandidatesState.next({
|
|
14983
15099
|
state: "timeout",
|
|
14984
15100
|
validSDP
|
|
@@ -14987,7 +15103,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
14987
15103
|
}
|
|
14988
15104
|
}
|
|
14989
15105
|
restartICEGatheringWithRelayOnly() {
|
|
14990
|
-
logger$
|
|
15106
|
+
logger$21.debug("[ICEGatheringController] Restarting ICE gathering with relay-only candidates");
|
|
14991
15107
|
this.relayOnly = true;
|
|
14992
15108
|
this.peerConnection.setConfiguration({
|
|
14993
15109
|
...this.peerConnection.getConfiguration(),
|
|
@@ -15002,7 +15118,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
15002
15118
|
}
|
|
15003
15119
|
}
|
|
15004
15120
|
clearAllTimers() {
|
|
15005
|
-
logger$
|
|
15121
|
+
logger$21.debug("[ICEGatheringController] Clearing all timers");
|
|
15006
15122
|
this.removeTimer("iceGatheringTimer");
|
|
15007
15123
|
this.removeTimer("iceCandidateTimer");
|
|
15008
15124
|
}
|
|
@@ -15011,7 +15127,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
15011
15127
|
this.peerConnection.removeEventListener("icecandidate", this.onicecandidateHandler);
|
|
15012
15128
|
}
|
|
15013
15129
|
destroy() {
|
|
15014
|
-
logger$
|
|
15130
|
+
logger$21.debug("[ICEGatheringController] Destroying ICEGatheringController");
|
|
15015
15131
|
this.clearAllTimers();
|
|
15016
15132
|
this.removeEventListeners();
|
|
15017
15133
|
super.destroy();
|
|
@@ -15021,7 +15137,7 @@ var ICEGatheringController = class extends Destroyable {
|
|
|
15021
15137
|
//#endregion
|
|
15022
15138
|
//#region src/controllers/LocalAudioPipeline.ts
|
|
15023
15139
|
var import_cjs$18 = require_cjs();
|
|
15024
|
-
const logger$
|
|
15140
|
+
const logger$20 = getLogger();
|
|
15025
15141
|
/**
|
|
15026
15142
|
* Web Audio pipeline for the local microphone stream.
|
|
15027
15143
|
*
|
|
@@ -15133,7 +15249,7 @@ var LocalAudioPipeline = class extends Destroyable {
|
|
|
15133
15249
|
try {
|
|
15134
15250
|
this._inputSource.disconnect();
|
|
15135
15251
|
} catch (error) {
|
|
15136
|
-
logger$
|
|
15252
|
+
logger$20.debug("[LocalAudioPipeline] input disconnect warning:", error);
|
|
15137
15253
|
}
|
|
15138
15254
|
this._inputSource = null;
|
|
15139
15255
|
}
|
|
@@ -15143,7 +15259,7 @@ var LocalAudioPipeline = class extends Destroyable {
|
|
|
15143
15259
|
this._inputSource = this._audioContext.createMediaStreamSource(this._inputStream);
|
|
15144
15260
|
this._inputSource.connect(this._gainNode);
|
|
15145
15261
|
if (this._audioContext.state === "suspended") this._audioContext.resume().catch((error) => {
|
|
15146
|
-
logger$
|
|
15262
|
+
logger$20.warn("[LocalAudioPipeline] AudioContext resume failed:", error);
|
|
15147
15263
|
});
|
|
15148
15264
|
}
|
|
15149
15265
|
destroy() {
|
|
@@ -15158,7 +15274,7 @@ var LocalAudioPipeline = class extends Destroyable {
|
|
|
15158
15274
|
this._analyser.disconnect();
|
|
15159
15275
|
} catch {}
|
|
15160
15276
|
this._audioContext.close().catch((error) => {
|
|
15161
|
-
logger$
|
|
15277
|
+
logger$20.debug("[LocalAudioPipeline] audio context close warning:", error);
|
|
15162
15278
|
});
|
|
15163
15279
|
super.destroy();
|
|
15164
15280
|
}
|
|
@@ -15185,7 +15301,7 @@ var LocalAudioPipeline = class extends Destroyable {
|
|
|
15185
15301
|
//#endregion
|
|
15186
15302
|
//#region src/controllers/LocalStreamController.ts
|
|
15187
15303
|
var import_cjs$17 = require_cjs();
|
|
15188
|
-
const logger$
|
|
15304
|
+
const logger$19 = getLogger();
|
|
15189
15305
|
var LocalStreamController = class extends Destroyable {
|
|
15190
15306
|
constructor(options) {
|
|
15191
15307
|
super();
|
|
@@ -15223,26 +15339,26 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15223
15339
|
* Build the local media stream based on the provided options.
|
|
15224
15340
|
*/
|
|
15225
15341
|
async buildLocalStream() {
|
|
15226
|
-
logger$
|
|
15342
|
+
logger$19.debug("[LocalStreamController] Building local media stream.");
|
|
15227
15343
|
let stream;
|
|
15228
15344
|
if (this.options.inputAudioStream ?? this.options.inputVideoStream) {
|
|
15229
15345
|
const tracks = [...this.options.inputAudioStream?.getTracks() ?? [], ...this.options.inputVideoStream?.getTracks() ?? []];
|
|
15230
15346
|
stream = new MediaStream(tracks);
|
|
15231
15347
|
} else if (this.options.propose === "screenshare") {
|
|
15232
|
-
logger$
|
|
15348
|
+
logger$19.debug("[LocalStreamController] Requesting display media for screen sharing with audio:", Boolean(this.options.inputAudioDeviceConstraints));
|
|
15233
15349
|
stream = await this.options.getDisplayMedia({
|
|
15234
15350
|
video: true,
|
|
15235
15351
|
audio: Boolean(this.options.inputAudioDeviceConstraints)
|
|
15236
15352
|
});
|
|
15237
|
-
logger$
|
|
15353
|
+
logger$19.debug("[LocalStreamController] Screen share media obtained:", stream);
|
|
15238
15354
|
} else {
|
|
15239
15355
|
const constraints = {
|
|
15240
15356
|
audio: this.options.inputAudioDeviceConstraints,
|
|
15241
15357
|
video: this.options.inputVideoDeviceConstraints
|
|
15242
15358
|
};
|
|
15243
|
-
logger$
|
|
15359
|
+
logger$19.debug("[LocalStreamController] Requesting user media with constraints:", constraints);
|
|
15244
15360
|
stream = await this.options.getUserMedia(constraints);
|
|
15245
|
-
logger$
|
|
15361
|
+
logger$19.debug("[LocalStreamController] User media obtained:", stream);
|
|
15246
15362
|
}
|
|
15247
15363
|
this._localStream$.next(stream);
|
|
15248
15364
|
this._localAudioTracks$.next(stream.getAudioTracks());
|
|
@@ -15261,7 +15377,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15261
15377
|
this._localStream$.next(localStream);
|
|
15262
15378
|
if (track.kind === "video") this._localVideoTracks$.next(localStream.getVideoTracks());
|
|
15263
15379
|
else this._localAudioTracks$.next(localStream.getAudioTracks());
|
|
15264
|
-
logger$
|
|
15380
|
+
logger$19.debug(`[LocalStreamController] ${track.kind} track added:`, track.id);
|
|
15265
15381
|
return localStream;
|
|
15266
15382
|
}
|
|
15267
15383
|
/**
|
|
@@ -15273,7 +15389,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15273
15389
|
const stream = this._localStream$.value;
|
|
15274
15390
|
const track = stream?.getTracks().find((t) => t.id === trackId);
|
|
15275
15391
|
if (!track) {
|
|
15276
|
-
logger$
|
|
15392
|
+
logger$19.debug(`[LocalStreamController] track not found: ${trackId}`);
|
|
15277
15393
|
return;
|
|
15278
15394
|
}
|
|
15279
15395
|
track.removeEventListener("ended", this.mediaTrackEndedHandler);
|
|
@@ -15282,7 +15398,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15282
15398
|
this._localStream$.next(stream);
|
|
15283
15399
|
if (track.kind === "video") this._localVideoTracks$.next(stream?.getVideoTracks() ?? []);
|
|
15284
15400
|
else this._localAudioTracks$.next(stream?.getAudioTracks() ?? []);
|
|
15285
|
-
logger$
|
|
15401
|
+
logger$19.debug(`[LocalStreamController] ${track.kind} track removed:`, trackId);
|
|
15286
15402
|
return track;
|
|
15287
15403
|
}
|
|
15288
15404
|
/**
|
|
@@ -15317,7 +15433,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15317
15433
|
*/
|
|
15318
15434
|
stopAllTracks() {
|
|
15319
15435
|
this._localStream$.value?.getTracks().forEach((track) => {
|
|
15320
|
-
logger$
|
|
15436
|
+
logger$19.debug(`[LocalStreamController] Stopping local track: ${track.kind}`);
|
|
15321
15437
|
track.removeEventListener("ended", this.mediaTrackEndedHandler);
|
|
15322
15438
|
track.stop();
|
|
15323
15439
|
});
|
|
@@ -15333,7 +15449,7 @@ var LocalStreamController = class extends Destroyable {
|
|
|
15333
15449
|
|
|
15334
15450
|
//#endregion
|
|
15335
15451
|
//#region src/controllers/TransceiverController.ts
|
|
15336
|
-
const logger$
|
|
15452
|
+
const logger$18 = getLogger();
|
|
15337
15453
|
const getDirection = (send, recv) => {
|
|
15338
15454
|
if (send && recv) return "sendrecv";
|
|
15339
15455
|
else if (send && !recv) return "sendonly";
|
|
@@ -15441,7 +15557,7 @@ var TransceiverController = class extends Destroyable {
|
|
|
15441
15557
|
sendEncodings: isAudio ? void 0 : this.sendEncodings,
|
|
15442
15558
|
streams: direction === "recvonly" ? void 0 : [localStream]
|
|
15443
15559
|
};
|
|
15444
|
-
logger$
|
|
15560
|
+
logger$18.debug(`[TransceiverController] Setting up transceiver sender for local ${track.kind} track:`, {
|
|
15445
15561
|
transceiver,
|
|
15446
15562
|
transceiverParams
|
|
15447
15563
|
});
|
|
@@ -15449,11 +15565,11 @@ var TransceiverController = class extends Destroyable {
|
|
|
15449
15565
|
await transceiver.sender.replaceTrack(track);
|
|
15450
15566
|
transceiver.direction = transceiverParams.direction;
|
|
15451
15567
|
if (transceiverParams.streams?.some((stream) => Boolean(stream))) {
|
|
15452
|
-
logger$
|
|
15568
|
+
logger$18.debug(`[TransceiverController] Setting streams for transceiver sender for local ${track.kind} track:`, transceiverParams.streams);
|
|
15453
15569
|
transceiver.sender.setStreams(...transceiverParams.streams);
|
|
15454
15570
|
}
|
|
15455
15571
|
} else {
|
|
15456
|
-
logger$
|
|
15572
|
+
logger$18.debug(`[TransceiverController] Adding new transceiver for local ${track.kind} track:`, track.id);
|
|
15457
15573
|
this.peerConnection.addTransceiver(track, transceiverParams);
|
|
15458
15574
|
}
|
|
15459
15575
|
}
|
|
@@ -15467,13 +15583,13 @@ var TransceiverController = class extends Destroyable {
|
|
|
15467
15583
|
if (options.updateTransceiverDirection) transceiver.direction = "inactive";
|
|
15468
15584
|
}
|
|
15469
15585
|
} catch (error) {
|
|
15470
|
-
logger$
|
|
15586
|
+
logger$18.error("[TransceiverController] stopTrackSender error", kind, error);
|
|
15471
15587
|
this.options.onError?.(new MediaTrackError("stopTrackSender", kind, error));
|
|
15472
15588
|
}
|
|
15473
15589
|
}
|
|
15474
15590
|
async restoreTrackSender(kind) {
|
|
15475
15591
|
try {
|
|
15476
|
-
logger$
|
|
15592
|
+
logger$18.debug("[TransceiverController] restoreTrackSender called", kind);
|
|
15477
15593
|
const constraints = {};
|
|
15478
15594
|
const transceivers = this.transceiverByKind(kind);
|
|
15479
15595
|
for (const transceiver of transceivers) {
|
|
@@ -15483,23 +15599,23 @@ var TransceiverController = class extends Destroyable {
|
|
|
15483
15599
|
if (trackKind === "audio" || trackKind === "video") constraints[trackKind] = this.getConstraintsFor(trackKind);
|
|
15484
15600
|
}
|
|
15485
15601
|
}
|
|
15486
|
-
logger$
|
|
15602
|
+
logger$18.debug("[TransceiverController] restoreTrackSender constraints:", constraints);
|
|
15487
15603
|
if (Object.keys(constraints).length === 0) {
|
|
15488
|
-
logger$
|
|
15604
|
+
logger$18.warn("[TransceiverController] restoreTrackSender: no tracks need restoration", kind);
|
|
15489
15605
|
return;
|
|
15490
15606
|
}
|
|
15491
15607
|
const newTracks = (await this.options.getUserMedia(constraints)).getTracks();
|
|
15492
|
-
logger$
|
|
15608
|
+
logger$18.debug("[TransceiverController] restoreTrackSender new tracks:", newTracks);
|
|
15493
15609
|
for (const newTrack of newTracks) {
|
|
15494
15610
|
this.options.localStreamController.addTrack(newTrack);
|
|
15495
15611
|
const trackKind = newTrack.kind;
|
|
15496
15612
|
const transceiverOfKind = this.transceiverByKind(trackKind)[0];
|
|
15497
15613
|
transceiverOfKind.direction = trackKind === "audio" ? this.audioDirection : this.videoDirection;
|
|
15498
|
-
logger$
|
|
15614
|
+
logger$18.debug("[TransceiverController] restoreTrackSender setting direction for", trackKind, transceiverOfKind.direction);
|
|
15499
15615
|
await transceiverOfKind.sender.replaceTrack(newTrack);
|
|
15500
15616
|
}
|
|
15501
15617
|
} catch (error) {
|
|
15502
|
-
logger$
|
|
15618
|
+
logger$18.error("[TransceiverController] restoreTrackSender error", kind, error);
|
|
15503
15619
|
this.options.onError?.(new MediaTrackError("restoreTrackSender", kind, error));
|
|
15504
15620
|
}
|
|
15505
15621
|
}
|
|
@@ -15540,14 +15656,14 @@ var TransceiverController = class extends Destroyable {
|
|
|
15540
15656
|
};
|
|
15541
15657
|
try {
|
|
15542
15658
|
await track.applyConstraints(constraintsToApply);
|
|
15543
|
-
logger$
|
|
15544
|
-
logger$
|
|
15659
|
+
logger$18.debug(`[TransceiverController] Updated ${kind} sender constraints:`, constraintsToApply);
|
|
15660
|
+
logger$18.debug(`[TransceiverController] Updated ${kind} sender constraints:`, track.getConstraints());
|
|
15545
15661
|
} catch (error) {
|
|
15546
|
-
logger$
|
|
15662
|
+
logger$18.warn(`[TransceiverController] applyConstraints failed for ${kind} track ${track.id}, attempting track replacement fallback:`, error);
|
|
15547
15663
|
try {
|
|
15548
15664
|
await this.replaceTrackFallback(sender, track, kind, constraintsToApply);
|
|
15549
15665
|
} catch (fallbackError) {
|
|
15550
|
-
logger$
|
|
15666
|
+
logger$18.warn(`[TransceiverController] Track replacement fallback also failed for ${kind} track:`, fallbackError);
|
|
15551
15667
|
this.options.onError?.(new MediaTrackError("updateSendersConstraints", kind, fallbackError));
|
|
15552
15668
|
}
|
|
15553
15669
|
}
|
|
@@ -15575,7 +15691,7 @@ var TransceiverController = class extends Destroyable {
|
|
|
15575
15691
|
if (!newTrack) throw new MediaTrackError("replaceTrackFallback", kind, /* @__PURE__ */ new Error("getUserMedia returned no track of the requested kind"));
|
|
15576
15692
|
await sender.replaceTrack(newTrack);
|
|
15577
15693
|
this.options.localStreamController.addTrack(newTrack);
|
|
15578
|
-
logger$
|
|
15694
|
+
logger$18.debug(`[TransceiverController] Track replacement fallback succeeded for ${kind}. New track: ${newTrack.id}`);
|
|
15579
15695
|
}
|
|
15580
15696
|
getMediaDirections() {
|
|
15581
15697
|
if (this.peerConnection.connectionState === "connected") return this.peerConnection.getTransceivers().reduce((acc, transceiver) => {
|
|
@@ -15606,7 +15722,7 @@ var TransceiverController = class extends Destroyable {
|
|
|
15606
15722
|
//#endregion
|
|
15607
15723
|
//#region src/controllers/RTCPeerConnectionController.ts
|
|
15608
15724
|
var import_cjs$16 = require_cjs();
|
|
15609
|
-
const logger$
|
|
15725
|
+
const logger$17 = getLogger();
|
|
15610
15726
|
var RTCPeerConnectionController = class extends Destroyable {
|
|
15611
15727
|
constructor(options = {}, remoteSessionDescription, deviceController) {
|
|
15612
15728
|
super();
|
|
@@ -15622,43 +15738,43 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15622
15738
|
this.oniceconnectionstatechangeHandler = () => {
|
|
15623
15739
|
if (this.peerConnection) {
|
|
15624
15740
|
const { iceConnectionState } = this.peerConnection;
|
|
15625
|
-
logger$
|
|
15741
|
+
logger$17.debug(`[RTCPeerConnectionController] ICE connection state changed to: ${iceConnectionState}`);
|
|
15626
15742
|
this._iceConnectionState$.next(this.peerConnection.iceConnectionState);
|
|
15627
15743
|
}
|
|
15628
15744
|
};
|
|
15629
15745
|
this.onconnectionstatechangeHandler = () => {
|
|
15630
15746
|
if (this.peerConnection) {
|
|
15631
15747
|
const { connectionState } = this.peerConnection;
|
|
15632
|
-
logger$
|
|
15748
|
+
logger$17.debug(`[RTCPeerConnectionController] Connection state changed to: ${connectionState}`);
|
|
15633
15749
|
if (connectionState === "connected") this.removeConnectionTimer();
|
|
15634
15750
|
this._connectionState$.next(this.peerConnection.connectionState);
|
|
15635
15751
|
}
|
|
15636
15752
|
};
|
|
15637
15753
|
this.onsignalingstatechangeHandler = () => {
|
|
15638
|
-
logger$
|
|
15754
|
+
logger$17.debug(`[RTCPeerConnectionController] Signaling state changed to: ${this.peerConnection?.signalingState}`);
|
|
15639
15755
|
};
|
|
15640
15756
|
this.onicegatheringstatechangeHandler = () => {
|
|
15641
15757
|
if (this.peerConnection) this._iceGatheringState$.next(this.peerConnection.iceGatheringState);
|
|
15642
15758
|
};
|
|
15643
15759
|
this.onnegotiationneededHandler = (event) => {
|
|
15644
|
-
logger$
|
|
15760
|
+
logger$17.debug("[RTCPeerConnectionController] Negotiation needed event received.", event);
|
|
15645
15761
|
this.negotiationNeeded$.next();
|
|
15646
15762
|
};
|
|
15647
15763
|
this.updateSelectedInputDevice = async (kind, deviceInfo) => {
|
|
15648
15764
|
try {
|
|
15649
15765
|
const { localStream } = this;
|
|
15650
15766
|
if (!localStream) {
|
|
15651
|
-
logger$
|
|
15767
|
+
logger$17.warn("[RTCPeerConnectionController] No local stream available to update input device.");
|
|
15652
15768
|
return;
|
|
15653
15769
|
}
|
|
15654
|
-
logger$
|
|
15770
|
+
logger$17.debug(`[RTCPeerConnectionController] Updating selected ${kind} input device:`, localStream.getTracks());
|
|
15655
15771
|
const track = localStream.getTracks().find((track$1) => track$1.kind === kind);
|
|
15656
15772
|
if (track) {
|
|
15657
15773
|
this.transceiverController?.stopTrackSender(kind);
|
|
15658
15774
|
this.localStreamController.removeTrack(track.id);
|
|
15659
|
-
logger$
|
|
15775
|
+
logger$17.debug(`[RTCPeerConnectionController] Stopped existing ${kind} track: ${track.id}`, localStream.getTracks());
|
|
15660
15776
|
if (!deviceInfo) {
|
|
15661
|
-
logger$
|
|
15777
|
+
logger$17.debug(`[RTCPeerConnectionController] ${kind} input device selected: none`);
|
|
15662
15778
|
return;
|
|
15663
15779
|
}
|
|
15664
15780
|
const streamTrack = (await this.getUserMedia({ [kind]: {
|
|
@@ -15666,16 +15782,16 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15666
15782
|
...this.deviceController.deviceInfoToConstraints(deviceInfo)
|
|
15667
15783
|
} })).getTracks().find((t) => t.kind === kind);
|
|
15668
15784
|
if (streamTrack) {
|
|
15669
|
-
logger$
|
|
15785
|
+
logger$17.debug(`[RTCPeerConnectionController] Adding new ${kind} track: ${streamTrack.id}`);
|
|
15670
15786
|
this.localStreamController.addTrack(streamTrack);
|
|
15671
15787
|
await this.transceiverController?.replaceSenderTrack(kind, streamTrack);
|
|
15672
|
-
logger$
|
|
15788
|
+
logger$17.debug(`[RTCPeerConnectionController] Added new ${kind} track: ${streamTrack.id}`, this.localStream?.getTracks());
|
|
15673
15789
|
}
|
|
15674
15790
|
}
|
|
15675
|
-
logger$
|
|
15791
|
+
logger$17.debug(`[RTCPeerConnectionController] ${kind} input device selected:`, deviceInfo?.label);
|
|
15676
15792
|
} catch (error) {
|
|
15677
|
-
logger$
|
|
15678
|
-
this._errors$.next(
|
|
15793
|
+
logger$17.error(`[RTCPeerConnectionController] Failed to select ${kind} input device:`, error);
|
|
15794
|
+
this._errors$.next(new MediaTrackError("updateSelectedInputDevice", kind, error));
|
|
15679
15795
|
throw error;
|
|
15680
15796
|
}
|
|
15681
15797
|
};
|
|
@@ -15897,7 +16013,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15897
16013
|
case "main":
|
|
15898
16014
|
default: return {
|
|
15899
16015
|
...options,
|
|
15900
|
-
offerToReceiveAudio: true,
|
|
16016
|
+
offerToReceiveAudio: this.options.receiveAudio ?? true,
|
|
15901
16017
|
offerToReceiveVideo: this.options.receiveVideo ?? Boolean(this.inputVideoDeviceConstraints)
|
|
15902
16018
|
};
|
|
15903
16019
|
}
|
|
@@ -15924,15 +16040,15 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15924
16040
|
this.setupPeerConnection();
|
|
15925
16041
|
this.subscribeTo(this.negotiationNeeded$.pipe((0, import_cjs$16.auditTime)(0), (0, import_cjs$16.exhaustMap)(async () => this.startNegotiation())), {
|
|
15926
16042
|
next: () => {
|
|
15927
|
-
logger$
|
|
16043
|
+
logger$17.debug("[RTCPeerConnectionController] Start Negotiation completed successfully");
|
|
15928
16044
|
},
|
|
15929
16045
|
error: (error) => {
|
|
15930
|
-
logger$
|
|
16046
|
+
logger$17.error("[RTCPeerConnectionController] Start Negotiation error:", error);
|
|
15931
16047
|
this._errors$.next(toError(error));
|
|
15932
16048
|
}
|
|
15933
16049
|
});
|
|
15934
16050
|
this.subscribeTo((0, import_cjs$16.merge)(this.deviceController.selectedAudioInputDevice$.pipe((0, import_cjs$16.map)((deviceInfo) => ["audio", deviceInfo])), this.deviceController.selectedVideoInputDevice$.pipe((0, import_cjs$16.map)((deviceInfo) => ["video", deviceInfo]))).pipe((0, import_cjs$16.skipWhile)(() => !this.localStreamController.localStream)), async ([kind, deviceInfo]) => {
|
|
15935
|
-
logger$
|
|
16051
|
+
logger$17.debug(`[RTCPeerConnectionController] Selected input device changed for:`, {
|
|
15936
16052
|
kind,
|
|
15937
16053
|
deviceInfo
|
|
15938
16054
|
});
|
|
@@ -15949,7 +16065,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15949
16065
|
this._initialized$.next(true);
|
|
15950
16066
|
}
|
|
15951
16067
|
} catch (error) {
|
|
15952
|
-
logger$
|
|
16068
|
+
logger$17.error("[RTCPeerConnectionController] Initialization error:", error);
|
|
15953
16069
|
this._errors$.next(toError(error));
|
|
15954
16070
|
this.destroy();
|
|
15955
16071
|
}
|
|
@@ -15981,22 +16097,22 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
15981
16097
|
}
|
|
15982
16098
|
async startNegotiation() {
|
|
15983
16099
|
if (this.isNegotiating) {
|
|
15984
|
-
logger$
|
|
16100
|
+
logger$17.debug("[RTCPeerConnectionController] Negotiation already in progress, skipping.");
|
|
15985
16101
|
return;
|
|
15986
16102
|
}
|
|
15987
16103
|
this.setupEventListeners();
|
|
15988
16104
|
if (this.type === "answer") {
|
|
15989
|
-
logger$
|
|
16105
|
+
logger$17.debug("[RTCPeerConnectionController] This is an answer type still, skipping offer creation.");
|
|
15990
16106
|
return;
|
|
15991
16107
|
}
|
|
15992
16108
|
this._isNegotiating$.next(true);
|
|
15993
|
-
logger$
|
|
16109
|
+
logger$17.debug("[RTCPeerConnectionController] Starting negotiation.");
|
|
15994
16110
|
try {
|
|
15995
16111
|
const { offerOptions } = this;
|
|
15996
|
-
logger$
|
|
16112
|
+
logger$17.debug("[RTCPeerConnectionController] Creating offer with options:", offerOptions);
|
|
15997
16113
|
await this.createOffer(offerOptions);
|
|
15998
16114
|
} catch (error) {
|
|
15999
|
-
logger$
|
|
16115
|
+
logger$17.error("[RTCPeerConnectionController] Error during negotiation:", error);
|
|
16000
16116
|
this._errors$.next(toError(error));
|
|
16001
16117
|
}
|
|
16002
16118
|
}
|
|
@@ -16012,14 +16128,14 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16012
16128
|
let readyToConnect = status !== "failed";
|
|
16013
16129
|
try {
|
|
16014
16130
|
if (status === "received" && sdp) {
|
|
16015
|
-
logger$
|
|
16131
|
+
logger$17.debug("[RTCPeerConnectionController] Received answer SDP:", sdp);
|
|
16016
16132
|
await this._setRemoteDescription({
|
|
16017
16133
|
type: "answer",
|
|
16018
16134
|
sdp
|
|
16019
16135
|
});
|
|
16020
16136
|
}
|
|
16021
16137
|
} catch (error) {
|
|
16022
|
-
logger$
|
|
16138
|
+
logger$17.error("[RTCPeerConnectionController] Error updating answer status:", error);
|
|
16023
16139
|
this._errors$.next(toError(error));
|
|
16024
16140
|
readyToConnect = false;
|
|
16025
16141
|
} finally {
|
|
@@ -16038,7 +16154,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16038
16154
|
await this.handleOfferReceived();
|
|
16039
16155
|
break;
|
|
16040
16156
|
case "failed":
|
|
16041
|
-
logger$
|
|
16157
|
+
logger$17.error("[RTCPeerConnectionController] Offer failed to be processed by remote.");
|
|
16042
16158
|
break;
|
|
16043
16159
|
case "sent":
|
|
16044
16160
|
default:
|
|
@@ -16051,13 +16167,14 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16051
16167
|
*/
|
|
16052
16168
|
async acceptInbound(mediaOverrides) {
|
|
16053
16169
|
if (mediaOverrides) {
|
|
16054
|
-
const { audio, video, receiveAudio, receiveVideo } = mediaOverrides;
|
|
16170
|
+
const { audio, video, receiveAudio, receiveVideo, fallbackToReceiveOnly } = mediaOverrides;
|
|
16055
16171
|
this.options = {
|
|
16056
16172
|
...this.options,
|
|
16057
16173
|
...audio !== void 0 ? { audio } : {},
|
|
16058
16174
|
...video !== void 0 ? { video } : {},
|
|
16059
16175
|
...receiveAudio !== void 0 ? { receiveAudio } : {},
|
|
16060
|
-
...receiveVideo !== void 0 ? { receiveVideo } : {}
|
|
16176
|
+
...receiveVideo !== void 0 ? { receiveVideo } : {},
|
|
16177
|
+
...fallbackToReceiveOnly !== void 0 ? { fallbackToReceiveOnly } : {}
|
|
16061
16178
|
};
|
|
16062
16179
|
this.transceiverController?.updateOptions({
|
|
16063
16180
|
receiveAudio: this.receiveAudio,
|
|
@@ -16070,7 +16187,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16070
16187
|
}
|
|
16071
16188
|
await this.setupLocalTracks();
|
|
16072
16189
|
const { answerOptions } = this;
|
|
16073
|
-
logger$
|
|
16190
|
+
logger$17.debug("[RTCPeerConnectionController] Creating inbound answer with options:", answerOptions);
|
|
16074
16191
|
await this.createAnswer(answerOptions);
|
|
16075
16192
|
}
|
|
16076
16193
|
async handleOfferReceived() {
|
|
@@ -16078,7 +16195,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16078
16195
|
this._isNegotiating$.next(true);
|
|
16079
16196
|
await this._setRemoteDescription(this.sdpInit);
|
|
16080
16197
|
const { answerOptions } = this;
|
|
16081
|
-
logger$
|
|
16198
|
+
logger$17.debug("[RTCPeerConnectionController] Creating answer with options:", answerOptions);
|
|
16082
16199
|
await this.createAnswer(answerOptions);
|
|
16083
16200
|
}
|
|
16084
16201
|
readyToConnect() {
|
|
@@ -16086,7 +16203,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16086
16203
|
this.connectionTimer = setTimeout(() => {
|
|
16087
16204
|
this.removeConnectionTimer();
|
|
16088
16205
|
if (this.peerConnection?.connectionState !== "connected") {
|
|
16089
|
-
logger$
|
|
16206
|
+
logger$17.debug("[RTCPeerConnectionController] Connection timeout, restarting ICE gathering with relay only.");
|
|
16090
16207
|
this.iceGatheringController.restartICEGatheringWithRelayOnly();
|
|
16091
16208
|
}
|
|
16092
16209
|
}, this.connectionTimeout);
|
|
@@ -16108,14 +16225,14 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16108
16225
|
const stereo = this.options.stereo ?? PreferencesContainer.instance.stereoAudio;
|
|
16109
16226
|
if (preferredAudioCodecs.length > 0 || preferredVideoCodecs.length > 0) {
|
|
16110
16227
|
result = setCodecPreferences(result, preferredAudioCodecs, preferredVideoCodecs);
|
|
16111
|
-
logger$
|
|
16228
|
+
logger$17.debug("[RTCPeerConnectionController] Applied codec preferences to SDP", {
|
|
16112
16229
|
preferredAudioCodecs,
|
|
16113
16230
|
preferredVideoCodecs
|
|
16114
16231
|
});
|
|
16115
16232
|
}
|
|
16116
16233
|
if (stereo) {
|
|
16117
16234
|
result = enableStereoOpus(result);
|
|
16118
|
-
logger$
|
|
16235
|
+
logger$17.debug("[RTCPeerConnectionController] Applied stereo Opus to SDP");
|
|
16119
16236
|
}
|
|
16120
16237
|
return Promise.resolve(result);
|
|
16121
16238
|
}
|
|
@@ -16171,25 +16288,25 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16171
16288
|
...this.peerConnection.getConfiguration(),
|
|
16172
16289
|
iceTransportPolicy: "relay"
|
|
16173
16290
|
});
|
|
16174
|
-
logger$
|
|
16291
|
+
logger$17.debug("[RTCPeerConnectionController] ICE transport policy set to relay-only");
|
|
16175
16292
|
} catch (error) {
|
|
16176
|
-
logger$
|
|
16293
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to set relay-only policy:", error);
|
|
16177
16294
|
}
|
|
16178
16295
|
this.setupEventListeners();
|
|
16179
16296
|
this._isNegotiating$.next(true);
|
|
16180
|
-
logger$
|
|
16297
|
+
logger$17.debug(`[RTCPeerConnectionController] Triggering ICE restart${relayOnly ? " (relay-only)" : ""}.`);
|
|
16181
16298
|
try {
|
|
16182
16299
|
const offer = await this.peerConnection.createOffer({ iceRestart: true });
|
|
16183
16300
|
await this.setLocalDescription(offer);
|
|
16184
16301
|
} catch (error) {
|
|
16185
|
-
logger$
|
|
16302
|
+
logger$17.error("[RTCPeerConnectionController] ICE restart offer failed:", error);
|
|
16186
16303
|
this._errors$.next(toError(error));
|
|
16187
16304
|
this.negotiationEnded();
|
|
16188
16305
|
if (policyChanged) this.restoreIceTransportPolicy();
|
|
16189
16306
|
throw error;
|
|
16190
16307
|
}
|
|
16191
16308
|
if (policyChanged) (0, import_cjs$16.firstValueFrom)((0, import_cjs$16.race)(this._iceGatheringState$.pipe((0, import_cjs$16.filter)((state) => state === "complete"), (0, import_cjs$16.take)(1)), (0, import_cjs$16.timer)(ICE_GATHERING_COMPLETE_TIMEOUT_MS).pipe((0, import_cjs$16.map)(() => "timeout")))).then(() => this.restoreIceTransportPolicy()).catch((error) => {
|
|
16192
|
-
logger$
|
|
16309
|
+
logger$17.warn("[RTCPeerConnectionController] Error waiting for ICE gathering to complete:", error);
|
|
16193
16310
|
this.restoreIceTransportPolicy();
|
|
16194
16311
|
});
|
|
16195
16312
|
}
|
|
@@ -16199,9 +16316,9 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16199
16316
|
...this.peerConnection.getConfiguration(),
|
|
16200
16317
|
iceTransportPolicy: this.options.relayOnly ? "relay" : "all"
|
|
16201
16318
|
});
|
|
16202
|
-
logger$
|
|
16319
|
+
logger$17.debug("[RTCPeerConnectionController] ICE transport policy restored");
|
|
16203
16320
|
} catch (error) {
|
|
16204
|
-
logger$
|
|
16321
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to restore ICE transport policy:", error);
|
|
16205
16322
|
}
|
|
16206
16323
|
}
|
|
16207
16324
|
/**
|
|
@@ -16213,13 +16330,25 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16213
16330
|
await this.setupRemoteTracks();
|
|
16214
16331
|
}
|
|
16215
16332
|
async setupLocalTracks() {
|
|
16216
|
-
logger$
|
|
16217
|
-
|
|
16333
|
+
logger$17.debug("[RTCPeerConnectionController] Setting up local tracks/transceivers.");
|
|
16334
|
+
if (this.hasNoLocalMediaToSend()) {
|
|
16335
|
+
if (!this.receiveAudio && !this.receiveVideo) throw new InvalidParams("Call requests no media: enable audio/video or receiveAudio/receiveVideo");
|
|
16336
|
+
logger$17.debug("[RTCPeerConnectionController] No local media requested; negotiating receive-only.");
|
|
16337
|
+
this.setupReceiveOnlyTransceivers();
|
|
16338
|
+
return;
|
|
16339
|
+
}
|
|
16340
|
+
let localStream;
|
|
16341
|
+
try {
|
|
16342
|
+
localStream = this.localStream ?? await this.localStreamController.buildLocalStream();
|
|
16343
|
+
} catch (error) {
|
|
16344
|
+
this.handleLocalMediaFailure(error);
|
|
16345
|
+
return;
|
|
16346
|
+
}
|
|
16218
16347
|
if (this.transceiverController?.useAddStream ?? false) {
|
|
16219
|
-
logger$
|
|
16348
|
+
logger$17.warn("[RTCPeerConnectionController] Using deprecated addStream API to add local stream.");
|
|
16220
16349
|
this.peerConnection?.addStream(localStream);
|
|
16221
16350
|
if (!this.isNegotiating) {
|
|
16222
|
-
logger$
|
|
16351
|
+
logger$17.debug("[RTCPeerConnectionController] Forcing negotiationneeded after local tracks setup.");
|
|
16223
16352
|
this.negotiationNeeded$.next();
|
|
16224
16353
|
}
|
|
16225
16354
|
return;
|
|
@@ -16235,12 +16364,54 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16235
16364
|
const transceivers = (kind === "audio" ? this.transceiverController?.audioTransceivers : this.transceiverController?.videoTransceivers) ?? [];
|
|
16236
16365
|
await this.transceiverController?.setupTransceiverSender(track, localStream, transceivers[index]);
|
|
16237
16366
|
} else {
|
|
16238
|
-
logger$
|
|
16367
|
+
logger$17.debug(`[RTCPeerConnectionController] Using addTrack for local ${kind} track:`, track.id);
|
|
16239
16368
|
this.peerConnection?.addTrack(track, localStream);
|
|
16240
16369
|
}
|
|
16241
16370
|
}
|
|
16242
16371
|
}
|
|
16243
16372
|
}
|
|
16373
|
+
/** True for a main connection with no local media to send. */
|
|
16374
|
+
hasNoLocalMediaToSend() {
|
|
16375
|
+
const hasInputStreams = Boolean(this.options.inputAudioStream ?? this.options.inputVideoStream);
|
|
16376
|
+
return this.propose === "main" && !this.localStream && !hasInputStreams && !this.inputAudioDeviceConstraints && !this.inputVideoDeviceConstraints;
|
|
16377
|
+
}
|
|
16378
|
+
/** The media kinds this connection wants to send: 'audiovideo' | 'video' | 'audio'. */
|
|
16379
|
+
get requestedMediaKinds() {
|
|
16380
|
+
const wantsAudio = Boolean(this.inputAudioDeviceConstraints);
|
|
16381
|
+
const wantsVideo = Boolean(this.inputVideoDeviceConstraints);
|
|
16382
|
+
if (wantsAudio && wantsVideo) return "audiovideo";
|
|
16383
|
+
return wantsVideo ? "video" : "audio";
|
|
16384
|
+
}
|
|
16385
|
+
/**
|
|
16386
|
+
* Handle a local media acquisition failure with a typed, semantically
|
|
16387
|
+
* accurate MediaAccessError created at the acquisition site:
|
|
16388
|
+
* - Auxiliary connections (screenshare / additional-device) throw a
|
|
16389
|
+
* non-fatal error — VertoManager surfaces it and the call is unaffected.
|
|
16390
|
+
* - The main connection degrades to receive-only when allowed (default),
|
|
16391
|
+
* otherwise fails with a fatal error.
|
|
16392
|
+
*/
|
|
16393
|
+
handleLocalMediaFailure(error) {
|
|
16394
|
+
if (this.propose === "screenshare") throw new MediaAccessError("startScreenShare", "screen", error, false);
|
|
16395
|
+
if (this.propose === "additional-device") throw new MediaAccessError("addInputDevice", this.requestedMediaKinds, error, false);
|
|
16396
|
+
const canReceive = this.receiveAudio || this.receiveVideo;
|
|
16397
|
+
if (!((this.options.fallbackToReceiveOnly ?? true) && canReceive)) throw new MediaAccessError("acquireLocalMedia", this.requestedMediaKinds, error, true);
|
|
16398
|
+
logger$17.warn("[RTCPeerConnectionController] Local media unavailable; continuing receive-only:", error);
|
|
16399
|
+
this._errors$.next(new MediaAccessError("acquireLocalMedia", this.requestedMediaKinds, error, false));
|
|
16400
|
+
this.setupReceiveOnlyTransceivers();
|
|
16401
|
+
}
|
|
16402
|
+
/**
|
|
16403
|
+
* Negotiate receive-only m-lines when there are no local tracks to send.
|
|
16404
|
+
* Only offer-type connections add transceivers — answer-type connections
|
|
16405
|
+
* reuse the transceivers created from the remote offer.
|
|
16406
|
+
*/
|
|
16407
|
+
setupReceiveOnlyTransceivers() {
|
|
16408
|
+
if (this.type !== "offer") return;
|
|
16409
|
+
if (this.transceiverController?.useAddTransceivers ?? false) {
|
|
16410
|
+
this.peerConnection?.addTransceiver("audio", { direction: this.receiveAudio ? "recvonly" : "inactive" });
|
|
16411
|
+
this.peerConnection?.addTransceiver("video", { direction: this.receiveVideo ? "recvonly" : "inactive" });
|
|
16412
|
+
}
|
|
16413
|
+
if (!this.isNegotiating) this.negotiationNeeded$.next();
|
|
16414
|
+
}
|
|
16244
16415
|
async getUserMedia(constraints) {
|
|
16245
16416
|
return (this.options.webRTCApiProvider?.mediaDevices ?? navigator.mediaDevices).getUserMedia(constraints);
|
|
16246
16417
|
}
|
|
@@ -16252,7 +16423,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16252
16423
|
async setupRemoteTracks() {
|
|
16253
16424
|
if (!this.peerConnection) throw new DependencyError("RTCPeerConnection is not initialized");
|
|
16254
16425
|
this.peerConnection.ontrack = (event) => {
|
|
16255
|
-
logger$
|
|
16426
|
+
logger$17.debug("[RTCPeerConnectionController] Remote track received:", event.track.kind);
|
|
16256
16427
|
if (event.streams[0]) this._remoteStream$.next(event.streams[0]);
|
|
16257
16428
|
else {
|
|
16258
16429
|
const existingTracks = this._remoteStream$.value?.getTracks() ?? [];
|
|
@@ -16276,8 +16447,8 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16276
16447
|
try {
|
|
16277
16448
|
stream = await this.getUserMedia({ audio: constraints });
|
|
16278
16449
|
} catch (error) {
|
|
16279
|
-
logger$
|
|
16280
|
-
this._errors$.next(
|
|
16450
|
+
logger$17.error("[RTCPeerConnectionController] Failed to re-acquire mic for pipeline restore:", error);
|
|
16451
|
+
this._errors$.next(new MediaTrackError("restoreAudioPipelineInput", "audio", error));
|
|
16281
16452
|
return;
|
|
16282
16453
|
}
|
|
16283
16454
|
const newTrack = stream.getAudioTracks().at(0);
|
|
@@ -16298,7 +16469,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16298
16469
|
try {
|
|
16299
16470
|
this._localAudioPipeline = new LocalAudioPipeline();
|
|
16300
16471
|
} catch (error) {
|
|
16301
|
-
logger$
|
|
16472
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to create LocalAudioPipeline:", error);
|
|
16302
16473
|
return null;
|
|
16303
16474
|
}
|
|
16304
16475
|
this.subscribeTo(this.localStreamController.localAudioTracks$, () => {
|
|
@@ -16320,7 +16491,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16320
16491
|
try {
|
|
16321
16492
|
await sender.replaceTrack(this._localAudioPipeline.outputTrack);
|
|
16322
16493
|
} catch (error) {
|
|
16323
|
-
logger$
|
|
16494
|
+
logger$17.warn("[RTCPeerConnectionController] Failed to route audio sender through pipeline:", error);
|
|
16324
16495
|
}
|
|
16325
16496
|
}
|
|
16326
16497
|
/**
|
|
@@ -16336,10 +16507,10 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16336
16507
|
try {
|
|
16337
16508
|
const localStream = this.localStreamController.addTrack(track);
|
|
16338
16509
|
this.peerConnection.addTrack(track, localStream);
|
|
16339
|
-
logger$
|
|
16510
|
+
logger$17.debug(`[RTCPeerConnectionController] ${track.kind} track added:`, track.id);
|
|
16340
16511
|
} catch (error) {
|
|
16341
|
-
logger$
|
|
16342
|
-
this._errors$.next(
|
|
16512
|
+
logger$17.error(`[RTCPeerConnectionController] Failed to add ${track.kind} track:`, error);
|
|
16513
|
+
this._errors$.next(new MediaTrackError("addLocalTrack", track.kind, error));
|
|
16343
16514
|
throw error;
|
|
16344
16515
|
}
|
|
16345
16516
|
}
|
|
@@ -16355,16 +16526,16 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16355
16526
|
}
|
|
16356
16527
|
const sender = this.peerConnection.getSenders().find((sender$1) => sender$1.track?.id === trackId);
|
|
16357
16528
|
if (!sender) {
|
|
16358
|
-
logger$
|
|
16529
|
+
logger$17.debug(`[RTCPeerConnectionController] track not found: ${trackId}`);
|
|
16359
16530
|
return;
|
|
16360
16531
|
}
|
|
16361
16532
|
try {
|
|
16362
16533
|
this.peerConnection.removeTrack(sender);
|
|
16363
16534
|
this.localStreamController.removeTrack(trackId);
|
|
16364
|
-
logger$
|
|
16535
|
+
logger$17.debug(`[RTCPeerConnectionController] ${sender.track?.kind} track removed:`, trackId);
|
|
16365
16536
|
} catch (error) {
|
|
16366
|
-
logger$
|
|
16367
|
-
this._errors$.next(
|
|
16537
|
+
logger$17.error(`[RTCPeerConnectionController] Failed to remove ${sender.track?.kind} track:`, error);
|
|
16538
|
+
this._errors$.next(new MediaTrackError("removeLocalTrack", sender.track?.kind ?? "unknown", error));
|
|
16368
16539
|
throw error;
|
|
16369
16540
|
}
|
|
16370
16541
|
}
|
|
@@ -16390,7 +16561,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16390
16561
|
async replaceAudioTrackWithConstraints(constraints) {
|
|
16391
16562
|
const senders = this.peerConnection?.getSenders().filter((s) => s.track?.kind === "audio" && s.track.readyState === "live");
|
|
16392
16563
|
if (!senders || senders.length === 0) {
|
|
16393
|
-
logger$
|
|
16564
|
+
logger$17.warn("[RTCPeerConnectionController] No live audio sender to replace");
|
|
16394
16565
|
return;
|
|
16395
16566
|
}
|
|
16396
16567
|
for (const sender of senders) {
|
|
@@ -16408,7 +16579,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16408
16579
|
const newTrack = (await this.getUserMedia({ audio: mergedConstraints })).getAudioTracks()[0];
|
|
16409
16580
|
await sender.replaceTrack(newTrack);
|
|
16410
16581
|
this.localStreamController.addTrack(newTrack);
|
|
16411
|
-
logger$
|
|
16582
|
+
logger$17.debug(`[RTCPeerConnectionController] Audio track replaced for server-pushed params. New track: ${newTrack.id}`);
|
|
16412
16583
|
}
|
|
16413
16584
|
}
|
|
16414
16585
|
/**
|
|
@@ -16416,7 +16587,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16416
16587
|
* Completes all observables to prevent memory leaks.
|
|
16417
16588
|
*/
|
|
16418
16589
|
destroy() {
|
|
16419
|
-
logger$
|
|
16590
|
+
logger$17.debug(`[RTCPeerConnectionController] Destroying RTCPeerConnectionController. ${this.propose}`);
|
|
16420
16591
|
this.removeConnectionTimer();
|
|
16421
16592
|
this._iceGatheringController?.destroy();
|
|
16422
16593
|
this._localAudioPipeline?.destroy();
|
|
@@ -16442,7 +16613,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16442
16613
|
}
|
|
16443
16614
|
stopRemoteTracks() {
|
|
16444
16615
|
this._remoteStream$.value?.getTracks().forEach((track) => {
|
|
16445
|
-
logger$
|
|
16616
|
+
logger$17.debug(`[RTCPeerConnectionController] Stopping remote track: ${track.kind}`);
|
|
16446
16617
|
track.stop();
|
|
16447
16618
|
});
|
|
16448
16619
|
}
|
|
@@ -16459,7 +16630,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
16459
16630
|
...params,
|
|
16460
16631
|
sdp: finalRemote
|
|
16461
16632
|
};
|
|
16462
|
-
logger$
|
|
16633
|
+
logger$17.debug("[RTCPeerConnectionController] Setting remote description:", answer);
|
|
16463
16634
|
return this.peerConnection.setRemoteDescription(answer);
|
|
16464
16635
|
}
|
|
16465
16636
|
};
|
|
@@ -16474,7 +16645,11 @@ function isVertoInviteMessage(value) {
|
|
|
16474
16645
|
const msg = value;
|
|
16475
16646
|
return msg.method === "verto.invite" && isObject(msg.params) && hasProperty(msg.params, "sdp") && hasProperty(msg.params, "callID");
|
|
16476
16647
|
}
|
|
16477
|
-
|
|
16648
|
+
/**
|
|
16649
|
+
* Guards inbound `verto.bye` frames (server → client). Outbound bye messages are
|
|
16650
|
+
* built locally and never type-guarded, so only the inbound shape is asserted.
|
|
16651
|
+
*/
|
|
16652
|
+
function isVertoByeInboundMessage(value) {
|
|
16478
16653
|
if (!isVertoMethodMessage(value)) return false;
|
|
16479
16654
|
return value.method === "verto.bye";
|
|
16480
16655
|
}
|
|
@@ -16494,11 +16669,19 @@ function isVertoMediaParamsInnerParams(value) {
|
|
|
16494
16669
|
function isVertoPingInnerParams(value) {
|
|
16495
16670
|
return isObject(value) && hasProperty(value, "jsonrpc") && value.jsonrpc === "2.0" && hasProperty(value, "method") && value.method === "verto.ping";
|
|
16496
16671
|
}
|
|
16672
|
+
/**
|
|
16673
|
+
* Guards the params of an inbound `verto.bye` frame (top-level `callID`). Use
|
|
16674
|
+
* this to recognise the bye payload extracted by `vertoBye$`, e.g. when racing
|
|
16675
|
+
* it against a boolean answer/reject signal.
|
|
16676
|
+
*/
|
|
16677
|
+
function isVertoByeInboundParamsGuard(value) {
|
|
16678
|
+
return isObject(value) && hasProperty(value, "callID") && typeof value.callID === "string";
|
|
16679
|
+
}
|
|
16497
16680
|
|
|
16498
16681
|
//#endregion
|
|
16499
16682
|
//#region src/managers/VertoManager.ts
|
|
16500
16683
|
var import_cjs$15 = require_cjs();
|
|
16501
|
-
const logger$
|
|
16684
|
+
const logger$16 = getLogger();
|
|
16502
16685
|
/**
|
|
16503
16686
|
* Decide what value goes on the `node_id` field of a `webrtc.verto` envelope.
|
|
16504
16687
|
*
|
|
@@ -16554,7 +16737,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16554
16737
|
try {
|
|
16555
16738
|
await this.executeVerto(vertoModifyMessage);
|
|
16556
16739
|
} catch (error) {
|
|
16557
|
-
logger$
|
|
16740
|
+
logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto hold:", error);
|
|
16558
16741
|
throw error;
|
|
16559
16742
|
}
|
|
16560
16743
|
}
|
|
@@ -16567,7 +16750,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16567
16750
|
try {
|
|
16568
16751
|
await this.executeVerto(vertoModifyMessage);
|
|
16569
16752
|
} catch (error) {
|
|
16570
|
-
logger$
|
|
16753
|
+
logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto unhold:", error);
|
|
16571
16754
|
throw error;
|
|
16572
16755
|
}
|
|
16573
16756
|
}
|
|
@@ -16620,7 +16803,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16620
16803
|
if (event.member_id) this.setSelfIdIfNull(event.member_id);
|
|
16621
16804
|
});
|
|
16622
16805
|
this.subscribeTo(this.vertoMedia$, (event) => {
|
|
16623
|
-
logger$
|
|
16806
|
+
logger$16.debug("[WebRTCManager] Received Verto media event (early media SDP):", event);
|
|
16624
16807
|
const { sdp, callID } = event;
|
|
16625
16808
|
this.emitMainSignalingStatus(callID, "ringing");
|
|
16626
16809
|
this._rtcPeerConnectionsMap.get(callID)?.updateAnswerStatus({
|
|
@@ -16629,7 +16812,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16629
16812
|
});
|
|
16630
16813
|
});
|
|
16631
16814
|
this.subscribeTo(this.vertoAnswer$, (event) => {
|
|
16632
|
-
logger$
|
|
16815
|
+
logger$16.debug("[WebRTCManager] Received Verto answer event:", event);
|
|
16633
16816
|
const { sdp, callID } = event;
|
|
16634
16817
|
this.emitMainSignalingStatus(callID, "connecting");
|
|
16635
16818
|
this._rtcPeerConnectionsMap.get(callID)?.updateAnswerStatus({
|
|
@@ -16638,7 +16821,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16638
16821
|
});
|
|
16639
16822
|
});
|
|
16640
16823
|
this.subscribeTo(this.vertoMediaParams$, (event) => {
|
|
16641
|
-
logger$
|
|
16824
|
+
logger$16.debug("[WebRTCManager] Received Verto mediaParams event:", event);
|
|
16642
16825
|
const { mediaParams, callID } = event;
|
|
16643
16826
|
const rtcPeerConnController = this._rtcPeerConnectionsMap.get(callID);
|
|
16644
16827
|
const { audio, video } = mediaParams;
|
|
@@ -16652,7 +16835,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16652
16835
|
timestamp: Date.now()
|
|
16653
16836
|
});
|
|
16654
16837
|
} catch (error) {
|
|
16655
|
-
logger$
|
|
16838
|
+
logger$16.warn("[WebRTCManager] Error applying server-pushed media params:", error);
|
|
16656
16839
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
16657
16840
|
}
|
|
16658
16841
|
})();
|
|
@@ -16674,13 +16857,13 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16674
16857
|
*/
|
|
16675
16858
|
setNodeIdIfNull(nodeId) {
|
|
16676
16859
|
if (!this._nodeId$.value && nodeId) {
|
|
16677
|
-
logger$
|
|
16860
|
+
logger$16.debug(`[WebRTCManager] Early node_id set: ${nodeId}`);
|
|
16678
16861
|
this._nodeId$.next(nodeId);
|
|
16679
16862
|
}
|
|
16680
16863
|
}
|
|
16681
16864
|
setSelfIdIfNull(selfId) {
|
|
16682
16865
|
if (!this._selfId$.value && selfId) {
|
|
16683
|
-
logger$
|
|
16866
|
+
logger$16.debug(`[WebRTCManager] Early selfId set: ${selfId}`);
|
|
16684
16867
|
this._selfId$.next(selfId);
|
|
16685
16868
|
}
|
|
16686
16869
|
}
|
|
@@ -16689,7 +16872,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16689
16872
|
const vertoPongMessage = VertoPong({ ...vertoPing });
|
|
16690
16873
|
await this.executeVerto(vertoPongMessage);
|
|
16691
16874
|
} catch (error) {
|
|
16692
|
-
logger$
|
|
16875
|
+
logger$16.warn("[WebRTCManager] Call might disconnect, error sending Verto pong:", error);
|
|
16693
16876
|
this.onError?.(new VertoPongError(error));
|
|
16694
16877
|
}
|
|
16695
16878
|
}
|
|
@@ -16699,7 +16882,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16699
16882
|
if (audio) await this.mainPeerConnection.updateSendersConstraints("audio", audio);
|
|
16700
16883
|
if (video) await this.mainPeerConnection.updateSendersConstraints("video", video);
|
|
16701
16884
|
} catch (error) {
|
|
16702
|
-
logger$
|
|
16885
|
+
logger$16.warn("[WebRTCManager] Error updating media constraints:", error);
|
|
16703
16886
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
16704
16887
|
throw error;
|
|
16705
16888
|
}
|
|
@@ -16729,20 +16912,20 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16729
16912
|
try {
|
|
16730
16913
|
const pc = this.mainPeerConnection.peerConnection;
|
|
16731
16914
|
if (!pc) {
|
|
16732
|
-
logger$
|
|
16915
|
+
logger$16.warn("[WebRTCManager] No peer connection for keyframe request");
|
|
16733
16916
|
return;
|
|
16734
16917
|
}
|
|
16735
16918
|
const videoReceiver = pc.getReceivers().find((r) => r.track.kind === "video");
|
|
16736
16919
|
if (!videoReceiver) {
|
|
16737
|
-
logger$
|
|
16920
|
+
logger$16.warn("[WebRTCManager] No video receiver for keyframe request");
|
|
16738
16921
|
return;
|
|
16739
16922
|
}
|
|
16740
16923
|
if (typeof videoReceiver.requestKeyFrame === "function") {
|
|
16741
16924
|
videoReceiver.requestKeyFrame();
|
|
16742
|
-
logger$
|
|
16743
|
-
} else logger$
|
|
16925
|
+
logger$16.debug("[WebRTCManager] Keyframe requested via RTCRtpReceiver.requestKeyFrame()");
|
|
16926
|
+
} else logger$16.debug("[WebRTCManager] requestKeyFrame() not supported, skipping");
|
|
16744
16927
|
} catch (error) {
|
|
16745
|
-
logger$
|
|
16928
|
+
logger$16.warn("[WebRTCManager] Keyframe request failed (non-fatal):", error);
|
|
16746
16929
|
}
|
|
16747
16930
|
}
|
|
16748
16931
|
/**
|
|
@@ -16760,13 +16943,13 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16760
16943
|
try {
|
|
16761
16944
|
const controller = this.mainPeerConnection;
|
|
16762
16945
|
if (!controller.peerConnection) {
|
|
16763
|
-
logger$
|
|
16946
|
+
logger$16.warn("[WebRTCManager] No peer connection for ICE restart");
|
|
16764
16947
|
return;
|
|
16765
16948
|
}
|
|
16766
16949
|
await controller.triggerIceRestart(relayOnly);
|
|
16767
|
-
logger$
|
|
16950
|
+
logger$16.info(`[WebRTCManager] ICE restart initiated${relayOnly ? " (relay-only)" : ""}`);
|
|
16768
16951
|
} catch (error) {
|
|
16769
|
-
logger$
|
|
16952
|
+
logger$16.error("[WebRTCManager] ICE restart failed:", error);
|
|
16770
16953
|
throw error;
|
|
16771
16954
|
}
|
|
16772
16955
|
}
|
|
@@ -16784,13 +16967,13 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16784
16967
|
const entries = Array.from(this._rtcPeerConnectionsMap.entries());
|
|
16785
16968
|
for (const [id, controller] of entries) try {
|
|
16786
16969
|
if (!controller.peerConnection) {
|
|
16787
|
-
logger$
|
|
16970
|
+
logger$16.debug(`[WebRTCManager] No peer connection for leg ${id}, skipping ICE restart`);
|
|
16788
16971
|
continue;
|
|
16789
16972
|
}
|
|
16790
16973
|
await controller.triggerIceRestart(relayOnly);
|
|
16791
|
-
logger$
|
|
16974
|
+
logger$16.info(`[WebRTCManager] ICE restart initiated for leg ${id}${relayOnly ? " (relay-only)" : ""}`);
|
|
16792
16975
|
} catch (error) {
|
|
16793
|
-
logger$
|
|
16976
|
+
logger$16.warn(`[WebRTCManager] ICE restart failed for leg ${id}:`, error);
|
|
16794
16977
|
}
|
|
16795
16978
|
}
|
|
16796
16979
|
/**
|
|
@@ -16802,7 +16985,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16802
16985
|
requestKeyframeAll() {
|
|
16803
16986
|
for (const [id, controller] of this._rtcPeerConnectionsMap) {
|
|
16804
16987
|
if (controller.isScreenShare) {
|
|
16805
|
-
logger$
|
|
16988
|
+
logger$16.debug(`[WebRTCManager] Skipping keyframe for send-only screen share leg ${id}`);
|
|
16806
16989
|
continue;
|
|
16807
16990
|
}
|
|
16808
16991
|
try {
|
|
@@ -16812,10 +16995,10 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16812
16995
|
if (!videoReceiver) continue;
|
|
16813
16996
|
if (typeof videoReceiver.requestKeyFrame === "function") {
|
|
16814
16997
|
videoReceiver.requestKeyFrame();
|
|
16815
|
-
logger$
|
|
16998
|
+
logger$16.debug(`[WebRTCManager] Keyframe requested for leg ${id}`);
|
|
16816
16999
|
}
|
|
16817
17000
|
} catch (error) {
|
|
16818
|
-
logger$
|
|
17001
|
+
logger$16.warn(`[WebRTCManager] Keyframe request failed for leg ${id} (non-fatal):`, error);
|
|
16819
17002
|
}
|
|
16820
17003
|
}
|
|
16821
17004
|
}
|
|
@@ -16832,7 +17015,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16832
17015
|
return this.cachedObservable("vertoMediaParams$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoMediaParamsInnerParams, "params"), (0, import_cjs$15.takeUntil)(this.destroyed$)));
|
|
16833
17016
|
}
|
|
16834
17017
|
get vertoBye$() {
|
|
16835
|
-
return this.cachedObservable("vertoBye$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(
|
|
17018
|
+
return this.cachedObservable("vertoBye$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoByeInboundMessage, "params"), (0, import_cjs$15.takeUntil)(this.destroyed$)));
|
|
16836
17019
|
}
|
|
16837
17020
|
get vertoAttach$() {
|
|
16838
17021
|
return this.cachedObservable("vertoAttach$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoAttachMessage, "params"), (0, import_cjs$15.takeUntil)(this.destroyed$)));
|
|
@@ -16876,7 +17059,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16876
17059
|
default:
|
|
16877
17060
|
}
|
|
16878
17061
|
} catch (error) {
|
|
16879
|
-
logger$
|
|
17062
|
+
logger$16.error(`[WebRTCManager] Error sending Verto ${vertoMethod}:`, error);
|
|
16880
17063
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
16881
17064
|
if (vertoMethod === "verto.modify") this.onModifyFailed?.();
|
|
16882
17065
|
}
|
|
@@ -16891,7 +17074,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16891
17074
|
sdp
|
|
16892
17075
|
});
|
|
16893
17076
|
} catch (error) {
|
|
16894
|
-
logger$
|
|
17077
|
+
logger$16.warn("[WebRTCManager] Error processing modify response:", error);
|
|
16895
17078
|
const modifyError = error instanceof Error ? error : new Error(String(error), { cause: error });
|
|
16896
17079
|
this.onError?.(modifyError);
|
|
16897
17080
|
}
|
|
@@ -16901,7 +17084,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16901
17084
|
const rtcPeerConnController = this._rtcPeerConnectionsMap.get(callId);
|
|
16902
17085
|
if (!rtcPeerConnController) {
|
|
16903
17086
|
const signalingError = new DependencyError(`Cannot emit signaling status, RTCPeerConnectionController not found for callID: ${callId}`);
|
|
16904
|
-
logger$
|
|
17087
|
+
logger$16.error("[WebRTCManager] Failed to emit signaling status:", {
|
|
16905
17088
|
callId,
|
|
16906
17089
|
status,
|
|
16907
17090
|
signalingError
|
|
@@ -16917,7 +17100,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16917
17100
|
this._nodeId$.next(getValueFrom(response, "result.node_id") ?? null);
|
|
16918
17101
|
const memberId = getValueFrom(response, "result.result.result.memberID") ?? null;
|
|
16919
17102
|
const callId = getValueFrom(response, "result.result.result.callID") ?? null;
|
|
16920
|
-
logger$
|
|
17103
|
+
logger$16.debug("[WebRTCManager] Verto invite response:", {
|
|
16921
17104
|
callId,
|
|
16922
17105
|
memberId,
|
|
16923
17106
|
response
|
|
@@ -16927,14 +17110,14 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16927
17110
|
if (callId) {
|
|
16928
17111
|
this.webRtcCallSession.addCallId(callId);
|
|
16929
17112
|
this.attachManager.attach(this.buildAttachableCall(callId));
|
|
16930
|
-
} else logger$
|
|
17113
|
+
} else logger$16.warn("[WebRTCManager] Cannot attach call, missing callId:", {
|
|
16931
17114
|
nodeId: this.nodeId,
|
|
16932
17115
|
callId
|
|
16933
17116
|
});
|
|
16934
|
-
logger$
|
|
16935
|
-
logger$
|
|
17117
|
+
logger$16.info("[WebRTCManager] Verto invite successful");
|
|
17118
|
+
logger$16.debug(`[WebRTCManager] nodeid: ${this._nodeId$.value}, selfId: ${this._selfId$.value}`);
|
|
16936
17119
|
} else {
|
|
16937
|
-
logger$
|
|
17120
|
+
logger$16.error("[WebRTCManager] Verto invite failed:", response);
|
|
16938
17121
|
const inviteError = response.error ? new JSONRPCError(response.error.code, response.error.message, response.error.data) : /* @__PURE__ */ new Error("Verto invite failed: unexpected response");
|
|
16939
17122
|
this.onError?.(inviteError);
|
|
16940
17123
|
}
|
|
@@ -16961,6 +17144,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16961
17144
|
inputVideoStream: options.inputVideoStream,
|
|
16962
17145
|
receiveAudio: options.receiveAudio,
|
|
16963
17146
|
receiveVideo: options.receiveVideo,
|
|
17147
|
+
fallbackToReceiveOnly: options.fallbackToReceiveOnly,
|
|
16964
17148
|
webRTCApiProvider: this.webRTCApiProvider,
|
|
16965
17149
|
preferredVideoCodecs: options.preferredVideoCodecs,
|
|
16966
17150
|
preferredAudioCodecs: options.preferredAudioCodecs,
|
|
@@ -16979,17 +17163,17 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16979
17163
|
if (options.initOffer) this.handleInboundAnswer(rtcPeerConnController);
|
|
16980
17164
|
}
|
|
16981
17165
|
async handleInboundAnswer(rtcPeerConnController) {
|
|
16982
|
-
logger$
|
|
17166
|
+
logger$16.debug("[WebRTCManager] Waiting for inbound call to be accepted or rejected");
|
|
16983
17167
|
const vertoByeOrAccepted = await (0, import_cjs$15.firstValueFrom)((0, import_cjs$15.race)(this.vertoBye$, this.webRtcCallSession.answered$).pipe((0, import_cjs$15.takeUntil)(this.destroyed$))).catch(() => null);
|
|
16984
17168
|
if (vertoByeOrAccepted === null) {
|
|
16985
|
-
logger$
|
|
17169
|
+
logger$16.debug("[WebRTCManager] Inbound answer handler aborted (destroyed).");
|
|
16986
17170
|
return;
|
|
16987
17171
|
}
|
|
16988
|
-
if (
|
|
16989
|
-
logger$
|
|
17172
|
+
if (isVertoByeInboundParamsGuard(vertoByeOrAccepted)) {
|
|
17173
|
+
logger$16.info("[WebRTCManager] Inbound call ended by remote before answer.");
|
|
16990
17174
|
this.callSession?.destroy();
|
|
16991
17175
|
} else if (!vertoByeOrAccepted) {
|
|
16992
|
-
logger$
|
|
17176
|
+
logger$16.info("[WebRTCManager] Inbound call rejected by user.");
|
|
16993
17177
|
try {
|
|
16994
17178
|
await this.bye("USER_BUSY");
|
|
16995
17179
|
} finally {
|
|
@@ -16997,19 +17181,19 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
16997
17181
|
this.callSession?.destroy();
|
|
16998
17182
|
}
|
|
16999
17183
|
} else {
|
|
17000
|
-
logger$
|
|
17184
|
+
logger$16.debug("[WebRTCManager] Inbound call accepted, creating SDP answer");
|
|
17001
17185
|
const answerOptions = this.webRtcCallSession.answerMediaOptions;
|
|
17002
17186
|
try {
|
|
17003
17187
|
await rtcPeerConnController.acceptInbound(answerOptions);
|
|
17004
17188
|
} catch (error) {
|
|
17005
|
-
logger$
|
|
17189
|
+
logger$16.error("[WebRTCManager] Error creating inbound answer:", error);
|
|
17006
17190
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
17007
17191
|
}
|
|
17008
17192
|
}
|
|
17009
17193
|
}
|
|
17010
17194
|
setupVertoAttachHandler() {
|
|
17011
17195
|
this.subscribeTo(this.vertoAttach$, async (vertoAttach) => {
|
|
17012
|
-
logger$
|
|
17196
|
+
logger$16.debug("[WebRTCManager] Received Verto attach event for existing call:", vertoAttach);
|
|
17013
17197
|
const { callID } = vertoAttach;
|
|
17014
17198
|
await this.attachManager.attach({
|
|
17015
17199
|
nodeId: this.nodeId ?? void 0,
|
|
@@ -17081,17 +17265,17 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17081
17265
|
};
|
|
17082
17266
|
}
|
|
17083
17267
|
async sendLocalDescriptionOnceAccepted(vertoMessageRequest, rtcPeerConnectionController) {
|
|
17084
|
-
logger$
|
|
17268
|
+
logger$16.debug("[WebRTCManager] Waiting for call to be accepted or ended before sending answer");
|
|
17085
17269
|
const vertoByeOrAccepted = await (0, import_cjs$15.firstValueFrom)((0, import_cjs$15.race)(this.vertoBye$, this.webRtcCallSession.answered$).pipe((0, import_cjs$15.takeUntil)(this.destroyed$))).catch(() => null);
|
|
17086
17270
|
if (vertoByeOrAccepted === null) {
|
|
17087
|
-
logger$
|
|
17271
|
+
logger$16.debug("[WebRTCManager] Destroyed while waiting for call acceptance");
|
|
17088
17272
|
return;
|
|
17089
17273
|
}
|
|
17090
|
-
if (
|
|
17091
|
-
logger$
|
|
17274
|
+
if (isVertoByeInboundParamsGuard(vertoByeOrAccepted)) {
|
|
17275
|
+
logger$16.info("[WebRTCManager] Call ended before answer was sent.");
|
|
17092
17276
|
this.callSession?.destroy();
|
|
17093
17277
|
} else if (!vertoByeOrAccepted) {
|
|
17094
|
-
logger$
|
|
17278
|
+
logger$16.info("[WebRTCManager] Call was not accepted, sending verto.bye.");
|
|
17095
17279
|
try {
|
|
17096
17280
|
await this.bye("USER_BUSY");
|
|
17097
17281
|
} finally {
|
|
@@ -17099,14 +17283,14 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17099
17283
|
this.callSession?.destroy();
|
|
17100
17284
|
}
|
|
17101
17285
|
} else {
|
|
17102
|
-
logger$
|
|
17286
|
+
logger$16.debug("[WebRTCManager] Call accepted, sending answer");
|
|
17103
17287
|
try {
|
|
17104
17288
|
this.emitMainSignalingStatus(rtcPeerConnectionController.id, "connecting");
|
|
17105
17289
|
await this.sendLocalDescription(vertoMessageRequest, rtcPeerConnectionController);
|
|
17106
17290
|
await rtcPeerConnectionController.updateAnswerStatus({ status: "sent" });
|
|
17107
17291
|
await this.attachManager.attach(this.buildAttachableCall());
|
|
17108
17292
|
} catch (error) {
|
|
17109
|
-
logger$
|
|
17293
|
+
logger$16.error("[WebRTCManager] Error sending Verto answer:", error);
|
|
17110
17294
|
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
17111
17295
|
await rtcPeerConnectionController.updateAnswerStatus({ status: "failed" });
|
|
17112
17296
|
}
|
|
@@ -17187,9 +17371,11 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17187
17371
|
await this.initAdditionalPeerConnection("screenshare", options);
|
|
17188
17372
|
}
|
|
17189
17373
|
async initAdditionalPeerConnection(propose, options) {
|
|
17374
|
+
const isScreenShare = propose === "screenshare";
|
|
17375
|
+
let firstPeerConnectionError;
|
|
17190
17376
|
let rtcPeerConnController = null;
|
|
17191
17377
|
try {
|
|
17192
|
-
this._screenShareStatus$.next("starting");
|
|
17378
|
+
if (isScreenShare) this._screenShareStatus$.next("starting");
|
|
17193
17379
|
rtcPeerConnController = new RTCPeerConnectionController({
|
|
17194
17380
|
...options,
|
|
17195
17381
|
...this.RTCPeerConnectionConfig,
|
|
@@ -17197,21 +17383,27 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17197
17383
|
webRTCApiProvider: this.webRTCApiProvider
|
|
17198
17384
|
}, void 0, this.deviceController);
|
|
17199
17385
|
this.setupLocalDescriptionHandler(rtcPeerConnController);
|
|
17200
|
-
if (
|
|
17386
|
+
if (isScreenShare) this._screenShareId = rtcPeerConnController.id;
|
|
17201
17387
|
this._rtcPeerConnectionsMap.set(rtcPeerConnController.id, rtcPeerConnController);
|
|
17202
17388
|
this._rtcPeerConnections$.next(Array.from(this._rtcPeerConnectionsMap.values()));
|
|
17203
17389
|
this.subscribeTo(rtcPeerConnController.errors$, (error) => {
|
|
17204
|
-
|
|
17390
|
+
firstPeerConnectionError ??= error;
|
|
17391
|
+
this.onError?.(error, { fatal: false });
|
|
17205
17392
|
});
|
|
17206
17393
|
await (0, import_cjs$15.firstValueFrom)(rtcPeerConnController.connectionState$.pipe((0, import_cjs$15.filter)((state) => state === "connected"), (0, import_cjs$15.take)(1), (0, import_cjs$15.timeout)(this._screenShareTimeoutMs), (0, import_cjs$15.takeUntil)(this.destroyed$)));
|
|
17207
|
-
this._screenShareStatus$.next("started");
|
|
17208
|
-
logger$
|
|
17394
|
+
if (isScreenShare) this._screenShareStatus$.next("started");
|
|
17395
|
+
logger$16.info(`[WebRTCManager] Additional peer connection connected (${propose}).`);
|
|
17209
17396
|
return rtcPeerConnController.id;
|
|
17210
17397
|
} catch (error) {
|
|
17211
|
-
logger$
|
|
17212
|
-
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
17398
|
+
logger$16.warn("[WebRTCManager] Error initializing additional peer connection:", error);
|
|
17213
17399
|
if (rtcPeerConnController) rtcPeerConnController.destroy();
|
|
17214
|
-
this._screenShareStatus$.next("none");
|
|
17400
|
+
if (isScreenShare) this._screenShareStatus$.next("none");
|
|
17401
|
+
if (firstPeerConnectionError) throw firstPeerConnectionError instanceof MediaAccessError && firstPeerConnectionError.originalError instanceof Error ? firstPeerConnectionError.originalError : firstPeerConnectionError;
|
|
17402
|
+
if (error instanceof import_cjs$15.EmptyError) {
|
|
17403
|
+
logger$16.debug("[WebRTCManager] Additional peer connection aborted before connecting.");
|
|
17404
|
+
return;
|
|
17405
|
+
}
|
|
17406
|
+
throw error instanceof Error ? error : new Error(String(error), { cause: error });
|
|
17215
17407
|
}
|
|
17216
17408
|
}
|
|
17217
17409
|
async removeInputDevices(id) {
|
|
@@ -17227,9 +17419,9 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17227
17419
|
if (removeTrack) return this.mainPeerConnection.stopTrackSender(removeTrack, { updateTransceiverDirection: true });
|
|
17228
17420
|
}
|
|
17229
17421
|
async removeScreenMedia() {
|
|
17230
|
-
if (!["starting", "started"].includes(this._screenShareStatus$.value)) logger$
|
|
17422
|
+
if (!["starting", "started"].includes(this._screenShareStatus$.value)) logger$16.warn("[WebRTCManager] No active screen share to stop.");
|
|
17231
17423
|
if (!this._screenShareId) {
|
|
17232
|
-
logger$
|
|
17424
|
+
logger$16.debug("[WebRTCManager] No screen share peer connection found.");
|
|
17233
17425
|
return;
|
|
17234
17426
|
}
|
|
17235
17427
|
this._screenShareStatus$.next("stopping");
|
|
@@ -17258,7 +17450,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17258
17450
|
dialogParams: this.dialogParams(rtcPeerConnController)
|
|
17259
17451
|
}));
|
|
17260
17452
|
} catch (error) {
|
|
17261
|
-
logger$
|
|
17453
|
+
logger$16.warn("[WebRTCManager] Call might already be disconnected, error sending Verto bye:", error);
|
|
17262
17454
|
throw error;
|
|
17263
17455
|
}
|
|
17264
17456
|
}
|
|
@@ -17276,7 +17468,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17276
17468
|
try {
|
|
17277
17469
|
await this.executeVerto(vertoInfoMessage);
|
|
17278
17470
|
} catch (error) {
|
|
17279
|
-
logger$
|
|
17471
|
+
logger$16.warn("[WebRTCManager] Error sending DTMF digits:", error);
|
|
17280
17472
|
throw error;
|
|
17281
17473
|
}
|
|
17282
17474
|
}
|
|
@@ -17287,10 +17479,10 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17287
17479
|
action: "transfer"
|
|
17288
17480
|
});
|
|
17289
17481
|
try {
|
|
17290
|
-
logger$
|
|
17482
|
+
logger$16.debug("[WebRTCManager] Transferring call with options:", options);
|
|
17291
17483
|
await this.executeVerto(message);
|
|
17292
17484
|
} catch (error) {
|
|
17293
|
-
logger$
|
|
17485
|
+
logger$16.error("[WebRTCManager] Error transferring call:", error);
|
|
17294
17486
|
throw error;
|
|
17295
17487
|
}
|
|
17296
17488
|
}
|
|
@@ -17307,7 +17499,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17307
17499
|
//#endregion
|
|
17308
17500
|
//#region src/controllers/RemoteAudioMeter.ts
|
|
17309
17501
|
var import_cjs$14 = require_cjs();
|
|
17310
|
-
const logger$
|
|
17502
|
+
const logger$15 = getLogger();
|
|
17311
17503
|
/**
|
|
17312
17504
|
* Read-only audio level meter for a remote MediaStream. Attaches an
|
|
17313
17505
|
* AnalyserNode to a MediaStreamAudioSourceNode so it observes the stream
|
|
@@ -17342,7 +17534,7 @@ var RemoteAudioMeter = class extends Destroyable {
|
|
|
17342
17534
|
try {
|
|
17343
17535
|
this._source.disconnect();
|
|
17344
17536
|
} catch (error) {
|
|
17345
|
-
logger$
|
|
17537
|
+
logger$15.debug("[RemoteAudioMeter] source disconnect warning:", error);
|
|
17346
17538
|
}
|
|
17347
17539
|
this._source = null;
|
|
17348
17540
|
this._stream = null;
|
|
@@ -17359,7 +17551,7 @@ var RemoteAudioMeter = class extends Destroyable {
|
|
|
17359
17551
|
this._source = null;
|
|
17360
17552
|
}
|
|
17361
17553
|
this._audioContext.close().catch((error) => {
|
|
17362
|
-
logger$
|
|
17554
|
+
logger$15.debug("[RemoteAudioMeter] audio context close warning:", error);
|
|
17363
17555
|
});
|
|
17364
17556
|
super.destroy();
|
|
17365
17557
|
}
|
|
@@ -17378,7 +17570,7 @@ var RemoteAudioMeter = class extends Destroyable {
|
|
|
17378
17570
|
//#endregion
|
|
17379
17571
|
//#region src/controllers/RTCStatsMonitor.ts
|
|
17380
17572
|
var import_cjs$13 = require_cjs();
|
|
17381
|
-
const logger$
|
|
17573
|
+
const logger$14 = getLogger();
|
|
17382
17574
|
const DEFAULT_POLLING_INTERVAL_MS = 1e3;
|
|
17383
17575
|
const DEFAULT_BASELINE_SAMPLES = 10;
|
|
17384
17576
|
const DEFAULT_NO_AUDIO_PACKET_THRESHOLD_MS = 2e3;
|
|
@@ -17468,9 +17660,9 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
17468
17660
|
const now = Date.now();
|
|
17469
17661
|
this.lastAudioPacketChangeTime = now;
|
|
17470
17662
|
this.lastVideoPacketChangeTime = now;
|
|
17471
|
-
logger$
|
|
17663
|
+
logger$14.debug("[RTCStatsMonitor] Starting stats monitoring");
|
|
17472
17664
|
this.subscribeTo((0, import_cjs$13.interval)(this.pollingIntervalMs).pipe((0, import_cjs$13.filter)(() => this.running), (0, import_cjs$13.switchMap)(() => (0, import_cjs$13.from)(this.peerConnection.getStats()).pipe((0, import_cjs$13.catchError)((err) => {
|
|
17473
|
-
logger$
|
|
17665
|
+
logger$14.warn("[RTCStatsMonitor] Failed to get stats:", err);
|
|
17474
17666
|
return import_cjs$13.EMPTY;
|
|
17475
17667
|
}))), (0, import_cjs$13.filter)(() => this.running), (0, import_cjs$13.map)((report) => this.extractSample(report))), (sample$1) => this._sample$.next(sample$1));
|
|
17476
17668
|
this.subscribeTo(this._sample$.pipe((0, import_cjs$13.take)(this.baselineSampleCount), (0, import_cjs$13.toArray)(), (0, import_cjs$13.map)((samples) => ({
|
|
@@ -17478,7 +17670,7 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
17478
17670
|
jitter: samples.reduce((s, b) => s + b.audioJitter, 0) / samples.length,
|
|
17479
17671
|
ready: true
|
|
17480
17672
|
}))), (baseline) => {
|
|
17481
|
-
logger$
|
|
17673
|
+
logger$14.debug(`[RTCStatsMonitor] Baseline established: rtt=${baseline.rtt.toFixed(1)}ms, jitter=${baseline.jitter.toFixed(1)}ms`);
|
|
17482
17674
|
this._baseline$.next(baseline);
|
|
17483
17675
|
});
|
|
17484
17676
|
this.subscribeTo(this._sample$.pipe((0, import_cjs$13.scan)((acc, sample$1) => ({
|
|
@@ -17515,10 +17707,10 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
17515
17707
|
stop() {
|
|
17516
17708
|
if (!this.running) return;
|
|
17517
17709
|
this.running = false;
|
|
17518
|
-
logger$
|
|
17710
|
+
logger$14.debug("[RTCStatsMonitor] Stopping stats monitoring");
|
|
17519
17711
|
}
|
|
17520
17712
|
destroy() {
|
|
17521
|
-
logger$
|
|
17713
|
+
logger$14.debug("[RTCStatsMonitor] Destroying RTCStatsMonitor");
|
|
17522
17714
|
this.stop();
|
|
17523
17715
|
super.destroy();
|
|
17524
17716
|
}
|
|
@@ -17647,7 +17839,7 @@ var RTCStatsMonitor = class extends Destroyable {
|
|
|
17647
17839
|
//#endregion
|
|
17648
17840
|
//#region src/managers/CallRecoveryManager.ts
|
|
17649
17841
|
var import_cjs$12 = require_cjs();
|
|
17650
|
-
const logger$
|
|
17842
|
+
const logger$13 = getLogger();
|
|
17651
17843
|
const DEFAULT_DEBOUNCE_TIME_MS = 2e3;
|
|
17652
17844
|
const DEFAULT_COOLDOWN_MS = 1e4;
|
|
17653
17845
|
const DEFAULT_ICE_GRACE_PERIOD_MS = 3e3;
|
|
@@ -17744,10 +17936,10 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17744
17936
|
*/
|
|
17745
17937
|
async requestIceRestart() {
|
|
17746
17938
|
if (this._recoveryState$.value === "recovering") {
|
|
17747
|
-
logger$
|
|
17939
|
+
logger$13.info("CallRecoveryManager: manual ICE restart skipped — recovery already in progress");
|
|
17748
17940
|
return;
|
|
17749
17941
|
}
|
|
17750
|
-
logger$
|
|
17942
|
+
logger$13.info("CallRecoveryManager: manual ICE restart requested");
|
|
17751
17943
|
this.transitionTo("recovering");
|
|
17752
17944
|
await this.executeIceRestart(false);
|
|
17753
17945
|
this.startCooldown();
|
|
@@ -17763,7 +17955,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17763
17955
|
* WebSocket reconnect or call state recovers to 'connected'.
|
|
17764
17956
|
*/
|
|
17765
17957
|
reset() {
|
|
17766
|
-
logger$
|
|
17958
|
+
logger$13.info("CallRecoveryManager: resetting counters");
|
|
17767
17959
|
this._attemptCount = 0;
|
|
17768
17960
|
this._keyframeBurstCount = 0;
|
|
17769
17961
|
this._keyframeBurstStart = 0;
|
|
@@ -17778,7 +17970,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17778
17970
|
*/
|
|
17779
17971
|
notifyModifyFailed() {
|
|
17780
17972
|
if (this._recoveryState$.value === "cooldown" || this._recoveryState$.value === "idle") {
|
|
17781
|
-
logger$
|
|
17973
|
+
logger$13.info("CallRecoveryManager: verto.modify failed — re-entering recovery");
|
|
17782
17974
|
this._cooldownUntil = 0;
|
|
17783
17975
|
this.transitionTo("idle");
|
|
17784
17976
|
this.pushTrigger({
|
|
@@ -17802,7 +17994,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17802
17994
|
reason: `bandwidth ${bitrateKbps}kbps below threshold ${this._config.degradationBitrateThreshold}kbps`,
|
|
17803
17995
|
timestamp: Date.now()
|
|
17804
17996
|
});
|
|
17805
|
-
logger$
|
|
17997
|
+
logger$13.warn(`CallRecoveryManager: disabling video — bandwidth ${bitrateKbps}kbps < ${this._config.degradationBitrateThreshold}kbps`);
|
|
17806
17998
|
} else if (wasConstrained && bitrateKbps >= this._config.degradationRecoveryThreshold) {
|
|
17807
17999
|
this._bandwidthConstrained$.next(false);
|
|
17808
18000
|
this._callbacks.enableVideo();
|
|
@@ -17811,7 +18003,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17811
18003
|
reason: `bandwidth ${bitrateKbps}kbps recovered above ${this._config.degradationRecoveryThreshold}kbps`,
|
|
17812
18004
|
timestamp: Date.now()
|
|
17813
18005
|
});
|
|
17814
|
-
logger$
|
|
18006
|
+
logger$13.info(`CallRecoveryManager: restoring video — bandwidth ${bitrateKbps}kbps >= ${this._config.degradationRecoveryThreshold}kbps`);
|
|
17815
18007
|
}
|
|
17816
18008
|
}
|
|
17817
18009
|
/**
|
|
@@ -17830,14 +18022,14 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17830
18022
|
handleWebSocketReconnect() {
|
|
17831
18023
|
const pcState = this._callbacks.getPeerConnectionState();
|
|
17832
18024
|
if (pcState === "connected" || pcState === "completed") {
|
|
17833
|
-
logger$
|
|
18025
|
+
logger$13.info("CallRecoveryManager: signal-only reconnect — peer connection still alive");
|
|
17834
18026
|
this.emitEvent({
|
|
17835
18027
|
action: "signal_reconnect",
|
|
17836
18028
|
reason: "WebSocket reconnected, peer connection still connected",
|
|
17837
18029
|
timestamp: Date.now()
|
|
17838
18030
|
});
|
|
17839
18031
|
} else {
|
|
17840
|
-
logger$
|
|
18032
|
+
logger$13.info("CallRecoveryManager: full reconnect — peer connection also down");
|
|
17841
18033
|
this.emitEvent({
|
|
17842
18034
|
action: "full_reconnect",
|
|
17843
18035
|
reason: "WebSocket reconnected, peer connection not connected — ICE restart needed",
|
|
@@ -17861,7 +18053,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17861
18053
|
}), (0, import_cjs$12.debounceTime)(this._config.debounceTimeMs), (0, import_cjs$12.withLatestFrom)(this._inputs.signalingReady$), (0, import_cjs$12.filter)(([, signalingReady]) => this.passGateChecks(signalingReady)), (0, import_cjs$12.map)(([trigger]) => trigger), (0, import_cjs$12.exhaustMap)((trigger) => this.executeTieredRecovery(trigger)), (0, import_cjs$12.takeUntil)((0, import_cjs$12.merge)(this._destroyed$, this._pipelineStop$))), {
|
|
17862
18054
|
next: () => {},
|
|
17863
18055
|
error: (err) => {
|
|
17864
|
-
logger$
|
|
18056
|
+
logger$13.error("CallRecoveryManager: pipeline error", err);
|
|
17865
18057
|
this.transitionTo("idle");
|
|
17866
18058
|
}
|
|
17867
18059
|
});
|
|
@@ -17888,27 +18080,27 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17888
18080
|
reason: `no packet loss for ${this._config.packetLossRecoveryDelaySec}s — restoring video`,
|
|
17889
18081
|
timestamp: Date.now()
|
|
17890
18082
|
});
|
|
17891
|
-
logger$
|
|
18083
|
+
logger$13.info(`CallRecoveryManager: restoring video — no packet loss for ${this._config.packetLossRecoveryDelaySec}s`);
|
|
17892
18084
|
});
|
|
17893
18085
|
}
|
|
17894
18086
|
passGateChecks(signalingReady) {
|
|
17895
18087
|
if (this._callbacks.isNegotiating()) {
|
|
17896
|
-
logger$
|
|
18088
|
+
logger$13.debug("CallRecoveryManager: gate blocked — negotiation in progress");
|
|
17897
18089
|
this.transitionTo("idle");
|
|
17898
18090
|
return false;
|
|
17899
18091
|
}
|
|
17900
18092
|
if (!signalingReady) {
|
|
17901
|
-
logger$
|
|
18093
|
+
logger$13.debug("CallRecoveryManager: gate blocked — signaling not ready");
|
|
17902
18094
|
this.transitionTo("idle");
|
|
17903
18095
|
return false;
|
|
17904
18096
|
}
|
|
17905
18097
|
if (!this._callbacks.isCallConnected()) {
|
|
17906
|
-
logger$
|
|
18098
|
+
logger$13.debug("CallRecoveryManager: gate blocked — call not connected");
|
|
17907
18099
|
this.transitionTo("idle");
|
|
17908
18100
|
return false;
|
|
17909
18101
|
}
|
|
17910
18102
|
if (this.isCooldownActive()) {
|
|
17911
|
-
logger$
|
|
18103
|
+
logger$13.debug("CallRecoveryManager: gate blocked — cooldown active");
|
|
17912
18104
|
this.transitionTo("cooldown");
|
|
17913
18105
|
return false;
|
|
17914
18106
|
}
|
|
@@ -17919,9 +18111,9 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17919
18111
|
}
|
|
17920
18112
|
executeTieredRecovery(trigger) {
|
|
17921
18113
|
this.transitionTo("recovering");
|
|
17922
|
-
logger$
|
|
18114
|
+
logger$13.info(`CallRecoveryManager: starting tiered recovery — source=${trigger.source} detail=${trigger.detail}`);
|
|
17923
18115
|
return (0, import_cjs$12.from)(this.runTiers(trigger)).pipe((0, import_cjs$12.tap)(() => this.startCooldown()), (0, import_cjs$12.catchError)((err) => {
|
|
17924
|
-
logger$
|
|
18116
|
+
logger$13.error("CallRecoveryManager: tiered recovery failed", err);
|
|
17925
18117
|
this.startCooldown();
|
|
17926
18118
|
return import_cjs$12.EMPTY;
|
|
17927
18119
|
}));
|
|
@@ -17929,7 +18121,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17929
18121
|
async runTiers(trigger) {
|
|
17930
18122
|
this.executeKeyframe(trigger.detail);
|
|
17931
18123
|
if (trigger.issueType && DEGRADATION_ONLY_ISSUES.has(trigger.issueType)) {
|
|
17932
|
-
logger$
|
|
18124
|
+
logger$13.debug(`CallRecoveryManager: degradation-only issue (${trigger.issueType}) — Tier 1 only, skipping ICE restart`);
|
|
17933
18125
|
return;
|
|
17934
18126
|
}
|
|
17935
18127
|
if (this._attemptCount < this._config.maxAttempts) {
|
|
@@ -17946,13 +18138,13 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17946
18138
|
maxAttempts: this._config.maxAttempts,
|
|
17947
18139
|
timestamp: Date.now()
|
|
17948
18140
|
});
|
|
17949
|
-
logger$
|
|
18141
|
+
logger$13.warn("CallRecoveryManager: max recovery attempts reached");
|
|
17950
18142
|
}
|
|
17951
18143
|
}
|
|
17952
18144
|
executeKeyframe(reason) {
|
|
17953
18145
|
const now = Date.now();
|
|
17954
18146
|
if (now < this._keyframeCooldownUntil) {
|
|
17955
|
-
logger$
|
|
18147
|
+
logger$13.debug("CallRecoveryManager: keyframe request skipped — cooldown active");
|
|
17956
18148
|
return;
|
|
17957
18149
|
}
|
|
17958
18150
|
if (now - this._keyframeBurstStart > this._config.keyframeBurstWindowMs) {
|
|
@@ -17961,7 +18153,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17961
18153
|
}
|
|
17962
18154
|
if (this._keyframeBurstCount >= this._config.keyframeMaxBurst) {
|
|
17963
18155
|
this._keyframeCooldownUntil = now + this._config.keyframeCooldownMs;
|
|
17964
|
-
logger$
|
|
18156
|
+
logger$13.debug(`CallRecoveryManager: keyframe burst limit reached (${this._config.keyframeMaxBurst}), cooldown until ${this._keyframeCooldownUntil}`);
|
|
17965
18157
|
return;
|
|
17966
18158
|
}
|
|
17967
18159
|
this._keyframeBurstCount += 1;
|
|
@@ -17971,12 +18163,12 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17971
18163
|
reason,
|
|
17972
18164
|
timestamp: now
|
|
17973
18165
|
});
|
|
17974
|
-
logger$
|
|
18166
|
+
logger$13.debug(`CallRecoveryManager: keyframe requested (burst ${this._keyframeBurstCount}/${this._config.keyframeMaxBurst})`);
|
|
17975
18167
|
}
|
|
17976
18168
|
async executeIceRestart(relayOnly) {
|
|
17977
18169
|
this._attemptCount += 1;
|
|
17978
18170
|
const tier = relayOnly ? "Tier 3 (relay-only)" : "Tier 2 (standard)";
|
|
17979
|
-
logger$
|
|
18171
|
+
logger$13.info(`CallRecoveryManager: ${tier} ICE restart — attempt ${this._attemptCount}/${this._config.maxAttempts}`);
|
|
17980
18172
|
this.emitEvent({
|
|
17981
18173
|
action: "reinvite_started",
|
|
17982
18174
|
reason: `${tier} ICE restart`,
|
|
@@ -17993,7 +18185,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
17993
18185
|
maxAttempts: this._config.maxAttempts,
|
|
17994
18186
|
timestamp: Date.now()
|
|
17995
18187
|
});
|
|
17996
|
-
logger$
|
|
18188
|
+
logger$13.info(`CallRecoveryManager: ${tier} ICE restart succeeded`);
|
|
17997
18189
|
this._attemptCount = 0;
|
|
17998
18190
|
return true;
|
|
17999
18191
|
}
|
|
@@ -18004,7 +18196,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
18004
18196
|
maxAttempts: this._config.maxAttempts,
|
|
18005
18197
|
timestamp: Date.now()
|
|
18006
18198
|
});
|
|
18007
|
-
logger$
|
|
18199
|
+
logger$13.warn(`CallRecoveryManager: ${tier} ICE restart failed`);
|
|
18008
18200
|
return false;
|
|
18009
18201
|
} catch {
|
|
18010
18202
|
this.emitEvent({
|
|
@@ -18014,7 +18206,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
18014
18206
|
maxAttempts: this._config.maxAttempts,
|
|
18015
18207
|
timestamp: Date.now()
|
|
18016
18208
|
});
|
|
18017
|
-
logger$
|
|
18209
|
+
logger$13.warn(`CallRecoveryManager: ${tier} ICE restart timed out`);
|
|
18018
18210
|
return false;
|
|
18019
18211
|
}
|
|
18020
18212
|
}
|
|
@@ -18035,7 +18227,7 @@ var CallRecoveryManager = class extends Destroyable {
|
|
|
18035
18227
|
transitionTo(state) {
|
|
18036
18228
|
const prev = this._recoveryState$.value;
|
|
18037
18229
|
if (prev !== state) {
|
|
18038
|
-
logger$
|
|
18230
|
+
logger$13.debug(`CallRecoveryManager: state ${prev} -> ${state}`);
|
|
18039
18231
|
this._recoveryState$.next(state);
|
|
18040
18232
|
}
|
|
18041
18233
|
}
|
|
@@ -18138,7 +18330,7 @@ function mosToQualityLevel(mos) {
|
|
|
18138
18330
|
//#endregion
|
|
18139
18331
|
//#region src/core/entities/Call.ts
|
|
18140
18332
|
var import_cjs$11 = require_cjs();
|
|
18141
|
-
const logger$
|
|
18333
|
+
const logger$12 = getLogger();
|
|
18142
18334
|
/**
|
|
18143
18335
|
* Ratio between the critical and warning RTT spike multipliers.
|
|
18144
18336
|
* Warning threshold = baseline * warningMultiplier (default 3x)
|
|
@@ -18156,7 +18348,7 @@ const fromDestinationParams = (destination) => {
|
|
|
18156
18348
|
});
|
|
18157
18349
|
return params;
|
|
18158
18350
|
} catch (error) {
|
|
18159
|
-
logger$
|
|
18351
|
+
logger$12.warn(`Failed to parse destination URI: ${destination}`, error);
|
|
18160
18352
|
return {};
|
|
18161
18353
|
}
|
|
18162
18354
|
};
|
|
@@ -18290,7 +18482,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18290
18482
|
/** Toggles the call lock state, preventing or allowing new participants from joining. */
|
|
18291
18483
|
async toggleLock() {
|
|
18292
18484
|
const method = this.locked ? "call.unlock" : "call.lock";
|
|
18293
|
-
await this.executeMethod(this.
|
|
18485
|
+
await this.executeMethod(this.callSelf, method, {});
|
|
18294
18486
|
}
|
|
18295
18487
|
/**
|
|
18296
18488
|
* Toggles the hold state of the call (pauses/resumes local media transmission).
|
|
@@ -18339,14 +18531,24 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18339
18531
|
*
|
|
18340
18532
|
* Constructs call context (node_id, call_id, member_id) and sends the RPC request.
|
|
18341
18533
|
*
|
|
18342
|
-
* @param target - Target
|
|
18534
|
+
* @param target - Target {@link MemberTarget} triple, or the local member's
|
|
18535
|
+
* ID string for self-operations (any other string is rejected — a bare
|
|
18536
|
+
* member id cannot carry the remote member's own call context).
|
|
18343
18537
|
* @param method - Verto method name (e.g. `'call.mute'`, `'call.member.remove'`).
|
|
18344
18538
|
* @param args - Parameters for the RPC method.
|
|
18345
18539
|
* @returns The RPC response.
|
|
18540
|
+
* @throws {CallNotReadyError} If the call has no self member context yet.
|
|
18541
|
+
* @throws {InvalidParams} If a string target is not the local member's ID.
|
|
18346
18542
|
* @throws {JSONRPCError} If the RPC call returns an error.
|
|
18347
18543
|
*/
|
|
18348
18544
|
async executeMethod(target, method, args) {
|
|
18349
|
-
const
|
|
18545
|
+
const self = this.callSelf;
|
|
18546
|
+
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}`);
|
|
18547
|
+
const params = {
|
|
18548
|
+
...args,
|
|
18549
|
+
self,
|
|
18550
|
+
target: typeof target === "string" ? self : target
|
|
18551
|
+
};
|
|
18350
18552
|
const request = buildRPCRequest({
|
|
18351
18553
|
method,
|
|
18352
18554
|
params
|
|
@@ -18356,29 +18558,27 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18356
18558
|
if (isJSONRPCErrorResponse(response)) throw new JSONRPCError(parseInt(response.result?.code ?? "0"), `Error response from method ${method}: ${response.result?.code} ${response.result?.message}`, void 0, void 0, request.id);
|
|
18357
18559
|
return response;
|
|
18358
18560
|
} catch (error) {
|
|
18359
|
-
logger$
|
|
18561
|
+
logger$12.error(`[Call] Error executing method ${method} with params`, params, error);
|
|
18360
18562
|
throw error;
|
|
18361
18563
|
}
|
|
18362
18564
|
}
|
|
18363
|
-
|
|
18364
|
-
|
|
18365
|
-
|
|
18366
|
-
|
|
18367
|
-
|
|
18368
|
-
|
|
18369
|
-
|
|
18370
|
-
|
|
18371
|
-
|
|
18372
|
-
|
|
18373
|
-
|
|
18565
|
+
/**
|
|
18566
|
+
* The local leg's member triple — sent as `self` in every member RPC
|
|
18567
|
+
* envelope, and as the `target` of call-scoped self-operations (e.g. lock,
|
|
18568
|
+
* layout).
|
|
18569
|
+
*
|
|
18570
|
+
* @throws {CallNotReadyError} Before `call.joined` delivers the self member
|
|
18571
|
+
* context (`selfId`/`nodeId`) — an RPC without it cannot be routed, so fail
|
|
18572
|
+
* fast instead of sending a doomed request.
|
|
18573
|
+
*/
|
|
18574
|
+
get callSelf() {
|
|
18575
|
+
const node_id = this.nodeId;
|
|
18576
|
+
const member_id = this.vertoManager.selfId;
|
|
18577
|
+
if (!node_id || !member_id) throw new CallNotReadyError(this.id);
|
|
18374
18578
|
return {
|
|
18375
|
-
|
|
18376
|
-
|
|
18377
|
-
|
|
18378
|
-
node_id: this.nodeId ?? "",
|
|
18379
|
-
call_id: this.id,
|
|
18380
|
-
member_id: target
|
|
18381
|
-
}
|
|
18579
|
+
node_id,
|
|
18580
|
+
call_id: this.id,
|
|
18581
|
+
member_id
|
|
18382
18582
|
};
|
|
18383
18583
|
}
|
|
18384
18584
|
/** Observable of the current call status (e.g. `'ringing'`, `'connected'`). */
|
|
@@ -18559,9 +18759,9 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18559
18759
|
*/
|
|
18560
18760
|
initResilienceSubsystems() {
|
|
18561
18761
|
const pc = this.rtcPeerConnection;
|
|
18562
|
-
logger$
|
|
18762
|
+
logger$12.debug(`[Call] initResilienceSubsystems: pc=${pc ? "exists" : "undefined"}, connectionState=${pc?.connectionState}`);
|
|
18563
18763
|
if (!pc) {
|
|
18564
|
-
logger$
|
|
18764
|
+
logger$12.warn("[Call] No peer connection available, skipping resilience init");
|
|
18565
18765
|
return;
|
|
18566
18766
|
}
|
|
18567
18767
|
try {
|
|
@@ -18596,14 +18796,14 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18596
18796
|
disableVideo: () => {
|
|
18597
18797
|
try {
|
|
18598
18798
|
this.vertoManager.muteMainVideoInputDevice();
|
|
18599
|
-
logger$
|
|
18799
|
+
logger$12.debug("[Call] Recovery manager disabled video");
|
|
18600
18800
|
} catch {
|
|
18601
|
-
logger$
|
|
18801
|
+
logger$12.debug("[Call] Recovery manager failed to disable video");
|
|
18602
18802
|
}
|
|
18603
18803
|
},
|
|
18604
18804
|
enableVideo: () => {
|
|
18605
18805
|
this.vertoManager.unmuteMainVideoInputDevice().catch(() => {
|
|
18606
|
-
logger$
|
|
18806
|
+
logger$12.debug("[Call] Recovery manager failed to enable video");
|
|
18607
18807
|
});
|
|
18608
18808
|
},
|
|
18609
18809
|
isNegotiating: () => this.vertoManager.mainPeerConnection.isNegotiating,
|
|
@@ -18653,7 +18853,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18653
18853
|
this.subscribeTo(this._recoveryManager.recoveryEvent$, (event) => {
|
|
18654
18854
|
this._recoveryEvent$.next(event);
|
|
18655
18855
|
if (event.action === "max_attempts_reached") {
|
|
18656
|
-
logger$
|
|
18856
|
+
logger$12.warn("[Call] All recovery attempts exhausted, terminating call");
|
|
18657
18857
|
this.emitError({
|
|
18658
18858
|
kind: "network",
|
|
18659
18859
|
fatal: true,
|
|
@@ -18673,13 +18873,13 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18673
18873
|
else if (event.type === "online") this._recoveryManager?.handleWebSocketReconnect();
|
|
18674
18874
|
});
|
|
18675
18875
|
this.subscribeTo(this.clientSession.authenticated$.pipe((0, import_cjs$11.skip)(1), (0, import_cjs$11.filter)(Boolean)), () => {
|
|
18676
|
-
logger$
|
|
18876
|
+
logger$12.debug("[Call] WebSocket reconnected — notifying recovery manager");
|
|
18677
18877
|
this._recoveryManager?.handleWebSocketReconnect();
|
|
18678
18878
|
});
|
|
18679
18879
|
this._statsMonitor.start();
|
|
18680
|
-
logger$
|
|
18880
|
+
logger$12.debug("[Call] Resilience subsystems initialized for call", this.id);
|
|
18681
18881
|
} catch (error) {
|
|
18682
|
-
logger$
|
|
18882
|
+
logger$12.warn("[Call] Failed to initialize resilience subsystems:", error);
|
|
18683
18883
|
}
|
|
18684
18884
|
}
|
|
18685
18885
|
/**
|
|
@@ -18762,19 +18962,19 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18762
18962
|
}
|
|
18763
18963
|
isCallSessionEvent(event) {
|
|
18764
18964
|
try {
|
|
18765
|
-
logger$
|
|
18965
|
+
logger$12.debug("[Call] Checking if event is for this call session:", event);
|
|
18766
18966
|
const callId = getValueFrom(event, "params.params.callID") ?? getValueFrom(event, "params.call_id");
|
|
18767
18967
|
const roomSessionId = getValueFrom(event, "params.room_session_id");
|
|
18768
|
-
logger$
|
|
18968
|
+
logger$12.debug(`[Call] Extracted session identifiers callID: ${callId} and roomSessionID: ${roomSessionId} from event:`);
|
|
18769
18969
|
return callId === this.id || !!callId && this.callEventsManager.isCallIdValid(callId) || !!roomSessionId && this.callEventsManager.isRoomSessionIdValid(roomSessionId);
|
|
18770
18970
|
} catch (error) {
|
|
18771
|
-
logger$
|
|
18971
|
+
logger$12.error("[Call] Error checking if event is for this call session:", error);
|
|
18772
18972
|
return false;
|
|
18773
18973
|
}
|
|
18774
18974
|
}
|
|
18775
18975
|
get callSessionEvents$() {
|
|
18776
18976
|
return this.cachedObservable("callSessionEvents$", () => this.clientSession.signalingEvent$.pipe((0, import_cjs$11.filter)((event) => this.isCallSessionEvent(event)), (0, import_cjs$11.tap)((event) => {
|
|
18777
|
-
logger$
|
|
18977
|
+
logger$12.debug("[Call] Received call session event:", event);
|
|
18778
18978
|
}), (0, import_cjs$11.takeUntil)(this.destroyed$), (0, import_cjs$11.share)()));
|
|
18779
18979
|
}
|
|
18780
18980
|
/** Observable of call-updated events. */
|
|
@@ -18844,16 +19044,16 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18844
19044
|
this._customSubscriptions.set(eventType, filtered$);
|
|
18845
19045
|
}, (error) => {
|
|
18846
19046
|
this._customSubscriptions.delete(eventType);
|
|
18847
|
-
logger$
|
|
19047
|
+
logger$12.warn(`[Call] verto.subscribe for '${eventType}' failed, not caching:`, error);
|
|
18848
19048
|
});
|
|
18849
19049
|
this._customSubscriptions.set(eventType, filtered$);
|
|
18850
19050
|
return filtered$;
|
|
18851
19051
|
}
|
|
18852
19052
|
get webrtcMessages$() {
|
|
18853
|
-
return this.cachedObservable("webrtcMessages$", () => this.callSessionEvents$.pipe(filterAs(isWebrtcMessageMetadata, "params"), (0, import_cjs$11.tap)((event) => logger$
|
|
19053
|
+
return this.cachedObservable("webrtcMessages$", () => this.callSessionEvents$.pipe(filterAs(isWebrtcMessageMetadata, "params"), (0, import_cjs$11.tap)((event) => logger$12.debug("[Call] Event is a WebRTC message event:", event)), (0, import_cjs$11.takeUntil)(this.destroyed$), (0, import_cjs$11.share)()));
|
|
18854
19054
|
}
|
|
18855
19055
|
get callEvent$() {
|
|
18856
|
-
return this.cachedObservable("callEvent$", () => this.callSessionEvents$.pipe(filterAs(isSignalwireCallMetadata, "params"), (0, import_cjs$11.tap)((event) => logger$
|
|
19056
|
+
return this.cachedObservable("callEvent$", () => this.callSessionEvents$.pipe(filterAs(isSignalwireCallMetadata, "params"), (0, import_cjs$11.tap)((event) => logger$12.debug("[Call] Event is a call event:", event)), (0, import_cjs$11.takeUntil)(this.destroyed$), (0, import_cjs$11.share)()));
|
|
18857
19057
|
}
|
|
18858
19058
|
get layoutEvent$() {
|
|
18859
19059
|
return this.cachedObservable("layoutEvent$", () => this.callEvent$.pipe(filterAs(isLayoutChangedMetadata, "params")));
|
|
@@ -18931,11 +19131,27 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18931
19131
|
return this.deferEmission(this._answered$.asObservable());
|
|
18932
19132
|
}
|
|
18933
19133
|
/**
|
|
18934
|
-
* Sets the call layout and participant positions.
|
|
19134
|
+
* Sets the call layout and, optionally, individual participant positions.
|
|
19135
|
+
*
|
|
19136
|
+
* The gateway `call.layout.set` DTO has **no** `positions` member, so when
|
|
19137
|
+
* `positions` is provided this method issues a `call.member.position.set`
|
|
19138
|
+
* request per member (via {@link Participant.setPosition}, which keys each
|
|
19139
|
+
* position by that member's own call context) alongside `call.layout.set`
|
|
19140
|
+
* (issue #19400, Flag #6).
|
|
19141
|
+
*
|
|
19142
|
+
* **These operations are NOT atomic.** The layout is applied first, then each
|
|
19143
|
+
* member position sequentially, so members may briefly flash into their
|
|
19144
|
+
* default slots before being moved to the requested positions. Targeted
|
|
19145
|
+
* members are validated upfront, though: when any of them has no
|
|
19146
|
+
* {@link Participant.target | member call context} yet, the whole call
|
|
19147
|
+
* rejects before any request is sent and the layout is left unchanged.
|
|
18935
19148
|
*
|
|
18936
19149
|
* @param layout - Layout name (must be one of {@link layouts}).
|
|
18937
|
-
* @param positions -
|
|
19150
|
+
* @param positions - Optional map of member IDs to {@link VideoPosition} values.
|
|
19151
|
+
* When omitted or empty, only the layout is changed.
|
|
18938
19152
|
* @throws {InvalidParams} If the layout is not in the available {@link layouts}.
|
|
19153
|
+
* @throws {ParticipantNotReadyError} If a targeted member's call context has
|
|
19154
|
+
* not been received yet — thrown before any request is sent.
|
|
18939
19155
|
*
|
|
18940
19156
|
* @example
|
|
18941
19157
|
* ```ts
|
|
@@ -18946,11 +19162,19 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18946
19162
|
*/
|
|
18947
19163
|
async setLayout(layout, positions) {
|
|
18948
19164
|
if (!this.layouts.includes(layout)) throw new InvalidParams(`Layout ${layout} is not available in the current call layouts: ${this.layouts.join(", ")}`);
|
|
19165
|
+
const targets = [];
|
|
19166
|
+
for (const [memberId, position] of Object.entries(positions ?? {})) {
|
|
19167
|
+
const participant = this.participants.find((p) => p.id === memberId);
|
|
19168
|
+
if (!participant) {
|
|
19169
|
+
logger$12.warn(`[Call] setLayout: member ${memberId} not found in participants; skipping position ${position}`);
|
|
19170
|
+
continue;
|
|
19171
|
+
}
|
|
19172
|
+
participant.target;
|
|
19173
|
+
targets.push([participant, position]);
|
|
19174
|
+
}
|
|
18949
19175
|
const selfId = await (0, import_cjs$11.firstValueFrom)(this.selfId$.pipe((0, import_cjs$11.filter)((id) => id !== null)));
|
|
18950
|
-
await this.executeMethod(selfId, "call.layout.set", {
|
|
18951
|
-
|
|
18952
|
-
positions
|
|
18953
|
-
});
|
|
19176
|
+
await this.executeMethod(selfId, "call.layout.set", { layout });
|
|
19177
|
+
for (const [participant, position] of targets) await participant.setPosition(position);
|
|
18954
19178
|
}
|
|
18955
19179
|
/**
|
|
18956
19180
|
* Transfers the call to another destination.
|
|
@@ -18982,7 +19206,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18982
19206
|
setLocalMicrophoneGain(value) {
|
|
18983
19207
|
const pipeline = this.vertoManager.ensureLocalAudioPipeline();
|
|
18984
19208
|
if (!pipeline) {
|
|
18985
|
-
logger$
|
|
19209
|
+
logger$12.warn("[Call] setLocalMicrophoneGain: audio pipeline unavailable");
|
|
18986
19210
|
return;
|
|
18987
19211
|
}
|
|
18988
19212
|
const percent = Math.max(0, Math.min(200, value));
|
|
@@ -19027,7 +19251,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
19027
19251
|
enablePushToTalk() {
|
|
19028
19252
|
const pipeline = this.vertoManager.ensureLocalAudioPipeline();
|
|
19029
19253
|
if (!pipeline) {
|
|
19030
|
-
logger$
|
|
19254
|
+
logger$12.warn("[Call] enablePushToTalk: audio pipeline unavailable");
|
|
19031
19255
|
return;
|
|
19032
19256
|
}
|
|
19033
19257
|
pipeline.setPTTActive(false);
|
|
@@ -19124,6 +19348,7 @@ function inferCallErrorKind(error) {
|
|
|
19124
19348
|
if (error instanceof RPCTimeoutError) return "timeout";
|
|
19125
19349
|
if (error instanceof JSONRPCError) return "signaling";
|
|
19126
19350
|
if (error instanceof MediaTrackError) return "media";
|
|
19351
|
+
if (error instanceof MediaAccessError) return "media";
|
|
19127
19352
|
if (error instanceof WebSocketConnectionError || error instanceof TransportConnectionError) return "network";
|
|
19128
19353
|
return "internal";
|
|
19129
19354
|
}
|
|
@@ -19140,6 +19365,7 @@ const RECOVERABLE_RPC_CODES = new Set([
|
|
|
19140
19365
|
function isFatalError(error) {
|
|
19141
19366
|
if (error instanceof VertoPongError) return false;
|
|
19142
19367
|
if (error instanceof MediaTrackError) return false;
|
|
19368
|
+
if (error instanceof MediaAccessError) return error.fatal;
|
|
19143
19369
|
if (error instanceof RPCTimeoutError) return false;
|
|
19144
19370
|
if (error instanceof JSONRPCError && RECOVERABLE_RPC_CODES.has(error.code)) return false;
|
|
19145
19371
|
return true;
|
|
@@ -19165,10 +19391,10 @@ var CallFactory = class {
|
|
|
19165
19391
|
return {
|
|
19166
19392
|
vertoManager: new WebRTCVertoManager(callInstance, this.attachManager, this.deviceController, this.webRTCApiProvider, {
|
|
19167
19393
|
nodeId: options.nodeId,
|
|
19168
|
-
onError: (error) => {
|
|
19394
|
+
onError: (error, options$1) => {
|
|
19169
19395
|
const callError = {
|
|
19170
19396
|
kind: inferCallErrorKind(error),
|
|
19171
|
-
fatal: isFatalError(error),
|
|
19397
|
+
fatal: options$1?.fatal ?? isFatalError(error),
|
|
19172
19398
|
error,
|
|
19173
19399
|
callId: callInstance.id
|
|
19174
19400
|
};
|
|
@@ -19190,7 +19416,7 @@ var CallFactory = class {
|
|
|
19190
19416
|
//#endregion
|
|
19191
19417
|
//#region src/behaviors/Collection.ts
|
|
19192
19418
|
var import_cjs$10 = require_cjs();
|
|
19193
|
-
const logger$
|
|
19419
|
+
const logger$11 = getLogger();
|
|
19194
19420
|
var Fetcher = class {
|
|
19195
19421
|
constructor(endpoint, params, http) {
|
|
19196
19422
|
this.endpoint = endpoint;
|
|
@@ -19214,7 +19440,7 @@ var Fetcher = class {
|
|
|
19214
19440
|
this.hasMore = !!this.nextUrl;
|
|
19215
19441
|
return result.data.filter(this.filter).map(this.mapper);
|
|
19216
19442
|
}
|
|
19217
|
-
logger$
|
|
19443
|
+
logger$11.error("Failed to fetch entity");
|
|
19218
19444
|
return [];
|
|
19219
19445
|
}
|
|
19220
19446
|
async id(v) {
|
|
@@ -19290,7 +19516,7 @@ var EntityCollection = class extends Destroyable {
|
|
|
19290
19516
|
this._hasMore$.next(this.fetchController.hasMore ?? false);
|
|
19291
19517
|
this._loading$.next(false);
|
|
19292
19518
|
} catch (error) {
|
|
19293
|
-
logger$
|
|
19519
|
+
logger$11.error(`Failed to fetch initial collection data`, error);
|
|
19294
19520
|
this._hasMore$.next(this.fetchController.hasMore ?? false);
|
|
19295
19521
|
this._loading$.next(false);
|
|
19296
19522
|
this.onError?.(new CollectionFetchError("fetchMore", error));
|
|
@@ -19304,7 +19530,7 @@ var EntityCollection = class extends Destroyable {
|
|
|
19304
19530
|
if (data) this.upsertData(data);
|
|
19305
19531
|
return data;
|
|
19306
19532
|
} catch (error) {
|
|
19307
|
-
logger$
|
|
19533
|
+
logger$11.error(`Failed to fetch data for (${String(key)}:${String(value)}) :`, error);
|
|
19308
19534
|
this._loading$.next(false);
|
|
19309
19535
|
this.onError?.(new CollectionFetchError(`tryFetch(${String(key)})`, error));
|
|
19310
19536
|
}
|
|
@@ -19553,13 +19779,13 @@ var Address = class extends Destroyable {
|
|
|
19553
19779
|
//#endregion
|
|
19554
19780
|
//#region src/core/utils.ts
|
|
19555
19781
|
var import_cjs$8 = require_cjs();
|
|
19556
|
-
const logger$
|
|
19782
|
+
const logger$10 = getLogger();
|
|
19557
19783
|
const isRPCConnectResult = (e) => {
|
|
19558
|
-
logger$
|
|
19784
|
+
logger$10.debug("isRPCConnectResult check:", e);
|
|
19559
19785
|
if (!e || typeof e !== "object") return false;
|
|
19560
19786
|
const result = e;
|
|
19561
19787
|
const is = typeof result.identity === "string" && typeof result.protocol === "string" && typeof result.authorization === "object" && typeof result.authorization.jti === "string" && typeof result.authorization.project_id === "string" && typeof result.authorization.fabric_subscriber === "object";
|
|
19562
|
-
logger$
|
|
19788
|
+
logger$10.debug("isRPCConnectResult check result:", is);
|
|
19563
19789
|
return is;
|
|
19564
19790
|
};
|
|
19565
19791
|
var PendingRPC = class PendingRPC {
|
|
@@ -19568,7 +19794,7 @@ var PendingRPC = class PendingRPC {
|
|
|
19568
19794
|
}
|
|
19569
19795
|
constructor(request, responses$, options) {
|
|
19570
19796
|
this.id = v4_default();
|
|
19571
|
-
logger$
|
|
19797
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}: method:${request.method}] Creating PendingRPC`);
|
|
19572
19798
|
this.request = request;
|
|
19573
19799
|
const timeoutMs = options?.timeoutMs ?? PendingRPC.defaultTimeoutMs;
|
|
19574
19800
|
const signal = options?.signal;
|
|
@@ -19594,22 +19820,22 @@ var PendingRPC = class PendingRPC {
|
|
|
19594
19820
|
isSettled = true;
|
|
19595
19821
|
if (response.error) {
|
|
19596
19822
|
const rpcError = new JSONRPCError(response.error.code, response.error.message, response.error.data, void 0, request.id);
|
|
19597
|
-
logger$
|
|
19823
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with RPC error:`, rpcError);
|
|
19598
19824
|
reject(rpcError);
|
|
19599
19825
|
} else {
|
|
19600
|
-
logger$
|
|
19826
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Resolving promise with response:`, response);
|
|
19601
19827
|
resolve(response);
|
|
19602
19828
|
}
|
|
19603
19829
|
subscription.unsubscribe();
|
|
19604
19830
|
},
|
|
19605
19831
|
error: (error) => {
|
|
19606
|
-
logger$
|
|
19832
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with error:`, error);
|
|
19607
19833
|
isSettled = true;
|
|
19608
19834
|
reject(error);
|
|
19609
19835
|
subscription.unsubscribe();
|
|
19610
19836
|
},
|
|
19611
19837
|
complete: () => {
|
|
19612
|
-
logger$
|
|
19838
|
+
logger$10.debug(`[PendingRPC(${this.id}) request:${request.id}] Observable completed`);
|
|
19613
19839
|
if (!isSettled) reject(new RPCTimeoutError(request.id, timeoutMs));
|
|
19614
19840
|
subscription.unsubscribe();
|
|
19615
19841
|
}
|
|
@@ -19630,7 +19856,18 @@ var PendingRPC = class PendingRPC {
|
|
|
19630
19856
|
//#endregion
|
|
19631
19857
|
//#region src/managers/ClientSessionManager.ts
|
|
19632
19858
|
var import_cjs$7 = require_cjs();
|
|
19633
|
-
const logger$
|
|
19859
|
+
const logger$9 = getLogger();
|
|
19860
|
+
/**
|
|
19861
|
+
* Decide whether an error emitted on `call.errors$` during dial should
|
|
19862
|
+
* abort the dial. A non-fatal MediaAccessError means the call degraded to
|
|
19863
|
+
* receive-only and still connects — everything else rejects `dial()` with
|
|
19864
|
+
* the real cause.
|
|
19865
|
+
*
|
|
19866
|
+
* Pure function — exported for unit testing.
|
|
19867
|
+
*/
|
|
19868
|
+
function shouldAbortDial(callError) {
|
|
19869
|
+
return callError.fatal || !(callError.error instanceof MediaAccessError);
|
|
19870
|
+
}
|
|
19634
19871
|
const getAddressSearchURI = (options) => {
|
|
19635
19872
|
const to = options.to?.split("?")[0];
|
|
19636
19873
|
const from$9 = options.from?.startsWith("subscriber://") ? options.from.replace("subscriber://", "") : options.from;
|
|
@@ -19728,7 +19965,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19728
19965
|
try {
|
|
19729
19966
|
return await this.transport.execute(request, options);
|
|
19730
19967
|
} catch (error) {
|
|
19731
|
-
logger$
|
|
19968
|
+
logger$9.debug("[Session] Execute Error", error);
|
|
19732
19969
|
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
19733
19970
|
throw error;
|
|
19734
19971
|
}
|
|
@@ -19742,13 +19979,13 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19742
19979
|
return true;
|
|
19743
19980
|
}
|
|
19744
19981
|
setupMessageHandlers() {
|
|
19745
|
-
logger$
|
|
19982
|
+
logger$9.debug("[Session] Setting up message handlers");
|
|
19746
19983
|
this.subscribeTo(this.authStateEvent$, async (authStateEvent) => {
|
|
19747
|
-
logger$
|
|
19984
|
+
logger$9.debug("[Session] Authorization state event received:", authStateEvent);
|
|
19748
19985
|
try {
|
|
19749
19986
|
await this.updateAuthorizationStateInStorage(authStateEvent.authorization_state);
|
|
19750
19987
|
} catch (error) {
|
|
19751
|
-
logger$
|
|
19988
|
+
logger$9.error("[Session] Failed to handle authorization state update:", error);
|
|
19752
19989
|
this._errors$.next(new AuthStateHandlerError(error));
|
|
19753
19990
|
}
|
|
19754
19991
|
});
|
|
@@ -19756,29 +19993,29 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19756
19993
|
if (this._authState$.value.kind === "authenticated") this._authState$.next({ kind: "unauthenticated" });
|
|
19757
19994
|
});
|
|
19758
19995
|
this.subscribeTo(this.transport.connectionStatus$.pipe((0, import_cjs$7.filter)((status) => status === "connected"), (0, import_cjs$7.exhaustMap)(() => {
|
|
19759
|
-
logger$
|
|
19996
|
+
logger$9.debug("[Session] Connection established, initiating authentication");
|
|
19760
19997
|
return (0, import_cjs$7.from)(this.authenticate()).pipe((0, import_cjs$7.catchError)((error) => {
|
|
19761
19998
|
this.handleAuthenticationError(error).catch((err) => {
|
|
19762
|
-
logger$
|
|
19999
|
+
logger$9.error("[Session] Error handling authentication failure:", err);
|
|
19763
20000
|
});
|
|
19764
20001
|
return import_cjs$7.EMPTY;
|
|
19765
20002
|
}));
|
|
19766
20003
|
})), void 0);
|
|
19767
20004
|
this.subscribeTo(this.vertoInvite$, async (invite) => {
|
|
19768
|
-
logger$
|
|
20005
|
+
logger$9.debug("[Session] Verto invite received:", invite);
|
|
19769
20006
|
try {
|
|
19770
20007
|
await this.createInboundCall(invite);
|
|
19771
20008
|
} catch (error) {
|
|
19772
|
-
logger$
|
|
20009
|
+
logger$9.error("[Session] Error handling Verto invite:", error);
|
|
19773
20010
|
this._errors$.next(new VertoInviteHandlerError(error));
|
|
19774
20011
|
}
|
|
19775
20012
|
});
|
|
19776
20013
|
this.subscribeTo(this.vertoAttach$, async (attach) => {
|
|
19777
|
-
logger$
|
|
20014
|
+
logger$9.debug("[Session] Verto attach received:", attach);
|
|
19778
20015
|
try {
|
|
19779
20016
|
await this.handleVertoAttach(attach);
|
|
19780
20017
|
} catch (error) {
|
|
19781
|
-
logger$
|
|
20018
|
+
logger$9.error("[Session] Error handling Verto attach:", error);
|
|
19782
20019
|
this._errors$.next(new VertoAttachHandlerError(error));
|
|
19783
20020
|
}
|
|
19784
20021
|
});
|
|
@@ -19788,36 +20025,36 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19788
20025
|
const storedState = await this.storage.getItem(this.authorizationStateKey);
|
|
19789
20026
|
this.authorizationState$.next(storedState ?? void 0);
|
|
19790
20027
|
} catch (error) {
|
|
19791
|
-
logger$
|
|
20028
|
+
logger$9.error("Failed to retrieve authorization state from storage:", error);
|
|
19792
20029
|
this.authorizationState$.next(void 0);
|
|
19793
20030
|
}
|
|
19794
20031
|
}
|
|
19795
20032
|
async updateAuthorizationStateInStorage(authorizationState) {
|
|
19796
20033
|
if (!authorizationState) {
|
|
19797
|
-
logger$
|
|
20034
|
+
logger$9.debug("[Session] Removing authorization state from storage");
|
|
19798
20035
|
try {
|
|
19799
20036
|
await this.storage.removeItem(this.authorizationStateKey);
|
|
19800
20037
|
this.authorizationState$.next(void 0);
|
|
19801
20038
|
} catch (error) {
|
|
19802
|
-
logger$
|
|
20039
|
+
logger$9.error("Failed to remove authorization state from storage:", error);
|
|
19803
20040
|
throw error;
|
|
19804
20041
|
}
|
|
19805
20042
|
return;
|
|
19806
20043
|
}
|
|
19807
20044
|
try {
|
|
19808
|
-
logger$
|
|
20045
|
+
logger$9.debug("[Session] Updating authorization state in storage");
|
|
19809
20046
|
await this.storage.setItem(this.authorizationStateKey, authorizationState);
|
|
19810
20047
|
this.authorizationState$.next(authorizationState);
|
|
19811
20048
|
} catch (error) {
|
|
19812
|
-
logger$
|
|
20049
|
+
logger$9.error("Failed to retrieve authorization state from storage:", error);
|
|
19813
20050
|
throw error;
|
|
19814
20051
|
}
|
|
19815
20052
|
}
|
|
19816
20053
|
get authStateEvent$() {
|
|
19817
20054
|
return this.cachedObservable("authStateEvent$", () => this.signalingEvent$.pipe((0, import_cjs$7.tap)((msg) => {
|
|
19818
|
-
logger$
|
|
20055
|
+
logger$9.debug("[Session] Received incoming message:", msg);
|
|
19819
20056
|
}), filterAs(isSignalwireAuthorizationStateMetadata, "params"), (0, import_cjs$7.tap)((event) => {
|
|
19820
|
-
logger$
|
|
20057
|
+
logger$9.debug("[Session] Authorization state event received:", event.authorization_state);
|
|
19821
20058
|
})));
|
|
19822
20059
|
}
|
|
19823
20060
|
get signalingEvent$() {
|
|
@@ -19855,42 +20092,72 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19855
20092
|
await (0, import_cjs$7.firstValueFrom)(this.authenticated$.pipe((0, import_cjs$7.takeUntil)(this.destroyed$), (0, import_cjs$7.filter)(Boolean), (0, import_cjs$7.take)(1), (0, import_cjs$7.timeout)({ first: 15e3 })));
|
|
19856
20093
|
}
|
|
19857
20094
|
async handleAuthenticationError(error) {
|
|
19858
|
-
logger$
|
|
20095
|
+
logger$9.error("Authentication error:", error);
|
|
19859
20096
|
const isRecoverableAuthError = error instanceof JSONRPCError && (error.code === RPC_ERROR_REQUESTER_VALIDATION_FAILED || error.code === RPC_ERROR_INVALID_PARAMS || error.code === RPC_ERROR_AUTHENTICATION_FAILED);
|
|
19860
20097
|
const hasStoredState = await (0, import_cjs$7.firstValueFrom)(this.authorizationState$.pipe((0, import_cjs$7.take)(1))) !== void 0;
|
|
19861
20098
|
if (isRecoverableAuthError && hasStoredState) {
|
|
19862
|
-
logger$
|
|
20099
|
+
logger$9.debug("[Session] Recoverable auth error — cleaning up stored state and reconnecting fresh");
|
|
19863
20100
|
try {
|
|
19864
20101
|
await this.cleanupStoredConnectionParams();
|
|
19865
20102
|
} catch (cleanupError) {
|
|
19866
|
-
logger$
|
|
20103
|
+
logger$9.error("Failed to cleanup stored connection params:", cleanupError);
|
|
19867
20104
|
} finally {
|
|
19868
20105
|
this.transport.reconnect();
|
|
19869
20106
|
}
|
|
19870
20107
|
} else this._errors$.next(error);
|
|
19871
20108
|
}
|
|
20109
|
+
/**
|
|
20110
|
+
* Clear the resume state (authorization_state + protocol) only.
|
|
20111
|
+
*
|
|
20112
|
+
* This is the stale-auth-state recovery helper used by handleAuthError:
|
|
20113
|
+
* the server rejected a reconnect, so the resume state is discarded and a
|
|
20114
|
+
* fresh connect follows. Attach records are deliberately preserved — the
|
|
20115
|
+
* session lives on through the reconnect and reattachCalls() needs the
|
|
20116
|
+
* stored call references afterwards. Do NOT add detachAll() here.
|
|
20117
|
+
*
|
|
20118
|
+
* For public teardown (disconnect/destroy), use {@link teardownSessionState}
|
|
20119
|
+
* instead, which clears the attach records as well.
|
|
20120
|
+
*/
|
|
19872
20121
|
async cleanupStoredConnectionParams() {
|
|
19873
20122
|
await this.transport.setProtocol(void 0);
|
|
19874
20123
|
await this.updateAuthorizationStateInStorage(void 0);
|
|
19875
20124
|
this._authorization$.next(void 0);
|
|
19876
20125
|
}
|
|
20126
|
+
/**
|
|
20127
|
+
* Public-teardown helper for disconnect()/destroy(). Clears the resume
|
|
20128
|
+
* state (authorization_state + protocol) AND the attach records as one
|
|
20129
|
+
* atomic unit.
|
|
20130
|
+
*
|
|
20131
|
+
* The two stores are coupled: the backend only honors attach records
|
|
20132
|
+
* within the session identified by the resume state, so ending the
|
|
20133
|
+
* session must clear both. Clearing one without the other strands records
|
|
20134
|
+
* no future session can honor (disconnect) or revives a session the
|
|
20135
|
+
* developer explicitly ended (destroy).
|
|
20136
|
+
*
|
|
20137
|
+
* Distinct from {@link cleanupStoredConnectionParams}, which keeps the
|
|
20138
|
+
* attach records for the stale-auth-state recovery path.
|
|
20139
|
+
*/
|
|
20140
|
+
async teardownSessionState() {
|
|
20141
|
+
await this.cleanupStoredConnectionParams();
|
|
20142
|
+
await this.attachManager.detachAll();
|
|
20143
|
+
}
|
|
19877
20144
|
async updateAuthState(authorization_state) {
|
|
19878
20145
|
try {
|
|
19879
20146
|
await this.storage.setItem(this.authorizationStateKey, authorization_state);
|
|
19880
20147
|
} catch (error) {
|
|
19881
|
-
logger$
|
|
20148
|
+
logger$9.error("Failed to update authorization state in storage:", error);
|
|
19882
20149
|
this._errors$.next(new AuthStateHandlerError(error));
|
|
19883
20150
|
}
|
|
19884
20151
|
}
|
|
19885
20152
|
async reauthenticate(token, dpopToken, options) {
|
|
19886
|
-
logger$
|
|
20153
|
+
logger$9.debug("[Session] Re-authenticating session");
|
|
19887
20154
|
try {
|
|
19888
20155
|
let resolvedDpopToken = dpopToken;
|
|
19889
20156
|
if (!resolvedDpopToken && this.dpopManager?.initialized) try {
|
|
19890
20157
|
resolvedDpopToken = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
|
|
19891
20158
|
} catch (error) {
|
|
19892
20159
|
if (this.clientBound) throw error;
|
|
19893
|
-
logger$
|
|
20160
|
+
logger$9.warn("[Session] Failed to create DPoP proof for reauthenticate:", error);
|
|
19894
20161
|
}
|
|
19895
20162
|
const request = RPCReauthenticate({
|
|
19896
20163
|
project: this._authorization$.value?.project_id ?? "",
|
|
@@ -19898,24 +20165,24 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19898
20165
|
...resolvedDpopToken ? { dpop_token: resolvedDpopToken } : {}
|
|
19899
20166
|
});
|
|
19900
20167
|
await (0, import_cjs$7.lastValueFrom)((0, import_cjs$7.from)(this.transport.execute(request)).pipe(throwOnRPCError(), (0, import_cjs$7.take)(1), (0, import_cjs$7.catchError)((err) => {
|
|
19901
|
-
logger$
|
|
20168
|
+
logger$9.error("[Session] Re-authentication RPC failed:", err);
|
|
19902
20169
|
throw err;
|
|
19903
20170
|
})));
|
|
19904
20171
|
if (options?.clientBound) this._wasClientBound = true;
|
|
19905
|
-
logger$
|
|
20172
|
+
logger$9.debug("[Session] Re-authentication successful, updating stored auth state");
|
|
19906
20173
|
} catch (error) {
|
|
19907
|
-
logger$
|
|
20174
|
+
logger$9.error("[Session] Re-authentication failed:", error);
|
|
19908
20175
|
this._errors$.next(new AuthStateHandlerError(error));
|
|
19909
20176
|
throw error;
|
|
19910
20177
|
}
|
|
19911
20178
|
}
|
|
19912
20179
|
async authenticate() {
|
|
19913
|
-
logger$
|
|
20180
|
+
logger$9.debug("[Session] Starting authentication process");
|
|
19914
20181
|
const persistedParams = await (0, import_cjs$7.firstValueFrom)((0, import_cjs$7.combineLatest)({
|
|
19915
20182
|
protocol: this.transport.protocol$,
|
|
19916
20183
|
authorization_state: this.authorizationState$
|
|
19917
20184
|
}).pipe((0, import_cjs$7.take)(1)));
|
|
19918
|
-
logger$
|
|
20185
|
+
logger$9.debug("[Session] Persisted params:\n", {
|
|
19919
20186
|
protocol: persistedParams.protocol,
|
|
19920
20187
|
authStateLength: persistedParams.authorization_state?.length
|
|
19921
20188
|
});
|
|
@@ -19923,16 +20190,16 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19923
20190
|
const storedToken = this.getCredential().token;
|
|
19924
20191
|
const isReconnect = hasReconnectState && storedToken;
|
|
19925
20192
|
let dpopToken;
|
|
19926
|
-
if (isReconnect) logger$
|
|
20193
|
+
if (isReconnect) logger$9.debug("[Session] Reconnecting with stored jwt_token + authorization_state");
|
|
19927
20194
|
else if (this.onBeforeReconnect && this.clientBound) {
|
|
19928
|
-
logger$
|
|
20195
|
+
logger$9.debug("[Session] Refreshing credentials before fresh connect");
|
|
19929
20196
|
await this.onBeforeReconnect();
|
|
19930
20197
|
}
|
|
19931
20198
|
if ((!isReconnect || this.clientBound) && this.dpopManager?.initialized) try {
|
|
19932
20199
|
dpopToken = await this.dpopManager.createRpcProof({ method: "signalwire.connect" });
|
|
19933
20200
|
} catch (error) {
|
|
19934
20201
|
if (this.clientBound) throw error;
|
|
19935
|
-
logger$
|
|
20202
|
+
logger$9.warn("[Session] Failed to create DPoP proof for connect, proceeding without:", error);
|
|
19936
20203
|
}
|
|
19937
20204
|
const rpcConnectRequest = RPCConnect({
|
|
19938
20205
|
authentication: isReconnect ? { jwt_token: storedToken } : this.authentication,
|
|
@@ -19949,12 +20216,12 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19949
20216
|
} : {}
|
|
19950
20217
|
});
|
|
19951
20218
|
const response = await (0, import_cjs$7.lastValueFrom)((0, import_cjs$7.from)(this.transport.execute(rpcConnectRequest)).pipe(throwOnRPCError(), (0, import_cjs$7.map)((res) => res.result), (0, import_cjs$7.filter)(isRPCConnectResult), (0, import_cjs$7.tap)(() => {
|
|
19952
|
-
logger$
|
|
20219
|
+
logger$9.debug("[Session] Response passed filter, processing authentication result");
|
|
19953
20220
|
}), (0, import_cjs$7.take)(1), (0, import_cjs$7.catchError)((err) => {
|
|
19954
|
-
logger$
|
|
20221
|
+
logger$9.error("[Session] Authentication RPC failed:", err);
|
|
19955
20222
|
throw err;
|
|
19956
20223
|
})));
|
|
19957
|
-
logger$
|
|
20224
|
+
logger$9.debug("[Session] Processing authentication result:", {
|
|
19958
20225
|
hasProtocol: !!response.protocol,
|
|
19959
20226
|
hasAuthorization: !!response.authorization,
|
|
19960
20227
|
hasIceServers: !!response.ice_servers
|
|
@@ -19963,12 +20230,12 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19963
20230
|
this._authorization$.next(response.authorization);
|
|
19964
20231
|
this._iceServers$.next(response.ice_servers ?? []);
|
|
19965
20232
|
this._authState$.next({ kind: "authenticated" });
|
|
19966
|
-
logger$
|
|
20233
|
+
logger$9.debug("[Session] Authentication completed successfully");
|
|
19967
20234
|
}
|
|
19968
20235
|
async disconnect() {
|
|
19969
20236
|
this.transport.disconnect();
|
|
19970
20237
|
this._authState$.next({ kind: "unauthenticated" });
|
|
19971
|
-
await this.
|
|
20238
|
+
await this.teardownSessionState();
|
|
19972
20239
|
}
|
|
19973
20240
|
async createInboundCall(invite) {
|
|
19974
20241
|
const callSession = await this.createCall({
|
|
@@ -19999,11 +20266,11 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19999
20266
|
async handleVertoAttach(attach) {
|
|
20000
20267
|
const { callID } = attach;
|
|
20001
20268
|
if (callID in this._calls$.value) {
|
|
20002
|
-
logger$
|
|
20269
|
+
logger$9.debug(`[Session] Verto attach for existing call ${callID}, deferring to per-call handler`);
|
|
20003
20270
|
return;
|
|
20004
20271
|
}
|
|
20005
20272
|
const storedOptions = await this.attachManager.consumePendingAttachment(callID);
|
|
20006
|
-
logger$
|
|
20273
|
+
logger$9.debug(`[Session] Creating reattached call for callID: ${callID}`);
|
|
20007
20274
|
const callSession = await this.createCall({
|
|
20008
20275
|
nodeId: attach.node_id,
|
|
20009
20276
|
callId: callID,
|
|
@@ -20027,14 +20294,14 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
20027
20294
|
to: destinationURI,
|
|
20028
20295
|
...options
|
|
20029
20296
|
});
|
|
20030
|
-
await (0, import_cjs$7.firstValueFrom)((0, import_cjs$7.race)(callSession.selfId$.pipe((0, import_cjs$7.filter)((id) => Boolean(id)), (0, import_cjs$7.take)(1), (0, import_cjs$7.timeout)(this.callCreateTimeout)), callSession.errors$.pipe((0, import_cjs$7.take)(1), (0, import_cjs$7.switchMap)((callError) => (0, import_cjs$7.throwError)(() => callError.error)))));
|
|
20297
|
+
await (0, import_cjs$7.firstValueFrom)((0, import_cjs$7.race)(callSession.selfId$.pipe((0, import_cjs$7.filter)((id) => Boolean(id)), (0, import_cjs$7.take)(1), (0, import_cjs$7.timeout)(this.callCreateTimeout)), callSession.errors$.pipe((0, import_cjs$7.filter)(shouldAbortDial), (0, import_cjs$7.take)(1), (0, import_cjs$7.switchMap)((callError) => (0, import_cjs$7.throwError)(() => callError.error)))));
|
|
20031
20298
|
this._calls$.next({
|
|
20032
20299
|
[`${callSession.id}`]: callSession,
|
|
20033
20300
|
...this._calls$.value
|
|
20034
20301
|
});
|
|
20035
20302
|
return callSession;
|
|
20036
20303
|
} catch (error) {
|
|
20037
|
-
logger$
|
|
20304
|
+
logger$9.error("[Session] Error creating outbound call:", error);
|
|
20038
20305
|
callSession?.destroy();
|
|
20039
20306
|
const callError = new CallCreateError(error instanceof import_cjs$7.TimeoutError ? "Call create timeout" : "Call creation failed", error, "outbound");
|
|
20040
20307
|
this._errors$.next(callError);
|
|
@@ -20052,7 +20319,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
20052
20319
|
address = this._directory.get(addressId);
|
|
20053
20320
|
if (!address) throw new DependencyError(`Address ID: ${addressId} not found`);
|
|
20054
20321
|
} catch {
|
|
20055
|
-
logger$
|
|
20322
|
+
logger$9.warn(`[Session] Directory lookup failed for ${addressURI}, proceeding with raw URI`);
|
|
20056
20323
|
}
|
|
20057
20324
|
const callSession = this.callFactory.createCall(address, { ...options });
|
|
20058
20325
|
this.subscribeTo(callSession.status$.pipe((0, import_cjs$7.filter)((status) => status === "destroyed"), (0, import_cjs$7.take)(1)), () => {
|
|
@@ -20061,7 +20328,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
20061
20328
|
});
|
|
20062
20329
|
return callSession;
|
|
20063
20330
|
} catch (error) {
|
|
20064
|
-
logger$
|
|
20331
|
+
logger$9.error("[Session] Error creating call session:", error);
|
|
20065
20332
|
throw new CallCreateError("Call create error", error, options.initOffer ? "inbound" : "outbound");
|
|
20066
20333
|
}
|
|
20067
20334
|
}
|
|
@@ -20110,7 +20377,7 @@ const isString = (obj) => typeof obj === "string";
|
|
|
20110
20377
|
//#endregion
|
|
20111
20378
|
//#region src/managers/ConversationsManager.ts
|
|
20112
20379
|
var import_cjs$6 = require_cjs();
|
|
20113
|
-
const logger$
|
|
20380
|
+
const logger$8 = getLogger();
|
|
20114
20381
|
var ConversationMessagesFetcher = class extends Fetcher {
|
|
20115
20382
|
constructor(groupId, http) {
|
|
20116
20383
|
super(`/api/fabric/conversations/${groupId}/messages`, "page_size=100", http);
|
|
@@ -20150,13 +20417,13 @@ var ConversationsManager = class {
|
|
|
20150
20417
|
}
|
|
20151
20418
|
throw new ConversationError("Join Failed - Unexpected response");
|
|
20152
20419
|
} catch (error) {
|
|
20153
|
-
logger$
|
|
20420
|
+
logger$8.error("[ConversationsManager] Failed to join conversation:", error);
|
|
20154
20421
|
throw error;
|
|
20155
20422
|
}
|
|
20156
20423
|
}
|
|
20157
20424
|
async getConversationMessageCollection(addressId) {
|
|
20158
20425
|
const groupId = this.groupIds.get(addressId) ?? await this.join(addressId);
|
|
20159
|
-
return Promise.resolve(new ConversationMessageCollection(groupId, this.clientSession.signalingEvent$.pipe(filterAs(isConversationMessageMetadata, "params"), (0, import_cjs$6.tap)((event) => logger$
|
|
20426
|
+
return Promise.resolve(new ConversationMessageCollection(groupId, this.clientSession.signalingEvent$.pipe(filterAs(isConversationMessageMetadata, "params"), (0, import_cjs$6.tap)((event) => logger$8.debug("[ConversationsManager ] Conversation Event:", event)), (0, import_cjs$6.map)((params) => ({ ...params }))), this.http, this.onError));
|
|
20160
20427
|
}
|
|
20161
20428
|
async sendText(text, destinationAddressId) {
|
|
20162
20429
|
const groupId = this.groupIds.get(destinationAddressId) ?? await this.join(destinationAddressId);
|
|
@@ -20173,7 +20440,7 @@ var ConversationsManager = class {
|
|
|
20173
20440
|
})).ok) return;
|
|
20174
20441
|
throw new ConversationError("Send Text Failed - Unexpected response");
|
|
20175
20442
|
} catch (error) {
|
|
20176
|
-
logger$
|
|
20443
|
+
logger$8.error("[ConversationsManager] Failed to send text message:", error);
|
|
20177
20444
|
throw error;
|
|
20178
20445
|
}
|
|
20179
20446
|
}
|
|
@@ -20182,7 +20449,7 @@ var ConversationsManager = class {
|
|
|
20182
20449
|
//#endregion
|
|
20183
20450
|
//#region src/managers/DeviceTokenManager.ts
|
|
20184
20451
|
var import_cjs$5 = require_cjs();
|
|
20185
|
-
const logger$
|
|
20452
|
+
const logger$7 = getLogger();
|
|
20186
20453
|
/**
|
|
20187
20454
|
* Resolves the token expiry timestamp (epoch seconds) using a 3-tier priority chain:
|
|
20188
20455
|
* 1. `data.expires_at` — server-provided absolute timestamp
|
|
@@ -20192,7 +20459,7 @@ const logger$6 = getLogger();
|
|
|
20192
20459
|
function resolveExpiresAt(data) {
|
|
20193
20460
|
if (data.expires_at) return data.expires_at;
|
|
20194
20461
|
if (data.expires_in) return Math.floor(Date.now() / 1e3) + data.expires_in;
|
|
20195
|
-
logger$
|
|
20462
|
+
logger$7.warn("[DeviceToken] Could not determine token expiry, using default");
|
|
20196
20463
|
return Math.floor(Date.now() / 1e3) + DEVICE_TOKEN_DEFAULT_EXPIRE_IN;
|
|
20197
20464
|
}
|
|
20198
20465
|
/**
|
|
@@ -20225,11 +20492,12 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20225
20492
|
this.getCredential = getCredential;
|
|
20226
20493
|
this._currentToken$ = this.createBehaviorSubject(null);
|
|
20227
20494
|
this._refreshInProgress = false;
|
|
20495
|
+
this._paused = false;
|
|
20228
20496
|
this._effectiveExpireIn = DEVICE_TOKEN_DEFAULT_EXPIRE_IN;
|
|
20229
20497
|
this.subscribeTo(this._currentToken$.pipe((0, import_cjs$5.filter)(Boolean), (0, import_cjs$5.switchMap)((tokenData) => {
|
|
20230
20498
|
const expiresAt = resolveExpiresAt(tokenData);
|
|
20231
20499
|
const refreshIn = Math.max(expiresAt * 1e3 - Date.now() - DEVICE_TOKEN_REFRESH_BUFFER_MS, 1e3);
|
|
20232
|
-
logger$
|
|
20500
|
+
logger$7.debug(`[DeviceToken] Scheduling Client Bound SAT refresh in ${refreshIn}ms`);
|
|
20233
20501
|
return (0, import_cjs$5.timer)(refreshIn);
|
|
20234
20502
|
})), () => {
|
|
20235
20503
|
this.executeRefresh();
|
|
@@ -20243,7 +20511,12 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20243
20511
|
* Activates the Client Bound SAT flow when the user's token has
|
|
20244
20512
|
* `sat:refresh` scope.
|
|
20245
20513
|
*
|
|
20246
|
-
*
|
|
20514
|
+
* Returns an {@link ActivationResult} indicating whether the manager
|
|
20515
|
+
* took ownership of refresh duties. The caller must use the `activated`
|
|
20516
|
+
* boolean to decide whether to keep its own refresh path armed — when
|
|
20517
|
+
* `activated` is `false`, the caller is responsible for refresh.
|
|
20518
|
+
*
|
|
20519
|
+
* Steps on success:
|
|
20247
20520
|
* 1. Check user's `sat_claims` for `sat:refresh` scope
|
|
20248
20521
|
* 2. Call `/api/fabric/subscriber/devices/token` with a DPoP proof
|
|
20249
20522
|
* 3. Reauthenticate the session with the Client Bound SAT + DPoP proof
|
|
@@ -20252,32 +20525,51 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20252
20525
|
async activate(user, session, updateCredential) {
|
|
20253
20526
|
const { satClaims } = user;
|
|
20254
20527
|
if (!satClaims?.scope?.includes(SAT_REFRESH_SCOPE)) {
|
|
20255
|
-
logger$
|
|
20256
|
-
return
|
|
20528
|
+
logger$7.debug("[DeviceToken] No sat:refresh scope, skipping Client Bound SAT activation");
|
|
20529
|
+
return {
|
|
20530
|
+
activated: false,
|
|
20531
|
+
reason: "no-scope"
|
|
20532
|
+
};
|
|
20257
20533
|
}
|
|
20258
20534
|
this._session = session;
|
|
20259
20535
|
this._updateCredential = updateCredential;
|
|
20260
20536
|
try {
|
|
20537
|
+
const cached = this._currentToken$.value;
|
|
20538
|
+
if (cached && this.isTokenFresh(cached)) {
|
|
20539
|
+
logger$7.debug("[DeviceToken] Reusing cached Client Bound SAT — skipping /devices/token");
|
|
20540
|
+
const rpcProof$1 = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
|
|
20541
|
+
await session.reauthenticate(cached.token, rpcProof$1, { clientBound: true });
|
|
20542
|
+
updateCredential({ token: cached.token });
|
|
20543
|
+
return { activated: true };
|
|
20544
|
+
}
|
|
20261
20545
|
const tokenData = await this.obtainToken();
|
|
20262
20546
|
if (!tokenData.expires_at && !tokenData.expires_in && satClaims.expires_at) tokenData.expires_at = satClaims.expires_at;
|
|
20263
20547
|
this._effectiveExpireIn = resolveExpireIn(tokenData);
|
|
20264
20548
|
const rpcProof = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
|
|
20265
20549
|
await session.reauthenticate(tokenData.token, rpcProof, { clientBound: true });
|
|
20266
20550
|
updateCredential({ token: tokenData.token });
|
|
20267
|
-
logger$
|
|
20551
|
+
logger$7.info("[DeviceToken] Client Bound SAT activated successfully");
|
|
20268
20552
|
this._currentToken$.next(tokenData);
|
|
20553
|
+
return { activated: true };
|
|
20269
20554
|
} catch (error) {
|
|
20270
|
-
logger$
|
|
20555
|
+
logger$7.error("[DeviceToken] Failed to activate Client Bound SAT:", error);
|
|
20271
20556
|
this.errorHandler(new DPoPInitError(error, "Failed to activate Client Bound SAT"));
|
|
20272
|
-
|
|
20273
|
-
|
|
20274
|
-
|
|
20275
|
-
|
|
20276
|
-
expires_at: expiresAt
|
|
20277
|
-
});
|
|
20557
|
+
return {
|
|
20558
|
+
activated: false,
|
|
20559
|
+
reason: "endpoint-failed"
|
|
20560
|
+
};
|
|
20278
20561
|
}
|
|
20279
20562
|
}
|
|
20280
20563
|
/**
|
|
20564
|
+
* Returns true when the cached token has enough headroom before expiry to
|
|
20565
|
+
* be safely reused on reactivation. The headroom matches the refresh
|
|
20566
|
+
* buffer, so a token within the refresh window is treated as stale (the
|
|
20567
|
+
* reactive pipeline is about to refresh it anyway).
|
|
20568
|
+
*/
|
|
20569
|
+
isTokenFresh(token) {
|
|
20570
|
+
return resolveExpiresAt(token) * 1e3 - Date.now() > DEVICE_TOKEN_REFRESH_BUFFER_MS;
|
|
20571
|
+
}
|
|
20572
|
+
/**
|
|
20281
20573
|
* Obtains a Client Bound SAT from `/api/fabric/subscriber/devices/token`.
|
|
20282
20574
|
* Returns the full {@link DeviceTokenResponse} including expiry metadata.
|
|
20283
20575
|
*/
|
|
@@ -20307,7 +20599,7 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20307
20599
|
* handled by the reactive pipeline).
|
|
20308
20600
|
*/
|
|
20309
20601
|
async refreshToken(session, currentToken, updateCredential) {
|
|
20310
|
-
logger$
|
|
20602
|
+
logger$7.debug("[DeviceToken] Refreshing Client Bound SAT");
|
|
20311
20603
|
const dpopProof = await this.dpopManager.createHttpProof({
|
|
20312
20604
|
method: "POST",
|
|
20313
20605
|
uri: DEVICE_REFRESH_ENDPOINT,
|
|
@@ -20329,7 +20621,7 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20329
20621
|
const rpcProof = await this.dpopManager.createRpcProof({ method: "signalwire.reauthenticate" });
|
|
20330
20622
|
await session.reauthenticate(data.token, rpcProof);
|
|
20331
20623
|
updateCredential({ token: data.token });
|
|
20332
|
-
logger$
|
|
20624
|
+
logger$7.info("[DeviceToken] Client Bound SAT refreshed successfully");
|
|
20333
20625
|
return data;
|
|
20334
20626
|
}
|
|
20335
20627
|
/**
|
|
@@ -20338,18 +20630,22 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20338
20630
|
* On all retries exhausted, emits to `errorHandler`.
|
|
20339
20631
|
*/
|
|
20340
20632
|
async executeRefresh() {
|
|
20633
|
+
if (this._paused) {
|
|
20634
|
+
logger$7.debug("[DeviceToken] Manager paused, skipping refresh");
|
|
20635
|
+
return;
|
|
20636
|
+
}
|
|
20341
20637
|
if (this._refreshInProgress) {
|
|
20342
|
-
logger$
|
|
20638
|
+
logger$7.debug("[DeviceToken] Refresh already in progress, skipping");
|
|
20343
20639
|
return;
|
|
20344
20640
|
}
|
|
20345
20641
|
const session = this._session;
|
|
20346
20642
|
const updateCredential = this._updateCredential;
|
|
20347
20643
|
if (!session || !updateCredential) {
|
|
20348
|
-
logger$
|
|
20644
|
+
logger$7.warn("[DeviceToken] Cannot refresh: session or updateCredential not set");
|
|
20349
20645
|
return;
|
|
20350
20646
|
}
|
|
20351
20647
|
if (!session.authenticated) {
|
|
20352
|
-
logger$
|
|
20648
|
+
logger$7.debug("[DeviceToken] Session not authenticated, deferring refresh");
|
|
20353
20649
|
return;
|
|
20354
20650
|
}
|
|
20355
20651
|
this._refreshInProgress = true;
|
|
@@ -20359,7 +20655,7 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20359
20655
|
const newTokenData = await this.retryRefresh(session, currentToken, updateCredential);
|
|
20360
20656
|
this._currentToken$.next(newTokenData);
|
|
20361
20657
|
} catch (error) {
|
|
20362
|
-
logger$
|
|
20658
|
+
logger$7.error("[DeviceToken] Automatic Client Bound SAT refresh failed:", error);
|
|
20363
20659
|
this.errorHandler(error instanceof TokenRefreshError ? error : new TokenRefreshError("Automatic token refresh failed", error));
|
|
20364
20660
|
} finally {
|
|
20365
20661
|
this._refreshInProgress = false;
|
|
@@ -20377,18 +20673,215 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20377
20673
|
lastError = error;
|
|
20378
20674
|
if (attempt < DEVICE_TOKEN_REFRESH_MAX_RETRIES - 1) {
|
|
20379
20675
|
const delay$1 = DEVICE_TOKEN_REFRESH_RETRY_BASE_MS * Math.pow(2, attempt);
|
|
20380
|
-
logger$
|
|
20676
|
+
logger$7.warn(`[DeviceToken] Refresh attempt ${attempt + 1} failed, retrying in ${delay$1}ms`);
|
|
20381
20677
|
await new Promise((resolve) => setTimeout(resolve, delay$1));
|
|
20382
20678
|
}
|
|
20383
20679
|
}
|
|
20384
20680
|
throw lastError instanceof Error ? lastError : new TokenRefreshError("All refresh retries exhausted", lastError);
|
|
20385
20681
|
}
|
|
20682
|
+
/**
|
|
20683
|
+
* Stops the reactive refresh pipeline from firing. Use when the underlying
|
|
20684
|
+
* session is being torn down (e.g., during {@link SignalWire.disconnect})
|
|
20685
|
+
* so a scheduled refresh cannot fire against a destroyed session.
|
|
20686
|
+
*
|
|
20687
|
+
* The manager's state (cached token, effective TTL, subscriptions) is
|
|
20688
|
+
* preserved — call {@link resume} to re-enable firing after reconnect.
|
|
20689
|
+
*/
|
|
20690
|
+
pause() {
|
|
20691
|
+
this._paused = true;
|
|
20692
|
+
}
|
|
20693
|
+
/** Re-enables the reactive refresh pipeline after a previous {@link pause}. */
|
|
20694
|
+
resume() {
|
|
20695
|
+
this._paused = false;
|
|
20696
|
+
}
|
|
20386
20697
|
/** Cleans up the manager, cancelling the reactive pipeline and all subscriptions. */
|
|
20387
20698
|
destroy() {
|
|
20388
20699
|
super.destroy();
|
|
20389
20700
|
}
|
|
20390
20701
|
};
|
|
20391
20702
|
|
|
20703
|
+
//#endregion
|
|
20704
|
+
//#region src/managers/CredentialRefreshCoordinator.ts
|
|
20705
|
+
const logger$6 = getLogger();
|
|
20706
|
+
const defaultDeviceTokenManagerFactory = (dpopManager, http, errorHandler, getCredential) => new DeviceTokenManager(dpopManager, http, errorHandler, getCredential);
|
|
20707
|
+
/**
|
|
20708
|
+
* Centralizes credential-refresh ownership across the two competing
|
|
20709
|
+
* mechanisms — developer-provided `CredentialProvider.refresh()` and the
|
|
20710
|
+
* Client Bound SAT path via {@link DeviceTokenManager}.
|
|
20711
|
+
*
|
|
20712
|
+
* Maintains the invariant: **at most one refresh mechanism is armed at a
|
|
20713
|
+
* time, and at least one is armed whenever the current credential has an
|
|
20714
|
+
* `expiry_at`**.
|
|
20715
|
+
*
|
|
20716
|
+
* Replaces the previous design where refresh state was distributed across
|
|
20717
|
+
* `SignalWire` (timer field, scheduler method, activation helper) and
|
|
20718
|
+
* `DeviceTokenManager` (reactive pipeline). Centralizing the invariant in
|
|
20719
|
+
* one component eliminates the bug class that produced issue #19074.
|
|
20720
|
+
*
|
|
20721
|
+
* Race-safety:
|
|
20722
|
+
* - `_activating` flag prevents overlapping `activate()` calls from racing.
|
|
20723
|
+
* - `_activationGeneration` lets late resolutions detect they've been
|
|
20724
|
+
* preempted by a newer activation (e.g., reconnect during in-flight
|
|
20725
|
+
* `obtainToken`).
|
|
20726
|
+
*/
|
|
20727
|
+
var CredentialRefreshCoordinator = class extends Destroyable {
|
|
20728
|
+
constructor(dpopManager, deps) {
|
|
20729
|
+
super();
|
|
20730
|
+
this.deps = deps;
|
|
20731
|
+
this._activating = false;
|
|
20732
|
+
this._activationGeneration = 0;
|
|
20733
|
+
if (dpopManager?.initialized) this._deviceTokenManager = (deps.deviceTokenManagerFactory ?? defaultDeviceTokenManagerFactory)(dpopManager, deps.http, (error) => deps.notifier.onError(error), () => deps.store.read());
|
|
20734
|
+
}
|
|
20735
|
+
/** True when the Client Bound SAT path is available (DPoP initialized). */
|
|
20736
|
+
get clientBoundSATAvailable() {
|
|
20737
|
+
return this._deviceTokenManager !== void 0;
|
|
20738
|
+
}
|
|
20739
|
+
/** True when the developer-provided refresh timer is currently armed. */
|
|
20740
|
+
get developerRefreshArmed() {
|
|
20741
|
+
return this._developerTimerId !== void 0;
|
|
20742
|
+
}
|
|
20743
|
+
/**
|
|
20744
|
+
* Arms the developer-provided refresh timer to fire shortly before
|
|
20745
|
+
* `expiresAt`. Replaces any previously scheduled developer refresh.
|
|
20746
|
+
*
|
|
20747
|
+
* Idempotent — multiple calls just reschedule. On retry exhaustion,
|
|
20748
|
+
* invokes `deps.onRefreshExhausted` so the orchestrator can disconnect.
|
|
20749
|
+
*/
|
|
20750
|
+
scheduleDeveloperRefresh(provider, expiresAt, attempt = 0) {
|
|
20751
|
+
if (this._developerTimerId !== void 0) clearTimeout(this._developerTimerId);
|
|
20752
|
+
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);
|
|
20753
|
+
this._developerTimerId = setTimeout(async () => {
|
|
20754
|
+
try {
|
|
20755
|
+
if (!provider.refresh) throw new InvalidCredentialsError("Credential provider does not support refresh");
|
|
20756
|
+
const newCredentials = await provider.refresh();
|
|
20757
|
+
this.deps.store.write(newCredentials);
|
|
20758
|
+
this.deps.store.persist(newCredentials);
|
|
20759
|
+
logger$6.info("[Coordinator] Credentials refreshed successfully.");
|
|
20760
|
+
if (newCredentials.expiry_at) this.scheduleDeveloperRefresh(provider, newCredentials.expiry_at, 0);
|
|
20761
|
+
} catch (error) {
|
|
20762
|
+
const nextAttempt = attempt + 1;
|
|
20763
|
+
logger$6.error(`[Coordinator] Credential refresh failed (attempt ${nextAttempt}/${CREDENTIAL_REFRESH_MAX_RETRIES}):`, error);
|
|
20764
|
+
this.deps.notifier.onError(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
20765
|
+
if (nextAttempt < CREDENTIAL_REFRESH_MAX_RETRIES) this.scheduleDeveloperRefresh(provider, expiresAt, nextAttempt);
|
|
20766
|
+
else {
|
|
20767
|
+
logger$6.error("[Coordinator] Credential refresh exhausted all retries. Disconnecting.");
|
|
20768
|
+
this.deps.notifier.onError(new TokenRefreshError("Credential refresh failed after max retries"));
|
|
20769
|
+
this.deps.notifier.onRefreshExhausted();
|
|
20770
|
+
}
|
|
20771
|
+
}
|
|
20772
|
+
}, refreshInterval);
|
|
20773
|
+
}
|
|
20774
|
+
/**
|
|
20775
|
+
* Cancels any scheduled developer-provided refresh. Idempotent.
|
|
20776
|
+
*
|
|
20777
|
+
* @internal Used by the coordinator's own activation flow. External
|
|
20778
|
+
* callers should use {@link suspend} for disconnect-time quiescence —
|
|
20779
|
+
* `suspend()` also pauses the internal Client Bound SAT pipeline.
|
|
20780
|
+
*/
|
|
20781
|
+
cancelDeveloperRefresh() {
|
|
20782
|
+
if (this._developerTimerId !== void 0) {
|
|
20783
|
+
clearTimeout(this._developerTimerId);
|
|
20784
|
+
this._developerTimerId = void 0;
|
|
20785
|
+
}
|
|
20786
|
+
}
|
|
20787
|
+
/**
|
|
20788
|
+
* Suspends both refresh paths — cancels the developer timer and pauses
|
|
20789
|
+
* the internal reactive pipeline. Use when the underlying session is
|
|
20790
|
+
* being torn down (e.g., {@link SignalWire.disconnect}). The next
|
|
20791
|
+
* {@link activate} call re-enables the internal pipeline.
|
|
20792
|
+
*
|
|
20793
|
+
* The internal manager's cached token survives — see
|
|
20794
|
+
* {@link DeviceTokenManager.pause} — so a subsequent reconnect can
|
|
20795
|
+
* skip the `/devices/token` exchange entirely.
|
|
20796
|
+
*/
|
|
20797
|
+
suspend() {
|
|
20798
|
+
this.cancelDeveloperRefresh();
|
|
20799
|
+
this._deviceTokenManager?.pause();
|
|
20800
|
+
}
|
|
20801
|
+
/**
|
|
20802
|
+
* Asks the Client Bound SAT path to take over refresh. If it accepts,
|
|
20803
|
+
* the developer-provided timer (if any) is cancelled. If it declines, the
|
|
20804
|
+
* developer timer remains armed and a `credential_refresh_fallback`
|
|
20805
|
+
* warning is emitted.
|
|
20806
|
+
*
|
|
20807
|
+
* **Idempotent** — re-entrant calls during an in-flight activation are
|
|
20808
|
+
* dropped. Use `_activationGeneration` to detect stale resolutions
|
|
20809
|
+
* (e.g., a reconnect-triggered activate() that races with an earlier one).
|
|
20810
|
+
*/
|
|
20811
|
+
async activate(user, session) {
|
|
20812
|
+
if (this._activating) {
|
|
20813
|
+
logger$6.debug("[Coordinator] activate() in flight; ignoring re-entrant call");
|
|
20814
|
+
return;
|
|
20815
|
+
}
|
|
20816
|
+
if (!this._deviceTokenManager) return;
|
|
20817
|
+
this._deviceTokenManager.resume();
|
|
20818
|
+
const generation = ++this._activationGeneration;
|
|
20819
|
+
this._activating = true;
|
|
20820
|
+
try {
|
|
20821
|
+
const result = await this.withActivationTimeout(this._deviceTokenManager.activate(user, session, (cred) => this.deps.store.merge(cred)));
|
|
20822
|
+
if (generation !== this._activationGeneration) {
|
|
20823
|
+
logger$6.debug("[Coordinator] activate() result discarded (preempted by newer activation)");
|
|
20824
|
+
return;
|
|
20825
|
+
}
|
|
20826
|
+
if (result.activated) {
|
|
20827
|
+
this.cancelDeveloperRefresh();
|
|
20828
|
+
logger$6.debug("[Coordinator] Developer refresh disabled — Client Bound SAT owns refresh");
|
|
20829
|
+
return;
|
|
20830
|
+
}
|
|
20831
|
+
logger$6.warn(`[SignalWire] [SW-REFRESH-FALLBACK] Client Bound SAT declined (reason=${result.reason}); using developer-provided refresh handler.`);
|
|
20832
|
+
this.deps.notifier.onWarning({
|
|
20833
|
+
code: "credential_refresh_fallback",
|
|
20834
|
+
source: "CredentialProvider",
|
|
20835
|
+
reason: result.reason,
|
|
20836
|
+
message: `Client Bound SAT activation declined (${result.reason}); using developer-provided refresh.`
|
|
20837
|
+
});
|
|
20838
|
+
} finally {
|
|
20839
|
+
this._activating = false;
|
|
20840
|
+
}
|
|
20841
|
+
}
|
|
20842
|
+
destroy() {
|
|
20843
|
+
this.cancelDeveloperRefresh();
|
|
20844
|
+
this._deviceTokenManager?.destroy();
|
|
20845
|
+
this._deviceTokenManager = void 0;
|
|
20846
|
+
super.destroy();
|
|
20847
|
+
}
|
|
20848
|
+
/**
|
|
20849
|
+
* Races the manager's `activate()` against a hard timeout. A wedged HTTP
|
|
20850
|
+
* layer (e.g., proxy issues) could otherwise hang the activation
|
|
20851
|
+
* indefinitely, leaving the session with no refresh mechanism while the
|
|
20852
|
+
* `_activating` guard blocks subsequent retries.
|
|
20853
|
+
*
|
|
20854
|
+
* On timeout, the manager is paused immediately. The inner `activate()`
|
|
20855
|
+
* may still complete in the background and emit to `_currentToken$`,
|
|
20856
|
+
* which would normally arm the reactive refresh pipeline; pausing
|
|
20857
|
+
* prevents that pipeline from firing while the developer refresh path
|
|
20858
|
+
* is the active mechanism — preserving the "at most one mechanism
|
|
20859
|
+
* armed" invariant. The next `activate()` call resumes the manager.
|
|
20860
|
+
*/
|
|
20861
|
+
async withActivationTimeout(inner) {
|
|
20862
|
+
return new Promise((resolve) => {
|
|
20863
|
+
const timer$4 = setTimeout(() => {
|
|
20864
|
+
this._deviceTokenManager?.pause();
|
|
20865
|
+
resolve({
|
|
20866
|
+
activated: false,
|
|
20867
|
+
reason: "activation-timeout"
|
|
20868
|
+
});
|
|
20869
|
+
}, CREDENTIAL_ACTIVATE_TIMEOUT_MS);
|
|
20870
|
+
inner.then((result) => {
|
|
20871
|
+
clearTimeout(timer$4);
|
|
20872
|
+
resolve(result);
|
|
20873
|
+
}, (error) => {
|
|
20874
|
+
clearTimeout(timer$4);
|
|
20875
|
+
this.deps.notifier.onError(error instanceof Error ? error : new Error(String(error)));
|
|
20876
|
+
resolve({
|
|
20877
|
+
activated: false,
|
|
20878
|
+
reason: "endpoint-failed"
|
|
20879
|
+
});
|
|
20880
|
+
});
|
|
20881
|
+
});
|
|
20882
|
+
}
|
|
20883
|
+
};
|
|
20884
|
+
|
|
20392
20885
|
//#endregion
|
|
20393
20886
|
//#region src/managers/DiagnosticsCollector.ts
|
|
20394
20887
|
const logger$5 = getLogger();
|
|
@@ -20909,6 +21402,10 @@ var TransportManager = class extends Destroyable {
|
|
|
20909
21402
|
if (!isSignalwireRequest(message)) return true;
|
|
20910
21403
|
const eventChannel = message.params.event_channel;
|
|
20911
21404
|
if (!eventChannel) return true;
|
|
21405
|
+
if (message.params.event_type.startsWith("conversation.")) {
|
|
21406
|
+
logger$2.debug(`[Transport] Received conversation event: ${message.params.event_type} (event_channel: ${eventChannel})`);
|
|
21407
|
+
return true;
|
|
21408
|
+
}
|
|
20912
21409
|
const currentProtocol = this._currentProtocol;
|
|
20913
21410
|
if (!currentProtocol) return true;
|
|
20914
21411
|
if (!eventChannel.includes(currentProtocol)) {
|
|
@@ -21102,6 +21599,7 @@ var SignalWire = class extends Destroyable {
|
|
|
21102
21599
|
this._isConnected$ = this.createBehaviorSubject(false);
|
|
21103
21600
|
this._isRegistered$ = this.createBehaviorSubject(false);
|
|
21104
21601
|
this._errors$ = this.createReplaySubject(1);
|
|
21602
|
+
this._warnings$ = this.createReplaySubject(10);
|
|
21105
21603
|
this._options = {};
|
|
21106
21604
|
this._deps = new DependencyContainer();
|
|
21107
21605
|
this._credentialProvider = credentialProvider;
|
|
@@ -21161,6 +21659,27 @@ var SignalWire = class extends Destroyable {
|
|
|
21161
21659
|
*/
|
|
21162
21660
|
async resolveCredentials() {
|
|
21163
21661
|
const fingerprint = await this.initDPoP();
|
|
21662
|
+
this._refreshCoordinator = new CredentialRefreshCoordinator(this._dpopManager, {
|
|
21663
|
+
http: this._deps.http,
|
|
21664
|
+
notifier: {
|
|
21665
|
+
onError: (error) => this._errors$.next(error),
|
|
21666
|
+
onWarning: (warning) => this._warnings$.next(warning),
|
|
21667
|
+
onRefreshExhausted: () => void this.disconnect()
|
|
21668
|
+
},
|
|
21669
|
+
store: {
|
|
21670
|
+
read: () => this._deps.credential,
|
|
21671
|
+
write: (credential) => {
|
|
21672
|
+
this._deps.credential = credential;
|
|
21673
|
+
},
|
|
21674
|
+
merge: (partial) => {
|
|
21675
|
+
this._deps.credential = {
|
|
21676
|
+
...this._deps.credential,
|
|
21677
|
+
...partial
|
|
21678
|
+
};
|
|
21679
|
+
},
|
|
21680
|
+
persist: (credential) => this.persistCredential(credential)
|
|
21681
|
+
}
|
|
21682
|
+
});
|
|
21164
21683
|
if (this._credentialProvider) return this.validateCredentials(this._credentialProvider, void 0, fingerprint);
|
|
21165
21684
|
for (const scope of this._deps.persistSession ? ["local", "session"] : ["session"]) try {
|
|
21166
21685
|
const cached = await this._deps.storage.getItem("sw:cached_credential", scope);
|
|
@@ -21190,7 +21709,16 @@ var SignalWire = class extends Destroyable {
|
|
|
21190
21709
|
logger$1.error("[SignalWire] Provided credentials have expired.");
|
|
21191
21710
|
throw new InvalidCredentialsError("Provided credentials have expired.");
|
|
21192
21711
|
}
|
|
21193
|
-
if (_credentials.expiry_at && credentialProvider?.refresh) this.
|
|
21712
|
+
if (_credentials.expiry_at && credentialProvider?.refresh) this._refreshCoordinator?.scheduleDeveloperRefresh(credentialProvider, _credentials.expiry_at);
|
|
21713
|
+
else if (_credentials.expiry_at && !credentialProvider?.refresh) {
|
|
21714
|
+
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.`);
|
|
21715
|
+
this._warnings$.next({
|
|
21716
|
+
code: "credential_no_refresh_handler",
|
|
21717
|
+
source: "CredentialProvider",
|
|
21718
|
+
message: "Credential has expiry_at but no refresh handler. Session will terminate at expiry unless the SAT carries 'sat:refresh' scope.",
|
|
21719
|
+
expiresAt: _credentials.expiry_at
|
|
21720
|
+
});
|
|
21721
|
+
}
|
|
21194
21722
|
this._deps.credential = _credentials;
|
|
21195
21723
|
this.persistCredential(_credentials);
|
|
21196
21724
|
if (this.isConnected && this._clientSession.authenticated && _credentials.token) try {
|
|
@@ -21201,35 +21729,6 @@ var SignalWire = class extends Destroyable {
|
|
|
21201
21729
|
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
21202
21730
|
}
|
|
21203
21731
|
}
|
|
21204
|
-
/**
|
|
21205
|
-
* Schedules credential refresh with exponential backoff retry on failure.
|
|
21206
|
-
* On success, resets attempt counter and schedules the next refresh.
|
|
21207
|
-
* After exhausting retries, emits TokenRefreshError and disconnects.
|
|
21208
|
-
*/
|
|
21209
|
-
scheduleCredentialRefresh(credentialProvider, expiresAt, attempt = 0) {
|
|
21210
|
-
if (this._refreshTimerId !== void 0) clearTimeout(this._refreshTimerId);
|
|
21211
|
-
const refreshInterval = attempt === 0 ? Math.max(expiresAt - Date.now() - CREDENTIAL_REFRESH_BUFFER_MS, 1e3) : Math.min(CREDENTIAL_REFRESH_RETRY_BASE_MS * Math.pow(2, attempt) * (.5 + Math.random() * .5), CREDENTIAL_REFRESH_MAX_DELAY_MS);
|
|
21212
|
-
this._refreshTimerId = setTimeout(async () => {
|
|
21213
|
-
try {
|
|
21214
|
-
if (!credentialProvider.refresh) throw new InvalidCredentialsError("Credential provider does not support refresh");
|
|
21215
|
-
const newCredentials = await credentialProvider.refresh();
|
|
21216
|
-
this._deps.credential = newCredentials;
|
|
21217
|
-
this.persistCredential(newCredentials);
|
|
21218
|
-
logger$1.info("[SignalWire] Credentials refreshed successfully.");
|
|
21219
|
-
if (newCredentials.expiry_at) this.scheduleCredentialRefresh(credentialProvider, newCredentials.expiry_at, 0);
|
|
21220
|
-
} catch (error) {
|
|
21221
|
-
const nextAttempt = attempt + 1;
|
|
21222
|
-
logger$1.error(`[SignalWire] Credential refresh failed (attempt ${nextAttempt}/${CREDENTIAL_REFRESH_MAX_RETRIES}):`, error);
|
|
21223
|
-
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
21224
|
-
if (nextAttempt < CREDENTIAL_REFRESH_MAX_RETRIES) this.scheduleCredentialRefresh(credentialProvider, expiresAt, nextAttempt);
|
|
21225
|
-
else {
|
|
21226
|
-
logger$1.error("[SignalWire] Credential refresh exhausted all retries. Disconnecting.");
|
|
21227
|
-
this._errors$.next(new TokenRefreshError("Credential refresh failed after max retries"));
|
|
21228
|
-
this.disconnect();
|
|
21229
|
-
}
|
|
21230
|
-
}
|
|
21231
|
-
}, refreshInterval);
|
|
21232
|
-
}
|
|
21233
21732
|
/** Persist credential to localStorage when persistSession is enabled. */
|
|
21234
21733
|
persistCredential(credential) {
|
|
21235
21734
|
if (!credential.token) return;
|
|
@@ -21323,6 +21822,7 @@ var SignalWire = class extends Destroyable {
|
|
|
21323
21822
|
logger$1.debug("[SignalWire] Credential expired, refreshing before reconnect");
|
|
21324
21823
|
const newCredentials = await this._credentialProvider.authenticate(fingerprint ? { fingerprint } : void 0);
|
|
21325
21824
|
this._deps.credential = newCredentials;
|
|
21825
|
+
if (newCredentials.expiry_at && this._credentialProvider.refresh) this._refreshCoordinator?.scheduleDeveloperRefresh(this._credentialProvider, newCredentials.expiry_at);
|
|
21326
21826
|
logger$1.debug("[SignalWire] Credential refreshed successfully for reconnect");
|
|
21327
21827
|
} catch (error) {
|
|
21328
21828
|
logger$1.error("[SignalWire] Failed to refresh credentials for reconnect:", error);
|
|
@@ -21334,33 +21834,12 @@ var SignalWire = class extends Destroyable {
|
|
|
21334
21834
|
this._errors$.next(error);
|
|
21335
21835
|
});
|
|
21336
21836
|
await this._clientSession.connect();
|
|
21337
|
-
|
|
21338
|
-
if (this._refreshTimerId) {
|
|
21339
|
-
clearTimeout(this._refreshTimerId);
|
|
21340
|
-
this._refreshTimerId = void 0;
|
|
21341
|
-
logger$1.debug("[SignalWire] Developer refresh disabled — Client Bound SAT activation starting");
|
|
21342
|
-
}
|
|
21343
|
-
this._deviceTokenManager = new DeviceTokenManager(this._dpopManager, this._deps.http, (error) => this._errors$.next(error), () => this._deps.credential);
|
|
21344
|
-
await this._deviceTokenManager.activate(this._deps.user, this._clientSession, (cred) => {
|
|
21345
|
-
this._deps.credential = {
|
|
21346
|
-
...this._deps.credential,
|
|
21347
|
-
...cred
|
|
21348
|
-
};
|
|
21349
|
-
});
|
|
21350
|
-
}
|
|
21837
|
+
await this._refreshCoordinator?.activate(this._deps.user, this._clientSession);
|
|
21351
21838
|
this.subscribeTo(this._clientSession.authenticated$.pipe((0, import_cjs$1.skip)(1), (0, import_cjs$1.filter)(Boolean)), async () => {
|
|
21352
21839
|
try {
|
|
21353
|
-
|
|
21354
|
-
await this._deviceTokenManager.activate(this._deps.user, this._clientSession, (cred) => {
|
|
21355
|
-
this._deps.credential = {
|
|
21356
|
-
...this._deps.credential,
|
|
21357
|
-
...cred
|
|
21358
|
-
};
|
|
21359
|
-
});
|
|
21360
|
-
logger$1.debug("[SignalWire] Client Bound SAT re-activated after reconnect");
|
|
21361
|
-
}
|
|
21840
|
+
await this._refreshCoordinator?.activate(this._deps.user, this._clientSession);
|
|
21362
21841
|
} catch (error) {
|
|
21363
|
-
logger$1.error("[SignalWire]
|
|
21842
|
+
logger$1.error("[SignalWire] Refresh re-arm after reconnect failed (non-fatal):", error);
|
|
21364
21843
|
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
21365
21844
|
}
|
|
21366
21845
|
try {
|
|
@@ -21446,6 +21925,19 @@ var SignalWire = class extends Destroyable {
|
|
|
21446
21925
|
get errors$() {
|
|
21447
21926
|
return this.deferEmission(this._errors$.asObservable());
|
|
21448
21927
|
}
|
|
21928
|
+
/**
|
|
21929
|
+
* Observable stream of non-fatal SDK warnings.
|
|
21930
|
+
*
|
|
21931
|
+
* Subscribe to detect SDK behaviors that affect session liveness or developer-facing
|
|
21932
|
+
* contracts but do not warrant disconnection — e.g., a fallback from Client Bound SAT
|
|
21933
|
+
* refresh to the developer-provided `refresh()` because the SAT lacks `sat:refresh`
|
|
21934
|
+
* scope. Discriminated by `code`.
|
|
21935
|
+
*
|
|
21936
|
+
* Independent from {@link errors$}: existing error consumers are not notified.
|
|
21937
|
+
*/
|
|
21938
|
+
get warnings$() {
|
|
21939
|
+
return this.deferEmission(this._warnings$.asObservable());
|
|
21940
|
+
}
|
|
21449
21941
|
/** Platform WebRTC capabilities detected at construction time. */
|
|
21450
21942
|
get platformCapabilities() {
|
|
21451
21943
|
this._platformCapabilities ??= detectPlatformCapabilities(this._options.webRTCApiProvider);
|
|
@@ -21514,7 +22006,7 @@ var SignalWire = class extends Destroyable {
|
|
|
21514
22006
|
logger$1.warn("[SignalWire] Failed to initialize VisibilityController:", error);
|
|
21515
22007
|
}
|
|
21516
22008
|
try {
|
|
21517
|
-
this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "
|
|
22009
|
+
this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "4.0.0-rc.2" });
|
|
21518
22010
|
} catch (error) {
|
|
21519
22011
|
logger$1.warn("[SignalWire] Failed to initialize DiagnosticsCollector:", error);
|
|
21520
22012
|
}
|
|
@@ -21522,14 +22014,19 @@ var SignalWire = class extends Destroyable {
|
|
|
21522
22014
|
/**
|
|
21523
22015
|
* Disconnects the WebSocket and tears down the current session.
|
|
21524
22016
|
*
|
|
22017
|
+
* Ends the session identified by the protocol and clears its persisted
|
|
22018
|
+
* resume state (`authorization_state` + protocol) and attach records
|
|
22019
|
+
* together — a later {@link connect} with the same credentials starts a
|
|
22020
|
+
* fresh session and cannot reattach to the ended session's calls.
|
|
22021
|
+
* Credentials and device preferences are preserved. To temporarily stop
|
|
22022
|
+
* receiving inbound calls while keeping the session alive, use
|
|
22023
|
+
* `unregister()` instead.
|
|
22024
|
+
*
|
|
21525
22025
|
* The client can be reconnected by calling {@link connect} again,
|
|
21526
22026
|
* which creates a fresh transport and session.
|
|
21527
22027
|
*/
|
|
21528
22028
|
async disconnect() {
|
|
21529
|
-
|
|
21530
|
-
clearTimeout(this._refreshTimerId);
|
|
21531
|
-
this._refreshTimerId = void 0;
|
|
21532
|
-
}
|
|
22029
|
+
this._refreshCoordinator?.suspend();
|
|
21533
22030
|
this._diagnosticsCollector?.record("connection", "disconnected");
|
|
21534
22031
|
await this.teardownTransportAndSession();
|
|
21535
22032
|
this._isConnected$.next(false);
|
|
@@ -21916,15 +22413,20 @@ var SignalWire = class extends Destroyable {
|
|
|
21916
22413
|
prefs.preferredVideoInput = null;
|
|
21917
22414
|
await this._deviceController.clearDeviceState();
|
|
21918
22415
|
}
|
|
21919
|
-
/**
|
|
22416
|
+
/**
|
|
22417
|
+
* Destroys the client, clearing timers and releasing all resources.
|
|
22418
|
+
*
|
|
22419
|
+
* Intentionally destroying the client ends its session: the resume state
|
|
22420
|
+
* (`authorization_state` + protocol) and the attach records are both
|
|
22421
|
+
* cleared. Credentials and device preferences are preserved — use
|
|
22422
|
+
* {@link resetToDefaults} for a full wipe. To temporarily stop receiving
|
|
22423
|
+
* inbound calls while keeping the session alive, use `unregister()`.
|
|
22424
|
+
*/
|
|
21920
22425
|
destroy() {
|
|
21921
|
-
|
|
21922
|
-
|
|
21923
|
-
this._refreshTimerId = void 0;
|
|
21924
|
-
}
|
|
21925
|
-
this._deviceTokenManager?.destroy();
|
|
22426
|
+
this._refreshCoordinator?.destroy();
|
|
22427
|
+
this._refreshCoordinator = void 0;
|
|
21926
22428
|
this._dpopManager?.destroy();
|
|
21927
|
-
|
|
22429
|
+
this._clientSession.teardownSessionState();
|
|
21928
22430
|
this._transport.destroy();
|
|
21929
22431
|
this._clientSession.destroy();
|
|
21930
22432
|
try {
|
|
@@ -22039,7 +22541,7 @@ var StaticCredentialProvider = class {
|
|
|
22039
22541
|
/**
|
|
22040
22542
|
* Library version from package.json, injected at build time.
|
|
22041
22543
|
*/
|
|
22042
|
-
const version = "
|
|
22544
|
+
const version = "4.0.0-rc.2";
|
|
22043
22545
|
/**
|
|
22044
22546
|
* Flag indicating the library has been loaded and is ready to use.
|
|
22045
22547
|
* For UMD builds: `window.SignalWire.ready`
|
|
@@ -22061,7 +22563,7 @@ const ready = true;
|
|
|
22061
22563
|
*/
|
|
22062
22564
|
const emitReadyEvent = () => {
|
|
22063
22565
|
if (typeof window !== "undefined") {
|
|
22064
|
-
const event = new CustomEvent("signalwire:js:ready", { detail: { version: "
|
|
22566
|
+
const event = new CustomEvent("signalwire:js:ready", { detail: { version: "4.0.0-rc.2" } });
|
|
22065
22567
|
window.dispatchEvent(event);
|
|
22066
22568
|
}
|
|
22067
22569
|
};
|
|
@@ -22085,5 +22587,5 @@ emitReadyEvent();
|
|
|
22085
22587
|
if (typeof process === "undefined") globalThis.process = { env: { NODE_ENV: "production" } };
|
|
22086
22588
|
|
|
22087
22589
|
//#endregion
|
|
22088
|
-
export { Address, CallCreateError, ClientPreferences, CollectionFetchError, DPoPInitError, DeviceTokenError, EmbedTokenCredentialProvider, InvalidCredentialsError, MediaTrackError, MessageParseError, OverconstrainedFallbackError, Participant, PreflightError, RecoveryError, SelfCapabilities, SelfParticipant, SignalWire, StaticCredentialProvider, TokenRefreshError, UnexpectedError, User, VertoPongError, WebRTCCall, embeddableCall, getLogger, isSelfParticipant, ready, setDebugOptions, setLogLevel, setLogger, version };
|
|
22590
|
+
export { Address, CallCreateError, CallNotReadyError, ClientPreferences, CollectionFetchError, DPoPInitError, DeviceTokenError, EmbedTokenCredentialProvider, InvalidCredentialsError, MediaAccessError, MediaTrackError, MessageParseError, OverconstrainedFallbackError, Participant, ParticipantNotReadyError, PreflightError, RecoveryError, SelfCapabilities, SelfParticipant, SignalWire, StaticCredentialProvider, TokenRefreshError, UnexpectedError, User, VertoPongError, WebRTCCall, embeddableCall, getLogger, isSelfParticipant, ready, setDebugOptions, setLogLevel, setLogger, version };
|
|
22089
22591
|
//# sourceMappingURL=browser.mjs.map
|