@signalwire/js 4.0.0-dev-20260804221355 → 4.0.0-dev-20260826210511
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 +208 -7
- package/dist/browser.mjs.map +1 -1
- package/dist/browser.umd.js +208 -7
- package/dist/browser.umd.js.map +1 -1
- package/dist/index.cjs +208 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +110 -0
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +110 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +208 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/browser.mjs
CHANGED
|
@@ -14508,6 +14508,33 @@ function throwOnRPCError() {
|
|
|
14508
14508
|
});
|
|
14509
14509
|
}
|
|
14510
14510
|
|
|
14511
|
+
//#endregion
|
|
14512
|
+
//#region src/utils/unwrapVertoReply.ts
|
|
14513
|
+
/**
|
|
14514
|
+
* Unwrap a method's own reply from a `webrtc.verto` envelope.
|
|
14515
|
+
*
|
|
14516
|
+
* A control verb sent in-dialog comes back nested two levels deep:
|
|
14517
|
+
*
|
|
14518
|
+
* ```
|
|
14519
|
+
* { result: { node_id, code, result: { jsonrpc, id, result: <method payload> } } }
|
|
14520
|
+
* ```
|
|
14521
|
+
*
|
|
14522
|
+
* whereas the routed transport resolves the method payload directly under
|
|
14523
|
+
* `.result`. Readers want the latter shape, and the difference is silent when it
|
|
14524
|
+
* is wrong — `response.result.layouts` simply evaluates to `undefined` against the
|
|
14525
|
+
* envelope, so the data arrives, nothing throws, and the caller sees an empty
|
|
14526
|
+
* value. That exact failure produced an empty layout dropdown with a successful
|
|
14527
|
+
* request behind it.
|
|
14528
|
+
*
|
|
14529
|
+
* Accepting both shapes here keeps every reader indifferent to which transport
|
|
14530
|
+
* produced the response. Anything that is not a nested envelope (a plain ack, or
|
|
14531
|
+
* an already-unwrapped reply) passes through untouched.
|
|
14532
|
+
*/
|
|
14533
|
+
function unwrapVertoReply(response) {
|
|
14534
|
+
const inner = getValueFrom(response, "result.result");
|
|
14535
|
+
return inner && typeof inner === "object" && "result" in inner ? inner : response;
|
|
14536
|
+
}
|
|
14537
|
+
|
|
14511
14538
|
//#endregion
|
|
14512
14539
|
//#region src/managers/CallEventsManager.ts
|
|
14513
14540
|
var import_cjs$20 = require_cjs();
|
|
@@ -14700,9 +14727,10 @@ var CallEventsManager = class extends Destroyable {
|
|
|
14700
14727
|
updateLayouts() {
|
|
14701
14728
|
if (!this.selfId) return;
|
|
14702
14729
|
this.webRtcCallSession.executeMethod(this.selfId, "call.layout.list", {}).then((response) => {
|
|
14730
|
+
const { result } = unwrapVertoReply(response);
|
|
14703
14731
|
this._sessionState$.next({
|
|
14704
14732
|
...this._sessionState$.value,
|
|
14705
|
-
layouts:
|
|
14733
|
+
layouts: result.layouts
|
|
14706
14734
|
});
|
|
14707
14735
|
}).catch((error) => {
|
|
14708
14736
|
logger$22.error("[CallEventsManager] Error fetching layouts:", error);
|
|
@@ -16707,6 +16735,48 @@ const logger$16 = getLogger();
|
|
|
16707
16735
|
function resolveInviteNodeId(args) {
|
|
16708
16736
|
return args.isInvite && !args.reattach && !args.explicitNodeId ? "" : args.currentNodeId ?? "";
|
|
16709
16737
|
}
|
|
16738
|
+
/**
|
|
16739
|
+
* Surface the real outcome of a `webrtc.verto` reply.
|
|
16740
|
+
*
|
|
16741
|
+
* A webrtc.verto response nests several envelopes, each keyed by a verto-style
|
|
16742
|
+
* string `code` ("200" ok, "400"/etc. fail) rather than a JSON-RPC `error`. An outer
|
|
16743
|
+
* layer reports only whether the frame was delivered; an inner layer carries the op's
|
|
16744
|
+
* own outcome:
|
|
16745
|
+
*
|
|
16746
|
+
* response.result = { code:"200", result:{…} } ← delivery acknowledgement
|
|
16747
|
+
* .result = { jsonrpc, id, result:{…} } ← the reply payload
|
|
16748
|
+
* .result = { code:"400", message:"Bad request" } ← the actual op outcome
|
|
16749
|
+
*
|
|
16750
|
+
* A failure can appear at any layer (delivery refused, or the op itself rejected
|
|
16751
|
+
* deeper down), so walk every nested `.result` object and return the FIRST non-2xx
|
|
16752
|
+
* `code` with its message. Returns null when every `code` seen is 2xx or absent —
|
|
16753
|
+
* i.e. the op succeeded. This is the only way to detect that e.g. a mute/kick was
|
|
16754
|
+
* rejected, since the outer delivery `code` is "200" (delivered) even then.
|
|
16755
|
+
*
|
|
16756
|
+
* Pure function — exported for unit testing.
|
|
16757
|
+
*/
|
|
16758
|
+
function findNestedVertoFailure(response) {
|
|
16759
|
+
let node = response;
|
|
16760
|
+
while (node !== null && typeof node === "object") {
|
|
16761
|
+
const obj = node;
|
|
16762
|
+
const err = obj.error;
|
|
16763
|
+
if (err !== null && typeof err === "object") {
|
|
16764
|
+
const e = err;
|
|
16765
|
+
const errCode = typeof e.code === "string" || typeof e.code === "number" ? String(e.code) : void 0;
|
|
16766
|
+
if (errCode !== void 0 && !/^2\d\d$/.test(errCode)) return {
|
|
16767
|
+
code: errCode,
|
|
16768
|
+
message: typeof e.message === "string" ? e.message : void 0
|
|
16769
|
+
};
|
|
16770
|
+
}
|
|
16771
|
+
const code = typeof obj.code === "string" || typeof obj.code === "number" ? String(obj.code) : void 0;
|
|
16772
|
+
if (code !== void 0 && !/^2\d\d$/.test(code)) return {
|
|
16773
|
+
code,
|
|
16774
|
+
message: typeof obj.message === "string" ? obj.message : void 0
|
|
16775
|
+
};
|
|
16776
|
+
node = obj.result !== null && typeof obj.result === "object" ? obj.result : null;
|
|
16777
|
+
}
|
|
16778
|
+
return null;
|
|
16779
|
+
}
|
|
16710
16780
|
var VertoManager = class extends Destroyable {
|
|
16711
16781
|
constructor(callSession) {
|
|
16712
16782
|
super();
|
|
@@ -17031,6 +17101,33 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17031
17101
|
get vertoPing$() {
|
|
17032
17102
|
return this.cachedObservable("vertoPing$", () => this.webRtcCallSession.webrtcMessages$.pipe(filterAs(isVertoPingInnerParams, "params"), (0, import_cjs$15.takeUntil)(this.destroyed$)));
|
|
17033
17103
|
}
|
|
17104
|
+
/**
|
|
17105
|
+
* Send a member-control op in-dialog via verto.info.
|
|
17106
|
+
*
|
|
17107
|
+
* The control payload rides in `params.command` — a sibling of `dialogParams`,
|
|
17108
|
+
* at the same level as `dtmf` in {@link sendDigits} — and the inner verto.info
|
|
17109
|
+
* is matched to this call's channel by `dialogParams.callID`, the in-dialog
|
|
17110
|
+
* convention for member-scoped frames. Because it is delivered on the dialog
|
|
17111
|
+
* itself, control lands on the call's own channel with no {node_id,call_id,member_id}
|
|
17112
|
+
* "self" tuple to get wrong. The outer webrtc.verto envelope (added by executeVerto)
|
|
17113
|
+
* still carries the own-leg callID + node_id for session routing.
|
|
17114
|
+
*
|
|
17115
|
+
* Keep `command` OUT of `dialogParams`: it is read at the params level, and
|
|
17116
|
+
* filterVertoParams rewrites/filters dialogParams keys but passes params-level
|
|
17117
|
+
* keys through verbatim.
|
|
17118
|
+
*/
|
|
17119
|
+
async sendCallControl(method, params) {
|
|
17120
|
+
const response = await this.executeVerto(VertoInfo({
|
|
17121
|
+
dialogParams: { callID: this.webRtcCallSession.id },
|
|
17122
|
+
command: {
|
|
17123
|
+
method,
|
|
17124
|
+
params
|
|
17125
|
+
}
|
|
17126
|
+
}));
|
|
17127
|
+
const failure = findNestedVertoFailure(response);
|
|
17128
|
+
if (failure) throw new JSONRPCError(Number.parseInt(failure.code, 10) || 0, failure.message ?? `Call control "${method}" failed (code ${failure.code})`, void 0);
|
|
17129
|
+
return response;
|
|
17130
|
+
}
|
|
17034
17131
|
async executeVerto(message, optionals = {}) {
|
|
17035
17132
|
const webrtcVertoMessage = WebrtcVerto({
|
|
17036
17133
|
callID: optionals.callID ?? this.webRtcCallSession.id,
|
|
@@ -17039,15 +17136,16 @@ var WebRTCVertoManager = class extends VertoManager {
|
|
|
17039
17136
|
subscribe: optionals.subscribe
|
|
17040
17137
|
});
|
|
17041
17138
|
const response = await this.webRtcCallSession.execute(webrtcVertoMessage);
|
|
17139
|
+
const nonFatal = message.method === "verto.info" ? { fatal: false } : void 0;
|
|
17042
17140
|
if (response.error) {
|
|
17043
17141
|
const error = new JSONRPCError(response.error.code, response.error.message, response.error.data);
|
|
17044
|
-
this.onError?.(error);
|
|
17142
|
+
this.onError?.(error, nonFatal);
|
|
17045
17143
|
return response;
|
|
17046
17144
|
}
|
|
17047
17145
|
const innerResult = getValueFrom(response, "result.result");
|
|
17048
17146
|
if (innerResult?.error) {
|
|
17049
17147
|
const error = new JSONRPCError(innerResult.error.code, innerResult.error.message, innerResult.error.data);
|
|
17050
|
-
this.onError?.(error);
|
|
17148
|
+
this.onError?.(error, nonFatal);
|
|
17051
17149
|
return response;
|
|
17052
17150
|
}
|
|
17053
17151
|
return response;
|
|
@@ -18434,8 +18532,11 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18434
18532
|
emitError(callError) {
|
|
18435
18533
|
if (this._status$.value === "destroyed" || this._status$.value === "failed") return;
|
|
18436
18534
|
this._errors$.next(callError);
|
|
18437
|
-
if (callError.fatal) {
|
|
18535
|
+
if (callError.fatal && this._status$.value !== "disconnecting") {
|
|
18438
18536
|
this._status$.next("failed");
|
|
18537
|
+
this.vertoManager.bye().catch((error) => {
|
|
18538
|
+
logger$12.debug("[Call] fatal-teardown bye failed (signaling likely already dead):", error);
|
|
18539
|
+
});
|
|
18439
18540
|
this.destroy();
|
|
18440
18541
|
}
|
|
18441
18542
|
}
|
|
@@ -18552,6 +18653,7 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18552
18653
|
async executeMethod(target, method, args) {
|
|
18553
18654
|
const self = this.callSelf;
|
|
18554
18655
|
if (typeof target === "string" && target !== self.member_id) throw new InvalidParams(`Target member ID ${target} does not match call's self member ID ${self.member_id}`);
|
|
18656
|
+
if (this.clientSession.callControl === "in-dialog") return this.executeMethodInDialog(target, method, args);
|
|
18555
18657
|
const params = {
|
|
18556
18658
|
...args,
|
|
18557
18659
|
self,
|
|
@@ -18571,6 +18673,81 @@ var WebRTCCall = class extends Destroyable {
|
|
|
18571
18673
|
}
|
|
18572
18674
|
}
|
|
18573
18675
|
/**
|
|
18676
|
+
* `executeMethod` for a call opened with `callControl: 'in-dialog'`.
|
|
18677
|
+
*
|
|
18678
|
+
* Translates the routed transport's calling convention into the in-dialog one. No
|
|
18679
|
+
* `self` tuple is sent, but a `target` is — the same {call_id, member_id} the routed
|
|
18680
|
+
* transport puts in `target` (minus node_id), for self-ops and cross-member ops alike.
|
|
18681
|
+
*
|
|
18682
|
+
* Target shapes are per-verb and irregular, so they are centralised here rather
|
|
18683
|
+
* than left to callers: most verbs take a singular `target`, `call.member.remove`
|
|
18684
|
+
* takes a plural `targets` array, and `call.member.position.set` takes a flat
|
|
18685
|
+
* `targets` of `{call_id, position}` — the one verb keyed on call_id rather than
|
|
18686
|
+
* member_id, so the member triple `Participant.setPosition` built is unwrapped.
|
|
18687
|
+
*/
|
|
18688
|
+
async executeMethodInDialog(target, method, args) {
|
|
18689
|
+
const control = { ...args };
|
|
18690
|
+
if (method === "call.member.position.set") control.targets = (args.targets ?? []).map((entry) => ({
|
|
18691
|
+
call_id: entry.target?.call_id ?? entry.call_id,
|
|
18692
|
+
position: entry.position
|
|
18693
|
+
}));
|
|
18694
|
+
else {
|
|
18695
|
+
const member = typeof target === "object" ? {
|
|
18696
|
+
call_id: target.call_id,
|
|
18697
|
+
member_id: target.member_id
|
|
18698
|
+
} : {
|
|
18699
|
+
call_id: this.id,
|
|
18700
|
+
member_id: target
|
|
18701
|
+
};
|
|
18702
|
+
if (method === "call.member.remove") control.targets = [member];
|
|
18703
|
+
else control.target = member;
|
|
18704
|
+
}
|
|
18705
|
+
return this.sendCommand(method, control);
|
|
18706
|
+
}
|
|
18707
|
+
/**
|
|
18708
|
+
* Sends a `call.*` control verb **in-dialog** via `verto.info`, as an alternative
|
|
18709
|
+
* to the routed {@link executeMethod} transport.
|
|
18710
|
+
*
|
|
18711
|
+
* Why both exist: `executeMethod` addresses the member with an explicit
|
|
18712
|
+
* `{node_id, call_id, member_id}` tuple, which does not resolve for every conference,
|
|
18713
|
+
* so the op can fail. An in-dialog frame carries the verb on the member's own
|
|
18714
|
+
* signaling channel instead, so control works without the client needing to know how
|
|
18715
|
+
* the conference is hosted.
|
|
18716
|
+
*
|
|
18717
|
+
* The trade-off is reach: the in-dialog transport is only accepted for calls that
|
|
18718
|
+
* join a conference over SWML (e.g. an SWML `join_conference`); use the routed
|
|
18719
|
+
* default otherwise.
|
|
18720
|
+
*
|
|
18721
|
+
* `params` are sent verbatim — nothing is built for you, which includes the target.
|
|
18722
|
+
* **A self-directed op still needs one**, or it is refused; name yourself explicitly:
|
|
18723
|
+
*
|
|
18724
|
+
* ```ts
|
|
18725
|
+
* const { call_id, member_id } = call.self.target;
|
|
18726
|
+
* await call.sendCommand('call.mute', { channels: ['audio'], target: { call_id, member_id } });
|
|
18727
|
+
* ```
|
|
18728
|
+
*
|
|
18729
|
+
* Never include `node_id` — only the two ids. The shapes are per-verb: most take a
|
|
18730
|
+
* singular `target`, `call.member.remove` takes a plural `targets` array, and
|
|
18731
|
+
* `call.member.position.set` takes a flat `targets: [{call_id, position}]` (the one
|
|
18732
|
+
* verb keyed on `call_id` rather than `member_id`). Verbs that act on the call as a
|
|
18733
|
+
* whole, or that the SDK does not wrap at all, take no target.
|
|
18734
|
+
*
|
|
18735
|
+
* For the typed alternative that handles all of this, create the client with
|
|
18736
|
+
* `callControl: 'in-dialog'` and use the ordinary `Call`/`Participant` methods.
|
|
18737
|
+
*
|
|
18738
|
+
* @internal Not part of the supported surface while the in-dialog transport is still
|
|
18739
|
+
* rolling out. `WebRTCCall` is exported from the package entry, so without this tag
|
|
18740
|
+
* TypeDoc publishes the method — and the example above — as public API.
|
|
18741
|
+
*
|
|
18742
|
+
* @param method - A `call.*` method name (e.g. `'call.mute'`).
|
|
18743
|
+
* @param params - Method parameters, sent verbatim.
|
|
18744
|
+
* @returns The method's own reply, unwrapped from the `verto.info` envelope.
|
|
18745
|
+
* @throws {JSONRPCError} If the control op fails.
|
|
18746
|
+
*/
|
|
18747
|
+
async sendCommand(method, params = {}) {
|
|
18748
|
+
return unwrapVertoReply(await this.vertoManager.sendCallControl(method, params));
|
|
18749
|
+
}
|
|
18750
|
+
/**
|
|
18574
18751
|
* The local leg's member triple — sent as `self` in every member RPC
|
|
18575
18752
|
* envelope, and as the `target` of call-scoped self-operations (e.g. lock,
|
|
18576
18753
|
* layout).
|
|
@@ -19901,6 +20078,7 @@ var ClientSessionManager = class extends Destroyable {
|
|
|
19901
20078
|
minor: 0,
|
|
19902
20079
|
revision: 0
|
|
19903
20080
|
};
|
|
20081
|
+
this.callControl = "routed";
|
|
19904
20082
|
this._authorization$ = this.createBehaviorSubject(void 0);
|
|
19905
20083
|
this._errors$ = this.createReplaySubject(1);
|
|
19906
20084
|
this._authState$ = this.createBehaviorSubject({ kind: "unauthenticated" });
|
|
@@ -20374,6 +20552,9 @@ var ClientSessionWrapper = class {
|
|
|
20374
20552
|
get iceServers() {
|
|
20375
20553
|
return this.clientSessionManager.iceServers;
|
|
20376
20554
|
}
|
|
20555
|
+
get callControl() {
|
|
20556
|
+
return this.clientSessionManager.callControl;
|
|
20557
|
+
}
|
|
20377
20558
|
async execute(request, options) {
|
|
20378
20559
|
return this.clientSessionManager.execute(request, options);
|
|
20379
20560
|
}
|
|
@@ -20614,7 +20795,7 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20614
20795
|
method: "POST",
|
|
20615
20796
|
uri: DEVICE_TOKEN_ENDPOINT
|
|
20616
20797
|
});
|
|
20617
|
-
const response = await this.http.request({
|
|
20798
|
+
const response = await this.http().request({
|
|
20618
20799
|
url: DEVICE_TOKEN_ENDPOINT,
|
|
20619
20800
|
...POST_PARAMS,
|
|
20620
20801
|
body: JSON.stringify({
|
|
@@ -20641,7 +20822,7 @@ var DeviceTokenManager = class extends Destroyable {
|
|
|
20641
20822
|
uri: DEVICE_REFRESH_ENDPOINT,
|
|
20642
20823
|
accessToken: currentToken
|
|
20643
20824
|
});
|
|
20644
|
-
const response = await this.http.request({
|
|
20825
|
+
const response = await this.http().request({
|
|
20645
20826
|
url: DEVICE_REFRESH_ENDPOINT,
|
|
20646
20827
|
...POST_PARAMS,
|
|
20647
20828
|
body: JSON.stringify({
|
|
@@ -21806,6 +21987,16 @@ var SignalWire = class extends Destroyable {
|
|
|
21806
21987
|
});
|
|
21807
21988
|
}
|
|
21808
21989
|
/**
|
|
21990
|
+
* Build the refresh path's own HTTP controller, against whatever host is current.
|
|
21991
|
+
*
|
|
21992
|
+
* Called on first use rather than up front, so `apiHost` already reflects the
|
|
21993
|
+
* token's `ch` claim. Same credential source as the container's controller — only
|
|
21994
|
+
* the instance, and therefore its observable streams, is separate.
|
|
21995
|
+
*/
|
|
21996
|
+
createRefreshHttpController() {
|
|
21997
|
+
return new HTTPRequestController(this._deps.apiHost, () => this._deps.credential);
|
|
21998
|
+
}
|
|
21999
|
+
/**
|
|
21809
22000
|
* Initializes DPoP if not already set up. Returns the fingerprint on success.
|
|
21810
22001
|
*/
|
|
21811
22002
|
async initDPoP() {
|
|
@@ -21832,7 +22023,14 @@ var SignalWire = class extends Destroyable {
|
|
|
21832
22023
|
async resolveCredentials() {
|
|
21833
22024
|
const fingerprint = await this.initDPoP();
|
|
21834
22025
|
this._refreshCoordinator = new CredentialRefreshCoordinator(this._dpopManager, {
|
|
21835
|
-
http:
|
|
22026
|
+
http: () => {
|
|
22027
|
+
if (!this._refreshHttp || this._refreshHttpHost !== this._deps.apiHost) {
|
|
22028
|
+
this._refreshHttp?.destroy();
|
|
22029
|
+
this._refreshHttp = this.createRefreshHttpController();
|
|
22030
|
+
this._refreshHttpHost = this._deps.apiHost;
|
|
22031
|
+
}
|
|
22032
|
+
return this._refreshHttp;
|
|
22033
|
+
},
|
|
21836
22034
|
notifier: {
|
|
21837
22035
|
onError: (error) => this._errors$.next(error),
|
|
21838
22036
|
onWarning: (warning) => this._warnings$.next(warning),
|
|
@@ -22098,6 +22296,7 @@ var SignalWire = class extends Destroyable {
|
|
|
22098
22296
|
this._attachManager = new AttachManager(this._deps.storage, this._deps.deviceController, PreferencesContainer.instance.reconnectCallsTimeout, this._deps.attachedCallsKey);
|
|
22099
22297
|
this._clientSession = new ClientSessionManager(() => this._deps.credential, this._transport, this._deps.storage, this._deps.authorizationStateKey, this._deps.deviceController, this._attachManager, this._deps.webRTCApiProvider, this._dpopManager, this._networkMonitor?.networkChange$);
|
|
22100
22298
|
this._publicSession = new ClientSessionWrapper(this._clientSession);
|
|
22299
|
+
this._clientSession.callControl = this._options.callControl ?? "routed";
|
|
22101
22300
|
this._clientSession.onBeforeReconnect = async () => this.refreshCredentialForReconnect();
|
|
22102
22301
|
this.subscribeTo(this._clientSession.errors$, (error) => {
|
|
22103
22302
|
this._errors$.next(error);
|
|
@@ -22714,6 +22913,8 @@ var SignalWire = class extends Destroyable {
|
|
|
22714
22913
|
destroy() {
|
|
22715
22914
|
this._refreshCoordinator?.destroy();
|
|
22716
22915
|
this._refreshCoordinator = void 0;
|
|
22916
|
+
this._refreshHttp?.destroy();
|
|
22917
|
+
this._refreshHttp = void 0;
|
|
22717
22918
|
this._dpopManager?.destroy();
|
|
22718
22919
|
const session = this._clientSession;
|
|
22719
22920
|
session?.teardownSessionState();
|