@signalwire/js 4.0.0-dev-20260714201720 → 4.0.0-dev-20260717142935

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9287,6 +9287,20 @@ var CallCreateError = class extends Error {
9287
9287
  this.name = "CallCreateError";
9288
9288
  }
9289
9289
  };
9290
+ var CallNotReadyError = class extends Error {
9291
+ constructor(callId, options) {
9292
+ super(`Call "${callId}" has no self member context yet: selfId/nodeId have not been received from the server`, options);
9293
+ this.callId = callId;
9294
+ this.name = "CallNotReadyError";
9295
+ }
9296
+ };
9297
+ var ParticipantNotReadyError = class extends Error {
9298
+ constructor(memberId, options) {
9299
+ super(`Participant "${memberId}" has no call context yet: its member state (call_id/node_id) has not been received from the server`, options);
9300
+ this.memberId = memberId;
9301
+ this.name = "ParticipantNotReadyError";
9302
+ }
9303
+ };
9290
9304
  var JSONRPCError = class extends Error {
9291
9305
  constructor(code, message, data, options, requestId) {
9292
9306
  super(message, options);
@@ -13650,9 +13664,9 @@ const initialState = {};
13650
13664
  * the local participant with additional device control.
13651
13665
  */
13652
13666
  var Participant = class extends Destroyable {
13653
- constructor(id, executeMethod, deviceController) {
13667
+ constructor(id, callExecuteMethod, deviceController) {
13654
13668
  super();
13655
- this.executeMethod = executeMethod;
13669
+ this.callExecuteMethod = callExecuteMethod;
13656
13670
  this.deviceController = deviceController;
13657
13671
  this._state$ = this.createBehaviorSubject(initialState);
13658
13672
  this.id = id;
@@ -13874,22 +13888,55 @@ var Participant = class extends Destroyable {
13874
13888
  get value() {
13875
13889
  return this._state$.value;
13876
13890
  }
13891
+ /**
13892
+ * Target triple for member RPCs, built from the participant's own state.
13893
+ * The backend locates the member's session by the target `call_id`/`node_id`,
13894
+ * so this must always be the participant's own call context — never the
13895
+ * local call's id (issue #19400).
13896
+ *
13897
+ * Reading it doubles as a readiness probe: it throws until the first full
13898
+ * member event (`member.joined`/`member.updated` or the `call.joined`
13899
+ * roster) arrives, and never regresses afterwards.
13900
+ *
13901
+ * @throws {ParticipantNotReadyError} If the member state has not been
13902
+ * received yet (e.g. a participant first seen via `member.talking`) — an
13903
+ * empty call context can never address the member, so fail fast instead of
13904
+ * sending a doomed RPC.
13905
+ */
13906
+ get target() {
13907
+ const { call_id, node_id } = this._state$.value;
13908
+ if (!call_id || !node_id) throw new ParticipantNotReadyError(this.id);
13909
+ return {
13910
+ member_id: this.id,
13911
+ call_id,
13912
+ node_id
13913
+ };
13914
+ }
13915
+ /**
13916
+ * Executes a member RPC against this participant, injecting its own
13917
+ * {@link target} as the target.
13918
+ *
13919
+ * @throws {ParticipantNotReadyError} Via {@link target}, when the
13920
+ * member state has not been received yet.
13921
+ */
13922
+ async executeMethod(method, args) {
13923
+ return this.callExecuteMethod(this.target, method, args);
13924
+ }
13877
13925
  /** Toggles the deafened state (mutes/unmutes incoming audio). */
13878
13926
  async toggleDeaf() {
13879
- const method = toggleDeafMethod(this.deaf);
13880
- await this.executeMethod(this.id, method, {});
13927
+ await this.executeMethod(toggleDeafMethod(this.deaf), {});
13881
13928
  }
13882
13929
  /** Toggles the hand-raised state. */
13883
13930
  async toggleHandraise() {
13884
- await this.executeMethod(this.id, toggleHandraiseMethod(this.handraised), {});
13931
+ await this.executeMethod(toggleHandraiseMethod(this.handraised), {});
13885
13932
  }
13886
13933
  /** Mutes the participant's audio. */
13887
13934
  async mute() {
13888
- await this.executeMethod(this.id, "call.mute", { channels: ["audio"] });
13935
+ await this.executeMethod("call.mute", { channels: ["audio"] });
13889
13936
  }
13890
13937
  /** Unmutes the participant's audio. */
13891
13938
  async unmute() {
13892
- await this.executeMethod(this.id, "call.unmute", { channels: ["audio"] });
13939
+ await this.executeMethod("call.unmute", { channels: ["audio"] });
13893
13940
  }
13894
13941
  /** Toggles the participant's audio mute state. */
13895
13942
  async toggleMute() {
@@ -13897,11 +13944,11 @@ var Participant = class extends Destroyable {
13897
13944
  }
13898
13945
  /** Mutes the participant's video. */
13899
13946
  async muteVideo() {
13900
- await this.executeMethod(this.id, "call.mute", { channels: ["video"] });
13947
+ await this.executeMethod("call.mute", { channels: ["video"] });
13901
13948
  }
13902
13949
  /** Unmutes the participant's video. */
13903
13950
  async unmuteVideo() {
13904
- await this.executeMethod(this.id, "call.unmute", { channels: ["video"] });
13951
+ await this.executeMethod("call.unmute", { channels: ["video"] });
13905
13952
  }
13906
13953
  /** Toggles the participant's video mute state. */
13907
13954
  async toggleMuteVideo() {
@@ -13909,7 +13956,7 @@ var Participant = class extends Destroyable {
13909
13956
  }
13910
13957
  /** Toggles echo cancellation on the audio input. */
13911
13958
  async toggleEchoCancellation() {
13912
- await this.executeMethod(this.id, "call.audioflags.set", {
13959
+ await this.executeMethod("call.audioflags.set", {
13913
13960
  echo_cancellation: !this.echoCancellation,
13914
13961
  auto_gain: this.autoGain,
13915
13962
  noise_suppression: this.noiseSuppression
@@ -13917,7 +13964,7 @@ var Participant = class extends Destroyable {
13917
13964
  }
13918
13965
  /** Toggles automatic gain control on the audio input. */
13919
13966
  async toggleAudioInputAutoGain() {
13920
- await this.executeMethod(this.id, "call.audioflags.set", {
13967
+ await this.executeMethod("call.audioflags.set", {
13921
13968
  echo_cancellation: this.echoCancellation,
13922
13969
  auto_gain: !this.autoGain,
13923
13970
  noise_suppression: this.noiseSuppression
@@ -13925,7 +13972,7 @@ var Participant = class extends Destroyable {
13925
13972
  }
13926
13973
  /** Toggles noise suppression on the audio input. */
13927
13974
  async toggleNoiseSuppression() {
13928
- await this.executeMethod(this.id, "call.audioflags.set", {
13975
+ await this.executeMethod("call.audioflags.set", {
13929
13976
  echo_cancellation: this.echoCancellation,
13930
13977
  auto_gain: this.autoGain,
13931
13978
  noise_suppression: !this.noiseSuppression
@@ -13933,7 +13980,7 @@ var Participant = class extends Destroyable {
13933
13980
  }
13934
13981
  /** Toggles low-bitrate mode for this participant's media. */
13935
13982
  async toggleLowbitrate() {
13936
- await this.executeMethod(this.id, "call.lowbitrate.set", { lowbitrate: !this.lowbitrate });
13983
+ await this.executeMethod("call.lowbitrate.set", { lowbitrate: !this.lowbitrate });
13937
13984
  }
13938
13985
  /**
13939
13986
  * Adjusts the **conference-only** microphone energy gate / sensitivity level
@@ -13949,7 +13996,7 @@ var Participant = class extends Destroyable {
13949
13996
  * (integer, larger values are more sensitive).
13950
13997
  */
13951
13998
  async setAudioInputSensitivity(value) {
13952
- await this.executeMethod(this.id, "call.microphone.sensitivity.set", { sensitivity: value });
13999
+ await this.executeMethod("call.microphone.sensitivity.set", { sensitivity: value });
13953
14000
  }
13954
14001
  /**
13955
14002
  * Sets the **server-side** microphone volume on this participant's bridged
@@ -13962,7 +14009,7 @@ var Participant = class extends Destroyable {
13962
14009
  * @param value - Volume level (0-100).
13963
14010
  */
13964
14011
  async setAudioInputVolume(value) {
13965
- await this.executeMethod(this.id, "call.microphone.volume.set", { volume: value });
14012
+ await this.executeMethod("call.microphone.volume.set", { volume: value });
13966
14013
  }
13967
14014
  /**
13968
14015
  * Sets the **server-side** speaker volume on this participant's bridged call
@@ -13976,45 +14023,31 @@ var Participant = class extends Destroyable {
13976
14023
  * @param value - Volume level (0-100).
13977
14024
  */
13978
14025
  async setAudioOutputVolume(value) {
13979
- await this.executeMethod(this.id, "call.speaker.volume.set", { volume: value });
14026
+ await this.executeMethod("call.speaker.volume.set", { volume: value });
13980
14027
  }
13981
14028
  /**
13982
14029
  * Sets the participant's position in the video layout.
13983
14030
  *
13984
- * Requires the `member.position` capability. The gateway keys positions by the
13985
- * **target member's own** `call_id`/`node_id` (see issue #19400 and the legacy
13986
- * `setPositions` implementation), so this sends the participant's own call
13987
- * context — matching {@link Participant.remove}. A resolved promise does not
13988
- * guarantee a visible change: the backend silently returns `200` (no-op) for
13989
- * non-conference targets.
14031
+ * Requires the `member.position` capability. The gateway requires a
14032
+ * `targets` array of `{ target, position }` entries (issue #19400). A
14033
+ * resolved promise does not guarantee a visible change: the backend silently
14034
+ * returns `200` (no-op) for non-conference targets.
13990
14035
  *
13991
14036
  * @param value - The {@link VideoPosition} to assign (e.g. `'auto'`, `'reserved-0'`).
13992
14037
  */
13993
14038
  async setPosition(value) {
13994
- const state = this._state$.value;
13995
- const target = {
13996
- member_id: this.id,
13997
- call_id: state.call_id ?? "",
13998
- node_id: state.node_id ?? ""
13999
- };
14000
- await this.executeMethod(target, "call.member.position.set", { targets: [{
14001
- target,
14039
+ await this.executeMethod("call.member.position.set", { targets: [{
14040
+ target: this.target,
14002
14041
  position: value
14003
14042
  }] });
14004
14043
  }
14005
14044
  /** Removes this participant from the call. */
14006
14045
  async remove() {
14007
- const state = this._state$.value;
14008
- const target = {
14009
- member_id: this.id,
14010
- call_id: state.call_id ?? "",
14011
- node_id: state.node_id ?? ""
14012
- };
14013
- await this.executeMethod(target, "call.member.remove", {});
14046
+ await this.executeMethod("call.member.remove", { targets: [this.target] });
14014
14047
  }
14015
14048
  /** Ends the call for this participant. */
14016
14049
  async end() {
14017
- await this.executeMethod(this.id, "call.end", {});
14050
+ await this.executeMethod("call.end", {});
14018
14051
  }
14019
14052
  /**
14020
14053
  * Replaces custom metadata for this participant.
@@ -14034,7 +14067,7 @@ var Participant = class extends Destroyable {
14034
14067
  }
14035
14068
  /** Destroys the participant, releasing all subscriptions and references. */
14036
14069
  destroy() {
14037
- this.executeMethod = void 0;
14070
+ this.callExecuteMethod = void 0;
14038
14071
  super.destroy();
14039
14072
  }
14040
14073
  };
@@ -14046,8 +14079,8 @@ var Participant = class extends Destroyable {
14046
14079
  */
14047
14080
  var SelfParticipant = class extends Participant {
14048
14081
  /** @internal */
14049
- constructor(id, executeMethod, vertoManager, deviceController) {
14050
- super(id, executeMethod, deviceController);
14082
+ constructor(id, callExecuteMethod, vertoManager, deviceController) {
14083
+ super(id, callExecuteMethod, deviceController);
14051
14084
  this.vertoManager = vertoManager;
14052
14085
  this._studioAudio$ = this.createBehaviorSubject(false);
14053
14086
  this.capabilities = new SelfCapabilities();
@@ -14071,7 +14104,7 @@ var SelfParticipant = class extends Participant {
14071
14104
  async enableStudioAudio() {
14072
14105
  if (this._studioAudio$.value) return;
14073
14106
  this._studioAudio$.next(true);
14074
- await this.executeMethod(this.id, "call.audioflags.set", {
14107
+ await this.executeMethod("call.audioflags.set", {
14075
14108
  echo_cancellation: false,
14076
14109
  auto_gain: false,
14077
14110
  noise_suppression: false
@@ -14084,7 +14117,7 @@ var SelfParticipant = class extends Participant {
14084
14117
  async disableStudioAudio() {
14085
14118
  if (!this._studioAudio$.value) return;
14086
14119
  this._studioAudio$.next(false);
14087
- await this.executeMethod(this.id, "call.audioflags.set", {
14120
+ await this.executeMethod("call.audioflags.set", {
14088
14121
  echo_cancellation: true,
14089
14122
  auto_gain: true,
14090
14123
  noise_suppression: true
@@ -18305,12 +18338,6 @@ function mosToQualityLevel(mos) {
18305
18338
  var import_cjs$11 = require_cjs();
18306
18339
  const logger$12 = getLogger();
18307
18340
  /**
18308
- * Verto method for setting member layout positions. Its gateway DTO requires a
18309
- * `targets` array whose entries are `{ target, position }` (NOT bare targets),
18310
- * so {@link WebRTCCall.buildMethodParams} special-cases it. See issue #19400.
18311
- */
18312
- const POSITION_SET_METHOD = "call.member.position.set";
18313
- /**
18314
18341
  * Ratio between the critical and warning RTT spike multipliers.
18315
18342
  * Warning threshold = baseline * warningMultiplier (default 3x)
18316
18343
  * Critical threshold = baseline * warningMultiplier * RTT_CRITICAL_TO_WARNING_RATIO
@@ -18461,7 +18488,7 @@ var WebRTCCall = class extends Destroyable {
18461
18488
  /** Toggles the call lock state, preventing or allowing new participants from joining. */
18462
18489
  async toggleLock() {
18463
18490
  const method = this.locked ? "call.unlock" : "call.lock";
18464
- await this.executeMethod(this.selfId ?? "", method, {});
18491
+ await this.executeMethod(this.callSelf, method, {});
18465
18492
  }
18466
18493
  /**
18467
18494
  * Toggles the hold state of the call (pauses/resumes local media transmission).
@@ -18510,14 +18537,24 @@ var WebRTCCall = class extends Destroyable {
18510
18537
  *
18511
18538
  * Constructs call context (node_id, call_id, member_id) and sends the RPC request.
18512
18539
  *
18513
- * @param target - Target member ID string, or a {@link MemberTarget} object.
18540
+ * @param target - Target {@link MemberTarget} triple, or the local member's
18541
+ * ID string for self-operations (any other string is rejected — a bare
18542
+ * member id cannot carry the remote member's own call context).
18514
18543
  * @param method - Verto method name (e.g. `'call.mute'`, `'call.member.remove'`).
18515
18544
  * @param args - Parameters for the RPC method.
18516
18545
  * @returns The RPC response.
18546
+ * @throws {CallNotReadyError} If the call has no self member context yet.
18547
+ * @throws {InvalidParams} If a string target is not the local member's ID.
18517
18548
  * @throws {JSONRPCError} If the RPC call returns an error.
18518
18549
  */
18519
18550
  async executeMethod(target, method, args) {
18520
- const params = this.buildMethodParams(target, args, method);
18551
+ const self = this.callSelf;
18552
+ 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}`);
18553
+ const params = {
18554
+ ...args,
18555
+ self,
18556
+ target: typeof target === "string" ? self : target
18557
+ };
18521
18558
  const request = buildRPCRequest({
18522
18559
  method,
18523
18560
  params
@@ -18531,29 +18568,23 @@ var WebRTCCall = class extends Destroyable {
18531
18568
  throw error;
18532
18569
  }
18533
18570
  }
18534
- buildMethodParams(target, args, method) {
18535
- const self = {
18536
- node_id: this.nodeId ?? "",
18537
- call_id: this.id,
18538
- member_id: this.vertoManager.selfId ?? ""
18539
- };
18540
- if (method === POSITION_SET_METHOD) return {
18541
- ...args,
18542
- self
18543
- };
18544
- if (typeof target === "object") return {
18545
- ...args,
18546
- self,
18547
- targets: [target]
18548
- };
18571
+ /**
18572
+ * The local leg's member triple — sent as `self` in every member RPC
18573
+ * envelope, and as the `target` of call-scoped self-operations (e.g. lock,
18574
+ * layout).
18575
+ *
18576
+ * @throws {CallNotReadyError} Before `call.joined` delivers the self member
18577
+ * context (`selfId`/`nodeId`) — an RPC without it cannot be routed, so fail
18578
+ * fast instead of sending a doomed request.
18579
+ */
18580
+ get callSelf() {
18581
+ const node_id = this.nodeId;
18582
+ const member_id = this.vertoManager.selfId;
18583
+ if (!node_id || !member_id) throw new CallNotReadyError(this.id);
18549
18584
  return {
18550
- ...args,
18551
- self,
18552
- target: {
18553
- node_id: this.nodeId ?? "",
18554
- call_id: this.id,
18555
- member_id: target
18556
- }
18585
+ node_id,
18586
+ call_id: this.id,
18587
+ member_id
18557
18588
  };
18558
18589
  }
18559
18590
  /** Observable of the current call status (e.g. `'ringing'`, `'connected'`). */
@@ -19116,12 +19147,17 @@ var WebRTCCall = class extends Destroyable {
19116
19147
  *
19117
19148
  * **These operations are NOT atomic.** The layout is applied first, then each
19118
19149
  * member position sequentially, so members may briefly flash into their
19119
- * default slots before being moved to the requested positions.
19150
+ * default slots before being moved to the requested positions. Targeted
19151
+ * members are validated upfront, though: when any of them has no
19152
+ * {@link Participant.target | member call context} yet, the whole call
19153
+ * rejects before any request is sent and the layout is left unchanged.
19120
19154
  *
19121
19155
  * @param layout - Layout name (must be one of {@link layouts}).
19122
19156
  * @param positions - Optional map of member IDs to {@link VideoPosition} values.
19123
19157
  * When omitted or empty, only the layout is changed.
19124
19158
  * @throws {InvalidParams} If the layout is not in the available {@link layouts}.
19159
+ * @throws {ParticipantNotReadyError} If a targeted member's call context has
19160
+ * not been received yet — thrown before any request is sent.
19125
19161
  *
19126
19162
  * @example
19127
19163
  * ```ts
@@ -19132,18 +19168,19 @@ var WebRTCCall = class extends Destroyable {
19132
19168
  */
19133
19169
  async setLayout(layout, positions) {
19134
19170
  if (!this.layouts.includes(layout)) throw new InvalidParams(`Layout ${layout} is not available in the current call layouts: ${this.layouts.join(", ")}`);
19135
- const selfId = await (0, import_cjs$11.firstValueFrom)(this.selfId$.pipe((0, import_cjs$11.filter)((id) => id !== null)));
19136
- await this.executeMethod(selfId, "call.layout.set", { layout });
19137
- const positionEntries = Object.entries(positions ?? {});
19138
- if (positionEntries.length === 0) return;
19139
- for (const [memberId, position] of positionEntries) {
19171
+ const targets = [];
19172
+ for (const [memberId, position] of Object.entries(positions ?? {})) {
19140
19173
  const participant = this.participants.find((p) => p.id === memberId);
19141
19174
  if (!participant) {
19142
19175
  logger$12.warn(`[Call] setLayout: member ${memberId} not found in participants; skipping position ${position}`);
19143
19176
  continue;
19144
19177
  }
19145
- await participant.setPosition(position);
19178
+ participant.target;
19179
+ targets.push([participant, position]);
19146
19180
  }
19181
+ const selfId = await (0, import_cjs$11.firstValueFrom)(this.selfId$.pipe((0, import_cjs$11.filter)((id) => id !== null)));
19182
+ await this.executeMethod(selfId, "call.layout.set", { layout });
19183
+ for (const [participant, position] of targets) await participant.setPosition(position);
19147
19184
  }
19148
19185
  /**
19149
19186
  * Transfers the call to another destination.
@@ -21975,7 +22012,7 @@ var SignalWire = class extends Destroyable {
21975
22012
  logger$1.warn("[SignalWire] Failed to initialize VisibilityController:", error);
21976
22013
  }
21977
22014
  try {
21978
- this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "4.0.0-rc.1" });
22015
+ this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "4.0.0-rc.2" });
21979
22016
  } catch (error) {
21980
22017
  logger$1.warn("[SignalWire] Failed to initialize DiagnosticsCollector:", error);
21981
22018
  }
@@ -22510,7 +22547,7 @@ var StaticCredentialProvider = class {
22510
22547
  /**
22511
22548
  * Library version from package.json, injected at build time.
22512
22549
  */
22513
- const version = "4.0.0-rc.1";
22550
+ const version = "4.0.0-rc.2";
22514
22551
  /**
22515
22552
  * Flag indicating the library has been loaded and is ready to use.
22516
22553
  * For UMD builds: `window.SignalWire.ready`
@@ -22532,7 +22569,7 @@ const ready = true;
22532
22569
  */
22533
22570
  const emitReadyEvent = () => {
22534
22571
  if (typeof window !== "undefined") {
22535
- const event = new CustomEvent("signalwire:js:ready", { detail: { version: "4.0.0-rc.1" } });
22572
+ const event = new CustomEvent("signalwire:js:ready", { detail: { version: "4.0.0-rc.2" } });
22536
22573
  window.dispatchEvent(event);
22537
22574
  }
22538
22575
  };
@@ -22558,6 +22595,7 @@ if (typeof process === "undefined") globalThis.process = { env: { NODE_ENV: "pro
22558
22595
  //#endregion
22559
22596
  exports.Address = Address;
22560
22597
  exports.CallCreateError = CallCreateError;
22598
+ exports.CallNotReadyError = CallNotReadyError;
22561
22599
  exports.ClientPreferences = ClientPreferences;
22562
22600
  exports.CollectionFetchError = CollectionFetchError;
22563
22601
  exports.DPoPInitError = DPoPInitError;
@@ -22569,6 +22607,7 @@ exports.MediaTrackError = MediaTrackError;
22569
22607
  exports.MessageParseError = MessageParseError;
22570
22608
  exports.OverconstrainedFallbackError = OverconstrainedFallbackError;
22571
22609
  exports.Participant = Participant;
22610
+ exports.ParticipantNotReadyError = ParticipantNotReadyError;
22572
22611
  exports.PreflightError = PreflightError;
22573
22612
  exports.RecoveryError = RecoveryError;
22574
22613
  exports.SelfCapabilities = SelfCapabilities;