@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/browser.mjs CHANGED
@@ -4325,13 +4325,13 @@ var require_race$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
4325
4325
  var innerFrom_1$28 = require_innerFrom();
4326
4326
  var argsOrArgArray_1$4 = require_argsOrArgArray();
4327
4327
  var OperatorSubscriber_1$48 = require_OperatorSubscriber();
4328
- function race$4() {
4328
+ function race$5() {
4329
4329
  var sources = [];
4330
4330
  for (var _i = 0; _i < arguments.length; _i++) sources[_i] = arguments[_i];
4331
4331
  sources = argsOrArgArray_1$4.argsOrArgArray(sources);
4332
4332
  return sources.length === 1 ? innerFrom_1$28.innerFrom(sources[0]) : new Observable_1$6.Observable(raceInit(sources));
4333
4333
  }
4334
- exports.race = race$4;
4334
+ exports.race = race$5;
4335
4335
  function raceInit(sources) {
4336
4336
  return function(subscriber) {
4337
4337
  var subscriptions = [];
@@ -6823,12 +6823,12 @@ var require_skip = /* @__PURE__ */ __commonJSMin(((exports) => {
6823
6823
  Object.defineProperty(exports, "__esModule", { value: true });
6824
6824
  exports.skip = void 0;
6825
6825
  var filter_1$3 = require_filter();
6826
- function skip$3(count$1) {
6826
+ function skip$5(count$1) {
6827
6827
  return filter_1$3.filter(function(_, index) {
6828
6828
  return count$1 <= index;
6829
6829
  });
6830
6830
  }
6831
- exports.skip = skip$3;
6831
+ exports.skip = skip$5;
6832
6832
  }));
6833
6833
 
6834
6834
  //#endregion
@@ -9023,6 +9023,18 @@ const DEFAULT_ICE_GATHERING_TIMEOUT_MS = 6e3;
9023
9023
  const DEFAULT_RECONNECT_CALLS_TIMEOUT_MS = 300 * 1e3;
9024
9024
  const DEFAULT_CONNECTION_TIMEOUT_MS = 1e4;
9025
9025
  /**
9026
+ * The server sends `signalwire.ping` every 10s. A socket that stays silent
9027
+ * longer than this is half-open: the server has dropped the session but the
9028
+ * browser never fired `close`.
9029
+ */
9030
+ const SERVER_PING_TIMEOUT_MS = 15e3;
9031
+ /**
9032
+ * How long a `signalwire.ping` the client sends after the server's pings
9033
+ * stopped may take to be answered. The server defers its own pings while its
9034
+ * threadpool is congested, so silence alone does not mean the socket is dead.
9035
+ */
9036
+ const SERVER_PING_PROBE_TIMEOUT_MS = 1e4;
9037
+ /**
9026
9038
  * How long call setup may wait for the network, measured from local media
9027
9039
  * settling until the member id arrives.
9028
9040
  *
@@ -12353,6 +12365,12 @@ const RPCReauthenticate = (params) => {
12353
12365
 
12354
12366
  //#endregion
12355
12367
  //#region src/core/RPCMessages/RPCPing.ts
12368
+ const RPCPing = () => {
12369
+ return buildRPCRequest({
12370
+ method: "signalwire.ping",
12371
+ params: { timestamp: Date.now() / 1e3 }
12372
+ });
12373
+ };
12356
12374
  const RPCPingResponse = (id, timestamp$1) => {
12357
12375
  return makeRPCResponse({
12358
12376
  id,
@@ -12736,12 +12754,12 @@ var require_race = /* @__PURE__ */ __commonJSMin(((exports) => {
12736
12754
  exports.race = void 0;
12737
12755
  var argsOrArgArray_1 = require_argsOrArgArray();
12738
12756
  var raceWith_1$1 = require_raceWith();
12739
- function race$3() {
12757
+ function race$4() {
12740
12758
  var args = [];
12741
12759
  for (var _i = 0; _i < arguments.length; _i++) args[_i] = arguments[_i];
12742
12760
  return raceWith_1$1.raceWith.apply(void 0, __spreadArray([], __read(argsOrArgArray_1.argsOrArgArray(args))));
12743
12761
  }
12744
- exports.race = race$3;
12762
+ exports.race = race$4;
12745
12763
  }));
12746
12764
 
12747
12765
  //#endregion
@@ -17207,6 +17225,7 @@ var WebRTCVertoManager = class extends VertoManager {
17207
17225
  this._signalingStatus$ = this.createReplaySubject(1);
17208
17226
  this._screenShareStatus$ = this.createBehaviorSubject("none");
17209
17227
  this._rtcPeerConnectionsMap = /* @__PURE__ */ new Map();
17228
+ this._lastVertoPings = /* @__PURE__ */ new Map();
17210
17229
  this._legErrors$ = this.createSubject();
17211
17230
  this._nodeId$ = this.createBehaviorSubject(options.nodeId ?? null);
17212
17231
  this.onError = options.onError;
@@ -17322,9 +17341,13 @@ var WebRTCVertoManager = class extends VertoManager {
17322
17341
  this.applyServerMediaParams(rtcPeerConnController, audio, video);
17323
17342
  });
17324
17343
  this.subscribeTo(this.vertoPing$, (vertoPing) => {
17344
+ this._lastVertoPings.set(vertoPing.callID, vertoPing);
17325
17345
  this.attachManager.refresh(this.buildAttachableCall());
17326
17346
  this.sendVertoPong(vertoPing);
17327
17347
  });
17348
+ this.subscribeTo(this.webRtcCallSession.clientSession.authenticated$.pipe((0, import_cjs$15.skip)(1), (0, import_cjs$15.filter)(Boolean)), () => {
17349
+ this._lastVertoPings.forEach((vertoPing) => void this.sendVertoPong(vertoPing));
17350
+ });
17328
17351
  }
17329
17352
  /**
17330
17353
  * An auxiliary-leg failure must never destroy the call; main-leg and
@@ -20666,6 +20689,8 @@ var ClientSessionManager = class extends Destroyable {
20666
20689
  this._errors$ = this.createReplaySubject(1);
20667
20690
  this._authState$ = this.createBehaviorSubject({ kind: "unauthenticated" });
20668
20691
  this._wasClientBound = false;
20692
+ this._lastFreshConnectReminted = false;
20693
+ this._remintBeforeNextFreshConnect = false;
20669
20694
  this._userInfo$ = this.createBehaviorSubject(null);
20670
20695
  this._calls$ = this.createBehaviorSubject({});
20671
20696
  this._iceServers$ = this.createBehaviorSubject([]);
@@ -20861,13 +20886,27 @@ var ClientSessionManager = class extends Destroyable {
20861
20886
  await (0, import_cjs$7.firstValueFrom)(this.authenticated$.pipe((0, import_cjs$7.takeUntil)(this.destroyed$), (0, import_cjs$7.filter)(Boolean), (0, import_cjs$7.take)(1), (0, import_cjs$7.timeout)({ first: 15e3 })));
20862
20887
  }
20863
20888
  async handleAuthenticationError(error) {
20889
+ if (error instanceof TransportConnectionError) {
20890
+ logger$9.debug("[Session] Socket closed during authentication, waiting for reconnect");
20891
+ return;
20892
+ }
20864
20893
  logger$9.error("Authentication error:", error);
20865
20894
  const isRecoverableAuthError$1 = error instanceof JSONRPCError && (error.code === RPC_ERROR_REQUESTER_VALIDATION_FAILED || error.code === RPC_ERROR_INVALID_PARAMS || error.code === RPC_ERROR_AUTHENTICATION_FAILED);
20866
20895
  const hasStoredState = await (0, import_cjs$7.firstValueFrom)(this.authorizationState$.pipe((0, import_cjs$7.take)(1))) !== void 0;
20867
20896
  if (isRecoverableAuthError$1 && hasStoredState) {
20868
20897
  logger$9.debug("[Session] Recoverable auth error — cleaning up stored state and reconnecting fresh");
20869
20898
  await this.discardResumeStateAndReconnect();
20870
- } else this._errors$.next(error);
20899
+ } else if (isRecoverableAuthError$1 && this.onBeforeReconnect && !this._lastFreshConnectReminted) {
20900
+ logger$9.debug("[Session] Fresh connect refused — re-minting the credential and retrying once");
20901
+ this._remintBeforeNextFreshConnect = true;
20902
+ this.transport.reconnect();
20903
+ } else {
20904
+ this._errors$.next(error);
20905
+ if (isRecoverableAuthError$1) {
20906
+ this.transport.disconnect();
20907
+ this.onAuthenticationRejected?.();
20908
+ }
20909
+ }
20871
20910
  }
20872
20911
  /**
20873
20912
  * Clear the resume state (authorization_state + protocol) and ask the
@@ -20959,6 +20998,7 @@ var ClientSessionManager = class extends Destroyable {
20959
20998
  }
20960
20999
  async authenticate() {
20961
21000
  logger$9.debug("[Session] Starting authentication process");
21001
+ this._lastFreshConnectReminted = false;
20962
21002
  const persistedParams = await (0, import_cjs$7.firstValueFrom)((0, import_cjs$7.combineLatest)({
20963
21003
  protocol: this.transport.protocol$,
20964
21004
  authorization_state: this.authorizationState$
@@ -20975,8 +21015,11 @@ var ClientSessionManager = class extends Destroyable {
20975
21015
  else {
20976
21016
  const credential = this.getCredential();
20977
21017
  const credentialExpired = credential.expiry_at !== void 0 && credential.expiry_at <= Date.now() + CREDENTIAL_EXPIRY_SKEW_MS;
20978
- if (this.onBeforeReconnect && (this.clientBound || credentialExpired)) {
21018
+ const remint = this.clientBound || credentialExpired || this._remintBeforeNextFreshConnect;
21019
+ this._remintBeforeNextFreshConnect = false;
21020
+ if (this.onBeforeReconnect && remint) {
20979
21021
  logger$9.debug("[Session] Refreshing credentials before fresh connect");
21022
+ this._lastFreshConnectReminted = true;
20980
21023
  await this.onBeforeReconnect();
20981
21024
  }
20982
21025
  }
@@ -21000,7 +21043,9 @@ var ClientSessionManager = class extends Destroyable {
21000
21043
  protocol: persistedParams.protocol
21001
21044
  } : {}
21002
21045
  });
21003
- const response = await (0, import_cjs$7.lastValueFrom)((0, import_cjs$7.from)(this.transport.execute(rpcConnectRequest)).pipe(throwOnRPCError(), (0, import_cjs$7.map)((res) => res.result), (0, import_cjs$7.filter)(isRPCConnectResult), (0, import_cjs$7.tap)(() => {
21046
+ if (this.transport.connectionStatus !== "connected") throw new TransportConnectionError("WebSocket closed during authentication");
21047
+ const socketClosed$ = this.transport.connectionStatus$.pipe((0, import_cjs$7.skip)(1), (0, import_cjs$7.filter)((status) => status === "reconnecting" || status === "disconnected"), (0, import_cjs$7.take)(1), (0, import_cjs$7.switchMap)(() => (0, import_cjs$7.throwError)(() => new TransportConnectionError("WebSocket closed during authentication"))));
21048
+ const response = await (0, import_cjs$7.lastValueFrom)((0, import_cjs$7.race)((0, import_cjs$7.from)(this.transport.execute(rpcConnectRequest)), socketClosed$).pipe(throwOnRPCError(), (0, import_cjs$7.map)((res) => res.result), (0, import_cjs$7.filter)(isRPCConnectResult), (0, import_cjs$7.tap)(() => {
21004
21049
  logger$9.debug("[Session] Response passed filter, processing authentication result");
21005
21050
  }), (0, import_cjs$7.take)(1), (0, import_cjs$7.catchError)((err) => {
21006
21051
  logger$9.error("[Session] Authentication RPC failed:", err);
@@ -21015,6 +21060,7 @@ var ClientSessionManager = class extends Destroyable {
21015
21060
  this._authorization$.next(response.authorization);
21016
21061
  if (response.authorization.cnf?.jkt) this._wasClientBound = true;
21017
21062
  this._iceServers$.next(response.ice_servers ?? []);
21063
+ this.transport.setAuthenticated();
21018
21064
  this._authState$.next({ kind: "authenticated" });
21019
21065
  logger$9.debug("[Session] Authentication completed successfully");
21020
21066
  }
@@ -22211,6 +22257,7 @@ var WebSocketController = class WebSocketController extends Destroyable {
22211
22257
  }
22212
22258
  handleClose(_event) {
22213
22259
  this.clearConnectionTimeout();
22260
+ this.messageQueue = [];
22214
22261
  if (this.shouldReconnect) {
22215
22262
  this._status$.next("reconnecting");
22216
22263
  this.scheduleReconnection();
@@ -22280,6 +22327,9 @@ var WebSocketController = class WebSocketController extends Destroyable {
22280
22327
  function isSignalwirePingRequest(value) {
22281
22328
  return isJSONRPCRequest(value) && value.method === "signalwire.ping";
22282
22329
  }
22330
+ function isSignalwireConnectRequest(value) {
22331
+ return isJSONRPCRequest(value) && value.method === "signalwire.connect";
22332
+ }
22283
22333
 
22284
22334
  //#endregion
22285
22335
  //#region src/managers/TransportManager.ts
@@ -22294,6 +22344,10 @@ var TransportManager = class extends Destroyable {
22294
22344
  this.protocol$ = this.createReplaySubject(1);
22295
22345
  this.isConnecting = false;
22296
22346
  this.isConnected = false;
22347
+ this.socketStatus = "disconnected";
22348
+ this.sessionAuthenticated = false;
22349
+ this.heldRequests = [];
22350
+ this.serverPingWatch = 0;
22297
22351
  this.ackEvent = () => {
22298
22352
  return (0, import_cjs$2.tap)((message) => {
22299
22353
  if (isSignalwireRequest(message)) try {
@@ -22309,6 +22363,7 @@ var TransportManager = class extends Destroyable {
22309
22363
  if (isSignalwirePingRequest(message)) {
22310
22364
  try {
22311
22365
  logger$2.debug("[Transport] Received ping, sending pong", { pingId: message.id });
22366
+ if (this.sessionAuthenticated) this.armServerPingWatchdog();
22312
22367
  this.send(RPCPingResponse(message.id));
22313
22368
  } catch (error) {
22314
22369
  logger$2.error("[Transport] Failed to send ping response:", error);
@@ -22346,6 +22401,14 @@ var TransportManager = class extends Destroyable {
22346
22401
  this.subscribeTo(this._webSocketConnections.errors$, (error) => {
22347
22402
  this.onError?.(error);
22348
22403
  });
22404
+ this.subscribeTo(this._webSocketConnections.status$, (status) => {
22405
+ this.socketStatus = status;
22406
+ if (status !== "connected") {
22407
+ this.sessionAuthenticated = false;
22408
+ this.clearServerPingWatchdog();
22409
+ }
22410
+ if (status === "disconnected") this.heldRequests = [];
22411
+ });
22349
22412
  this.initialized$ = (0, import_cjs$2.defer)(() => (0, import_cjs$2.from)(this._init())).pipe((0, import_cjs$2.shareReplay)(1), (0, import_cjs$2.takeUntil)(this.destroyed$));
22350
22413
  this._jsonRPCMessage$ = this._webSocketConnections.incomingMessages$.pipe((0, import_cjs$2.map)((event) => {
22351
22414
  try {
@@ -22374,6 +22437,9 @@ var TransportManager = class extends Destroyable {
22374
22437
  get connectionStatus$() {
22375
22438
  return this._webSocketConnections.status$;
22376
22439
  }
22440
+ get connectionStatus() {
22441
+ return this.socketStatus;
22442
+ }
22377
22443
  async connect() {
22378
22444
  if (this.isConnecting || this.isConnected) {
22379
22445
  logger$2.warn("[Transport] Already connecting or connected");
@@ -22418,16 +22484,45 @@ var TransportManager = class extends Destroyable {
22418
22484
  }
22419
22485
  async execute(request, options) {
22420
22486
  this.send(request);
22421
- return new PendingRPC(request, this._jsonRPCResponse$, options).promise;
22487
+ const pending = new PendingRPC(request, this._jsonRPCResponse$, options).promise;
22488
+ pending.catch(() => {
22489
+ this.heldRequests = this.heldRequests.filter((held) => held.id !== request.id);
22490
+ });
22491
+ return pending;
22422
22492
  }
22423
22493
  send(message) {
22424
22494
  const payload = JSON.stringify(message);
22495
+ if (isJSONRPCResponse(message)) {
22496
+ if (this.socketStatus !== "connected") {
22497
+ logger$2.debug("[Transport] Dropping response while disconnected", { id: message.id });
22498
+ return;
22499
+ }
22500
+ } else if (!this.sessionAuthenticated && !isSignalwireConnectRequest(message)) {
22501
+ this.heldRequests.push({
22502
+ id: message.id,
22503
+ payload
22504
+ });
22505
+ return;
22506
+ }
22425
22507
  this._outgoingMessages$.next(payload);
22426
22508
  }
22509
+ /**
22510
+ * Called by the session when signalwire.connect succeeds on the current
22511
+ * socket. Sends the requests held since the socket opened, in order.
22512
+ */
22513
+ setAuthenticated() {
22514
+ if (this.socketStatus !== "connected") return;
22515
+ this.sessionAuthenticated = true;
22516
+ this.armServerPingWatchdog();
22517
+ const held = this.heldRequests;
22518
+ this.heldRequests = [];
22519
+ held.forEach(({ payload }) => this._outgoingMessages$.next(payload));
22520
+ }
22427
22521
  disconnect() {
22428
22522
  logger$2.debug("[Transport] Disconnecting");
22429
22523
  this.isConnected = false;
22430
22524
  this.isConnecting = false;
22525
+ this.clearServerPingWatchdog();
22431
22526
  this._webSocketConnections.disconnect();
22432
22527
  }
22433
22528
  destroy() {
@@ -22436,6 +22531,28 @@ var TransportManager = class extends Destroyable {
22436
22531
  super.destroy();
22437
22532
  this._webSocketConnections.destroy();
22438
22533
  }
22534
+ armServerPingWatchdog() {
22535
+ this.clearServerPingWatchdog();
22536
+ const watch = this.serverPingWatch;
22537
+ this.serverPingTimer = setTimeout(() => void this.probeServer(watch), SERVER_PING_TIMEOUT_MS);
22538
+ }
22539
+ /** Bumps the watch so a probe still in flight no longer acts on its result. */
22540
+ clearServerPingWatchdog() {
22541
+ clearTimeout(this.serverPingTimer);
22542
+ this.serverPingTimer = void 0;
22543
+ this.serverPingWatch += 1;
22544
+ }
22545
+ async probeServer(watch) {
22546
+ logger$2.debug(`[Transport] No signalwire.ping for ${SERVER_PING_TIMEOUT_MS}ms, probing the server`);
22547
+ try {
22548
+ await this.execute(RPCPing(), { timeoutMs: SERVER_PING_PROBE_TIMEOUT_MS });
22549
+ if (watch === this.serverPingWatch) this.armServerPingWatchdog();
22550
+ } catch (error) {
22551
+ if (watch !== this.serverPingWatch) return;
22552
+ logger$2.warn("[Transport] Server did not answer the ping probe, replacing the socket", error);
22553
+ this.reconnect();
22554
+ }
22555
+ }
22439
22556
  async _loadProtocolFromStorage() {
22440
22557
  try {
22441
22558
  const storedProtocol = await this.storage.getItem(this.protocolKey);
@@ -22962,6 +23079,7 @@ var SignalWire = class extends Destroyable {
22962
23079
  this.persistClientBoundMarker(Boolean(authorization?.cnf?.jkt));
22963
23080
  });
22964
23081
  await this._clientSession.connect();
23082
+ this._clientSession.onAuthenticationRejected = () => void this.disconnect();
22965
23083
  await this._refreshCoordinator?.activate(this._deps.user, this._clientSession);
22966
23084
  this.subscribeTo(this._clientSession.authenticated$.pipe((0, import_cjs$1.skip)(1), (0, import_cjs$1.filter)(Boolean)), async () => {
22967
23085
  try {
@@ -23141,7 +23259,7 @@ var SignalWire = class extends Destroyable {
23141
23259
  logger$1.warn("[SignalWire] Failed to initialize VisibilityController:", error);
23142
23260
  }
23143
23261
  try {
23144
- this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "4.0.0-rc.3" });
23262
+ this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "4.0.0-rc.5" });
23145
23263
  } catch (error) {
23146
23264
  logger$1.warn("[SignalWire] Failed to initialize DiagnosticsCollector:", error);
23147
23265
  }
@@ -23696,7 +23814,7 @@ var StaticCredentialProvider = class {
23696
23814
  /**
23697
23815
  * Library version from package.json, injected at build time.
23698
23816
  */
23699
- const version = "4.0.0-rc.3";
23817
+ const version = "4.0.0-rc.5";
23700
23818
  /**
23701
23819
  * Flag indicating the library has been loaded and is ready to use.
23702
23820
  * For UMD builds: `window.SignalWire.ready`
@@ -23718,7 +23836,7 @@ const ready = true;
23718
23836
  */
23719
23837
  const emitReadyEvent = () => {
23720
23838
  if (typeof window !== "undefined") {
23721
- const event = new CustomEvent("signalwire:js:ready", { detail: { version: "4.0.0-rc.3" } });
23839
+ const event = new CustomEvent("signalwire:js:ready", { detail: { version: "4.0.0-rc.5" } });
23722
23840
  window.dispatchEvent(event);
23723
23841
  }
23724
23842
  };