@signalwire/js 4.0.0-dev-20260305000713 → 4.0.0-dev-20260310150416

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 CHANGED
@@ -6915,7 +6915,7 @@ var require_startWith = /* @__PURE__ */ __commonJSMin(((exports) => {
6915
6915
  var concat_1$2 = require_concat$1();
6916
6916
  var args_1$2 = require_args();
6917
6917
  var lift_1$14 = require_lift();
6918
- function startWith() {
6918
+ function startWith$1() {
6919
6919
  var values = [];
6920
6920
  for (var _i = 0; _i < arguments.length; _i++) values[_i] = arguments[_i];
6921
6921
  var scheduler = args_1$2.popScheduler(values);
@@ -6923,7 +6923,7 @@ var require_startWith = /* @__PURE__ */ __commonJSMin(((exports) => {
6923
6923
  (scheduler ? concat_1$2.concat(values, source, scheduler) : concat_1$2.concat(values, source)).subscribe(subscriber);
6924
6924
  });
6925
6925
  }
6926
- exports.startWith = startWith;
6926
+ exports.startWith = startWith$1;
6927
6927
  }));
6928
6928
 
6929
6929
  //#endregion
@@ -9122,6 +9122,15 @@ var JSONRPCError = class extends Error {
9122
9122
  this.name = "JSONRPCError";
9123
9123
  }
9124
9124
  };
9125
+ var RPCError = class extends Error {
9126
+ constructor(code, requestId, message, data, options) {
9127
+ super(message, options);
9128
+ this.code = code;
9129
+ this.requestId = requestId;
9130
+ this.data = data;
9131
+ this.name = "RPCError";
9132
+ }
9133
+ };
9125
9134
  var InvalidParams = class extends Error {
9126
9135
  constructor(message, options) {
9127
9136
  super(message, options);
@@ -9660,7 +9669,7 @@ var PreferencesContainer = class PreferencesContainer {
9660
9669
  skipDeviceMonitoring: false,
9661
9670
  savePreferences: false
9662
9671
  };
9663
- this.receiveVideo = true;
9672
+ this.receiveVideo = false;
9664
9673
  this.receiveAudio = true;
9665
9674
  this.preferredAudioInput = null;
9666
9675
  this.preferredAudioOutput = null;
@@ -12630,12 +12639,53 @@ var CallEventsManager = class extends Destroyable {
12630
12639
 
12631
12640
  //#endregion
12632
12641
  //#region src/helpers/SDPHelper.ts
12642
+ /** Valid SDP direction attribute values. */
12643
+ const SDP_DIRECTIONS = new Set([
12644
+ "sendrecv",
12645
+ "sendonly",
12646
+ "recvonly",
12647
+ "inactive"
12648
+ ]);
12633
12649
  /**
12634
- * SDPHelper - Utility functions for SDP (Session Description Protocol) parsing and validation.
12650
+ * Extracts the media directions (audio/video) from an SDP string.
12651
+ *
12652
+ * Parses each media section (`m=audio` / `m=video`) and reads the `a=` direction
12653
+ * attribute (`sendrecv`, `sendonly`, `recvonly`, `inactive`).
12654
+ * If no explicit direction attribute is found for a media section, defaults to `sendrecv`
12655
+ * per RFC 4566.
12656
+ *
12657
+ * @param sdp - The SDP string to parse
12658
+ * @returns The extracted audio and video directions
12635
12659
  *
12636
- * This module provides helper functions to analyze and validate SDP content,
12637
- * particularly for ICE candidate validation in WebRTC connections.
12660
+ * @example
12661
+ * ```typescript
12662
+ * 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`;
12663
+ * extractMediaDirectionsFromSDP(sdp);
12664
+ * // { audio: 'sendrecv', video: 'recvonly' }
12665
+ * ```
12638
12666
  */
12667
+ function extractMediaDirectionsFromSDP(sdp) {
12668
+ const result = {
12669
+ audio: "inactive",
12670
+ video: "inactive"
12671
+ };
12672
+ if (!sdp) return result;
12673
+ const lines = sdp.split(/\r?\n/);
12674
+ let currentMediaKind = null;
12675
+ let currentDirection = null;
12676
+ for (const line of lines) if (line.startsWith("m=")) {
12677
+ if (currentMediaKind) result[currentMediaKind] = currentDirection ?? "sendrecv";
12678
+ if (line.startsWith("m=audio")) currentMediaKind = "audio";
12679
+ else if (line.startsWith("m=video")) currentMediaKind = "video";
12680
+ else currentMediaKind = null;
12681
+ currentDirection = null;
12682
+ } else if (currentMediaKind && line.startsWith("a=")) {
12683
+ const attr = line.substring(2).trim();
12684
+ if (SDP_DIRECTIONS.has(attr)) currentDirection = attr;
12685
+ }
12686
+ if (currentMediaKind) result[currentMediaKind] = currentDirection ?? "sendrecv";
12687
+ return result;
12688
+ }
12639
12689
  /**
12640
12690
  * Validates that an SDP string has at least one non-host ICE candidate
12641
12691
  * for each media section (m= line).
@@ -12938,6 +12988,15 @@ var LocalStreamController = class extends Destroyable {
12938
12988
  track.addEventListener("ended", this.mediaTrackEndedHandler);
12939
12989
  }
12940
12990
  /**
12991
+ * Update the controller options (e.g., when media overrides are applied).
12992
+ */
12993
+ updateOptions(options) {
12994
+ this.options = {
12995
+ ...this.options,
12996
+ ...options
12997
+ };
12998
+ }
12999
+ /**
12941
13000
  * Stop all local tracks and clean up.
12942
13001
  */
12943
13002
  stopAllTracks() {
@@ -13281,6 +13340,7 @@ var RTCPeerConnectionController = class extends Destroyable {
13281
13340
  this._initialized$ = this.createReplaySubject(1);
13282
13341
  this._remoteDescription$ = this.createReplaySubject(1);
13283
13342
  this._remoteStream$ = this.createBehaviorSubject(null);
13343
+ this._remoteOfferMediaDirections = null;
13284
13344
  this.deviceController = deviceController ?? {};
13285
13345
  this.id = options.callId ?? v4_default();
13286
13346
  this._type = remoteSessionDescription ? "answer" : "offer";
@@ -13288,10 +13348,19 @@ var RTCPeerConnectionController = class extends Destroyable {
13288
13348
  type: "offer",
13289
13349
  sdp: remoteSessionDescription
13290
13350
  } : void 0;
13351
+ this._remoteOfferMediaDirections = remoteSessionDescription ? extractMediaDirectionsFromSDP(remoteSessionDescription) : null;
13352
+ const offerDefaults = this._remoteOfferMediaDirections ? {
13353
+ audio: this._remoteOfferMediaDirections.audio.includes("recv"),
13354
+ video: this._remoteOfferMediaDirections.video.includes("recv"),
13355
+ receiveAudio: this._remoteOfferMediaDirections.audio.includes("send"),
13356
+ receiveVideo: this._remoteOfferMediaDirections.video.includes("send")
13357
+ } : {};
13291
13358
  this.options = {
13292
- receiveAudio: options.receiveAudio ?? PreferencesContainer.instance.receiveAudio,
13293
- receiveVideo: options.receiveVideo ?? PreferencesContainer.instance.receiveVideo,
13294
- ...options
13359
+ ...options,
13360
+ audio: options.audio ?? offerDefaults.audio,
13361
+ video: options.video ?? offerDefaults.video,
13362
+ receiveAudio: options.receiveAudio ?? offerDefaults.receiveAudio ?? PreferencesContainer.instance.receiveAudio,
13363
+ receiveVideo: options.receiveVideo ?? offerDefaults.receiveVideo ?? PreferencesContainer.instance.receiveVideo
13295
13364
  };
13296
13365
  this.localStreamController = new LocalStreamController({
13297
13366
  propose: this.propose,
@@ -13458,12 +13527,12 @@ var RTCPeerConnectionController = class extends Destroyable {
13458
13527
  default: return {
13459
13528
  ...options,
13460
13529
  offerToReceiveAudio: true,
13461
- offerToReceiveVideo: Boolean(this.inputVideoDeviceConstraints)
13530
+ offerToReceiveVideo: this.options.receiveVideo ?? Boolean(this.inputVideoDeviceConstraints)
13462
13531
  };
13463
13532
  }
13464
13533
  }
13465
13534
  get answerOptions() {
13466
- return {};
13535
+ return { iceRestart: this.firstSDPExchangeCompleted ? true : void 0 };
13467
13536
  }
13468
13537
  /**
13469
13538
  * Initialize the RTCPeerConnection and setup event listeners.
@@ -13498,11 +13567,15 @@ var RTCPeerConnectionController = class extends Destroyable {
13498
13567
  });
13499
13568
  await this.updateSelectedInputDevice(kind, deviceInfo);
13500
13569
  });
13501
- await this.setupTrackHandling();
13502
- this._initialized$.next(true);
13503
13570
  if (this.type === "answer" && this.sdpInit) {
13571
+ await this.setupRemoteTracks();
13572
+ this._initialized$.next(true);
13504
13573
  this.setupEventListeners();
13505
- await this.handleOfferReceived();
13574
+ this._isNegotiating$.next(true);
13575
+ await this._setRemoteDescription(this.sdpInit);
13576
+ } else {
13577
+ await this.setupTrackHandling();
13578
+ this._initialized$.next(true);
13506
13579
  }
13507
13580
  } catch (error) {
13508
13581
  logger$11.error("[RTCPeerConnectionController] Initialization error:", error);
@@ -13600,6 +13673,35 @@ var RTCPeerConnectionController = class extends Destroyable {
13600
13673
  default:
13601
13674
  }
13602
13675
  }
13676
+ /**
13677
+ * Accept an inbound call by creating the SDP answer.
13678
+ * Optionally override media options before the answer is generated.
13679
+ * Must be called after initialization for inbound (answer-type) connections.
13680
+ */
13681
+ async acceptInbound(mediaOverrides) {
13682
+ if (mediaOverrides) {
13683
+ const { audio, video, receiveAudio, receiveVideo } = mediaOverrides;
13684
+ this.options = {
13685
+ ...this.options,
13686
+ ...audio !== void 0 ? { audio } : {},
13687
+ ...video !== void 0 ? { video } : {},
13688
+ ...receiveAudio !== void 0 ? { receiveAudio } : {},
13689
+ ...receiveVideo !== void 0 ? { receiveVideo } : {}
13690
+ };
13691
+ this.transceiverController?.updateOptions({
13692
+ receiveAudio: this.receiveAudio,
13693
+ receiveVideo: this.receiveVideo
13694
+ });
13695
+ this.localStreamController.updateOptions({
13696
+ inputAudioDeviceConstraints: this.inputAudioDeviceConstraints,
13697
+ inputVideoDeviceConstraints: this.inputVideoDeviceConstraints
13698
+ });
13699
+ }
13700
+ await this.setupLocalTracks();
13701
+ const { answerOptions } = this;
13702
+ logger$11.debug("[RTCPeerConnectionController] Creating inbound answer with options:", answerOptions);
13703
+ await this.createAnswer(answerOptions);
13704
+ }
13603
13705
  async handleOfferReceived() {
13604
13706
  if (!this.sdpInit) throw new DependencyError("SDP initialization parameters are not set");
13605
13707
  this._isNegotiating$.next(true);
@@ -13820,7 +13922,7 @@ var RTCPeerConnectionController = class extends Destroyable {
13820
13922
  });
13821
13923
  }
13822
13924
  get mediaDirections() {
13823
- return this.transceiverController?.getMediaDirections() ?? {
13925
+ return this.transceiverController?.getMediaDirections() ?? this._remoteOfferMediaDirections ?? {
13824
13926
  audio: "inactive",
13825
13927
  video: "inactive"
13826
13928
  };
@@ -14151,6 +14253,36 @@ var WebRTCVertoManager = class extends VertoManager {
14151
14253
  this.subscribeTo(rtcPeerConnController.errors$, (error) => {
14152
14254
  this.onError?.(error);
14153
14255
  });
14256
+ if (options.initOffer) this.handleInboundAnswer(rtcPeerConnController);
14257
+ }
14258
+ async handleInboundAnswer(rtcPeerConnController) {
14259
+ logger$10.debug("[WebRTCManager] Waiting for inbound call to be accepted or rejected");
14260
+ const vertoByeOrAccepted = await (0, import_cjs$10.firstValueFrom)((0, import_cjs$10.race)(this.vertoBye$, this.webRtcCallSession.answered$).pipe((0, import_cjs$10.takeUntil)(this.destroyed$))).catch(() => null);
14261
+ if (vertoByeOrAccepted === null) {
14262
+ logger$10.debug("[WebRTCManager] Inbound answer handler aborted (destroyed).");
14263
+ return;
14264
+ }
14265
+ if (isVertoByeMessage(vertoByeOrAccepted)) {
14266
+ logger$10.info("[WebRTCManager] Inbound call ended by remote before answer.");
14267
+ this.callSession?.destroy();
14268
+ } else if (!vertoByeOrAccepted) {
14269
+ logger$10.info("[WebRTCManager] Inbound call rejected by user.");
14270
+ try {
14271
+ await this.bye("USER_BUSY");
14272
+ } finally {
14273
+ this._signalingStatus$.next("disconnected");
14274
+ this.callSession?.destroy();
14275
+ }
14276
+ } else {
14277
+ logger$10.debug("[WebRTCManager] Inbound call accepted, creating SDP answer");
14278
+ const answerOptions = this.webRtcCallSession.answerMediaOptions;
14279
+ try {
14280
+ await rtcPeerConnController.acceptInbound(answerOptions);
14281
+ } catch (error) {
14282
+ logger$10.error("[WebRTCManager] Error creating inbound answer:", error);
14283
+ this.onError?.(error instanceof Error ? error : new Error(String(error), { cause: error }));
14284
+ }
14285
+ }
14154
14286
  }
14155
14287
  setupVertoAttachHandler() {
14156
14288
  this.subscribeTo(this.vertoAttach$, async (vertoAttach) => {
@@ -14168,7 +14300,7 @@ var WebRTCVertoManager = class extends VertoManager {
14168
14300
  });
14169
14301
  }
14170
14302
  initObservables(rtcPeerConnController) {
14171
- this.mediaDirections$ = rtcPeerConnController.connectionState$.pipe((0, import_cjs$10.filter)((state) => state === "connected"), (0, import_cjs$10.takeUntil)(this.destroyed$), (0, import_cjs$10.map)(() => rtcPeerConnController.mediaDirections));
14303
+ this.mediaDirections$ = rtcPeerConnController.connectionState$.pipe((0, import_cjs$10.filter)((state) => state === "connected"), (0, import_cjs$10.map)(() => rtcPeerConnController.mediaDirections), (0, import_cjs$10.startWith)(rtcPeerConnController.mediaDirections), (0, import_cjs$10.takeUntil)(this.destroyed$));
14172
14304
  this.localStream$ = rtcPeerConnController.localStream$.pipe(filterNull(), (0, import_cjs$10.takeUntil)(this.destroyed$));
14173
14305
  this.remoteStream$ = rtcPeerConnController.remoteStream$.pipe(filterNull(), (0, import_cjs$10.takeUntil)(this.destroyed$));
14174
14306
  }
@@ -14850,10 +14982,15 @@ var WebRTCCall = class extends Destroyable {
14850
14982
  async sendDigits(dtmf) {
14851
14983
  return this.vertoManager.sendDigits(dtmf);
14852
14984
  }
14853
- /** Accepts an inbound call. */
14854
- answer() {
14985
+ /** Accepts an inbound call, optionally overriding media options for the answer. */
14986
+ answer(options) {
14987
+ this._answerMediaOptions = options;
14855
14988
  this._answered$.next(true);
14856
14989
  }
14990
+ /** Media options provided when answering. Used internally by the VertoManager. */
14991
+ get answerMediaOptions() {
14992
+ return this._answerMediaOptions;
14993
+ }
14857
14994
  /** Rejects an inbound call. */
14858
14995
  reject() {
14859
14996
  this._answered$.next(false);
@@ -15292,9 +15429,15 @@ var PendingRPC = class PendingRPC {
15292
15429
  return () => signal.removeEventListener("abort", abortHandler);
15293
15430
  }) : import_cjs$6.NEVER).subscribe({
15294
15431
  next: (response) => {
15295
- logger$7.debug(`[PendingRPC(${this.id}) request:${request.id}] Resolving promise with response:`, response);
15296
15432
  isSettled = true;
15297
- resolve(response);
15433
+ if (response.error) {
15434
+ const rpcError = new RPCError(response.error.code, request.id, response.error.message, response.error.data);
15435
+ logger$7.debug(`[PendingRPC(${this.id}) request:${request.id}] Rejecting promise with RPC error:`, rpcError);
15436
+ reject(rpcError);
15437
+ } else {
15438
+ logger$7.debug(`[PendingRPC(${this.id}) request:${request.id}] Resolving promise with response:`, response);
15439
+ resolve(response);
15440
+ }
15298
15441
  subscription.unsubscribe();
15299
15442
  },
15300
15443
  error: (error) => {
@@ -16343,7 +16486,12 @@ var SignalWire = class extends Destroyable {
16343
16486
  this._subscriber$.next(new Subscriber(this._deps.http));
16344
16487
  if (!this._options.skipConnection) await this.connect();
16345
16488
  if (!this._options.reconnectAttachedCalls && this._attachManager) await this._attachManager.flush();
16346
- if (!this._options.skipRegister) this.register();
16489
+ if (!this._options.skipRegister) try {
16490
+ await this.register();
16491
+ } catch (error) {
16492
+ logger$1.error("[SignalWire] Registration failed:", error);
16493
+ this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
16494
+ }
16347
16495
  this.handleAttachments();
16348
16496
  }
16349
16497
  async handleAttachments() {
@@ -16506,8 +16654,22 @@ var SignalWire = class extends Destroyable {
16506
16654
  }));
16507
16655
  this._isRegistered$.next(true);
16508
16656
  } catch (error) {
16509
- logger$1.error("[SignalWire] Failed to register subscriber:", error);
16657
+ logger$1.debug("[SignalWire] Failed to register subscriber, trying reauthentication...");
16658
+ if (this._deps.credential.token) this._clientSession.reauthenticate(this._deps.credential.token).then(async () => {
16659
+ logger$1.debug("[SignalWire] Reauthentication successful, retrying register()");
16660
+ await this._transport.execute(RPCExecute({
16661
+ method: "subscriber.online",
16662
+ params: {}
16663
+ }));
16664
+ this._isRegistered$.next(true);
16665
+ }).catch((reauthError) => {
16666
+ logger$1.error("[SignalWire] Reauthentication failed during register():", reauthError);
16667
+ 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 }) });
16668
+ this._errors$.next(registerError);
16669
+ throw registerError;
16670
+ });
16510
16671
  this._errors$.next(error instanceof Error ? error : new Error(String(error), { cause: error }));
16672
+ throw error;
16511
16673
  }
16512
16674
  }
16513
16675
  /** Unregisters the subscriber, going offline for inbound calls. */