@signalwire/js 4.0.0-beta.7 → 4.0.0-beta.9
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/base-CQPEW1lJ.d.mts +31 -0
- package/dist/base-CQPEW1lJ.d.mts.map +1 -0
- package/dist/base-Cif20s3C.d.cts +31 -0
- package/dist/base-Cif20s3C.d.cts.map +1 -0
- package/dist/browser.mjs +502 -173
- package/dist/browser.mjs.map +1 -1
- package/dist/browser.umd.js +502 -173
- package/dist/browser.umd.js.map +1 -1
- package/dist/index.cjs +485 -158
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +333 -153
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +333 -153
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +486 -159
- package/dist/index.mjs.map +1 -1
- package/dist/operators/index.cjs +1 -1
- package/dist/operators/index.d.cts +1 -1
- package/dist/operators/index.d.mts +1 -1
- package/dist/operators/index.mjs +1 -1
- package/dist/{operators-DT4UB24-.cjs → operators-mm21prWr.cjs} +5 -3
- package/dist/operators-mm21prWr.cjs.map +1 -0
- package/dist/{operators-BHQMSEzq.mjs → operators-uT_fb8ba.mjs} +5 -3
- package/dist/operators-uT_fb8ba.mjs.map +1 -0
- package/package.json +1 -1
- package/dist/base-A5AZTrAd.d.cts +0 -23
- package/dist/base-A5AZTrAd.d.cts.map +0 -1
- package/dist/base-aVtoG8Wk.d.mts +0 -23
- package/dist/base-aVtoG8Wk.d.mts.map +0 -1
- package/dist/operators-BHQMSEzq.mjs.map +0 -1
- package/dist/operators-DT4UB24-.cjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { A as VertoPongError, C as StorageReadError, D as UnimplementedError, E as UnexpectedError, M as WebSocketTimeoutError, O as ValidationError, S as StorageNotAvailableError, T as TransportConnectionError, _ as MessageParseError, a as filterNull, b as RequestTimeoutError, c as CallCreateError, d as DependencyError, f as DeserializationError, g as MediaTrackError, h as JSONRPCError, i as getValueFrom, j as WebSocketConnectionError, k as VertoInviteHandlerError, l as CollectionFetchError, m as InvalidParams, n as filterAs, o as getLogger, p as InvalidCredentialsError, s as AuthStateHandlerError, t as throwOnRPCError, u as ConversationError, v as RPCTimeoutError, w as StorageWriteError, x as SerializationError, y as RequestError } from "./operators-
|
|
1
|
+
import { A as VertoPongError, C as StorageReadError, D as UnimplementedError, E as UnexpectedError, M as WebSocketTimeoutError, O as ValidationError, S as StorageNotAvailableError, T as TransportConnectionError, _ as MessageParseError, a as filterNull, b as RequestTimeoutError, c as CallCreateError, d as DependencyError, f as DeserializationError, g as MediaTrackError, h as JSONRPCError, i as getValueFrom, j as WebSocketConnectionError, k as VertoInviteHandlerError, l as CollectionFetchError, m as InvalidParams, n as filterAs, o as getLogger, p as InvalidCredentialsError, s as AuthStateHandlerError, t as throwOnRPCError, u as ConversationError, v as RPCTimeoutError, w as StorageWriteError, x as SerializationError, y as RequestError } from "./operators-uT_fb8ba.mjs";
|
|
2
2
|
import { jwtDecode } from "jwt-decode";
|
|
3
|
-
import { BehaviorSubject, EMPTY, NEVER, Observable, ReplaySubject, Subject, auditTime, catchError, combineLatest, debounceTime, defer, distinctUntilChanged, exhaustMap, filter, firstValueFrom, from, interval, lastValueFrom, map, merge, of, pipe, race, share, shareReplay, skip, skipWhile, switchMap, take, takeUntil, tap, timeout, withLatestFrom } from "rxjs";
|
|
3
|
+
import { BehaviorSubject, EMPTY, NEVER, Observable, ReplaySubject, Subject, TimeoutError, asapScheduler, auditTime, catchError, combineLatest, debounceTime, defer, distinctUntilChanged, exhaustMap, filter, firstValueFrom, from, interval, lastValueFrom, map, merge, observeOn, of, pipe, race, share, shareReplay, skip, skipWhile, startWith, switchMap, take, takeUntil, tap, throwError, timeout, 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
|
|
|
@@ -27,6 +27,40 @@ var Destroyable = class {
|
|
|
27
27
|
}
|
|
28
28
|
return cached;
|
|
29
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Like `cachedObservable`, but defers emissions to the microtask queue
|
|
32
|
+
* via `observeOn(asapScheduler)`.
|
|
33
|
+
*
|
|
34
|
+
* Use ONLY for public-facing observable getters that external consumers
|
|
35
|
+
* subscribe to. Prevents a class of bugs where `BehaviorSubject` or
|
|
36
|
+
* `ReplaySubject` replays synchronously during `subscribe()`, before
|
|
37
|
+
* the subscription variable is assigned in the caller's scope.
|
|
38
|
+
*
|
|
39
|
+
* Do NOT use for observables consumed internally by the SDK — internal
|
|
40
|
+
* code using `subscribeTo()`, `firstValueFrom()`, or `withLatestFrom()`
|
|
41
|
+
* depends on synchronous emission delivery.
|
|
42
|
+
*/
|
|
43
|
+
publicCachedObservable(key, factory) {
|
|
44
|
+
const publicKey = `public:${key}`;
|
|
45
|
+
this._observableCache ??= /* @__PURE__ */ new Map();
|
|
46
|
+
let cached = this._observableCache.get(publicKey);
|
|
47
|
+
if (!cached) {
|
|
48
|
+
cached = factory().pipe(observeOn(asapScheduler));
|
|
49
|
+
this._observableCache.set(publicKey, cached);
|
|
50
|
+
}
|
|
51
|
+
return cached;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Wraps an observable so emissions are deferred to the microtask queue.
|
|
55
|
+
*
|
|
56
|
+
* Use ONLY for public-facing getters that expose a subject via
|
|
57
|
+
* `.asObservable()` without going through `cachedObservable`.
|
|
58
|
+
*
|
|
59
|
+
* Do NOT use for observables consumed internally by the SDK.
|
|
60
|
+
*/
|
|
61
|
+
deferEmission(observable) {
|
|
62
|
+
return observable.pipe(observeOn(asapScheduler));
|
|
63
|
+
}
|
|
30
64
|
subscribeTo(observable, observerOrNext) {
|
|
31
65
|
const subscription = observable.subscribe(observerOrNext);
|
|
32
66
|
this.subscriptions.push(subscription);
|
|
@@ -295,7 +329,7 @@ var PreferencesContainer = class PreferencesContainer {
|
|
|
295
329
|
skipDeviceMonitoring: false,
|
|
296
330
|
savePreferences: false
|
|
297
331
|
};
|
|
298
|
-
this.receiveVideo =
|
|
332
|
+
this.receiveVideo = false;
|
|
299
333
|
this.receiveAudio = true;
|
|
300
334
|
this.preferredAudioInput = null;
|
|
301
335
|
this.preferredAudioOutput = null;
|
|
@@ -607,7 +641,7 @@ var NavigatorDeviceController = class extends Destroyable {
|
|
|
607
641
|
};
|
|
608
642
|
this._devicesState$ = this.createBehaviorSubject(initialDevicesState);
|
|
609
643
|
this._selectedDevicesState$ = this.createBehaviorSubject(initialSelectedDevicesState);
|
|
610
|
-
this._errors$ = this.
|
|
644
|
+
this._errors$ = this.createReplaySubject(1);
|
|
611
645
|
this.init();
|
|
612
646
|
}
|
|
613
647
|
get selectedAudioInputDeviceConstraints() {
|
|
@@ -1087,7 +1121,7 @@ const RPCExecute = ({ method, params }) => {
|
|
|
1087
1121
|
|
|
1088
1122
|
//#endregion
|
|
1089
1123
|
//#region src/core/RPCMessages/VertoMessages.ts
|
|
1090
|
-
const
|
|
1124
|
+
const SDK_TO_VERTO_FIELD_MAP = {
|
|
1091
1125
|
id: "callID",
|
|
1092
1126
|
destinationNumber: "destination_number",
|
|
1093
1127
|
remoteCallerName: "remote_caller_id_name",
|
|
@@ -1096,19 +1130,31 @@ const tmpMap = {
|
|
|
1096
1130
|
callerNumber: "caller_id_number",
|
|
1097
1131
|
fromCallAddressId: "from_fabric_address_id"
|
|
1098
1132
|
};
|
|
1133
|
+
const EXCLUDED_DIALOG_PARAMS = new Set([
|
|
1134
|
+
"remoteSdp",
|
|
1135
|
+
"localStream",
|
|
1136
|
+
"remoteStream"
|
|
1137
|
+
]);
|
|
1099
1138
|
/**
|
|
1100
|
-
* Translate SDK fields into verto variables
|
|
1139
|
+
* Translate SDK fields into verto variables.
|
|
1140
|
+
* Returns a new object — the input is never mutated.
|
|
1101
1141
|
*/
|
|
1142
|
+
/** @internal Exported for testing only. */
|
|
1102
1143
|
const filterVertoParams = (params) => {
|
|
1103
|
-
if (Object.prototype.hasOwnProperty.call(params, "dialogParams"))
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1144
|
+
if (!Object.prototype.hasOwnProperty.call(params, "dialogParams")) return params;
|
|
1145
|
+
const sourceDialogParams = params.dialogParams;
|
|
1146
|
+
const filteredDialogParams = Object.entries(sourceDialogParams).reduce((acc, [key, value]) => {
|
|
1147
|
+
if (EXCLUDED_DIALOG_PARAMS.has(key)) return acc;
|
|
1148
|
+
const mappedKey = SDK_TO_VERTO_FIELD_MAP[key] ?? key;
|
|
1149
|
+
return {
|
|
1150
|
+
...acc,
|
|
1151
|
+
[mappedKey]: value
|
|
1152
|
+
};
|
|
1153
|
+
}, {});
|
|
1154
|
+
return {
|
|
1155
|
+
...params,
|
|
1156
|
+
dialogParams: filteredDialogParams
|
|
1157
|
+
};
|
|
1112
1158
|
};
|
|
1113
1159
|
const buildVertoRPCMessage = (method) => {
|
|
1114
1160
|
return (params = {}) => {
|
|
@@ -1219,17 +1265,21 @@ var AttachManager = class {
|
|
|
1219
1265
|
buildCallOptions(attachment) {
|
|
1220
1266
|
const { audio: audioDirection, video: videoDirection } = attachment.mediaDirections;
|
|
1221
1267
|
const { audioInputDevice, videoInputDevice } = attachment;
|
|
1268
|
+
const receiveAudio = audioDirection.includes("recv");
|
|
1269
|
+
const receiveVideo = videoDirection.includes("recv");
|
|
1270
|
+
const sendAudio = audioDirection.includes("send");
|
|
1271
|
+
const sendVideo = videoDirection.includes("send");
|
|
1222
1272
|
return {
|
|
1223
|
-
receiveAudio
|
|
1224
|
-
receiveVideo
|
|
1225
|
-
inputAudioDeviceConstraints: {
|
|
1226
|
-
audio:
|
|
1273
|
+
receiveAudio,
|
|
1274
|
+
receiveVideo,
|
|
1275
|
+
inputAudioDeviceConstraints: sendAudio ? {
|
|
1276
|
+
audio: true,
|
|
1227
1277
|
...this.deviceController.deviceInfoToConstraints(audioInputDevice)
|
|
1228
|
-
},
|
|
1229
|
-
inputVideoDeviceConstraints: {
|
|
1230
|
-
video:
|
|
1278
|
+
} : void 0,
|
|
1279
|
+
inputVideoDeviceConstraints: sendVideo ? {
|
|
1280
|
+
video: true,
|
|
1231
1281
|
...this.deviceController.deviceInfoToConstraints(videoInputDevice)
|
|
1232
|
-
},
|
|
1282
|
+
} : void 0,
|
|
1233
1283
|
reattach: true
|
|
1234
1284
|
};
|
|
1235
1285
|
}
|
|
@@ -1759,7 +1809,13 @@ var Participant = class extends Destroyable {
|
|
|
1759
1809
|
}
|
|
1760
1810
|
/** Removes this participant from the call. */
|
|
1761
1811
|
async remove() {
|
|
1762
|
-
|
|
1812
|
+
const state = this._state$.value;
|
|
1813
|
+
const target = {
|
|
1814
|
+
member_id: this.id,
|
|
1815
|
+
call_id: state.call_id ?? "",
|
|
1816
|
+
node_id: state.node_id ?? ""
|
|
1817
|
+
};
|
|
1818
|
+
await this.executeMethod(target, "call.member.remove", {});
|
|
1763
1819
|
}
|
|
1764
1820
|
/** Ends the call for this participant. */
|
|
1765
1821
|
async end() {
|
|
@@ -1929,6 +1985,9 @@ function isJSONRPCRequest(value) {
|
|
|
1929
1985
|
function isJSONRPCResponse(value) {
|
|
1930
1986
|
return isObject(value) && hasProperty(value, "jsonrpc") && value.jsonrpc === "2.0" && hasProperty(value, "id") && typeof value.id === "string" && (hasProperty(value, "result") || hasProperty(value, "error"));
|
|
1931
1987
|
}
|
|
1988
|
+
function isJSONRPCErrorResponse(value) {
|
|
1989
|
+
return isObject(value) && hasProperty(value, "jsonrpc") && value.jsonrpc === "2.0" && hasProperty(value, "id") && typeof value.id === "string" && (hasProperty(value, "error") && isObject(value.error) && hasProperty(value.error, "code") && hasProperty(value.error, "message") || hasProperty(value, "result") && isObject(value.result) && hasProperty(value.result, "code") && value.result.code !== "200" && hasProperty(value.result, "message"));
|
|
1990
|
+
}
|
|
1932
1991
|
|
|
1933
1992
|
//#endregion
|
|
1934
1993
|
//#region src/core/RPCMessages/guards/events.guards.ts
|
|
@@ -2001,7 +2060,6 @@ var CallEventsManager = class extends Destroyable {
|
|
|
2001
2060
|
this.options = options;
|
|
2002
2061
|
this.callIds = /* @__PURE__ */ new Set();
|
|
2003
2062
|
this.roomSessionIds = /* @__PURE__ */ new Set();
|
|
2004
|
-
this._status$ = this.createBehaviorSubject("trying");
|
|
2005
2063
|
this._participants$ = this.createBehaviorSubject({});
|
|
2006
2064
|
this._self$ = this.createBehaviorSubject(null);
|
|
2007
2065
|
this._sessionState$ = this.createBehaviorSubject(initialSessionState);
|
|
@@ -2010,15 +2068,12 @@ var CallEventsManager = class extends Destroyable {
|
|
|
2010
2068
|
get participants$() {
|
|
2011
2069
|
return this.cachedObservable("participants$", () => this._participants$.asObservable().pipe(map((participantsRecord) => Object.values(participantsRecord))));
|
|
2012
2070
|
}
|
|
2071
|
+
get participants() {
|
|
2072
|
+
return Object.values(this._participants$.value);
|
|
2073
|
+
}
|
|
2013
2074
|
get self$() {
|
|
2014
2075
|
return this.cachedObservable("self$", () => this._self$.asObservable().pipe(filterNull()));
|
|
2015
2076
|
}
|
|
2016
|
-
get status$() {
|
|
2017
|
-
return this._status$.asObservable();
|
|
2018
|
-
}
|
|
2019
|
-
get status() {
|
|
2020
|
-
return this._status$.value;
|
|
2021
|
-
}
|
|
2022
2077
|
isRoomSessionIdValid(roomSessionId) {
|
|
2023
2078
|
return this.roomSessionIds.has(roomSessionId);
|
|
2024
2079
|
}
|
|
@@ -2103,7 +2158,6 @@ var CallEventsManager = class extends Destroyable {
|
|
|
2103
2158
|
callId: callJoinedEvent.call_id,
|
|
2104
2159
|
roomSessionId: callJoinedEvent.room_session_id
|
|
2105
2160
|
});
|
|
2106
|
-
this._status$.next("connected");
|
|
2107
2161
|
const sessionState = callJoinedEvent.room_session;
|
|
2108
2162
|
const { capabilities } = callJoinedEvent;
|
|
2109
2163
|
this.selfId = this.selfId ?? callJoinedEvent.member_id;
|
|
@@ -2230,12 +2284,53 @@ var CallEventsManager = class extends Destroyable {
|
|
|
2230
2284
|
|
|
2231
2285
|
//#endregion
|
|
2232
2286
|
//#region src/helpers/SDPHelper.ts
|
|
2287
|
+
/** Valid SDP direction attribute values. */
|
|
2288
|
+
const SDP_DIRECTIONS = new Set([
|
|
2289
|
+
"sendrecv",
|
|
2290
|
+
"sendonly",
|
|
2291
|
+
"recvonly",
|
|
2292
|
+
"inactive"
|
|
2293
|
+
]);
|
|
2233
2294
|
/**
|
|
2234
|
-
*
|
|
2295
|
+
* Extracts the media directions (audio/video) from an SDP string.
|
|
2296
|
+
*
|
|
2297
|
+
* Parses each media section (`m=audio` / `m=video`) and reads the `a=` direction
|
|
2298
|
+
* attribute (`sendrecv`, `sendonly`, `recvonly`, `inactive`).
|
|
2299
|
+
* If no explicit direction attribute is found for a media section, defaults to `sendrecv`
|
|
2300
|
+
* per RFC 4566.
|
|
2235
2301
|
*
|
|
2236
|
-
*
|
|
2237
|
-
*
|
|
2302
|
+
* @param sdp - The SDP string to parse
|
|
2303
|
+
* @returns The extracted audio and video directions
|
|
2304
|
+
*
|
|
2305
|
+
* @example
|
|
2306
|
+
* ```typescript
|
|
2307
|
+
* const sdp = `v=0\r\nm=audio 9 UDP/TLS/RTP/SAVPF 111\r\na=sendrecv\r\nm=video 9 UDP/TLS/RTP/SAVPF 96\r\na=recvonly`;
|
|
2308
|
+
* extractMediaDirectionsFromSDP(sdp);
|
|
2309
|
+
* // { audio: 'sendrecv', video: 'recvonly' }
|
|
2310
|
+
* ```
|
|
2238
2311
|
*/
|
|
2312
|
+
function extractMediaDirectionsFromSDP(sdp) {
|
|
2313
|
+
const result = {
|
|
2314
|
+
audio: "inactive",
|
|
2315
|
+
video: "inactive"
|
|
2316
|
+
};
|
|
2317
|
+
if (!sdp) return result;
|
|
2318
|
+
const lines = sdp.split(/\r?\n/);
|
|
2319
|
+
let currentMediaKind = null;
|
|
2320
|
+
let currentDirection = null;
|
|
2321
|
+
for (const line of lines) if (line.startsWith("m=")) {
|
|
2322
|
+
if (currentMediaKind) result[currentMediaKind] = currentDirection ?? "sendrecv";
|
|
2323
|
+
if (line.startsWith("m=audio")) currentMediaKind = "audio";
|
|
2324
|
+
else if (line.startsWith("m=video")) currentMediaKind = "video";
|
|
2325
|
+
else currentMediaKind = null;
|
|
2326
|
+
currentDirection = null;
|
|
2327
|
+
} else if (currentMediaKind && line.startsWith("a=")) {
|
|
2328
|
+
const attr = line.substring(2).trim();
|
|
2329
|
+
if (SDP_DIRECTIONS.has(attr)) currentDirection = attr;
|
|
2330
|
+
}
|
|
2331
|
+
if (currentMediaKind) result[currentMediaKind] = currentDirection ?? "sendrecv";
|
|
2332
|
+
return result;
|
|
2333
|
+
}
|
|
2239
2334
|
/**
|
|
2240
2335
|
* Validates that an SDP string has at least one non-host ICE candidate
|
|
2241
2336
|
* for each media section (m= line).
|
|
@@ -2536,6 +2631,15 @@ var LocalStreamController = class extends Destroyable {
|
|
|
2536
2631
|
track.addEventListener("ended", this.mediaTrackEndedHandler);
|
|
2537
2632
|
}
|
|
2538
2633
|
/**
|
|
2634
|
+
* Update the controller options (e.g., when media overrides are applied).
|
|
2635
|
+
*/
|
|
2636
|
+
updateOptions(options) {
|
|
2637
|
+
this.options = {
|
|
2638
|
+
...this.options,
|
|
2639
|
+
...options
|
|
2640
|
+
};
|
|
2641
|
+
}
|
|
2642
|
+
/**
|
|
2539
2643
|
* Stop all local tracks and clean up.
|
|
2540
2644
|
*/
|
|
2541
2645
|
stopAllTracks() {
|
|
@@ -2873,11 +2977,12 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
2873
2977
|
this._connectionState$ = this.createReplaySubject(1);
|
|
2874
2978
|
this._signalingState$ = this.createReplaySubject(1);
|
|
2875
2979
|
this._iceGatheringState$ = this.createReplaySubject(1);
|
|
2876
|
-
this._errors$ = this.
|
|
2980
|
+
this._errors$ = this.createReplaySubject(1);
|
|
2877
2981
|
this._iceCandidates$ = this.createReplaySubject(1);
|
|
2878
2982
|
this._initialized$ = this.createReplaySubject(1);
|
|
2879
2983
|
this._remoteDescription$ = this.createReplaySubject(1);
|
|
2880
2984
|
this._remoteStream$ = this.createBehaviorSubject(null);
|
|
2985
|
+
this._remoteOfferMediaDirections = null;
|
|
2881
2986
|
this.deviceController = deviceController ?? {};
|
|
2882
2987
|
this.id = options.callId ?? v4();
|
|
2883
2988
|
this._type = remoteSessionDescription ? "answer" : "offer";
|
|
@@ -2885,10 +2990,19 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
2885
2990
|
type: "offer",
|
|
2886
2991
|
sdp: remoteSessionDescription
|
|
2887
2992
|
} : void 0;
|
|
2993
|
+
this._remoteOfferMediaDirections = remoteSessionDescription ? extractMediaDirectionsFromSDP(remoteSessionDescription) : null;
|
|
2994
|
+
const offerDefaults = this._remoteOfferMediaDirections ? {
|
|
2995
|
+
audio: this._remoteOfferMediaDirections.audio.includes("recv"),
|
|
2996
|
+
video: this._remoteOfferMediaDirections.video.includes("recv"),
|
|
2997
|
+
receiveAudio: this._remoteOfferMediaDirections.audio.includes("send"),
|
|
2998
|
+
receiveVideo: this._remoteOfferMediaDirections.video.includes("send")
|
|
2999
|
+
} : {};
|
|
2888
3000
|
this.options = {
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
3001
|
+
...options,
|
|
3002
|
+
audio: options.audio ?? offerDefaults.audio,
|
|
3003
|
+
video: options.video ?? offerDefaults.video,
|
|
3004
|
+
receiveAudio: options.receiveAudio ?? offerDefaults.receiveAudio ?? PreferencesContainer.instance.receiveAudio,
|
|
3005
|
+
receiveVideo: options.receiveVideo ?? offerDefaults.receiveVideo ?? PreferencesContainer.instance.receiveVideo
|
|
2892
3006
|
};
|
|
2893
3007
|
this.localStreamController = new LocalStreamController({
|
|
2894
3008
|
propose: this.propose,
|
|
@@ -3033,7 +3147,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
3033
3147
|
};
|
|
3034
3148
|
}
|
|
3035
3149
|
get inputVideoDeviceConstraints() {
|
|
3036
|
-
if (this.options.video
|
|
3150
|
+
if (!this.options.video && !this.options.inputVideoDeviceConstraints) return false;
|
|
3037
3151
|
return {
|
|
3038
3152
|
...this.options.inputVideoDeviceConstraints,
|
|
3039
3153
|
...this.deviceController.selectedVideoInputDeviceConstraints
|
|
@@ -3055,12 +3169,12 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
3055
3169
|
default: return {
|
|
3056
3170
|
...options,
|
|
3057
3171
|
offerToReceiveAudio: true,
|
|
3058
|
-
offerToReceiveVideo: Boolean(this.inputVideoDeviceConstraints)
|
|
3172
|
+
offerToReceiveVideo: this.options.receiveVideo ?? Boolean(this.inputVideoDeviceConstraints)
|
|
3059
3173
|
};
|
|
3060
3174
|
}
|
|
3061
3175
|
}
|
|
3062
3176
|
get answerOptions() {
|
|
3063
|
-
return {};
|
|
3177
|
+
return { iceRestart: this.firstSDPExchangeCompleted ? true : void 0 };
|
|
3064
3178
|
}
|
|
3065
3179
|
/**
|
|
3066
3180
|
* Initialize the RTCPeerConnection and setup event listeners.
|
|
@@ -3095,11 +3209,15 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
3095
3209
|
});
|
|
3096
3210
|
await this.updateSelectedInputDevice(kind, deviceInfo);
|
|
3097
3211
|
});
|
|
3098
|
-
await this.setupTrackHandling();
|
|
3099
|
-
this._initialized$.next(true);
|
|
3100
3212
|
if (this.type === "answer" && this.sdpInit) {
|
|
3213
|
+
await this.setupRemoteTracks();
|
|
3214
|
+
this._initialized$.next(true);
|
|
3101
3215
|
this.setupEventListeners();
|
|
3102
|
-
|
|
3216
|
+
this._isNegotiating$.next(true);
|
|
3217
|
+
await this._setRemoteDescription(this.sdpInit);
|
|
3218
|
+
} else {
|
|
3219
|
+
await this.setupTrackHandling();
|
|
3220
|
+
this._initialized$.next(true);
|
|
3103
3221
|
}
|
|
3104
3222
|
} catch (error) {
|
|
3105
3223
|
logger$11.error("[RTCPeerConnectionController] Initialization error:", error);
|
|
@@ -3197,6 +3315,35 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
3197
3315
|
default:
|
|
3198
3316
|
}
|
|
3199
3317
|
}
|
|
3318
|
+
/**
|
|
3319
|
+
* Accept an inbound call by creating the SDP answer.
|
|
3320
|
+
* Optionally override media options before the answer is generated.
|
|
3321
|
+
* Must be called after initialization for inbound (answer-type) connections.
|
|
3322
|
+
*/
|
|
3323
|
+
async acceptInbound(mediaOverrides) {
|
|
3324
|
+
if (mediaOverrides) {
|
|
3325
|
+
const { audio, video, receiveAudio, receiveVideo } = mediaOverrides;
|
|
3326
|
+
this.options = {
|
|
3327
|
+
...this.options,
|
|
3328
|
+
...audio !== void 0 ? { audio } : {},
|
|
3329
|
+
...video !== void 0 ? { video } : {},
|
|
3330
|
+
...receiveAudio !== void 0 ? { receiveAudio } : {},
|
|
3331
|
+
...receiveVideo !== void 0 ? { receiveVideo } : {}
|
|
3332
|
+
};
|
|
3333
|
+
this.transceiverController?.updateOptions({
|
|
3334
|
+
receiveAudio: this.receiveAudio,
|
|
3335
|
+
receiveVideo: this.receiveVideo
|
|
3336
|
+
});
|
|
3337
|
+
this.localStreamController.updateOptions({
|
|
3338
|
+
inputAudioDeviceConstraints: this.inputAudioDeviceConstraints,
|
|
3339
|
+
inputVideoDeviceConstraints: this.inputVideoDeviceConstraints
|
|
3340
|
+
});
|
|
3341
|
+
}
|
|
3342
|
+
await this.setupLocalTracks();
|
|
3343
|
+
const { answerOptions } = this;
|
|
3344
|
+
logger$11.debug("[RTCPeerConnectionController] Creating inbound answer with options:", answerOptions);
|
|
3345
|
+
await this.createAnswer(answerOptions);
|
|
3346
|
+
}
|
|
3200
3347
|
async handleOfferReceived() {
|
|
3201
3348
|
if (!this.sdpInit) throw new DependencyError("SDP initialization parameters are not set");
|
|
3202
3349
|
this._isNegotiating$.next(true);
|
|
@@ -3271,37 +3418,29 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
3271
3418
|
}
|
|
3272
3419
|
async setupLocalTracks() {
|
|
3273
3420
|
logger$11.debug("[RTCPeerConnectionController] Setting up local tracks/transceivers.");
|
|
3274
|
-
|
|
3275
|
-
if (
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
if (localStream) {
|
|
3282
|
-
if (this.transceiverController?.useAddStream ?? false) {
|
|
3283
|
-
logger$11.warn("[RTCPeerConnectionController] Using deprecated addStream API to add local stream.");
|
|
3284
|
-
this.peerConnection?.addStream(localStream);
|
|
3285
|
-
if (!this.isNegotiating) {
|
|
3286
|
-
logger$11.debug("[RTCPeerConnectionController] Forcing negotiationneeded after local tracks setup.");
|
|
3287
|
-
this.negotiationNeeded$.next();
|
|
3288
|
-
}
|
|
3289
|
-
return;
|
|
3421
|
+
const localStream = this.localStream ?? await this.localStreamController.buildLocalStream();
|
|
3422
|
+
if (this.transceiverController?.useAddStream ?? false) {
|
|
3423
|
+
logger$11.warn("[RTCPeerConnectionController] Using deprecated addStream API to add local stream.");
|
|
3424
|
+
this.peerConnection?.addStream(localStream);
|
|
3425
|
+
if (!this.isNegotiating) {
|
|
3426
|
+
logger$11.debug("[RTCPeerConnectionController] Forcing negotiationneeded after local tracks setup.");
|
|
3427
|
+
this.negotiationNeeded$.next();
|
|
3290
3428
|
}
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
}
|
|
3429
|
+
return;
|
|
3430
|
+
}
|
|
3431
|
+
for (const kind of ["audio", "video"]) {
|
|
3432
|
+
const tracks = (kind === "audio" ? localStream.getAudioTracks() : localStream.getVideoTracks()).map((track, index) => ({
|
|
3433
|
+
index,
|
|
3434
|
+
track
|
|
3435
|
+
}));
|
|
3436
|
+
for (const { index, track } of tracks) {
|
|
3437
|
+
this.localStreamController.addTrackEndedListener(track);
|
|
3438
|
+
if (this.transceiverController?.useAddTransceivers ?? false) {
|
|
3439
|
+
const transceivers = (kind === "audio" ? this.transceiverController?.audioTransceivers : this.transceiverController?.videoTransceivers) ?? [];
|
|
3440
|
+
await this.transceiverController?.setupTransceiverSender(track, localStream, transceivers[index]);
|
|
3441
|
+
} else {
|
|
3442
|
+
logger$11.debug(`[RTCPeerConnectionController] Using addTrack for local ${kind} track:`, track.id);
|
|
3443
|
+
this.peerConnection?.addTrack(track, localStream);
|
|
3305
3444
|
}
|
|
3306
3445
|
}
|
|
3307
3446
|
}
|
|
@@ -3318,7 +3457,12 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
3318
3457
|
if (!this.peerConnection) throw new DependencyError("RTCPeerConnection is not initialized");
|
|
3319
3458
|
this.peerConnection.ontrack = (event) => {
|
|
3320
3459
|
logger$11.debug("[RTCPeerConnectionController] Remote track received:", event.track.kind);
|
|
3321
|
-
this._remoteStream$.next(event.streams[0]);
|
|
3460
|
+
if (event.streams[0]) this._remoteStream$.next(event.streams[0]);
|
|
3461
|
+
else {
|
|
3462
|
+
const existingTracks = this._remoteStream$.value?.getTracks() ?? [];
|
|
3463
|
+
const newStream = new MediaStream([...existingTracks, event.track]);
|
|
3464
|
+
this._remoteStream$.next(newStream);
|
|
3465
|
+
}
|
|
3322
3466
|
};
|
|
3323
3467
|
await this.transceiverController?.setupRemoteTransceivers(this.type);
|
|
3324
3468
|
}
|
|
@@ -3417,7 +3561,7 @@ var RTCPeerConnectionController = class extends Destroyable {
|
|
|
3417
3561
|
});
|
|
3418
3562
|
}
|
|
3419
3563
|
get mediaDirections() {
|
|
3420
|
-
return this.transceiverController?.getMediaDirections() ?? {
|
|
3564
|
+
return this.transceiverController?.getMediaDirections() ?? this._remoteOfferMediaDirections ?? {
|
|
3421
3565
|
audio: "inactive",
|
|
3422
3566
|
video: "inactive"
|
|
3423
3567
|
};
|
|
@@ -3565,6 +3709,11 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
3565
3709
|
].includes(connectionState)))));
|
|
3566
3710
|
}
|
|
3567
3711
|
initSubscriptions() {
|
|
3712
|
+
this.subscribeTo(this.callJoinedEvent$, (event) => {
|
|
3713
|
+
const memberNodeId = event.room_session.members.find((m) => m.call_id === event.call_id)?.node_id;
|
|
3714
|
+
if (memberNodeId) this.setNodeIdIfNull(memberNodeId);
|
|
3715
|
+
if (event.member_id) this.setSelfIdIfNull(event.member_id);
|
|
3716
|
+
});
|
|
3568
3717
|
this.subscribeTo(this.vertoMedia$, (event) => {
|
|
3569
3718
|
logger$10.debug("[WebRTCManager] Received Verto media event (early media SDP):", event);
|
|
3570
3719
|
this._signalingStatus$.next("ringing");
|
|
@@ -3576,6 +3725,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
3576
3725
|
});
|
|
3577
3726
|
this.subscribeTo(this.vertoAnswer$, (event) => {
|
|
3578
3727
|
logger$10.debug("[WebRTCManager] Received Verto answer event:", event);
|
|
3728
|
+
this._signalingStatus$.next("connecting");
|
|
3579
3729
|
const { sdp, callID } = event;
|
|
3580
3730
|
this._rtcPeerConnectionsMap.get(callID)?.updateAnswerStatus({
|
|
3581
3731
|
status: "received",
|
|
@@ -3595,6 +3745,28 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
3595
3745
|
this.sendVertoPong(vertoPing);
|
|
3596
3746
|
});
|
|
3597
3747
|
}
|
|
3748
|
+
/**
|
|
3749
|
+
* Set node_id/selfId only when the current value is null.
|
|
3750
|
+
*
|
|
3751
|
+
* During reattach, `call.joined` and `verto.answer` events can deliver
|
|
3752
|
+
* these identifiers before the `verto.invite` RPC response (`CALL CREATED`)
|
|
3753
|
+
* arrives. These methods let early events populate them eagerly so that
|
|
3754
|
+
* downstream RPC calls (e.g. `call.layout.list`) don't fail with empty
|
|
3755
|
+
* identifiers. `processInviteResponse()` remains the authoritative source
|
|
3756
|
+
* and always overwrites unconditionally.
|
|
3757
|
+
*/
|
|
3758
|
+
setNodeIdIfNull(nodeId) {
|
|
3759
|
+
if (!this._nodeId$.value && nodeId) {
|
|
3760
|
+
logger$10.debug(`[WebRTCManager] Early node_id set: ${nodeId}`);
|
|
3761
|
+
this._nodeId$.next(nodeId);
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3764
|
+
setSelfIdIfNull(selfId) {
|
|
3765
|
+
if (!this._selfId$.value && selfId) {
|
|
3766
|
+
logger$10.debug(`[WebRTCManager] Early selfId set: ${selfId}`);
|
|
3767
|
+
this._selfId$.next(selfId);
|
|
3768
|
+
}
|
|
3769
|
+
}
|
|
3598
3770
|
async sendVertoPong(vertoPing) {
|
|
3599
3771
|
try {
|
|
3600
3772
|
const vertoPongMessage = VertoPong({ ...vertoPing });
|
|
@@ -3618,6 +3790,9 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
3618
3790
|
get selfId() {
|
|
3619
3791
|
return this._selfId$.value;
|
|
3620
3792
|
}
|
|
3793
|
+
get callJoinedEvent$() {
|
|
3794
|
+
return this.webRtcCallSession.callEvent$.pipe(filter(isCallJoinedPayload), takeUntil(this.destroyed$));
|
|
3795
|
+
}
|
|
3621
3796
|
get vertoMedia$() {
|
|
3622
3797
|
return this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoMediaInnerParams, "params"), takeUntil(this.destroyed$));
|
|
3623
3798
|
}
|
|
@@ -3647,13 +3822,13 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
3647
3822
|
if (response.error) {
|
|
3648
3823
|
const error = new JSONRPCError(response.error.code, response.error.message, response.error.data);
|
|
3649
3824
|
this.onError?.(error);
|
|
3650
|
-
|
|
3825
|
+
return response;
|
|
3651
3826
|
}
|
|
3652
3827
|
const innerResult = getValueFrom(response, "result.result");
|
|
3653
3828
|
if (innerResult?.error) {
|
|
3654
3829
|
const error = new JSONRPCError(innerResult.error.code, innerResult.error.message, innerResult.error.data);
|
|
3655
3830
|
this.onError?.(error);
|
|
3656
|
-
|
|
3831
|
+
return response;
|
|
3657
3832
|
}
|
|
3658
3833
|
return response;
|
|
3659
3834
|
}
|
|
@@ -3673,7 +3848,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
3673
3848
|
}
|
|
3674
3849
|
} catch (error) {
|
|
3675
3850
|
logger$10.error(`[WebRTCManager] Error sending Verto ${vertoMethod}:`, error);
|
|
3676
|
-
|
|
3851
|
+
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
3677
3852
|
}
|
|
3678
3853
|
}
|
|
3679
3854
|
async processModifyResponse(response, rtcPeerConnController) {
|
|
@@ -3687,12 +3862,14 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
3687
3862
|
});
|
|
3688
3863
|
} catch (error) {
|
|
3689
3864
|
logger$10.warn("[WebRTCManager] Error processing modify response:", error);
|
|
3690
|
-
|
|
3865
|
+
const modifyError = error instanceof Error ? error : new Error(String(error), { cause: error });
|
|
3866
|
+
this.onError?.(modifyError);
|
|
3691
3867
|
}
|
|
3692
3868
|
}
|
|
3693
3869
|
}
|
|
3694
3870
|
processInviteResponse(response, rtcPeerConnController) {
|
|
3695
3871
|
if (!response.error && getValueFrom(response, "result.result.result.message") === "CALL CREATED") {
|
|
3872
|
+
this._signalingStatus$.next("trying");
|
|
3696
3873
|
this._nodeId$.next(getValueFrom(response, "result.node_id") ?? null);
|
|
3697
3874
|
const memberId = getValueFrom(response, "result.result.result.memberID") ?? null;
|
|
3698
3875
|
const callId = getValueFrom(response, "result.result.result.callID") ?? null;
|
|
@@ -3747,6 +3924,36 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
3747
3924
|
this.subscribeTo(rtcPeerConnController.errors$, (error) => {
|
|
3748
3925
|
this.onError?.(error);
|
|
3749
3926
|
});
|
|
3927
|
+
if (options.initOffer) this.handleInboundAnswer(rtcPeerConnController);
|
|
3928
|
+
}
|
|
3929
|
+
async handleInboundAnswer(rtcPeerConnController) {
|
|
3930
|
+
logger$10.debug("[WebRTCManager] Waiting for inbound call to be accepted or rejected");
|
|
3931
|
+
const vertoByeOrAccepted = await firstValueFrom(race(this.vertoBye$, this.webRtcCallSession.answered$).pipe(takeUntil(this.destroyed$))).catch(() => null);
|
|
3932
|
+
if (vertoByeOrAccepted === null) {
|
|
3933
|
+
logger$10.debug("[WebRTCManager] Inbound answer handler aborted (destroyed).");
|
|
3934
|
+
return;
|
|
3935
|
+
}
|
|
3936
|
+
if (isVertoByeMessage(vertoByeOrAccepted)) {
|
|
3937
|
+
logger$10.info("[WebRTCManager] Inbound call ended by remote before answer.");
|
|
3938
|
+
this.callSession?.destroy();
|
|
3939
|
+
} else if (!vertoByeOrAccepted) {
|
|
3940
|
+
logger$10.info("[WebRTCManager] Inbound call rejected by user.");
|
|
3941
|
+
try {
|
|
3942
|
+
await this.bye("USER_BUSY");
|
|
3943
|
+
} finally {
|
|
3944
|
+
this._signalingStatus$.next("disconnected");
|
|
3945
|
+
this.callSession?.destroy();
|
|
3946
|
+
}
|
|
3947
|
+
} else {
|
|
3948
|
+
logger$10.debug("[WebRTCManager] Inbound call accepted, creating SDP answer");
|
|
3949
|
+
const answerOptions = this.webRtcCallSession.answerMediaOptions;
|
|
3950
|
+
try {
|
|
3951
|
+
await rtcPeerConnController.acceptInbound(answerOptions);
|
|
3952
|
+
} catch (error) {
|
|
3953
|
+
logger$10.error("[WebRTCManager] Error creating inbound answer:", error);
|
|
3954
|
+
this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
3955
|
+
}
|
|
3956
|
+
}
|
|
3750
3957
|
}
|
|
3751
3958
|
setupVertoAttachHandler() {
|
|
3752
3959
|
this.subscribeTo(this.vertoAttach$, async (vertoAttach) => {
|
|
@@ -3764,7 +3971,7 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
3764
3971
|
});
|
|
3765
3972
|
}
|
|
3766
3973
|
initObservables(rtcPeerConnController) {
|
|
3767
|
-
this.mediaDirections$ = rtcPeerConnController.connectionState$.pipe(filter((state) => state === "connected"),
|
|
3974
|
+
this.mediaDirections$ = rtcPeerConnController.connectionState$.pipe(filter((state) => state === "connected"), map(() => rtcPeerConnController.mediaDirections), startWith(rtcPeerConnController.mediaDirections), takeUntil(this.destroyed$));
|
|
3768
3975
|
this.localStream$ = rtcPeerConnController.localStream$.pipe(filterNull(), takeUntil(this.destroyed$));
|
|
3769
3976
|
this.remoteStream$ = rtcPeerConnController.remoteStream$.pipe(filterNull(), takeUntil(this.destroyed$));
|
|
3770
3977
|
}
|
|
@@ -3780,7 +3987,6 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
3780
3987
|
});
|
|
3781
3988
|
this.sendLocalDescriptionOnceAccepted(vertoMessageRequest, rtcPeerConnController);
|
|
3782
3989
|
} else if (initial) {
|
|
3783
|
-
this._signalingStatus$.next("trying");
|
|
3784
3990
|
const vertoMessageRequest = VertoInvite({
|
|
3785
3991
|
dialogParams,
|
|
3786
3992
|
sdp
|
|
@@ -4086,8 +4292,8 @@ var WebRTCCall = class extends Destroyable {
|
|
|
4086
4292
|
this.clientSession = clientSession;
|
|
4087
4293
|
this.options = options;
|
|
4088
4294
|
this.address = address;
|
|
4089
|
-
this.
|
|
4090
|
-
this.
|
|
4295
|
+
this._errors$ = this.createReplaySubject(1);
|
|
4296
|
+
this._lastMergedStatus = "new";
|
|
4091
4297
|
this._answered$ = this.createReplaySubject();
|
|
4092
4298
|
this._holdState = false;
|
|
4093
4299
|
this._userVariables$ = this.createBehaviorSubject({ ...PreferencesContainer.instance.userVariables });
|
|
@@ -4108,18 +4314,28 @@ var WebRTCCall = class extends Destroyable {
|
|
|
4108
4314
|
const managers = initialization.initializeManagers(this);
|
|
4109
4315
|
this.vertoManager = managers.vertoManager;
|
|
4110
4316
|
this.callEventsManager = managers.callEventsManager;
|
|
4111
|
-
if (options.initOffer)
|
|
4112
|
-
|
|
4317
|
+
if (options.initOffer) {
|
|
4318
|
+
this._status$ = this.createBehaviorSubject("ringing");
|
|
4319
|
+
this._lastMergedStatus = "ringing";
|
|
4320
|
+
} else this._status$ = this.createBehaviorSubject("new");
|
|
4113
4321
|
const { deviceController } = initialization;
|
|
4114
4322
|
this.participantFactory = new ParticipantFactory(this.executeMethod.bind(this), this.vertoManager, deviceController);
|
|
4115
4323
|
}
|
|
4116
4324
|
/** Observable stream of errors from media, signaling, and peer connection layers. */
|
|
4117
4325
|
get errors$() {
|
|
4118
|
-
return this._errors$.asObservable();
|
|
4326
|
+
return this.deferEmission(this._errors$.asObservable());
|
|
4119
4327
|
}
|
|
4120
|
-
/**
|
|
4121
|
-
|
|
4122
|
-
|
|
4328
|
+
/**
|
|
4329
|
+
* @internal Push an error to the call's error stream.
|
|
4330
|
+
* Fatal errors automatically transition the call to `'failed'` and destroy it.
|
|
4331
|
+
*/
|
|
4332
|
+
emitError(callError) {
|
|
4333
|
+
if (this._status$.value === "destroyed" || this._status$.value === "failed") return;
|
|
4334
|
+
this._errors$.next(callError);
|
|
4335
|
+
if (callError.fatal) {
|
|
4336
|
+
this._status$.next("failed");
|
|
4337
|
+
this.destroy();
|
|
4338
|
+
}
|
|
4123
4339
|
}
|
|
4124
4340
|
/** Whether this call is `'inbound'` or `'outbound'`. */
|
|
4125
4341
|
get direction() {
|
|
@@ -4127,7 +4343,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
4127
4343
|
}
|
|
4128
4344
|
/** Observable of the address associated with this call. */
|
|
4129
4345
|
get address$() {
|
|
4130
|
-
return from([this.address]);
|
|
4346
|
+
return this.deferEmission(from([this.address])).pipe(takeUntil(this._destroyed$));
|
|
4131
4347
|
}
|
|
4132
4348
|
/** Display name of the caller. */
|
|
4133
4349
|
get fromName() {
|
|
@@ -4159,7 +4375,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
4159
4375
|
}
|
|
4160
4376
|
/** Current snapshot of all participants in the call. */
|
|
4161
4377
|
get participants() {
|
|
4162
|
-
return
|
|
4378
|
+
return this.callEventsManager.participants;
|
|
4163
4379
|
}
|
|
4164
4380
|
/** The local participant, or `null` if not yet joined. */
|
|
4165
4381
|
get self() {
|
|
@@ -4168,7 +4384,6 @@ var WebRTCCall = class extends Destroyable {
|
|
|
4168
4384
|
async toggleLock() {
|
|
4169
4385
|
const method = this.locked ? "call.unlock" : "call.lock";
|
|
4170
4386
|
await this.executeMethod(this.selfId ?? "", method, {});
|
|
4171
|
-
throw new UnimplementedError();
|
|
4172
4387
|
}
|
|
4173
4388
|
async toggleHold() {
|
|
4174
4389
|
if (this._holdState) await this.vertoManager.unhold();
|
|
@@ -4189,7 +4404,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
4189
4404
|
}
|
|
4190
4405
|
/** Observable of layout layer positions for all participants. */
|
|
4191
4406
|
get layoutLayers$() {
|
|
4192
|
-
return this.callEventsManager.layoutLayers
|
|
4407
|
+
return this.deferEmission(this.callEventsManager.layoutLayers$).pipe(takeUntil(this._destroyed$));
|
|
4193
4408
|
}
|
|
4194
4409
|
/** Current snapshot of layout layers. */
|
|
4195
4410
|
get layoutLayers() {
|
|
@@ -4203,72 +4418,80 @@ var WebRTCCall = class extends Destroyable {
|
|
|
4203
4418
|
params
|
|
4204
4419
|
});
|
|
4205
4420
|
try {
|
|
4206
|
-
|
|
4421
|
+
const response = await this.clientSession.execute(request);
|
|
4422
|
+
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);
|
|
4423
|
+
return response;
|
|
4207
4424
|
} catch (error) {
|
|
4208
4425
|
logger$9.error(`[Call] Error executing method ${method} with params`, params, error);
|
|
4209
4426
|
throw error;
|
|
4210
4427
|
}
|
|
4211
4428
|
}
|
|
4212
4429
|
buildMethodParams(target, args) {
|
|
4213
|
-
const
|
|
4214
|
-
node_id: this.nodeId,
|
|
4215
|
-
call_id: this.id
|
|
4430
|
+
const self = {
|
|
4431
|
+
node_id: this.nodeId ?? "",
|
|
4432
|
+
call_id: this.id,
|
|
4433
|
+
member_id: this.vertoManager.selfId ?? ""
|
|
4434
|
+
};
|
|
4435
|
+
if (typeof target === "object") return {
|
|
4436
|
+
...args,
|
|
4437
|
+
self,
|
|
4438
|
+
targets: [target]
|
|
4216
4439
|
};
|
|
4217
4440
|
return {
|
|
4218
4441
|
...args,
|
|
4219
|
-
self
|
|
4220
|
-
...reference,
|
|
4221
|
-
member_id: this.vertoManager.selfId
|
|
4222
|
-
},
|
|
4442
|
+
self,
|
|
4223
4443
|
target: {
|
|
4224
|
-
|
|
4444
|
+
node_id: this.nodeId ?? "",
|
|
4445
|
+
call_id: this.id,
|
|
4225
4446
|
member_id: target
|
|
4226
4447
|
}
|
|
4227
4448
|
};
|
|
4228
4449
|
}
|
|
4229
4450
|
/** Observable of the current call status (e.g. `'ringing'`, `'connected'`). */
|
|
4230
4451
|
get status$() {
|
|
4231
|
-
return this.
|
|
4452
|
+
return this.publicCachedObservable("status$", () => merge(this._status$.asObservable(), this.vertoManager.signalingStatus$).pipe(distinctUntilChanged(), tap((status) => {
|
|
4453
|
+
this._lastMergedStatus = status;
|
|
4454
|
+
})));
|
|
4232
4455
|
}
|
|
4233
4456
|
/** Observable of the participants list, emits on join/leave/update. */
|
|
4234
4457
|
get participants$() {
|
|
4235
|
-
return this.callEventsManager.participants
|
|
4458
|
+
return this.deferEmission(this.callEventsManager.participants$).pipe(takeUntil(this._destroyed$));
|
|
4236
4459
|
}
|
|
4237
4460
|
/** Observable of the local (self) participant. */
|
|
4238
4461
|
get self$() {
|
|
4239
|
-
return this.callEventsManager.self
|
|
4462
|
+
return this.deferEmission(this.callEventsManager.self$).pipe(takeUntil(this._destroyed$));
|
|
4240
4463
|
}
|
|
4241
4464
|
/** Observable indicating whether the call is being recorded. */
|
|
4242
4465
|
get recording$() {
|
|
4243
|
-
return this.callEventsManager.recording
|
|
4466
|
+
return this.deferEmission(this.callEventsManager.recording$).pipe(takeUntil(this._destroyed$));
|
|
4244
4467
|
}
|
|
4245
4468
|
/** Observable indicating whether the call is being streamed. */
|
|
4246
4469
|
get streaming$() {
|
|
4247
|
-
return this.callEventsManager.streaming
|
|
4470
|
+
return this.deferEmission(this.callEventsManager.streaming$).pipe(takeUntil(this._destroyed$));
|
|
4248
4471
|
}
|
|
4249
4472
|
/** Observable indicating whether raise-hand priority is active. */
|
|
4250
4473
|
get raiseHandPriority$() {
|
|
4251
|
-
return this.callEventsManager.raiseHandPriority
|
|
4474
|
+
return this.deferEmission(this.callEventsManager.raiseHandPriority$).pipe(takeUntil(this._destroyed$));
|
|
4252
4475
|
}
|
|
4253
4476
|
/** Observable indicating whether the call room is locked. */
|
|
4254
4477
|
get locked$() {
|
|
4255
|
-
return this.callEventsManager.locked
|
|
4478
|
+
return this.deferEmission(this.callEventsManager.locked$).pipe(takeUntil(this._destroyed$));
|
|
4256
4479
|
}
|
|
4257
4480
|
/** Observable of custom metadata associated with the call. */
|
|
4258
4481
|
get meta$() {
|
|
4259
|
-
return this.callEventsManager.meta
|
|
4482
|
+
return this.deferEmission(this.callEventsManager.meta$).pipe(takeUntil(this._destroyed$));
|
|
4260
4483
|
}
|
|
4261
4484
|
/** Observable of the call's capability flags. */
|
|
4262
4485
|
get capabilities$() {
|
|
4263
|
-
return this.callEventsManager.capabilities
|
|
4486
|
+
return this.deferEmission(this.callEventsManager.capabilities$).pipe(takeUntil(this._destroyed$));
|
|
4264
4487
|
}
|
|
4265
4488
|
/** Observable of the current layout name. */
|
|
4266
4489
|
get layout$() {
|
|
4267
|
-
return this.callEventsManager.layout
|
|
4490
|
+
return this.deferEmission(this.callEventsManager.layout$).pipe(takeUntil(this._destroyed$));
|
|
4268
4491
|
}
|
|
4269
4492
|
/** Current call status. */
|
|
4270
4493
|
get status() {
|
|
4271
|
-
return this.
|
|
4494
|
+
return this._lastMergedStatus;
|
|
4272
4495
|
}
|
|
4273
4496
|
/** Whether the call is currently being recorded. */
|
|
4274
4497
|
get recording() {
|
|
@@ -4296,7 +4519,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
4296
4519
|
}
|
|
4297
4520
|
/** Observable of available layout names. */
|
|
4298
4521
|
get layouts$() {
|
|
4299
|
-
return this.callEventsManager.layouts
|
|
4522
|
+
return this.deferEmission(this.callEventsManager.layouts$).pipe(takeUntil(this._destroyed$));
|
|
4300
4523
|
}
|
|
4301
4524
|
/** Current snapshot of available layout names. */
|
|
4302
4525
|
get layouts() {
|
|
@@ -4304,7 +4527,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
4304
4527
|
}
|
|
4305
4528
|
/** Observable of the local media stream (camera/microphone). */
|
|
4306
4529
|
get localStream$() {
|
|
4307
|
-
return this.vertoManager.localStream
|
|
4530
|
+
return this.deferEmission(this.vertoManager.localStream$).pipe(takeUntil(this._destroyed$));
|
|
4308
4531
|
}
|
|
4309
4532
|
/** Current local media stream, or `null` if not available. */
|
|
4310
4533
|
get localStream() {
|
|
@@ -4312,7 +4535,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
4312
4535
|
}
|
|
4313
4536
|
/** Observable of the remote media stream from the far end. */
|
|
4314
4537
|
get remoteStream$() {
|
|
4315
|
-
return this.vertoManager.remoteStream
|
|
4538
|
+
return this.deferEmission(this.vertoManager.remoteStream$).pipe(takeUntil(this._destroyed$));
|
|
4316
4539
|
}
|
|
4317
4540
|
/** Current remote media stream, or `null` if not available. */
|
|
4318
4541
|
get remoteStream() {
|
|
@@ -4320,7 +4543,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
4320
4543
|
}
|
|
4321
4544
|
/** Observable of custom user variables associated with the call. */
|
|
4322
4545
|
get userVariables$() {
|
|
4323
|
-
return this._userVariables$.asObservable();
|
|
4546
|
+
return this.deferEmission(this._userVariables$.asObservable());
|
|
4324
4547
|
}
|
|
4325
4548
|
/** a copy of the current custom user variables of the call. */
|
|
4326
4549
|
get userVariables() {
|
|
@@ -4340,7 +4563,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
4340
4563
|
}
|
|
4341
4564
|
/** Observable of the current audio/video send/receive directions. */
|
|
4342
4565
|
get mediaDirections$() {
|
|
4343
|
-
return this.vertoManager.mediaDirections
|
|
4566
|
+
return this.deferEmission(this.vertoManager.mediaDirections$).pipe(takeUntil(this._destroyed$));
|
|
4344
4567
|
}
|
|
4345
4568
|
/** Current audio/video send/receive directions. */
|
|
4346
4569
|
get mediaDirections() {
|
|
@@ -4386,31 +4609,31 @@ var WebRTCCall = class extends Destroyable {
|
|
|
4386
4609
|
}
|
|
4387
4610
|
/** Observable of call-updated events. */
|
|
4388
4611
|
get callUpdated$() {
|
|
4389
|
-
return this.
|
|
4612
|
+
return this.publicCachedObservable("callUpdated$", () => this.callSessionEvents$.pipe(filterAs(isCallUpdatedMetadata, "params"), takeUntil(this.destroyed$)));
|
|
4390
4613
|
}
|
|
4391
4614
|
/** Observable of member-joined events. */
|
|
4392
4615
|
get memberJoined$() {
|
|
4393
|
-
return this.
|
|
4616
|
+
return this.publicCachedObservable("memberJoined$", () => this.callSessionEvents$.pipe(filterAs(isMemberJoinedMetadata, "params"), takeUntil(this.destroyed$)));
|
|
4394
4617
|
}
|
|
4395
4618
|
/** Observable of member-left events. */
|
|
4396
4619
|
get memberLeft$() {
|
|
4397
|
-
return this.
|
|
4620
|
+
return this.publicCachedObservable("memberLeft$", () => this.callSessionEvents$.pipe(filterAs(isMemberLeftMetadata, "params"), takeUntil(this.destroyed$)));
|
|
4398
4621
|
}
|
|
4399
4622
|
/** Observable of member-updated events (mute, volume, etc.). */
|
|
4400
4623
|
get memberUpdated$() {
|
|
4401
|
-
return this.
|
|
4624
|
+
return this.publicCachedObservable("memberUpdated$", () => this.callSessionEvents$.pipe(filterAs(isMemberUpdatedMetadata, "params"), takeUntil(this.destroyed$)));
|
|
4402
4625
|
}
|
|
4403
4626
|
/** Observable of member-talking events (speech start/stop). */
|
|
4404
4627
|
get memberTalking$() {
|
|
4405
|
-
return this.
|
|
4628
|
+
return this.publicCachedObservable("memberTalking$", () => this.callSessionEvents$.pipe(filterAs(isMemberTalkingMetadata, "params"), takeUntil(this.destroyed$)));
|
|
4406
4629
|
}
|
|
4407
4630
|
/** Observable of call state-change events. */
|
|
4408
4631
|
get callStates$() {
|
|
4409
|
-
return this.
|
|
4632
|
+
return this.publicCachedObservable("callStates$", () => this.callSessionEvents$.pipe(filterAs(isCallStateMetadata, "params"), takeUntil(this.destroyed$)));
|
|
4410
4633
|
}
|
|
4411
4634
|
/** Observable of layout-changed events. */
|
|
4412
4635
|
get layoutUpdates$() {
|
|
4413
|
-
return this.
|
|
4636
|
+
return this.publicCachedObservable("layoutUpdates$", () => this.callSessionEvents$.pipe(filterAs(isLayoutChangedMetadata, "params"), takeUntil(this.destroyed$)));
|
|
4414
4637
|
}
|
|
4415
4638
|
/** Underlying `RTCPeerConnection`, for advanced use cases. */
|
|
4416
4639
|
get rtcPeerConnection() {
|
|
@@ -4418,7 +4641,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
4418
4641
|
}
|
|
4419
4642
|
/** Observable of raw signaling events as plain objects. */
|
|
4420
4643
|
get signalingEvent$() {
|
|
4421
|
-
return this.
|
|
4644
|
+
return this.publicCachedObservable("signalingEvent$", () => this.callEvent$.pipe(map((event) => JSON.parse(JSON.stringify(event)))));
|
|
4422
4645
|
}
|
|
4423
4646
|
/** Observable of WebRTC-specific signaling messages. */
|
|
4424
4647
|
get webrtcMessages$() {
|
|
@@ -4445,17 +4668,22 @@ var WebRTCCall = class extends Destroyable {
|
|
|
4445
4668
|
async sendDigits(dtmf) {
|
|
4446
4669
|
return this.vertoManager.sendDigits(dtmf);
|
|
4447
4670
|
}
|
|
4448
|
-
/** Accepts an inbound call. */
|
|
4449
|
-
answer() {
|
|
4671
|
+
/** Accepts an inbound call, optionally overriding media options for the answer. */
|
|
4672
|
+
answer(options) {
|
|
4673
|
+
this._answerMediaOptions = options;
|
|
4450
4674
|
this._answered$.next(true);
|
|
4451
4675
|
}
|
|
4676
|
+
/** Media options provided when answering. Used internally by the VertoManager. */
|
|
4677
|
+
get answerMediaOptions() {
|
|
4678
|
+
return this._answerMediaOptions;
|
|
4679
|
+
}
|
|
4452
4680
|
/** Rejects an inbound call. */
|
|
4453
4681
|
reject() {
|
|
4454
4682
|
this._answered$.next(false);
|
|
4455
4683
|
}
|
|
4456
4684
|
/** Observable that emits `true` when answered, `false` when rejected. */
|
|
4457
4685
|
get answered$() {
|
|
4458
|
-
return this._answered$.asObservable();
|
|
4686
|
+
return this.deferEmission(this._answered$.asObservable());
|
|
4459
4687
|
}
|
|
4460
4688
|
/**
|
|
4461
4689
|
* Sets the call layout and participant positions.
|
|
@@ -4476,10 +4704,10 @@ var WebRTCCall = class extends Destroyable {
|
|
|
4476
4704
|
}
|
|
4477
4705
|
/** Destroys the call, releasing all resources and subscriptions. */
|
|
4478
4706
|
destroy() {
|
|
4707
|
+
if (this._status$.value === "destroyed") return;
|
|
4479
4708
|
this._status$.next("destroyed");
|
|
4480
4709
|
this.vertoManager.destroy();
|
|
4481
4710
|
this.callEventsManager.destroy();
|
|
4482
|
-
this.participantsMap.clear();
|
|
4483
4711
|
super.destroy();
|
|
4484
4712
|
}
|
|
4485
4713
|
};
|
|
@@ -4487,6 +4715,23 @@ var WebRTCCall = class extends Destroyable {
|
|
|
4487
4715
|
//#endregion
|
|
4488
4716
|
//#region src/managers/CallFactory.ts
|
|
4489
4717
|
/**
|
|
4718
|
+
* Infers the semantic error category from a raw Error thrown by VertoManager
|
|
4719
|
+
* or an RTCPeerConnection layer.
|
|
4720
|
+
*/
|
|
4721
|
+
function inferCallErrorKind(error) {
|
|
4722
|
+
if (error instanceof RPCTimeoutError) return "timeout";
|
|
4723
|
+
if (error instanceof JSONRPCError) return "signaling";
|
|
4724
|
+
if (error instanceof MediaTrackError) return "media";
|
|
4725
|
+
if (error instanceof WebSocketConnectionError || error instanceof TransportConnectionError) return "network";
|
|
4726
|
+
return "internal";
|
|
4727
|
+
}
|
|
4728
|
+
/** Determines whether an error should be fatal (destroy the call). */
|
|
4729
|
+
function isFatalError(error) {
|
|
4730
|
+
if (error instanceof VertoPongError) return false;
|
|
4731
|
+
if (error instanceof MediaTrackError) return false;
|
|
4732
|
+
return true;
|
|
4733
|
+
}
|
|
4734
|
+
/**
|
|
4490
4735
|
* Factory for creating WebRTCCall instances with proper manager wiring.
|
|
4491
4736
|
* Eliminates circular dependencies by centralizing Call and Manager creation.
|
|
4492
4737
|
*/
|
|
@@ -4507,7 +4752,13 @@ var CallFactory = class {
|
|
|
4507
4752
|
vertoManager: new WebRTCVertoManager(callInstance, this.attachManager, this.deviceController, this.webRTCApiProvider, {
|
|
4508
4753
|
nodeId: options.nodeId,
|
|
4509
4754
|
onError: (error) => {
|
|
4510
|
-
|
|
4755
|
+
const callError = {
|
|
4756
|
+
kind: inferCallErrorKind(error),
|
|
4757
|
+
fatal: isFatalError(error),
|
|
4758
|
+
error,
|
|
4759
|
+
callId: callInstance.id
|
|
4760
|
+
};
|
|
4761
|
+
callInstance.emitError(callError);
|
|
4511
4762
|
}
|
|
4512
4763
|
}),
|
|
4513
4764
|
callEventsManager: new CallEventsManager(callInstance)
|
|
@@ -4884,9 +5135,15 @@ var PendingRPC = class PendingRPC {
|
|
|
4884
5135
|
return () => signal.removeEventListener("abort", abortHandler);
|
|
4885
5136
|
}) : NEVER).subscribe({
|
|
4886
5137
|
next: (response) => {
|
|
4887
|
-
logger$7.debug(`[PendingRPC(${this.id}) request:${request.id}] Resolving promise with response:`, response);
|
|
4888
5138
|
isSettled = true;
|
|
4889
|
-
|
|
5139
|
+
if (response.error) {
|
|
5140
|
+
const rpcError = new JSONRPCError(response.error.code, response.error.message, response.error.data, void 0, request.id);
|
|
5141
|
+
logger$7.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with RPC error:`, rpcError);
|
|
5142
|
+
reject(rpcError);
|
|
5143
|
+
} else {
|
|
5144
|
+
logger$7.debug(`[PendingRPC(${this.id}) request:${request.id}] Resolving promise with response:`, response);
|
|
5145
|
+
resolve(response);
|
|
5146
|
+
}
|
|
4890
5147
|
subscription.unsubscribe();
|
|
4891
5148
|
},
|
|
4892
5149
|
error: (error) => {
|
|
@@ -4942,7 +5199,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
4942
5199
|
revision: 0
|
|
4943
5200
|
};
|
|
4944
5201
|
this._authorization$ = this.createBehaviorSubject(void 0);
|
|
4945
|
-
this._errors$ = this.
|
|
5202
|
+
this._errors$ = this.createReplaySubject(1);
|
|
4946
5203
|
this._authenticated$ = this.createBehaviorSubject(false);
|
|
4947
5204
|
this._subscriberInfo$ = this.createBehaviorSubject(null);
|
|
4948
5205
|
this._calls$ = this.createBehaviorSubject({});
|
|
@@ -5170,6 +5427,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
5170
5427
|
...params,
|
|
5171
5428
|
...persistedParams
|
|
5172
5429
|
});
|
|
5430
|
+
this.transport.resetSessionEpoch();
|
|
5173
5431
|
const response = await lastValueFrom(from(this.transport.execute(rpcConnectRequest)).pipe(throwOnRPCError(), map((res) => res.result), filter(isRPCConnectResult), tap(() => {
|
|
5174
5432
|
logger$6.debug("[Session] Response passed filter, processing authentication result");
|
|
5175
5433
|
}), take(1), catchError((err) => {
|
|
@@ -5212,12 +5470,13 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
5212
5470
|
}
|
|
5213
5471
|
async createOutboundCall(destination, options = {}) {
|
|
5214
5472
|
const destinationURI = destination instanceof Address ? destination.defaultChannel : destination;
|
|
5473
|
+
let callSession;
|
|
5215
5474
|
try {
|
|
5216
|
-
|
|
5475
|
+
callSession = await this.createCall({
|
|
5217
5476
|
to: destinationURI,
|
|
5218
5477
|
...options
|
|
5219
5478
|
});
|
|
5220
|
-
await firstValueFrom(callSession.selfId$.pipe(filter((id) => Boolean(id)), take(1), timeout(this.callCreateTimeout)));
|
|
5479
|
+
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)))));
|
|
5221
5480
|
this._calls$.next({
|
|
5222
5481
|
[`${callSession.id}`]: callSession,
|
|
5223
5482
|
...this._calls$.value
|
|
@@ -5225,7 +5484,8 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
5225
5484
|
return callSession;
|
|
5226
5485
|
} catch (error) {
|
|
5227
5486
|
logger$6.error("[Session] Error creating outbound call:", error);
|
|
5228
|
-
|
|
5487
|
+
callSession?.destroy();
|
|
5488
|
+
const callError = new CallCreateError(error instanceof TimeoutError ? "Call create timeout" : "Call creation failed", error, "outbound");
|
|
5229
5489
|
this._errors$.next(callError);
|
|
5230
5490
|
throw callError;
|
|
5231
5491
|
}
|
|
@@ -5251,7 +5511,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
5251
5511
|
return callSession;
|
|
5252
5512
|
} catch (error) {
|
|
5253
5513
|
logger$6.error("[Session] Error creating call session:", error);
|
|
5254
|
-
throw new CallCreateError("Call create error", error);
|
|
5514
|
+
throw new CallCreateError("Call create error", error, options.initOffer ? "inbound" : "outbound");
|
|
5255
5515
|
}
|
|
5256
5516
|
}
|
|
5257
5517
|
destroy() {
|
|
@@ -5497,7 +5757,7 @@ var WebSocketController = class WebSocketController extends Destroyable {
|
|
|
5497
5757
|
this.shouldReconnect = false;
|
|
5498
5758
|
this._status$ = this.createBehaviorSubject("disconnected");
|
|
5499
5759
|
this._incomingMessages$ = this.createSubject();
|
|
5500
|
-
this._errors$ = this.
|
|
5760
|
+
this._errors$ = this.createReplaySubject(1);
|
|
5501
5761
|
this.reconnectDelayMin = options.reconnectDelayMin ?? WebSocketController.DEFAULT_RECONNECT_DELAY_MIN_MS;
|
|
5502
5762
|
this.reconnectDelayMax = options.reconnectDelayMax ?? WebSocketController.DEFAULT_RECONNECT_DELAY_MAX_MS;
|
|
5503
5763
|
this.connectionTimeout = options.connectionTimeout ?? WebSocketController.DEFAULT_CONNECTION_TIMEOUT_MS;
|
|
@@ -5647,7 +5907,30 @@ function isSignalwirePingRequest(value) {
|
|
|
5647
5907
|
//#endregion
|
|
5648
5908
|
//#region src/managers/TransportManager.ts
|
|
5649
5909
|
const logger$2 = getLogger();
|
|
5650
|
-
var TransportManager = class extends Destroyable {
|
|
5910
|
+
var TransportManager = class TransportManager extends Destroyable {
|
|
5911
|
+
/**
|
|
5912
|
+
* Normalise a server event timestamp to epoch seconds.
|
|
5913
|
+
*
|
|
5914
|
+
* The server uses two formats:
|
|
5915
|
+
* - `webrtc.message`: float epoch seconds (e.g. 1774372099.022817)
|
|
5916
|
+
* - all other events: int epoch microseconds (e.g. 1774372099925857)
|
|
5917
|
+
*
|
|
5918
|
+
* Values above 1e12 are treated as microseconds and divided by 1e6.
|
|
5919
|
+
*/
|
|
5920
|
+
static toEpochSeconds(ts) {
|
|
5921
|
+
const n = typeof ts === "string" ? Number(ts) : ts;
|
|
5922
|
+
return n > 0xe8d4a51000 ? n / 1e6 : n;
|
|
5923
|
+
}
|
|
5924
|
+
/**
|
|
5925
|
+
* Extract the event timestamp from a signalwire.event message.
|
|
5926
|
+
* Returns `null` for messages that have no timestamp
|
|
5927
|
+
* (e.g. signalwire.authorization.state, RPC responses).
|
|
5928
|
+
*/
|
|
5929
|
+
static extractEventTimestamp(message) {
|
|
5930
|
+
if (!isSignalwireRequest(message)) return null;
|
|
5931
|
+
if (message.params.event_type === "signalwire.authorization.state") return null;
|
|
5932
|
+
return TransportManager.toEpochSeconds(message.params.timestamp);
|
|
5933
|
+
}
|
|
5651
5934
|
constructor(storage, protocolKey, webSocketConstructor, relayHost, onError) {
|
|
5652
5935
|
super();
|
|
5653
5936
|
this.storage = storage;
|
|
@@ -5680,6 +5963,23 @@ var TransportManager = class extends Destroyable {
|
|
|
5680
5963
|
return true;
|
|
5681
5964
|
});
|
|
5682
5965
|
};
|
|
5966
|
+
this.discardStaleEvents = () => {
|
|
5967
|
+
return filter((message) => {
|
|
5968
|
+
const ts = TransportManager.extractEventTimestamp(message);
|
|
5969
|
+
if (ts === null) return true;
|
|
5970
|
+
if (this._sessionEpoch === null) {
|
|
5971
|
+
this._sessionEpoch = ts;
|
|
5972
|
+
return true;
|
|
5973
|
+
}
|
|
5974
|
+
if (ts < this._sessionEpoch) {
|
|
5975
|
+
const eventType = isSignalwireRequest(message) ? message.params.event_type : "unknown";
|
|
5976
|
+
logger$2.warn(`[Transport] Discarding stale event: ${eventType} (timestamp=${ts.toFixed(3)}, sessionEpoch=${this._sessionEpoch.toFixed(3)}, delta=${(this._sessionEpoch - ts).toFixed(3)}s)`);
|
|
5977
|
+
return false;
|
|
5978
|
+
}
|
|
5979
|
+
return true;
|
|
5980
|
+
});
|
|
5981
|
+
};
|
|
5982
|
+
this._sessionEpoch = null;
|
|
5683
5983
|
this._outgoingMessages$ = this.createSubject();
|
|
5684
5984
|
this._webSocketConnections = new WebSocketController(webSocketConstructor, relayHost, this._outgoingMessages$.asObservable(), {
|
|
5685
5985
|
connectionTimeout: PreferencesContainer.instance.connectionTimeout,
|
|
@@ -5704,7 +6004,14 @@ var TransportManager = class extends Destroyable {
|
|
|
5704
6004
|
return EMPTY;
|
|
5705
6005
|
}), share(), takeUntil(this.destroyed$));
|
|
5706
6006
|
this._jsonRPCResponse$ = this._jsonRPCMessage$.pipe(filter(isJSONRPCResponse));
|
|
5707
|
-
this._incomingEvent$ = this._jsonRPCMessage$.pipe(this.ackEvent(), this.replySignalwirePing(), filter((message) => !isJSONRPCResponse(message)), share(), takeUntil(this.destroyed$));
|
|
6007
|
+
this._incomingEvent$ = this._jsonRPCMessage$.pipe(this.ackEvent(), this.replySignalwirePing(), filter((message) => !isJSONRPCResponse(message)), this.discardStaleEvents(), share(), takeUntil(this.destroyed$));
|
|
6008
|
+
}
|
|
6009
|
+
/**
|
|
6010
|
+
* Reset the session epoch. Call this before each signalwire.connect
|
|
6011
|
+
* so that the first event after authentication establishes the new baseline.
|
|
6012
|
+
*/
|
|
6013
|
+
resetSessionEpoch() {
|
|
6014
|
+
this._sessionEpoch = null;
|
|
5708
6015
|
}
|
|
5709
6016
|
async setProtocol(protocol) {
|
|
5710
6017
|
this.protocol$.next(protocol);
|
|
@@ -5822,7 +6129,8 @@ const buildOptionsFromDestination = (destination) => {
|
|
|
5822
6129
|
const channel = new URLSearchParams(queryString).get("channel");
|
|
5823
6130
|
if (channel === "video") return {
|
|
5824
6131
|
audio: true,
|
|
5825
|
-
video: true
|
|
6132
|
+
video: true,
|
|
6133
|
+
receiveVideo: true
|
|
5826
6134
|
};
|
|
5827
6135
|
else if (channel === "audio") return {
|
|
5828
6136
|
audio: true,
|
|
@@ -5852,7 +6160,7 @@ var SignalWire = class extends Destroyable {
|
|
|
5852
6160
|
this._directory$ = this.createBehaviorSubject(void 0);
|
|
5853
6161
|
this._isConnected$ = this.createBehaviorSubject(false);
|
|
5854
6162
|
this._isRegistered$ = this.createBehaviorSubject(false);
|
|
5855
|
-
this._errors$ = this.
|
|
6163
|
+
this._errors$ = this.createReplaySubject(1);
|
|
5856
6164
|
this._options = {};
|
|
5857
6165
|
this._deps = new DependencyContainer();
|
|
5858
6166
|
this._options = {
|
|
@@ -5929,7 +6237,12 @@ var SignalWire = class extends Destroyable {
|
|
|
5929
6237
|
this._subscriber$.next(new Subscriber(this._deps.http));
|
|
5930
6238
|
if (!this._options.skipConnection) await this.connect();
|
|
5931
6239
|
if (!this._options.reconnectAttachedCalls && this._attachManager) await this._attachManager.flush();
|
|
5932
|
-
if (!this._options.skipRegister)
|
|
6240
|
+
if (!this._options.skipRegister) try {
|
|
6241
|
+
await this.register();
|
|
6242
|
+
} catch (error) {
|
|
6243
|
+
logger$1.error("[SignalWire] Registration failed:", error);
|
|
6244
|
+
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
6245
|
+
}
|
|
5933
6246
|
this.handleAttachments();
|
|
5934
6247
|
}
|
|
5935
6248
|
async handleAttachments() {
|
|
@@ -6021,7 +6334,7 @@ var SignalWire = class extends Destroyable {
|
|
|
6021
6334
|
* ```
|
|
6022
6335
|
*/
|
|
6023
6336
|
get subscriber$() {
|
|
6024
|
-
return this._subscriber$.asObservable();
|
|
6337
|
+
return this.deferEmission(this._subscriber$.asObservable());
|
|
6025
6338
|
}
|
|
6026
6339
|
/** Current subscriber snapshot, or `undefined` if not yet authenticated. */
|
|
6027
6340
|
get subscriber() {
|
|
@@ -6040,7 +6353,7 @@ var SignalWire = class extends Destroyable {
|
|
|
6040
6353
|
* ```
|
|
6041
6354
|
*/
|
|
6042
6355
|
get directory$() {
|
|
6043
|
-
return this._directory$.asObservable();
|
|
6356
|
+
return this.deferEmission(this._directory$.asObservable());
|
|
6044
6357
|
}
|
|
6045
6358
|
/**
|
|
6046
6359
|
* Current directory snapshot, or `undefined` if the client is not yet connected.
|
|
@@ -6051,7 +6364,7 @@ var SignalWire = class extends Destroyable {
|
|
|
6051
6364
|
}
|
|
6052
6365
|
/** Observable that emits when the subscriber registration state changes. */
|
|
6053
6366
|
get isRegistered$() {
|
|
6054
|
-
return this._isRegistered$.asObservable();
|
|
6367
|
+
return this.deferEmission(this._isRegistered$.asObservable());
|
|
6055
6368
|
}
|
|
6056
6369
|
/** Whether the subscriber is currently registered. */
|
|
6057
6370
|
get isRegistered() {
|
|
@@ -6063,15 +6376,15 @@ var SignalWire = class extends Destroyable {
|
|
|
6063
6376
|
}
|
|
6064
6377
|
/** Observable that emits when the connection state changes. */
|
|
6065
6378
|
get isConnected$() {
|
|
6066
|
-
return this._isConnected$.asObservable();
|
|
6379
|
+
return this.deferEmission(this._isConnected$.asObservable());
|
|
6067
6380
|
}
|
|
6068
6381
|
/** Observable that emits `true` when the client is both connected and authenticated. */
|
|
6069
6382
|
get ready$() {
|
|
6070
|
-
return this.
|
|
6383
|
+
return this.publicCachedObservable("ready$", () => this._isConnected$.pipe(switchMap((connected) => connected ? this._clientSession.authenticated$ : of(false))));
|
|
6071
6384
|
}
|
|
6072
6385
|
/** Observable stream of errors from transport, authentication, and devices. */
|
|
6073
6386
|
get errors$() {
|
|
6074
|
-
return this._errors$.asObservable();
|
|
6387
|
+
return this.deferEmission(this._errors$.asObservable());
|
|
6075
6388
|
}
|
|
6076
6389
|
/** Disconnects the WebSocket and tears down the session. */
|
|
6077
6390
|
async disconnect() {
|
|
@@ -6092,8 +6405,22 @@ var SignalWire = class extends Destroyable {
|
|
|
6092
6405
|
}));
|
|
6093
6406
|
this._isRegistered$.next(true);
|
|
6094
6407
|
} catch (error) {
|
|
6095
|
-
logger$1.
|
|
6408
|
+
logger$1.debug("[SignalWire] Failed to register subscriber, trying reauthentication...");
|
|
6409
|
+
if (this._deps.credential.token) this._clientSession.reauthenticate(this._deps.credential.token).then(async () => {
|
|
6410
|
+
logger$1.debug("[SignalWire] Reauthentication successful, retrying register()");
|
|
6411
|
+
await this._transport.execute(RPCExecute({
|
|
6412
|
+
method: "subscriber.online",
|
|
6413
|
+
params: {}
|
|
6414
|
+
}));
|
|
6415
|
+
this._isRegistered$.next(true);
|
|
6416
|
+
}).catch((reauthError) => {
|
|
6417
|
+
logger$1.error("[SignalWire] Reauthentication failed during register():", reauthError);
|
|
6418
|
+
const registerError = new InvalidCredentialsError("Failed to register subscriber, and reauthentication attempt also failed. Please check your credentials.", { cause: reauthError instanceof Error ? reauthError : new Error(String(reauthError), { cause: reauthError }) });
|
|
6419
|
+
this._errors$.next(registerError);
|
|
6420
|
+
throw registerError;
|
|
6421
|
+
});
|
|
6096
6422
|
this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
|
|
6423
|
+
throw error;
|
|
6097
6424
|
}
|
|
6098
6425
|
}
|
|
6099
6426
|
/** Unregisters the subscriber, going offline for inbound calls. */
|
|
@@ -6132,7 +6459,7 @@ var SignalWire = class extends Destroyable {
|
|
|
6132
6459
|
}
|
|
6133
6460
|
/** Observable list of available audio input (microphone) devices. */
|
|
6134
6461
|
get audioInputDevices$() {
|
|
6135
|
-
return this._deviceController.audioInputDevices
|
|
6462
|
+
return this.deferEmission(this._deviceController.audioInputDevices$);
|
|
6136
6463
|
}
|
|
6137
6464
|
/** Current snapshot of available audio input devices. */
|
|
6138
6465
|
get audioInputDevices() {
|
|
@@ -6140,7 +6467,7 @@ var SignalWire = class extends Destroyable {
|
|
|
6140
6467
|
}
|
|
6141
6468
|
/** Observable list of available audio output (speaker) devices. */
|
|
6142
6469
|
get audioOutputDevices$() {
|
|
6143
|
-
return this._deviceController.audioOutputDevices
|
|
6470
|
+
return this.deferEmission(this._deviceController.audioOutputDevices$);
|
|
6144
6471
|
}
|
|
6145
6472
|
/** Current snapshot of available audio output devices. */
|
|
6146
6473
|
get audioOutputDevices() {
|
|
@@ -6148,7 +6475,7 @@ var SignalWire = class extends Destroyable {
|
|
|
6148
6475
|
}
|
|
6149
6476
|
/** Observable list of available video input (camera) devices. */
|
|
6150
6477
|
get videoInputDevices$() {
|
|
6151
|
-
return this._deviceController.videoInputDevices
|
|
6478
|
+
return this.deferEmission(this._deviceController.videoInputDevices$);
|
|
6152
6479
|
}
|
|
6153
6480
|
/** Current snapshot of available video input devices. */
|
|
6154
6481
|
get videoInputDevices() {
|
|
@@ -6156,15 +6483,15 @@ var SignalWire = class extends Destroyable {
|
|
|
6156
6483
|
}
|
|
6157
6484
|
/** Observable of the currently selected audio input device. */
|
|
6158
6485
|
get selectedAudioInputDevice$() {
|
|
6159
|
-
return this._deviceController.selectedAudioInputDevice
|
|
6486
|
+
return this.deferEmission(this._deviceController.selectedAudioInputDevice$);
|
|
6160
6487
|
}
|
|
6161
6488
|
/** Observable of the currently selected audio output device. */
|
|
6162
6489
|
get selectedAudioOutputDevice$() {
|
|
6163
|
-
return this._deviceController.selectedAudioOutputDevice
|
|
6490
|
+
return this.deferEmission(this._deviceController.selectedAudioOutputDevice$);
|
|
6164
6491
|
}
|
|
6165
6492
|
/** Observable of the currently selected video input device. */
|
|
6166
6493
|
get selectedVideoInputDevice$() {
|
|
6167
|
-
return this._deviceController.selectedVideoInputDevice
|
|
6494
|
+
return this.deferEmission(this._deviceController.selectedVideoInputDevice$);
|
|
6168
6495
|
}
|
|
6169
6496
|
/** Currently selected audio input device, or `null` if none. */
|
|
6170
6497
|
get selectedAudioInputDevice() {
|