@signalwire/js 4.0.0-rc.3 → 4.0.0-rc.5

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/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- const require_operators = require('./operators-BJ6QW1VP.cjs');
1
+ const require_operators = require('./operators-IoitK-As.cjs');
2
2
  let jwt_decode = require("jwt-decode");
3
3
  let rxjs = require("rxjs");
4
4
  let uuid = require("uuid");
@@ -2589,6 +2589,12 @@ const RPCReauthenticate = (params) => {
2589
2589
 
2590
2590
  //#endregion
2591
2591
  //#region src/core/RPCMessages/RPCPing.ts
2592
+ const RPCPing = () => {
2593
+ return buildRPCRequest({
2594
+ method: "signalwire.ping",
2595
+ params: { timestamp: Date.now() / 1e3 }
2596
+ });
2597
+ };
2592
2598
  const RPCPingResponse = (id, timestamp) => {
2593
2599
  return makeRPCResponse({
2594
2600
  id,
@@ -6451,6 +6457,7 @@ var WebRTCVertoManager = class extends VertoManager {
6451
6457
  this._signalingStatus$ = this.createReplaySubject(1);
6452
6458
  this._screenShareStatus$ = this.createBehaviorSubject("none");
6453
6459
  this._rtcPeerConnectionsMap = /* @__PURE__ */ new Map();
6460
+ this._lastVertoPings = /* @__PURE__ */ new Map();
6454
6461
  this._legErrors$ = this.createSubject();
6455
6462
  this._nodeId$ = this.createBehaviorSubject(options.nodeId ?? null);
6456
6463
  this.onError = options.onError;
@@ -6566,9 +6573,13 @@ var WebRTCVertoManager = class extends VertoManager {
6566
6573
  this.applyServerMediaParams(rtcPeerConnController, audio, video);
6567
6574
  });
6568
6575
  this.subscribeTo(this.vertoPing$, (vertoPing) => {
6576
+ this._lastVertoPings.set(vertoPing.callID, vertoPing);
6569
6577
  this.attachManager.refresh(this.buildAttachableCall());
6570
6578
  this.sendVertoPong(vertoPing);
6571
6579
  });
6580
+ this.subscribeTo(this.webRtcCallSession.clientSession.authenticated$.pipe((0, rxjs.skip)(1), (0, rxjs.filter)(Boolean)), () => {
6581
+ this._lastVertoPings.forEach((vertoPing) => void this.sendVertoPong(vertoPing));
6582
+ });
6572
6583
  }
6573
6584
  /**
6574
6585
  * An auxiliary-leg failure must never destroy the call; main-leg and
@@ -9908,6 +9919,8 @@ var ClientSessionManager = class extends Destroyable {
9908
9919
  this._errors$ = this.createReplaySubject(1);
9909
9920
  this._authState$ = this.createBehaviorSubject({ kind: "unauthenticated" });
9910
9921
  this._wasClientBound = false;
9922
+ this._lastFreshConnectReminted = false;
9923
+ this._remintBeforeNextFreshConnect = false;
9911
9924
  this._userInfo$ = this.createBehaviorSubject(null);
9912
9925
  this._calls$ = this.createBehaviorSubject({});
9913
9926
  this._iceServers$ = this.createBehaviorSubject([]);
@@ -10103,13 +10116,27 @@ var ClientSessionManager = class extends Destroyable {
10103
10116
  await (0, rxjs.firstValueFrom)(this.authenticated$.pipe((0, rxjs.takeUntil)(this.destroyed$), (0, rxjs.filter)(Boolean), (0, rxjs.take)(1), (0, rxjs.timeout)({ first: 15e3 })));
10104
10117
  }
10105
10118
  async handleAuthenticationError(error) {
10119
+ if (error instanceof require_operators.TransportConnectionError) {
10120
+ logger$9.debug("[Session] Socket closed during authentication, waiting for reconnect");
10121
+ return;
10122
+ }
10106
10123
  logger$9.error("Authentication error:", error);
10107
10124
  const isRecoverableAuthError$1 = error instanceof require_operators.JSONRPCError && (error.code === require_operators.RPC_ERROR_REQUESTER_VALIDATION_FAILED || error.code === require_operators.RPC_ERROR_INVALID_PARAMS || error.code === require_operators.RPC_ERROR_AUTHENTICATION_FAILED);
10108
10125
  const hasStoredState = await (0, rxjs.firstValueFrom)(this.authorizationState$.pipe((0, rxjs.take)(1))) !== void 0;
10109
10126
  if (isRecoverableAuthError$1 && hasStoredState) {
10110
10127
  logger$9.debug("[Session] Recoverable auth error — cleaning up stored state and reconnecting fresh");
10111
10128
  await this.discardResumeStateAndReconnect();
10112
- } else this._errors$.next(error);
10129
+ } else if (isRecoverableAuthError$1 && this.onBeforeReconnect && !this._lastFreshConnectReminted) {
10130
+ logger$9.debug("[Session] Fresh connect refused — re-minting the credential and retrying once");
10131
+ this._remintBeforeNextFreshConnect = true;
10132
+ this.transport.reconnect();
10133
+ } else {
10134
+ this._errors$.next(error);
10135
+ if (isRecoverableAuthError$1) {
10136
+ this.transport.disconnect();
10137
+ this.onAuthenticationRejected?.();
10138
+ }
10139
+ }
10113
10140
  }
10114
10141
  /**
10115
10142
  * Clear the resume state (authorization_state + protocol) and ask the
@@ -10201,6 +10228,7 @@ var ClientSessionManager = class extends Destroyable {
10201
10228
  }
10202
10229
  async authenticate() {
10203
10230
  logger$9.debug("[Session] Starting authentication process");
10231
+ this._lastFreshConnectReminted = false;
10204
10232
  const persistedParams = await (0, rxjs.firstValueFrom)((0, rxjs.combineLatest)({
10205
10233
  protocol: this.transport.protocol$,
10206
10234
  authorization_state: this.authorizationState$
@@ -10217,8 +10245,11 @@ var ClientSessionManager = class extends Destroyable {
10217
10245
  else {
10218
10246
  const credential = this.getCredential();
10219
10247
  const credentialExpired = credential.expiry_at !== void 0 && credential.expiry_at <= Date.now() + require_operators.CREDENTIAL_EXPIRY_SKEW_MS;
10220
- if (this.onBeforeReconnect && (this.clientBound || credentialExpired)) {
10248
+ const remint = this.clientBound || credentialExpired || this._remintBeforeNextFreshConnect;
10249
+ this._remintBeforeNextFreshConnect = false;
10250
+ if (this.onBeforeReconnect && remint) {
10221
10251
  logger$9.debug("[Session] Refreshing credentials before fresh connect");
10252
+ this._lastFreshConnectReminted = true;
10222
10253
  await this.onBeforeReconnect();
10223
10254
  }
10224
10255
  }
@@ -10242,7 +10273,9 @@ var ClientSessionManager = class extends Destroyable {
10242
10273
  protocol: persistedParams.protocol
10243
10274
  } : {}
10244
10275
  });
10245
- const response = await (0, rxjs.lastValueFrom)((0, rxjs.from)(this.transport.execute(rpcConnectRequest)).pipe(require_operators.throwOnRPCError(), (0, rxjs.map)((res) => res.result), (0, rxjs.filter)(isRPCConnectResult), (0, rxjs.tap)(() => {
10276
+ if (this.transport.connectionStatus !== "connected") throw new require_operators.TransportConnectionError("WebSocket closed during authentication");
10277
+ const socketClosed$ = this.transport.connectionStatus$.pipe((0, rxjs.skip)(1), (0, rxjs.filter)((status) => status === "reconnecting" || status === "disconnected"), (0, rxjs.take)(1), (0, rxjs.switchMap)(() => (0, rxjs.throwError)(() => new require_operators.TransportConnectionError("WebSocket closed during authentication"))));
10278
+ const response = await (0, rxjs.lastValueFrom)((0, rxjs.race)((0, rxjs.from)(this.transport.execute(rpcConnectRequest)), socketClosed$).pipe(require_operators.throwOnRPCError(), (0, rxjs.map)((res) => res.result), (0, rxjs.filter)(isRPCConnectResult), (0, rxjs.tap)(() => {
10246
10279
  logger$9.debug("[Session] Response passed filter, processing authentication result");
10247
10280
  }), (0, rxjs.take)(1), (0, rxjs.catchError)((err) => {
10248
10281
  logger$9.error("[Session] Authentication RPC failed:", err);
@@ -10257,6 +10290,7 @@ var ClientSessionManager = class extends Destroyable {
10257
10290
  this._authorization$.next(response.authorization);
10258
10291
  if (response.authorization.cnf?.jkt) this._wasClientBound = true;
10259
10292
  this._iceServers$.next(response.ice_servers ?? []);
10293
+ this.transport.setAuthenticated();
10260
10294
  this._authState$.next({ kind: "authenticated" });
10261
10295
  logger$9.debug("[Session] Authentication completed successfully");
10262
10296
  }
@@ -11449,6 +11483,7 @@ var WebSocketController = class WebSocketController extends Destroyable {
11449
11483
  }
11450
11484
  handleClose(_event) {
11451
11485
  this.clearConnectionTimeout();
11486
+ this.messageQueue = [];
11452
11487
  if (this.shouldReconnect) {
11453
11488
  this._status$.next("reconnecting");
11454
11489
  this.scheduleReconnection();
@@ -11518,6 +11553,9 @@ var WebSocketController = class WebSocketController extends Destroyable {
11518
11553
  function isSignalwirePingRequest(value) {
11519
11554
  return isJSONRPCRequest(value) && value.method === "signalwire.ping";
11520
11555
  }
11556
+ function isSignalwireConnectRequest(value) {
11557
+ return isJSONRPCRequest(value) && value.method === "signalwire.connect";
11558
+ }
11521
11559
 
11522
11560
  //#endregion
11523
11561
  //#region src/managers/TransportManager.ts
@@ -11531,6 +11569,10 @@ var TransportManager = class extends Destroyable {
11531
11569
  this.protocol$ = this.createReplaySubject(1);
11532
11570
  this.isConnecting = false;
11533
11571
  this.isConnected = false;
11572
+ this.socketStatus = "disconnected";
11573
+ this.sessionAuthenticated = false;
11574
+ this.heldRequests = [];
11575
+ this.serverPingWatch = 0;
11534
11576
  this.ackEvent = () => {
11535
11577
  return (0, rxjs.tap)((message) => {
11536
11578
  if (isSignalwireRequest(message)) try {
@@ -11546,6 +11588,7 @@ var TransportManager = class extends Destroyable {
11546
11588
  if (isSignalwirePingRequest(message)) {
11547
11589
  try {
11548
11590
  logger$2.debug("[Transport] Received ping, sending pong", { pingId: message.id });
11591
+ if (this.sessionAuthenticated) this.armServerPingWatchdog();
11549
11592
  this.send(RPCPingResponse(message.id));
11550
11593
  } catch (error) {
11551
11594
  logger$2.error("[Transport] Failed to send ping response:", error);
@@ -11583,6 +11626,14 @@ var TransportManager = class extends Destroyable {
11583
11626
  this.subscribeTo(this._webSocketConnections.errors$, (error) => {
11584
11627
  this.onError?.(error);
11585
11628
  });
11629
+ this.subscribeTo(this._webSocketConnections.status$, (status) => {
11630
+ this.socketStatus = status;
11631
+ if (status !== "connected") {
11632
+ this.sessionAuthenticated = false;
11633
+ this.clearServerPingWatchdog();
11634
+ }
11635
+ if (status === "disconnected") this.heldRequests = [];
11636
+ });
11586
11637
  this.initialized$ = (0, rxjs.defer)(() => (0, rxjs.from)(this._init())).pipe((0, rxjs.shareReplay)(1), (0, rxjs.takeUntil)(this.destroyed$));
11587
11638
  this._jsonRPCMessage$ = this._webSocketConnections.incomingMessages$.pipe((0, rxjs.map)((event) => {
11588
11639
  try {
@@ -11611,6 +11662,9 @@ var TransportManager = class extends Destroyable {
11611
11662
  get connectionStatus$() {
11612
11663
  return this._webSocketConnections.status$;
11613
11664
  }
11665
+ get connectionStatus() {
11666
+ return this.socketStatus;
11667
+ }
11614
11668
  async connect() {
11615
11669
  if (this.isConnecting || this.isConnected) {
11616
11670
  logger$2.warn("[Transport] Already connecting or connected");
@@ -11655,16 +11709,45 @@ var TransportManager = class extends Destroyable {
11655
11709
  }
11656
11710
  async execute(request, options) {
11657
11711
  this.send(request);
11658
- return new PendingRPC(request, this._jsonRPCResponse$, options).promise;
11712
+ const pending = new PendingRPC(request, this._jsonRPCResponse$, options).promise;
11713
+ pending.catch(() => {
11714
+ this.heldRequests = this.heldRequests.filter((held) => held.id !== request.id);
11715
+ });
11716
+ return pending;
11659
11717
  }
11660
11718
  send(message) {
11661
11719
  const payload = JSON.stringify(message);
11720
+ if (isJSONRPCResponse(message)) {
11721
+ if (this.socketStatus !== "connected") {
11722
+ logger$2.debug("[Transport] Dropping response while disconnected", { id: message.id });
11723
+ return;
11724
+ }
11725
+ } else if (!this.sessionAuthenticated && !isSignalwireConnectRequest(message)) {
11726
+ this.heldRequests.push({
11727
+ id: message.id,
11728
+ payload
11729
+ });
11730
+ return;
11731
+ }
11662
11732
  this._outgoingMessages$.next(payload);
11663
11733
  }
11734
+ /**
11735
+ * Called by the session when signalwire.connect succeeds on the current
11736
+ * socket. Sends the requests held since the socket opened, in order.
11737
+ */
11738
+ setAuthenticated() {
11739
+ if (this.socketStatus !== "connected") return;
11740
+ this.sessionAuthenticated = true;
11741
+ this.armServerPingWatchdog();
11742
+ const held = this.heldRequests;
11743
+ this.heldRequests = [];
11744
+ held.forEach(({ payload }) => this._outgoingMessages$.next(payload));
11745
+ }
11664
11746
  disconnect() {
11665
11747
  logger$2.debug("[Transport] Disconnecting");
11666
11748
  this.isConnected = false;
11667
11749
  this.isConnecting = false;
11750
+ this.clearServerPingWatchdog();
11668
11751
  this._webSocketConnections.disconnect();
11669
11752
  }
11670
11753
  destroy() {
@@ -11673,6 +11756,28 @@ var TransportManager = class extends Destroyable {
11673
11756
  super.destroy();
11674
11757
  this._webSocketConnections.destroy();
11675
11758
  }
11759
+ armServerPingWatchdog() {
11760
+ this.clearServerPingWatchdog();
11761
+ const watch = this.serverPingWatch;
11762
+ this.serverPingTimer = setTimeout(() => void this.probeServer(watch), require_operators.SERVER_PING_TIMEOUT_MS);
11763
+ }
11764
+ /** Bumps the watch so a probe still in flight no longer acts on its result. */
11765
+ clearServerPingWatchdog() {
11766
+ clearTimeout(this.serverPingTimer);
11767
+ this.serverPingTimer = void 0;
11768
+ this.serverPingWatch += 1;
11769
+ }
11770
+ async probeServer(watch) {
11771
+ logger$2.debug(`[Transport] No signalwire.ping for ${require_operators.SERVER_PING_TIMEOUT_MS}ms, probing the server`);
11772
+ try {
11773
+ await this.execute(RPCPing(), { timeoutMs: require_operators.SERVER_PING_PROBE_TIMEOUT_MS });
11774
+ if (watch === this.serverPingWatch) this.armServerPingWatchdog();
11775
+ } catch (error) {
11776
+ if (watch !== this.serverPingWatch) return;
11777
+ logger$2.warn("[Transport] Server did not answer the ping probe, replacing the socket", error);
11778
+ this.reconnect();
11779
+ }
11780
+ }
11676
11781
  async _loadProtocolFromStorage() {
11677
11782
  try {
11678
11783
  const storedProtocol = await this.storage.getItem(this.protocolKey);
@@ -12198,6 +12303,7 @@ var SignalWire = class extends Destroyable {
12198
12303
  this.persistClientBoundMarker(Boolean(authorization?.cnf?.jkt));
12199
12304
  });
12200
12305
  await this._clientSession.connect();
12306
+ this._clientSession.onAuthenticationRejected = () => void this.disconnect();
12201
12307
  await this._refreshCoordinator?.activate(this._deps.user, this._clientSession);
12202
12308
  this.subscribeTo(this._clientSession.authenticated$.pipe((0, rxjs.skip)(1), (0, rxjs.filter)(Boolean)), async () => {
12203
12309
  try {
@@ -12377,7 +12483,7 @@ var SignalWire = class extends Destroyable {
12377
12483
  logger$1.warn("[SignalWire] Failed to initialize VisibilityController:", error);
12378
12484
  }
12379
12485
  try {
12380
- this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "4.0.0-rc.3" });
12486
+ this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "4.0.0-rc.5" });
12381
12487
  } catch (error) {
12382
12488
  logger$1.warn("[SignalWire] Failed to initialize DiagnosticsCollector:", error);
12383
12489
  }
@@ -12931,7 +13037,7 @@ var StaticCredentialProvider = class {
12931
13037
  /**
12932
13038
  * Library version from package.json, injected at build time.
12933
13039
  */
12934
- const version = "4.0.0-rc.3";
13040
+ const version = "4.0.0-rc.5";
12935
13041
  /**
12936
13042
  * Flag indicating the library has been loaded and is ready to use.
12937
13043
  * For UMD builds: `window.SignalWire.ready`
@@ -12953,7 +13059,7 @@ const ready = true;
12953
13059
  */
12954
13060
  const emitReadyEvent = () => {
12955
13061
  if (typeof window !== "undefined") {
12956
- const event = new CustomEvent("signalwire:js:ready", { detail: { version: "4.0.0-rc.3" } });
13062
+ const event = new CustomEvent("signalwire:js:ready", { detail: { version: "4.0.0-rc.5" } });
12957
13063
  window.dispatchEvent(event);
12958
13064
  }
12959
13065
  };