@signalwire/js 4.0.0-rc.0 → 4.0.0-rc.2
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 +418 -175
- package/dist/browser.mjs.map +1 -1
- package/dist/browser.umd.js +418 -174
- package/dist/browser.umd.js.map +1 -1
- package/dist/index.cjs +331 -251
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +175 -19
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +175 -19
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +256 -177
- package/dist/index.mjs.map +1 -1
- package/dist/operators/index.cjs +1 -1
- package/dist/operators/index.mjs +1 -1
- package/dist/{operators-D6a2J1KA.cjs → operators-B8ipd4Xl.cjs} +561 -1
- package/dist/operators-B8ipd4Xl.cjs.map +1 -0
- package/dist/{operators-CX_lCCJm.mjs → operators-BlUtq-t0.mjs} +166 -2
- package/dist/operators-BlUtq-t0.mjs.map +1 -0
- package/package.json +1 -1
- package/dist/operators-CX_lCCJm.mjs.map +0 -1
- package/dist/operators-D6a2J1KA.cjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { A as
|
|
1
|
+
import { $ as DEFAULT_DEGRADATION_BITRATE_THRESHOLD_KBPS, $t as VAD_HOLD_MS, A as SerializationError, At as DEFAULT_STATS_POLLING_INTERVAL_MS, B as VertoInviteHandlerError, Bt as DEVICE_TOKEN_ENDPOINT, C as MessageParseError, Ct as DEFAULT_REINVITE_MAX_ATTEMPTS, D as RecoveryError, Dt as DEFAULT_STATS_JITTER_SPIKE_MULTIPLIER, E as RPCTimeoutError, Et as DEFAULT_STATS_HISTORY_SIZE, F as TransportConnectionError, Ft as DEVICE_REFRESH_ENDPOINT, G as CREDENTIAL_ACTIVATE_TIMEOUT_MS, Gt as INVITE_VERSION, H as WebSocketConnectionError, Ht as DEVICE_TOKEN_REFRESH_MAX_RETRIES, I as UnexpectedError, It as DEVICE_STORAGE_KEY_AUDIO_INPUT, J as CREDENTIAL_REFRESH_MAX_RETRIES, Jt as PREFERENCES_STORAGE_KEY, K as CREDENTIAL_REFRESH_BUFFER_MS, Kt as PEER_CONNECTION_RECOVERY_POLL_MS, L as UnimplementedError, Lt as DEVICE_STORAGE_KEY_AUDIO_OUTPUT, M as StorageReadError, Mt as DEFAULT_STEREO_AUDIO, N as StorageWriteError, Nt as DEFAULT_STEREO_MAX_AVERAGE_BITRATE, O as RequestError, Ot as DEFAULT_STATS_NO_PACKET_THRESHOLD_MS, P as TokenRefreshError, Pt as DEFAULT_SYNC_DEVICES_TO_ACTIVE_CALLS, Q as DEFAULT_CONNECTION_TIMEOUT_MS, Qt as SAT_REFRESH_SCOPE, R as ValidationError, Rt as DEVICE_STORAGE_KEY_VIDEO_INPUT, S as MediaTrackError, St as DEFAULT_REINVITE_DEBOUNCE_TIME_MS, T as PreflightError, Tt as DEFAULT_STATS_BASELINE_SAMPLES, U as WebSocketTimeoutError, Ut as DEVICE_TOKEN_REFRESH_RETRY_BASE_MS, V as VertoPongError, Vt as DEVICE_TOKEN_REFRESH_BUFFER_MS, W as AUDIO_LEVEL_POLL_INTERVAL_MS, Wt as ICE_GATHERING_COMPLETE_TIMEOUT_MS, X as DEFAULT_AUTO_MUTE_VIDEO_ON_HIDDEN, Xt as RPC_ERROR_INVALID_PARAMS, Y as CREDENTIAL_REFRESH_RETRY_BASE_MS, Yt as RPC_ERROR_AUTHENTICATION_FAILED, Z as DEFAULT_CHECK_CONNECTION_ON_VISIBLE, Zt as RPC_ERROR_REQUESTER_VALIDATION_FAILED, _ as DeviceTokenError, _t as DEFAULT_RECONNECT_DELAY_MAX_MS, a as filterNull, at as DEFAULT_ENABLE_RELAY_FALLBACK, b as JSONRPCError, bt as DEFAULT_RECOVERY_DEBOUNCE_TIME_MS, c as setLogLevel, ct as DEFAULT_ICE_DISCONNECTED_GRACE_PERIOD_MS, d as CallCreateError, dt as DEFAULT_KEYFRAME_BURST_WINDOW_MS$1, en as VAD_THRESHOLD, et as DEFAULT_DEGRADATION_RECOVERY_THRESHOLD_KBPS, f as CollectionFetchError, ft as DEFAULT_KEYFRAME_COOLDOWN_MS$1, g as DeserializationError, gt as DEFAULT_RECONNECT_CALLS_TIMEOUT_MS, h as DependencyError, ht as DEFAULT_PERSIST_DEVICE_SELECTION, i as getValueFrom, it as DEFAULT_ENABLE_NETWORK_CHANGE_DETECTION, j as StorageNotAvailableError, jt as DEFAULT_STATS_RTT_SPIKE_MULTIPLIER, k as RequestTimeoutError, kt as DEFAULT_STATS_PACKET_LOSS_THRESHOLD, l as setLogger, lt as DEFAULT_ICE_GATHERING_TIMEOUT_MS, m as DPoPInitError, mt as DEFAULT_MAX_RECOVERY_ATTEMPTS, n as filterAs, nt as DEFAULT_DEVICE_POLLING_INTERVAL_MS, o as getLogger, ot as DEFAULT_ENABLE_SERVER_HANGUP_INTERCEPTION, p as ConversationError, pt as DEFAULT_KEYFRAME_MAX_BURST$1, q as CREDENTIAL_REFRESH_MAX_DELAY_MS, qt as PEER_CONNECTION_RECOVERY_WAIT_MS, rt as DEFAULT_ENABLE_AUTO_DEGRADATION, s as setDebugOptions, st as DEFAULT_ICE_CANDIDATE_TIMEOUT_MS, t as throwOnRPCError, tt as DEFAULT_DEVICE_DEBOUNCE_TIME_MS, u as AuthStateHandlerError, ut as DEFAULT_ICE_RESTART_TIMEOUT_MS$1, v as InvalidCredentialsError, vt as DEFAULT_RECONNECT_DELAY_MIN_MS, w as OverconstrainedFallbackError, wt as DEFAULT_REINVITE_TIMEOUT_MS, x as MediaAccessError, xt as DEFAULT_REFRESH_DEVICES_ON_VISIBLE, y as InvalidParams, yt as DEFAULT_RECOVERY_COOLDOWN_MS, z as VertoAttachHandlerError, zt as DEVICE_TOKEN_DEFAULT_EXPIRE_IN } from "./operators-BlUtq-t0.mjs";
|
|
2
2
|
import { jwtDecode } from "jwt-decode";
|
|
3
|
-
import { BehaviorSubject, EMPTY, NEVER, Observable, ReplaySubject, Subject, TimeoutError, animationFrameScheduler, asapScheduler, auditTime, catchError, combineLatest, debounceTime, defer, distinctUntilChanged, exhaustMap, filter, first, firstValueFrom, from, interval, lastValueFrom, map, merge, mergeMap, observeOn, of, pipe, race, scan, share, shareReplay, skip, skipWhile, startWith, switchMap, take, takeUntil, tap, throwError, timeout, timer, toArray, withLatestFrom } from "rxjs";
|
|
3
|
+
import { BehaviorSubject, EMPTY, EmptyError, NEVER, Observable, ReplaySubject, Subject, TimeoutError, animationFrameScheduler, asapScheduler, auditTime, catchError, combineLatest, debounceTime, defer, distinctUntilChanged, exhaustMap, filter, first, firstValueFrom, from, interval, lastValueFrom, map, merge, mergeMap, observeOn, of, pipe, race, scan, share, shareReplay, skip, skipWhile, startWith, switchMap, take, takeUntil, tap, throwError, timeout, timer, toArray, withLatestFrom } from "rxjs";
|
|
4
4
|
import { v4 } from "uuid";
|
|
5
5
|
import { distinctUntilChanged as distinctUntilChanged$1, map as map$1 } from "rxjs/operators";
|
|
6
6
|
|
|
@@ -404,137 +404,6 @@ var DeviceHistoryManager = class {
|
|
|
404
404
|
}
|
|
405
405
|
};
|
|
406
406
|
|
|
407
|
-
//#endregion
|
|
408
|
-
//#region src/core/constants.ts
|
|
409
|
-
const INVITE_VERSION = 1e3;
|
|
410
|
-
const DEFAULT_ICE_CANDIDATE_TIMEOUT_MS = 600;
|
|
411
|
-
const DEFAULT_ICE_GATHERING_TIMEOUT_MS = 6e3;
|
|
412
|
-
const DEFAULT_RECONNECT_CALLS_TIMEOUT_MS = 300 * 1e3;
|
|
413
|
-
const DEFAULT_CONNECTION_TIMEOUT_MS = 1e4;
|
|
414
|
-
const DEFAULT_RECONNECT_DELAY_MIN_MS = 100;
|
|
415
|
-
const DEFAULT_RECONNECT_DELAY_MAX_MS = 3e3;
|
|
416
|
-
const DEFAULT_DEVICE_DEBOUNCE_TIME_MS = 1500;
|
|
417
|
-
const DEFAULT_DEVICE_POLLING_INTERVAL_MS = 0;
|
|
418
|
-
const PREFERENCES_STORAGE_KEY = "sw:preferences";
|
|
419
|
-
/** Scope value that enables automatic token refresh. */
|
|
420
|
-
const SAT_REFRESH_SCOPE = "sat:refresh";
|
|
421
|
-
/** API endpoints for device token operations. */
|
|
422
|
-
const DEVICE_TOKEN_ENDPOINT = "/api/fabric/subscriber/devices/token";
|
|
423
|
-
const DEVICE_REFRESH_ENDPOINT = "/api/fabric/subscriber/devices/refresh";
|
|
424
|
-
/** Default device token TTL in seconds (15 minutes). */
|
|
425
|
-
const DEVICE_TOKEN_DEFAULT_EXPIRE_IN = 900;
|
|
426
|
-
/** Buffer time in milliseconds before expiry to trigger refresh. */
|
|
427
|
-
const DEVICE_TOKEN_REFRESH_BUFFER_MS = 3e4;
|
|
428
|
-
/** Maximum retry attempts for device token refresh on transient failure. */
|
|
429
|
-
const DEVICE_TOKEN_REFRESH_MAX_RETRIES = 3;
|
|
430
|
-
/** Base delay in milliseconds for exponential backoff on refresh retry. */
|
|
431
|
-
const DEVICE_TOKEN_REFRESH_RETRY_BASE_MS = 1e3;
|
|
432
|
-
/** Maximum retry attempts for developer credential refresh on transient failure. */
|
|
433
|
-
const CREDENTIAL_REFRESH_MAX_RETRIES = 5;
|
|
434
|
-
/** Base delay in milliseconds for exponential backoff on credential refresh retry. */
|
|
435
|
-
const CREDENTIAL_REFRESH_RETRY_BASE_MS = 1e3;
|
|
436
|
-
/** Maximum delay in milliseconds for credential refresh backoff. */
|
|
437
|
-
const CREDENTIAL_REFRESH_MAX_DELAY_MS = 3e4;
|
|
438
|
-
/** Buffer in milliseconds before token expiry to trigger refresh. */
|
|
439
|
-
const CREDENTIAL_REFRESH_BUFFER_MS = 5e3;
|
|
440
|
-
/**
|
|
441
|
-
* Maximum time the coordinator will wait for `DeviceTokenManager.activate()`
|
|
442
|
-
* to resolve before treating the activation as failed and falling back to
|
|
443
|
-
* the developer-provided refresh path. Prevents a wedged HTTP layer from
|
|
444
|
-
* leaving the session with no active refresh mechanism.
|
|
445
|
-
*/
|
|
446
|
-
const CREDENTIAL_ACTIVATE_TIMEOUT_MS = 3e4;
|
|
447
|
-
/** JSON-RPC error code for requester validation failure (corrupted auth state). */
|
|
448
|
-
const RPC_ERROR_REQUESTER_VALIDATION_FAILED = -32003;
|
|
449
|
-
/** JSON-RPC error code for invalid params (e.g., missing authentication block). */
|
|
450
|
-
const RPC_ERROR_INVALID_PARAMS = -32602;
|
|
451
|
-
/** JSON-RPC error code for authentication failure (invalid token, missing DPoP, etc.). */
|
|
452
|
-
const RPC_ERROR_AUTHENTICATION_FAILED = -32002;
|
|
453
|
-
/** Default polling interval for RTCPeerConnection.getStats() in milliseconds. */
|
|
454
|
-
const DEFAULT_STATS_POLLING_INTERVAL_MS = 1e3;
|
|
455
|
-
/** Number of initial samples used to build a baseline for spike detection. */
|
|
456
|
-
const DEFAULT_STATS_BASELINE_SAMPLES = 10;
|
|
457
|
-
/** Duration in ms with no inbound audio packets before emitting a critical issue. */
|
|
458
|
-
const DEFAULT_STATS_NO_PACKET_THRESHOLD_MS = 2e3;
|
|
459
|
-
/** Multiplier applied to baseline RTT to detect a warning-level RTT spike. */
|
|
460
|
-
const DEFAULT_STATS_RTT_SPIKE_MULTIPLIER = 3;
|
|
461
|
-
/** Packet loss fraction (0-1) above which a warning is emitted. */
|
|
462
|
-
const DEFAULT_STATS_PACKET_LOSS_THRESHOLD = .05;
|
|
463
|
-
/** Multiplier applied to baseline jitter to detect a jitter spike. */
|
|
464
|
-
const DEFAULT_STATS_JITTER_SPIKE_MULTIPLIER = 4;
|
|
465
|
-
/** Number of seconds of metrics history to retain. */
|
|
466
|
-
const DEFAULT_STATS_HISTORY_SIZE = 30;
|
|
467
|
-
/** Maximum keyframe requests allowed within a single burst window. */
|
|
468
|
-
const DEFAULT_KEYFRAME_MAX_BURST$1 = 3;
|
|
469
|
-
/** Duration of the keyframe burst window in milliseconds. */
|
|
470
|
-
const DEFAULT_KEYFRAME_BURST_WINDOW_MS$1 = 3e3;
|
|
471
|
-
/** Cooldown period in ms after burst limit is reached before allowing more keyframes. */
|
|
472
|
-
const DEFAULT_KEYFRAME_COOLDOWN_MS$1 = 1e4;
|
|
473
|
-
/** Minimum time between re-INVITE attempts in milliseconds. */
|
|
474
|
-
const DEFAULT_REINVITE_DEBOUNCE_TIME_MS = 1e4;
|
|
475
|
-
/** Maximum number of re-INVITE attempts per call. */
|
|
476
|
-
const DEFAULT_REINVITE_MAX_ATTEMPTS = 3;
|
|
477
|
-
/** Timeout for a single re-INVITE attempt in milliseconds. */
|
|
478
|
-
const DEFAULT_REINVITE_TIMEOUT_MS = 5e3;
|
|
479
|
-
/** Debounce window in ms to collapse multiple detection signals into one trigger. */
|
|
480
|
-
const DEFAULT_RECOVERY_DEBOUNCE_TIME_MS = 2e3;
|
|
481
|
-
/** Cooldown period in ms between recovery attempts. */
|
|
482
|
-
const DEFAULT_RECOVERY_COOLDOWN_MS = 1e4;
|
|
483
|
-
/** Grace period in ms before treating ICE 'disconnected' as a failure. */
|
|
484
|
-
const DEFAULT_ICE_DISCONNECTED_GRACE_PERIOD_MS = 3e3;
|
|
485
|
-
/** Timeout for a single ICE restart attempt in milliseconds. */
|
|
486
|
-
const DEFAULT_ICE_RESTART_TIMEOUT_MS$1 = 5e3;
|
|
487
|
-
/** Maximum recovery attempts before emitting 'max_attempts_reached'. */
|
|
488
|
-
const DEFAULT_MAX_RECOVERY_ATTEMPTS = 3;
|
|
489
|
-
/** Upper bound in ms for waiting on iceGatheringState === 'complete' after an ICE restart. */
|
|
490
|
-
const ICE_GATHERING_COMPLETE_TIMEOUT_MS = 1e4;
|
|
491
|
-
/** Upper bound in ms for waiting on RTCPeerConnection.connectionState === 'connected' after a recovery ICE restart. */
|
|
492
|
-
const PEER_CONNECTION_RECOVERY_WAIT_MS = 5e3;
|
|
493
|
-
/** Polling interval in ms while waiting for RTCPeerConnection.connectionState to transition. */
|
|
494
|
-
const PEER_CONNECTION_RECOVERY_POLL_MS = 100;
|
|
495
|
-
/** Polling interval for LocalAudioPipeline.level$ (ms). ~30fps is smooth for meters. */
|
|
496
|
-
const AUDIO_LEVEL_POLL_INTERVAL_MS = 33;
|
|
497
|
-
/** RMS level threshold (0..1) above which the local participant is considered speaking. */
|
|
498
|
-
const VAD_THRESHOLD = .03;
|
|
499
|
-
/** Hold window in ms below the threshold before speaking$ flips back to false. */
|
|
500
|
-
const VAD_HOLD_MS = 250;
|
|
501
|
-
/** Whether to persist device selections to storage by default. */
|
|
502
|
-
const DEFAULT_PERSIST_DEVICE_SELECTION = true;
|
|
503
|
-
/** Whether to auto-apply device changes to active calls by default. */
|
|
504
|
-
const DEFAULT_SYNC_DEVICES_TO_ACTIVE_CALLS = true;
|
|
505
|
-
/** Storage keys for persisted device selections. */
|
|
506
|
-
const DEVICE_STORAGE_KEY_AUDIO_INPUT = "sw:device:audioinput";
|
|
507
|
-
const DEVICE_STORAGE_KEY_AUDIO_OUTPUT = "sw:device:audiooutput";
|
|
508
|
-
const DEVICE_STORAGE_KEY_VIDEO_INPUT = "sw:device:videoinput";
|
|
509
|
-
/** Whether to auto-mute video when the tab becomes hidden. */
|
|
510
|
-
const DEFAULT_AUTO_MUTE_VIDEO_ON_HIDDEN = false;
|
|
511
|
-
/** Whether to re-enumerate devices when the page becomes visible. */
|
|
512
|
-
const DEFAULT_REFRESH_DEVICES_ON_VISIBLE = true;
|
|
513
|
-
/** Whether to check peer connection health when the page becomes visible. */
|
|
514
|
-
const DEFAULT_CHECK_CONNECTION_ON_VISIBLE = true;
|
|
515
|
-
/** Whether automatic video degradation on low bandwidth is enabled. */
|
|
516
|
-
const DEFAULT_ENABLE_AUTO_DEGRADATION = true;
|
|
517
|
-
/** Bitrate in kbps below which video is automatically disabled. */
|
|
518
|
-
const DEFAULT_DEGRADATION_BITRATE_THRESHOLD_KBPS = 150;
|
|
519
|
-
/** Bitrate in kbps above which video is automatically re-enabled (hysteresis). */
|
|
520
|
-
const DEFAULT_DEGRADATION_RECOVERY_THRESHOLD_KBPS = 300;
|
|
521
|
-
/** Whether relay-only escalation is enabled as a last-resort recovery tier. */
|
|
522
|
-
const DEFAULT_ENABLE_RELAY_FALLBACK = true;
|
|
523
|
-
/** Whether to listen for browser online/offline/connection events. */
|
|
524
|
-
const DEFAULT_ENABLE_NETWORK_CHANGE_DETECTION = true;
|
|
525
|
-
/** Whether to intercept server-sent media-timeout hangups and attempt recovery. */
|
|
526
|
-
const DEFAULT_ENABLE_SERVER_HANGUP_INTERCEPTION = true;
|
|
527
|
-
/** Default video track constraints applied when video is enabled without explicit constraints. */
|
|
528
|
-
const DEFAULT_VIDEO_CONSTRAINTS = {
|
|
529
|
-
width: { ideal: 1280 },
|
|
530
|
-
height: { ideal: 720 },
|
|
531
|
-
aspectRatio: 16 / 9
|
|
532
|
-
};
|
|
533
|
-
/** Whether stereo Opus is enabled by default. */
|
|
534
|
-
const DEFAULT_STEREO_AUDIO = false;
|
|
535
|
-
/** Max average bitrate for stereo Opus in bits per second. */
|
|
536
|
-
const DEFAULT_STEREO_MAX_AVERAGE_BITRATE = 51e4;
|
|
537
|
-
|
|
538
407
|
//#endregion
|
|
539
408
|
//#region src/utils/time.ts
|
|
540
409
|
function fromSecToMs(seconds) {
|
|
@@ -3005,7 +2874,9 @@ const DEFAULT_MEMBER_CAPABILITIES = {
|
|
|
3005
2874
|
position: false,
|
|
3006
2875
|
meta: false,
|
|
3007
2876
|
remove: false,
|
|
3008
|
-
audioFlags: false
|
|
2877
|
+
audioFlags: false,
|
|
2878
|
+
denoise: false,
|
|
2879
|
+
lowbitrate: false
|
|
3009
2880
|
};
|
|
3010
2881
|
/**
|
|
3011
2882
|
* Default call capabilities with no permissions
|
|
@@ -3078,7 +2949,9 @@ function computeMemberCapabilities(flags, memberType) {
|
|
|
3078
2949
|
position: hasBooleanCapability(flags, memberType, null, "position"),
|
|
3079
2950
|
meta: hasBooleanCapability(flags, memberType, null, "meta"),
|
|
3080
2951
|
remove: hasBooleanCapability(flags, memberType, null, "remove"),
|
|
3081
|
-
audioFlags: hasBooleanCapability(flags, memberType, null, "audioflags")
|
|
2952
|
+
audioFlags: hasBooleanCapability(flags, memberType, null, "audioflags"),
|
|
2953
|
+
denoise: hasBooleanCapability(flags, memberType, null, "denoise"),
|
|
2954
|
+
lowbitrate: hasBooleanCapability(flags, memberType, null, "lowbitrate")
|
|
3082
2955
|
};
|
|
3083
2956
|
}
|
|
3084
2957
|
/**
|
|
@@ -3459,6 +3332,10 @@ var Participant = class extends Destroyable {
|
|
|
3459
3332
|
get nodeId() {
|
|
3460
3333
|
return this._state$.value.node_id;
|
|
3461
3334
|
}
|
|
3335
|
+
/** Call ID for this participant's leg, or `undefined` if not available. */
|
|
3336
|
+
get callId() {
|
|
3337
|
+
return this._state$.value.call_id;
|
|
3338
|
+
}
|
|
3462
3339
|
/** @internal */
|
|
3463
3340
|
get value() {
|
|
3464
3341
|
return this._state$.value;
|
|
@@ -3520,8 +3397,9 @@ var Participant = class extends Destroyable {
|
|
|
3520
3397
|
noise_suppression: !this.noiseSuppression
|
|
3521
3398
|
});
|
|
3522
3399
|
}
|
|
3400
|
+
/** Toggles low-bitrate mode for this participant's media. */
|
|
3523
3401
|
async toggleLowbitrate() {
|
|
3524
|
-
|
|
3402
|
+
await this.executeMethod(this.id, "call.lowbitrate.set", { lowbitrate: !this.lowbitrate });
|
|
3525
3403
|
}
|
|
3526
3404
|
/**
|
|
3527
3405
|
* Adjusts the **conference-only** microphone energy gate / sensitivity level
|
|
@@ -3568,10 +3446,27 @@ var Participant = class extends Destroyable {
|
|
|
3568
3446
|
}
|
|
3569
3447
|
/**
|
|
3570
3448
|
* Sets the participant's position in the video layout.
|
|
3449
|
+
*
|
|
3450
|
+
* Requires the `member.position` capability. The gateway keys positions by the
|
|
3451
|
+
* **target member's own** `call_id`/`node_id` (see issue #19400 and the legacy
|
|
3452
|
+
* `setPositions` implementation), so this sends the participant's own call
|
|
3453
|
+
* context — matching {@link Participant.remove}. A resolved promise does not
|
|
3454
|
+
* guarantee a visible change: the backend silently returns `200` (no-op) for
|
|
3455
|
+
* non-conference targets.
|
|
3456
|
+
*
|
|
3571
3457
|
* @param value - The {@link VideoPosition} to assign (e.g. `'auto'`, `'reserved-0'`).
|
|
3572
3458
|
*/
|
|
3573
3459
|
async setPosition(value) {
|
|
3574
|
-
|
|
3460
|
+
const state = this._state$.value;
|
|
3461
|
+
const target = {
|
|
3462
|
+
member_id: this.id,
|
|
3463
|
+
call_id: state.call_id ?? "",
|
|
3464
|
+
node_id: state.node_id ?? ""
|
|
3465
|
+
};
|
|
3466
|
+
await this.executeMethod(target, "call.member.position.set", { targets: [{
|
|
3467
|
+
target,
|
|
3468
|
+
position: value
|
|
3469
|
+
}] });
|
|
3575
3470
|
}
|
|
3576
3471
|
/** Removes this participant from the call. */
|
|
3577
3472
|
async remove() {
|
|
@@ -3661,12 +3556,21 @@ var SelfParticipant = class extends Participant {
|
|
|
3661
3556
|
noise_suppression: true
|
|
3662
3557
|
});
|
|
3663
3558
|
}
|
|
3664
|
-
/**
|
|
3559
|
+
/**
|
|
3560
|
+
* Starts sharing the local screen.
|
|
3561
|
+
*
|
|
3562
|
+
* The call is unaffected when acquisition fails.
|
|
3563
|
+
*
|
|
3564
|
+
* @throws The raw `getDisplayMedia` error. A dismissed picker or a
|
|
3565
|
+
* permission denial rejects with a `NotAllowedError` `DOMException` —
|
|
3566
|
+
* inspect `error.name` to tell benign cancels apart from real failures.
|
|
3567
|
+
*/
|
|
3665
3568
|
async startScreenShare() {
|
|
3666
3569
|
try {
|
|
3667
3570
|
await this.vertoManager.addScreenMedia();
|
|
3668
3571
|
} catch (error) {
|
|
3669
3572
|
logger$23.error("[Participant.startScreenShare] Screen share error:", error);
|
|
3573
|
+
throw error;
|
|
3670
3574
|
}
|
|
3671
3575
|
}
|
|
3672
3576
|
/** Observable of the current screen share status. */
|
|
@@ -3681,12 +3585,20 @@ var SelfParticipant = class extends Participant {
|
|
|
3681
3585
|
async stopScreenShare() {
|
|
3682
3586
|
return this.vertoManager.removeScreenMedia();
|
|
3683
3587
|
}
|
|
3684
|
-
/**
|
|
3588
|
+
/**
|
|
3589
|
+
* Adds an additional media input device to the call.
|
|
3590
|
+
*
|
|
3591
|
+
* The call is unaffected when acquisition fails.
|
|
3592
|
+
*
|
|
3593
|
+
* @throws The raw `getUserMedia` error (e.g. `NotAllowedError` on
|
|
3594
|
+
* permission denial) — inspect `error.name` to decide how to react.
|
|
3595
|
+
*/
|
|
3685
3596
|
async addAdditionalDevice(options) {
|
|
3686
3597
|
try {
|
|
3687
3598
|
await this.vertoManager.addInputDevice(options);
|
|
3688
3599
|
} catch (error) {
|
|
3689
|
-
logger$23.error("[Participant.
|
|
3600
|
+
logger$23.error("[Participant.addAdditionalDevice] Additional device error:", error);
|
|
3601
|
+
throw error;
|
|
3690
3602
|
}
|
|
3691
3603
|
}
|
|
3692
3604
|
/** Removes an additional media input device by ID. */
|
|
@@ -5180,7 +5092,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5180
5092
|
logger$17.debug(`[RTCPeerConnectionController] ${kind} input device selected:`, deviceInfo?.label);
|
|
5181
5093
|
} catch (error) {
|
|
5182
5094
|
logger$17.error(`[RTCPeerConnectionController] Failed to select ${kind} input device:`, error);
|
|
5183
|
-
this._errors$.next(
|
|
5095
|
+
this._errors$.next(new MediaTrackError("updateSelectedInputDevice", kind, error));
|
|
5184
5096
|
throw error;
|
|
5185
5097
|
}
|
|
5186
5098
|
};
|
|
@@ -5402,7 +5314,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5402
5314
|
case "main":
|
|
5403
5315
|
default: return {
|
|
5404
5316
|
...options,
|
|
5405
|
-
offerToReceiveAudio: true,
|
|
5317
|
+
offerToReceiveAudio: this.options.receiveAudio ?? true,
|
|
5406
5318
|
offerToReceiveVideo: this.options.receiveVideo ?? Boolean(this.inputVideoDeviceConstraints)
|
|
5407
5319
|
};
|
|
5408
5320
|
}
|
|
@@ -5556,13 +5468,14 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5556
5468
|
*/
|
|
5557
5469
|
async acceptInbound(mediaOverrides) {
|
|
5558
5470
|
if (mediaOverrides) {
|
|
5559
|
-
const { audio, video, receiveAudio, receiveVideo } = mediaOverrides;
|
|
5471
|
+
const { audio, video, receiveAudio, receiveVideo, fallbackToReceiveOnly } = mediaOverrides;
|
|
5560
5472
|
this.options = {
|
|
5561
5473
|
...this.options,
|
|
5562
5474
|
...audio !== void 0 ? { audio } : {},
|
|
5563
5475
|
...video !== void 0 ? { video } : {},
|
|
5564
5476
|
...receiveAudio !== void 0 ? { receiveAudio } : {},
|
|
5565
|
-
...receiveVideo !== void 0 ? { receiveVideo } : {}
|
|
5477
|
+
...receiveVideo !== void 0 ? { receiveVideo } : {},
|
|
5478
|
+
...fallbackToReceiveOnly !== void 0 ? { fallbackToReceiveOnly } : {}
|
|
5566
5479
|
};
|
|
5567
5480
|
this.transceiverController?.updateOptions({
|
|
5568
5481
|
receiveAudio: this.receiveAudio,
|
|
@@ -5719,7 +5632,19 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5719
5632
|
}
|
|
5720
5633
|
async setupLocalTracks() {
|
|
5721
5634
|
logger$17.debug("[RTCPeerConnectionController] Setting up local tracks/transceivers.");
|
|
5722
|
-
|
|
5635
|
+
if (this.hasNoLocalMediaToSend()) {
|
|
5636
|
+
if (!this.receiveAudio && !this.receiveVideo) throw new InvalidParams("Call requests no media: enable audio/video or receiveAudio/receiveVideo");
|
|
5637
|
+
logger$17.debug("[RTCPeerConnectionController] No local media requested; negotiating receive-only.");
|
|
5638
|
+
this.setupReceiveOnlyTransceivers();
|
|
5639
|
+
return;
|
|
5640
|
+
}
|
|
5641
|
+
let localStream;
|
|
5642
|
+
try {
|
|
5643
|
+
localStream = this.localStream ?? await this.localStreamController.buildLocalStream();
|
|
5644
|
+
} catch (error) {
|
|
5645
|
+
this.handleLocalMediaFailure(error);
|
|
5646
|
+
return;
|
|
5647
|
+
}
|
|
5723
5648
|
if (this.transceiverController?.useAddStream ?? false) {
|
|
5724
5649
|
logger$17.warn("[RTCPeerConnectionController] Using deprecated addStream API to add local stream.");
|
|
5725
5650
|
this.peerConnection?.addStream(localStream);
|
|
@@ -5746,6 +5671,48 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5746
5671
|
}
|
|
5747
5672
|
}
|
|
5748
5673
|
}
|
|
5674
|
+
/** True for a main connection with no local media to send. */
|
|
5675
|
+
hasNoLocalMediaToSend() {
|
|
5676
|
+
const hasInputStreams = Boolean(this.options.inputAudioStream ?? this.options.inputVideoStream);
|
|
5677
|
+
return this.propose === "main" && !this.localStream && !hasInputStreams && !this.inputAudioDeviceConstraints && !this.inputVideoDeviceConstraints;
|
|
5678
|
+
}
|
|
5679
|
+
/** The media kinds this connection wants to send: 'audiovideo' | 'video' | 'audio'. */
|
|
5680
|
+
get requestedMediaKinds() {
|
|
5681
|
+
const wantsAudio = Boolean(this.inputAudioDeviceConstraints);
|
|
5682
|
+
const wantsVideo = Boolean(this.inputVideoDeviceConstraints);
|
|
5683
|
+
if (wantsAudio && wantsVideo) return "audiovideo";
|
|
5684
|
+
return wantsVideo ? "video" : "audio";
|
|
5685
|
+
}
|
|
5686
|
+
/**
|
|
5687
|
+
* Handle a local media acquisition failure with a typed, semantically
|
|
5688
|
+
* accurate MediaAccessError created at the acquisition site:
|
|
5689
|
+
* - Auxiliary connections (screenshare / additional-device) throw a
|
|
5690
|
+
* non-fatal error — VertoManager surfaces it and the call is unaffected.
|
|
5691
|
+
* - The main connection degrades to receive-only when allowed (default),
|
|
5692
|
+
* otherwise fails with a fatal error.
|
|
5693
|
+
*/
|
|
5694
|
+
handleLocalMediaFailure(error) {
|
|
5695
|
+
if (this.propose === "screenshare") throw new MediaAccessError("startScreenShare", "screen", error, false);
|
|
5696
|
+
if (this.propose === "additional-device") throw new MediaAccessError("addInputDevice", this.requestedMediaKinds, error, false);
|
|
5697
|
+
const canReceive = this.receiveAudio || this.receiveVideo;
|
|
5698
|
+
if (!((this.options.fallbackToReceiveOnly ?? true) && canReceive)) throw new MediaAccessError("acquireLocalMedia", this.requestedMediaKinds, error, true);
|
|
5699
|
+
logger$17.warn("[RTCPeerConnectionController] Local media unavailable; continuing receive-only:", error);
|
|
5700
|
+
this._errors$.next(new MediaAccessError("acquireLocalMedia", this.requestedMediaKinds, error, false));
|
|
5701
|
+
this.setupReceiveOnlyTransceivers();
|
|
5702
|
+
}
|
|
5703
|
+
/**
|
|
5704
|
+
* Negotiate receive-only m-lines when there are no local tracks to send.
|
|
5705
|
+
* Only offer-type connections add transceivers — answer-type connections
|
|
5706
|
+
* reuse the transceivers created from the remote offer.
|
|
5707
|
+
*/
|
|
5708
|
+
setupReceiveOnlyTransceivers() {
|
|
5709
|
+
if (this.type !== "offer") return;
|
|
5710
|
+
if (this.transceiverController?.useAddTransceivers ?? false) {
|
|
5711
|
+
this.peerConnection?.addTransceiver("audio", { direction: this.receiveAudio ? "recvonly" : "inactive" });
|
|
5712
|
+
this.peerConnection?.addTransceiver("video", { direction: this.receiveVideo ? "recvonly" : "inactive" });
|
|
5713
|
+
}
|
|
5714
|
+
if (!this.isNegotiating) this.negotiationNeeded$.next();
|
|
5715
|
+
}
|
|
5749
5716
|
async getUserMedia(constraints) {
|
|
5750
5717
|
return (this.options.webRTCApiProvider?.mediaDevices ?? navigator.mediaDevices).getUserMedia(constraints);
|
|
5751
5718
|
}
|
|
@@ -5782,7 +5749,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5782
5749
|
stream = await this.getUserMedia({ audio: constraints });
|
|
5783
5750
|
} catch (error) {
|
|
5784
5751
|
logger$17.error("[RTCPeerConnectionController] Failed to re-acquire mic for pipeline restore:", error);
|
|
5785
|
-
this._errors$.next(
|
|
5752
|
+
this._errors$.next(new MediaTrackError("restoreAudioPipelineInput", "audio", error));
|
|
5786
5753
|
return;
|
|
5787
5754
|
}
|
|
5788
5755
|
const newTrack = stream.getAudioTracks().at(0);
|
|
@@ -5844,7 +5811,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5844
5811
|
logger$17.debug(`[RTCPeerConnectionController] ${track.kind} track added:`, track.id);
|
|
5845
5812
|
} catch (error) {
|
|
5846
5813
|
logger$17.error(`[RTCPeerConnectionController] Failed to add ${track.kind} track:`, error);
|
|
5847
|
-
this._errors$.next(
|
|
5814
|
+
this._errors$.next(new MediaTrackError("addLocalTrack", track.kind, error));
|
|
5848
5815
|
throw error;
|
|
5849
5816
|
}
|
|
5850
5817
|
}
|
|
@@ -5869,7 +5836,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
5869
5836
|
logger$17.debug(`[RTCPeerConnectionController] ${sender.track?.kind} track removed:`, trackId);
|
|
5870
5837
|
} catch (error) {
|
|
5871
5838
|
logger$17.error(`[RTCPeerConnectionController] Failed to remove ${sender.track?.kind} track:`, error);
|
|
5872
|
-
this._errors$.next(
|
|
5839
|
+
this._errors$.next(new MediaTrackError("removeLocalTrack", sender.track?.kind ?? "unknown", error));
|
|
5873
5840
|
throw error;
|
|
5874
5841
|
}
|
|
5875
5842
|
}
|
|
@@ -5979,7 +5946,11 @@ function isVertoInviteMessage(value) {
|
|
|
5979
5946
|
const msg = value;
|
|
5980
5947
|
return msg.method === "verto.invite" && isObject(msg.params) && hasProperty(msg.params, "sdp") && hasProperty(msg.params, "callID");
|
|
5981
5948
|
}
|
|
5982
|
-
|
|
5949
|
+
/**
|
|
5950
|
+
* Guards inbound `verto.bye` frames (server → client). Outbound bye messages are
|
|
5951
|
+
* built locally and never type-guarded, so only the inbound shape is asserted.
|
|
5952
|
+
*/
|
|
5953
|
+
function isVertoByeInboundMessage(value) {
|
|
5983
5954
|
if (!isVertoMethodMessage(value)) return false;
|
|
5984
5955
|
return value.method === "verto.bye";
|
|
5985
5956
|
}
|
|
@@ -5999,6 +5970,14 @@ function isVertoMediaParamsInnerParams(value) {
|
|
|
5999
5970
|
function isVertoPingInnerParams(value) {
|
|
6000
5971
|
return isObject(value) && hasProperty(value, "jsonrpc") && value.jsonrpc === "2.0" && hasProperty(value, "method") && value.method === "verto.ping";
|
|
6001
5972
|
}
|
|
5973
|
+
/**
|
|
5974
|
+
* Guards the params of an inbound `verto.bye` frame (top-level `callID`). Use
|
|
5975
|
+
* this to recognise the bye payload extracted by `vertoBye$`, e.g. when racing
|
|
5976
|
+
* it against a boolean answer/reject signal.
|
|
5977
|
+
*/
|
|
5978
|
+
function isVertoByeInboundParamsGuard(value) {
|
|
5979
|
+
return isObject(value) && hasProperty(value, "callID") && typeof value.callID === "string";
|
|
5980
|
+
}
|
|
6002
5981
|
|
|
6003
5982
|
//#endregion
|
|
6004
5983
|
//#region src/managers/VertoManager.ts
|
|
@@ -6336,7 +6315,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6336
6315
|
return this.cachedObservable("vertoMediaParams$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoMediaParamsInnerParams, "params"), takeUntil(this.destroyed$)));
|
|
6337
6316
|
}
|
|
6338
6317
|
get vertoBye$() {
|
|
6339
|
-
return this.cachedObservable("vertoBye$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(
|
|
6318
|
+
return this.cachedObservable("vertoBye$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoByeInboundMessage, "params"), takeUntil(this.destroyed$)));
|
|
6340
6319
|
}
|
|
6341
6320
|
get vertoAttach$() {
|
|
6342
6321
|
return this.cachedObservable("vertoAttach$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoAttachMessage, "params"), takeUntil(this.destroyed$)));
|
|
@@ -6465,6 +6444,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6465
6444
|
inputVideoStream: options.inputVideoStream,
|
|
6466
6445
|
receiveAudio: options.receiveAudio,
|
|
6467
6446
|
receiveVideo: options.receiveVideo,
|
|
6447
|
+
fallbackToReceiveOnly: options.fallbackToReceiveOnly,
|
|
6468
6448
|
webRTCApiProvider: this.webRTCApiProvider,
|
|
6469
6449
|
preferredVideoCodecs: options.preferredVideoCodecs,
|
|
6470
6450
|
preferredAudioCodecs: options.preferredAudioCodecs,
|
|
@@ -6489,7 +6469,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6489
6469
|
logger$16.debug("[WebRTCManager] Inbound answer handler aborted (destroyed).");
|
|
6490
6470
|
return;
|
|
6491
6471
|
}
|
|
6492
|
-
if (
|
|
6472
|
+
if (isVertoByeInboundParamsGuard(vertoByeOrAccepted)) {
|
|
6493
6473
|
logger$16.info("[WebRTCManager] Inbound call ended by remote before answer.");
|
|
6494
6474
|
this.callSession?.destroy();
|
|
6495
6475
|
} else if (!vertoByeOrAccepted) {
|
|
@@ -6591,7 +6571,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6591
6571
|
logger$16.debug("[WebRTCManager] Destroyed while waiting for call acceptance");
|
|
6592
6572
|
return;
|
|
6593
6573
|
}
|
|
6594
|
-
if (
|
|
6574
|
+
if (isVertoByeInboundParamsGuard(vertoByeOrAccepted)) {
|
|
6595
6575
|
logger$16.info("[WebRTCManager] Call ended before answer was sent.");
|
|
6596
6576
|
this.callSession?.destroy();
|
|
6597
6577
|
} else if (!vertoByeOrAccepted) {
|
|
@@ -6691,9 +6671,11 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6691
6671
|
await this.initAdditionalPeerConnection("screenshare", options);
|
|
6692
6672
|
}
|
|
6693
6673
|
async initAdditionalPeerConnection(propose, options) {
|
|
6674
|
+
const isScreenShare = propose === "screenshare";
|
|
6675
|
+
let firstPeerConnectionError;
|
|
6694
6676
|
let rtcPeerConnController = null;
|
|
6695
6677
|
try {
|
|
6696
|
-
this._screenShareStatus$.next("starting");
|
|
6678
|
+
if (isScreenShare) this._screenShareStatus$.next("starting");
|
|
6697
6679
|
rtcPeerConnController = new RTCPeerConnectionController({
|
|
6698
6680
|
...options,
|
|
6699
6681
|
...this.RTCPeerConnectionConfig,
|
|
@@ -6701,21 +6683,27 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
6701
6683
|
webRTCApiProvider: this.webRTCApiProvider
|
|
6702
6684
|
}, void 0, this.deviceController);
|
|
6703
6685
|
this.setupLocalDescriptionHandler(rtcPeerConnController);
|
|
6704
|
-
if (
|
|
6686
|
+
if (isScreenShare) this._screenShareId = rtcPeerConnController.id;
|
|
6705
6687
|
this._rtcPeerConnectionsMap.set(rtcPeerConnController.id, rtcPeerConnController);
|
|
6706
6688
|
this._rtcPeerConnections$.next(Array.from(this._rtcPeerConnectionsMap.values()));
|
|
6707
6689
|
this.subscribeTo(rtcPeerConnController.errors$, (error) => {
|
|
6708
|
-
|
|
6690
|
+
firstPeerConnectionError ??= error;
|
|
6691
|
+
this.onError?.(error, { fatal: false });
|
|
6709
6692
|
});
|
|
6710
6693
|
await firstValueFrom(rtcPeerConnController.connectionState$.pipe(filter((state) => state === "connected"), take(1), timeout(this._screenShareTimeoutMs), takeUntil(this.destroyed$)));
|
|
6711
|
-
this._screenShareStatus$.next("started");
|
|
6712
|
-
logger$16.info(
|
|
6694
|
+
if (isScreenShare) this._screenShareStatus$.next("started");
|
|
6695
|
+
logger$16.info(`[WebRTCManager] Additional peer connection connected (${propose}).`);
|
|
6713
6696
|
return rtcPeerConnController.id;
|
|
6714
6697
|
} catch (error) {
|
|
6715
6698
|
logger$16.warn("[WebRTCManager] Error initializing additional peer connection:", error);
|
|
6716
|
-
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
6717
6699
|
if (rtcPeerConnController) rtcPeerConnController.destroy();
|
|
6718
|
-
this._screenShareStatus$.next("none");
|
|
6700
|
+
if (isScreenShare) this._screenShareStatus$.next("none");
|
|
6701
|
+
if (firstPeerConnectionError) throw firstPeerConnectionError instanceof MediaAccessError && firstPeerConnectionError.originalError instanceof Error ? firstPeerConnectionError.originalError : firstPeerConnectionError;
|
|
6702
|
+
if (error instanceof EmptyError) {
|
|
6703
|
+
logger$16.debug("[WebRTCManager] Additional peer connection aborted before connecting.");
|
|
6704
|
+
return;
|
|
6705
|
+
}
|
|
6706
|
+
throw error instanceof Error ? error : new Error(String(error), { cause: error });
|
|
6719
6707
|
}
|
|
6720
6708
|
}
|
|
6721
6709
|
async removeInputDevices(id) {
|
|
@@ -7646,6 +7634,12 @@ function mosToQualityLevel(mos) {
|
|
|
7646
7634
|
//#region src/core/entities/Call.ts
|
|
7647
7635
|
const logger$12 = getLogger();
|
|
7648
7636
|
/**
|
|
7637
|
+
* Verto method for setting member layout positions. Its gateway DTO requires a
|
|
7638
|
+
* `targets` array whose entries are `{ target, position }` (NOT bare targets),
|
|
7639
|
+
* so {@link WebRTCCall.buildMethodParams} special-cases it. See issue #19400.
|
|
7640
|
+
*/
|
|
7641
|
+
const POSITION_SET_METHOD = "call.member.position.set";
|
|
7642
|
+
/**
|
|
7649
7643
|
* Ratio between the critical and warning RTT spike multipliers.
|
|
7650
7644
|
* Warning threshold = baseline * warningMultiplier (default 3x)
|
|
7651
7645
|
* Critical threshold = baseline * warningMultiplier * RTT_CRITICAL_TO_WARNING_RATIO
|
|
@@ -7852,7 +7846,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
7852
7846
|
* @throws {JSONRPCError} If the RPC call returns an error.
|
|
7853
7847
|
*/
|
|
7854
7848
|
async executeMethod(target, method, args) {
|
|
7855
|
-
const params = this.buildMethodParams(target, args);
|
|
7849
|
+
const params = this.buildMethodParams(target, args, method);
|
|
7856
7850
|
const request = buildRPCRequest({
|
|
7857
7851
|
method,
|
|
7858
7852
|
params
|
|
@@ -7866,12 +7860,16 @@ var WebRTCCall = class extends Destroyable {
|
|
|
7866
7860
|
throw error;
|
|
7867
7861
|
}
|
|
7868
7862
|
}
|
|
7869
|
-
buildMethodParams(target, args) {
|
|
7863
|
+
buildMethodParams(target, args, method) {
|
|
7870
7864
|
const self = {
|
|
7871
7865
|
node_id: this.nodeId ?? "",
|
|
7872
7866
|
call_id: this.id,
|
|
7873
7867
|
member_id: this.vertoManager.selfId ?? ""
|
|
7874
7868
|
};
|
|
7869
|
+
if (method === POSITION_SET_METHOD) return {
|
|
7870
|
+
...args,
|
|
7871
|
+
self
|
|
7872
|
+
};
|
|
7875
7873
|
if (typeof target === "object") return {
|
|
7876
7874
|
...args,
|
|
7877
7875
|
self,
|
|
@@ -8437,10 +8435,21 @@ var WebRTCCall = class extends Destroyable {
|
|
|
8437
8435
|
return this.deferEmission(this._answered$.asObservable());
|
|
8438
8436
|
}
|
|
8439
8437
|
/**
|
|
8440
|
-
* Sets the call layout and participant positions.
|
|
8438
|
+
* Sets the call layout and, optionally, individual participant positions.
|
|
8439
|
+
*
|
|
8440
|
+
* The gateway `call.layout.set` DTO has **no** `positions` member, so when
|
|
8441
|
+
* `positions` is provided this method issues a `call.member.position.set`
|
|
8442
|
+
* request per member (via {@link Participant.setPosition}, which keys each
|
|
8443
|
+
* position by that member's own call context) alongside `call.layout.set`
|
|
8444
|
+
* (issue #19400, Flag #6).
|
|
8445
|
+
*
|
|
8446
|
+
* **These operations are NOT atomic.** The layout is applied first, then each
|
|
8447
|
+
* member position sequentially, so members may briefly flash into their
|
|
8448
|
+
* default slots before being moved to the requested positions.
|
|
8441
8449
|
*
|
|
8442
8450
|
* @param layout - Layout name (must be one of {@link layouts}).
|
|
8443
|
-
* @param positions -
|
|
8451
|
+
* @param positions - Optional map of member IDs to {@link VideoPosition} values.
|
|
8452
|
+
* When omitted or empty, only the layout is changed.
|
|
8444
8453
|
* @throws {InvalidParams} If the layout is not in the available {@link layouts}.
|
|
8445
8454
|
*
|
|
8446
8455
|
* @example
|
|
@@ -8453,10 +8462,17 @@ var WebRTCCall = class extends Destroyable {
|
|
|
8453
8462
|
async setLayout(layout, positions) {
|
|
8454
8463
|
if (!this.layouts.includes(layout)) throw new InvalidParams(`Layout ${layout} is not available in the current call layouts: ${this.layouts.join(", ")}`);
|
|
8455
8464
|
const selfId = await firstValueFrom(this.selfId$.pipe(filter((id) => id !== null)));
|
|
8456
|
-
await this.executeMethod(selfId, "call.layout.set", {
|
|
8457
|
-
|
|
8458
|
-
|
|
8459
|
-
|
|
8465
|
+
await this.executeMethod(selfId, "call.layout.set", { layout });
|
|
8466
|
+
const positionEntries = Object.entries(positions ?? {});
|
|
8467
|
+
if (positionEntries.length === 0) return;
|
|
8468
|
+
for (const [memberId, position] of positionEntries) {
|
|
8469
|
+
const participant = this.participants.find((p) => p.id === memberId);
|
|
8470
|
+
if (!participant) {
|
|
8471
|
+
logger$12.warn(`[Call] setLayout: member ${memberId} not found in participants; skipping position ${position}`);
|
|
8472
|
+
continue;
|
|
8473
|
+
}
|
|
8474
|
+
await participant.setPosition(position);
|
|
8475
|
+
}
|
|
8460
8476
|
}
|
|
8461
8477
|
/**
|
|
8462
8478
|
* Transfers the call to another destination.
|
|
@@ -8630,6 +8646,7 @@ function inferCallErrorKind(error) {
|
|
|
8630
8646
|
if (error instanceof RPCTimeoutError) return "timeout";
|
|
8631
8647
|
if (error instanceof JSONRPCError) return "signaling";
|
|
8632
8648
|
if (error instanceof MediaTrackError) return "media";
|
|
8649
|
+
if (error instanceof MediaAccessError) return "media";
|
|
8633
8650
|
if (error instanceof WebSocketConnectionError || error instanceof TransportConnectionError) return "network";
|
|
8634
8651
|
return "internal";
|
|
8635
8652
|
}
|
|
@@ -8646,6 +8663,7 @@ const RECOVERABLE_RPC_CODES = new Set([
|
|
|
8646
8663
|
function isFatalError(error) {
|
|
8647
8664
|
if (error instanceof VertoPongError) return false;
|
|
8648
8665
|
if (error instanceof MediaTrackError) return false;
|
|
8666
|
+
if (error instanceof MediaAccessError) return error.fatal;
|
|
8649
8667
|
if (error instanceof RPCTimeoutError) return false;
|
|
8650
8668
|
if (error instanceof JSONRPCError && RECOVERABLE_RPC_CODES.has(error.code)) return false;
|
|
8651
8669
|
return true;
|
|
@@ -8671,10 +8689,10 @@ var CallFactory = class {
|
|
|
8671
8689
|
return {
|
|
8672
8690
|
vertoManager: new WebRTCVertoManager(callInstance, this.attachManager, this.deviceController, this.webRTCApiProvider, {
|
|
8673
8691
|
nodeId: options.nodeId,
|
|
8674
|
-
onError: (error) => {
|
|
8692
|
+
onError: (error, options$1) => {
|
|
8675
8693
|
const callError = {
|
|
8676
8694
|
kind: inferCallErrorKind(error),
|
|
8677
|
-
fatal: isFatalError(error),
|
|
8695
|
+
fatal: options$1?.fatal ?? isFatalError(error),
|
|
8678
8696
|
error,
|
|
8679
8697
|
callId: callInstance.id
|
|
8680
8698
|
};
|
|
@@ -9133,6 +9151,17 @@ var PendingRPC = class PendingRPC {
|
|
|
9133
9151
|
//#endregion
|
|
9134
9152
|
//#region src/managers/ClientSessionManager.ts
|
|
9135
9153
|
const logger$9 = getLogger();
|
|
9154
|
+
/**
|
|
9155
|
+
* Decide whether an error emitted on `call.errors$` during dial should
|
|
9156
|
+
* abort the dial. A non-fatal MediaAccessError means the call degraded to
|
|
9157
|
+
* receive-only and still connects — everything else rejects `dial()` with
|
|
9158
|
+
* the real cause.
|
|
9159
|
+
*
|
|
9160
|
+
* Pure function — exported for unit testing.
|
|
9161
|
+
*/
|
|
9162
|
+
function shouldAbortDial(callError) {
|
|
9163
|
+
return callError.fatal || !(callError.error instanceof MediaAccessError);
|
|
9164
|
+
}
|
|
9136
9165
|
const getAddressSearchURI = (options) => {
|
|
9137
9166
|
const to = options.to?.split("?")[0];
|
|
9138
9167
|
const from$1 = options.from?.startsWith("subscriber://") ? options.from.replace("subscriber://", "") : options.from;
|
|
@@ -9371,11 +9400,41 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
9371
9400
|
}
|
|
9372
9401
|
} else this._errors$.next(error);
|
|
9373
9402
|
}
|
|
9403
|
+
/**
|
|
9404
|
+
* Clear the resume state (authorization_state + protocol) only.
|
|
9405
|
+
*
|
|
9406
|
+
* This is the stale-auth-state recovery helper used by handleAuthError:
|
|
9407
|
+
* the server rejected a reconnect, so the resume state is discarded and a
|
|
9408
|
+
* fresh connect follows. Attach records are deliberately preserved — the
|
|
9409
|
+
* session lives on through the reconnect and reattachCalls() needs the
|
|
9410
|
+
* stored call references afterwards. Do NOT add detachAll() here.
|
|
9411
|
+
*
|
|
9412
|
+
* For public teardown (disconnect/destroy), use {@link teardownSessionState}
|
|
9413
|
+
* instead, which clears the attach records as well.
|
|
9414
|
+
*/
|
|
9374
9415
|
async cleanupStoredConnectionParams() {
|
|
9375
9416
|
await this.transport.setProtocol(void 0);
|
|
9376
9417
|
await this.updateAuthorizationStateInStorage(void 0);
|
|
9377
9418
|
this._authorization$.next(void 0);
|
|
9378
9419
|
}
|
|
9420
|
+
/**
|
|
9421
|
+
* Public-teardown helper for disconnect()/destroy(). Clears the resume
|
|
9422
|
+
* state (authorization_state + protocol) AND the attach records as one
|
|
9423
|
+
* atomic unit.
|
|
9424
|
+
*
|
|
9425
|
+
* The two stores are coupled: the backend only honors attach records
|
|
9426
|
+
* within the session identified by the resume state, so ending the
|
|
9427
|
+
* session must clear both. Clearing one without the other strands records
|
|
9428
|
+
* no future session can honor (disconnect) or revives a session the
|
|
9429
|
+
* developer explicitly ended (destroy).
|
|
9430
|
+
*
|
|
9431
|
+
* Distinct from {@link cleanupStoredConnectionParams}, which keeps the
|
|
9432
|
+
* attach records for the stale-auth-state recovery path.
|
|
9433
|
+
*/
|
|
9434
|
+
async teardownSessionState() {
|
|
9435
|
+
await this.cleanupStoredConnectionParams();
|
|
9436
|
+
await this.attachManager.detachAll();
|
|
9437
|
+
}
|
|
9379
9438
|
async updateAuthState(authorization_state) {
|
|
9380
9439
|
try {
|
|
9381
9440
|
await this.storage.setItem(this.authorizationStateKey, authorization_state);
|
|
@@ -9470,7 +9529,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
9470
9529
|
async disconnect() {
|
|
9471
9530
|
this.transport.disconnect();
|
|
9472
9531
|
this._authState$.next({ kind: "unauthenticated" });
|
|
9473
|
-
await this.
|
|
9532
|
+
await this.teardownSessionState();
|
|
9474
9533
|
}
|
|
9475
9534
|
async createInboundCall(invite) {
|
|
9476
9535
|
const callSession = await this.createCall({
|
|
@@ -9529,7 +9588,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
9529
9588
|
to: destinationURI,
|
|
9530
9589
|
...options
|
|
9531
9590
|
});
|
|
9532
|
-
await firstValueFrom(race(callSession.selfId$.pipe(filter((id) => Boolean(id)), take(1), timeout(this.callCreateTimeout)), callSession.errors$.pipe(take(1), switchMap((callError) => throwError(() => callError.error)))));
|
|
9591
|
+
await firstValueFrom(race(callSession.selfId$.pipe(filter((id) => Boolean(id)), take(1), timeout(this.callCreateTimeout)), callSession.errors$.pipe(filter(shouldAbortDial), take(1), switchMap((callError) => throwError(() => callError.error)))));
|
|
9533
9592
|
this._calls$.next({
|
|
9534
9593
|
[`${callSession.id}`]: callSession,
|
|
9535
9594
|
...this._calls$.value
|
|
@@ -10632,6 +10691,10 @@ var TransportManager = class extends Destroyable {
|
|
|
10632
10691
|
if (!isSignalwireRequest(message)) return true;
|
|
10633
10692
|
const eventChannel = message.params.event_channel;
|
|
10634
10693
|
if (!eventChannel) return true;
|
|
10694
|
+
if (message.params.event_type.startsWith("conversation.")) {
|
|
10695
|
+
logger$2.debug(`[Transport] Received conversation event: ${message.params.event_type} (event_channel: ${eventChannel})`);
|
|
10696
|
+
return true;
|
|
10697
|
+
}
|
|
10635
10698
|
const currentProtocol = this._currentProtocol;
|
|
10636
10699
|
if (!currentProtocol) return true;
|
|
10637
10700
|
if (!eventChannel.includes(currentProtocol)) {
|
|
@@ -11231,7 +11294,7 @@ var SignalWire = class extends Destroyable {
|
|
|
11231
11294
|
logger$1.warn("[SignalWire] Failed to initialize VisibilityController:", error);
|
|
11232
11295
|
}
|
|
11233
11296
|
try {
|
|
11234
|
-
this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "4.0.0-rc.
|
|
11297
|
+
this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "4.0.0-rc.2" });
|
|
11235
11298
|
} catch (error) {
|
|
11236
11299
|
logger$1.warn("[SignalWire] Failed to initialize DiagnosticsCollector:", error);
|
|
11237
11300
|
}
|
|
@@ -11239,6 +11302,14 @@ var SignalWire = class extends Destroyable {
|
|
|
11239
11302
|
/**
|
|
11240
11303
|
* Disconnects the WebSocket and tears down the current session.
|
|
11241
11304
|
*
|
|
11305
|
+
* Ends the session identified by the protocol and clears its persisted
|
|
11306
|
+
* resume state (`authorization_state` + protocol) and attach records
|
|
11307
|
+
* together — a later {@link connect} with the same credentials starts a
|
|
11308
|
+
* fresh session and cannot reattach to the ended session's calls.
|
|
11309
|
+
* Credentials and device preferences are preserved. To temporarily stop
|
|
11310
|
+
* receiving inbound calls while keeping the session alive, use
|
|
11311
|
+
* `unregister()` instead.
|
|
11312
|
+
*
|
|
11242
11313
|
* The client can be reconnected by calling {@link connect} again,
|
|
11243
11314
|
* which creates a fresh transport and session.
|
|
11244
11315
|
*/
|
|
@@ -11630,12 +11701,20 @@ var SignalWire = class extends Destroyable {
|
|
|
11630
11701
|
prefs.preferredVideoInput = null;
|
|
11631
11702
|
await this._deviceController.clearDeviceState();
|
|
11632
11703
|
}
|
|
11633
|
-
/**
|
|
11704
|
+
/**
|
|
11705
|
+
* Destroys the client, clearing timers and releasing all resources.
|
|
11706
|
+
*
|
|
11707
|
+
* Intentionally destroying the client ends its session: the resume state
|
|
11708
|
+
* (`authorization_state` + protocol) and the attach records are both
|
|
11709
|
+
* cleared. Credentials and device preferences are preserved — use
|
|
11710
|
+
* {@link resetToDefaults} for a full wipe. To temporarily stop receiving
|
|
11711
|
+
* inbound calls while keeping the session alive, use `unregister()`.
|
|
11712
|
+
*/
|
|
11634
11713
|
destroy() {
|
|
11635
11714
|
this._refreshCoordinator?.destroy();
|
|
11636
11715
|
this._refreshCoordinator = void 0;
|
|
11637
11716
|
this._dpopManager?.destroy();
|
|
11638
|
-
|
|
11717
|
+
this._clientSession.teardownSessionState();
|
|
11639
11718
|
this._transport.destroy();
|
|
11640
11719
|
this._clientSession.destroy();
|
|
11641
11720
|
try {
|
|
@@ -11749,7 +11828,7 @@ var StaticCredentialProvider = class {
|
|
|
11749
11828
|
/**
|
|
11750
11829
|
* Library version from package.json, injected at build time.
|
|
11751
11830
|
*/
|
|
11752
|
-
const version = "4.0.0-rc.
|
|
11831
|
+
const version = "4.0.0-rc.2";
|
|
11753
11832
|
/**
|
|
11754
11833
|
* Flag indicating the library has been loaded and is ready to use.
|
|
11755
11834
|
* For UMD builds: `window.SignalWire.ready`
|
|
@@ -11771,12 +11850,12 @@ const ready = true;
|
|
|
11771
11850
|
*/
|
|
11772
11851
|
const emitReadyEvent = () => {
|
|
11773
11852
|
if (typeof window !== "undefined") {
|
|
11774
|
-
const event = new CustomEvent("signalwire:js:ready", { detail: { version: "4.0.0-rc.
|
|
11853
|
+
const event = new CustomEvent("signalwire:js:ready", { detail: { version: "4.0.0-rc.2" } });
|
|
11775
11854
|
window.dispatchEvent(event);
|
|
11776
11855
|
}
|
|
11777
11856
|
};
|
|
11778
11857
|
emitReadyEvent();
|
|
11779
11858
|
|
|
11780
11859
|
//#endregion
|
|
11781
|
-
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 };
|
|
11860
|
+
export { Address, CallCreateError, ClientPreferences, CollectionFetchError, DPoPInitError, DeviceTokenError, EmbedTokenCredentialProvider, InvalidCredentialsError, MediaAccessError, MediaTrackError, MessageParseError, OverconstrainedFallbackError, Participant, PreflightError, RecoveryError, SelfCapabilities, SelfParticipant, SignalWire, StaticCredentialProvider, TokenRefreshError, UnexpectedError, User, VertoPongError, WebRTCCall, embeddableCall, getLogger, isSelfParticipant, ready, setDebugOptions, setLogLevel, setLogger, version };
|
|
11782
11861
|
//# sourceMappingURL=index.mjs.map
|