@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.
@@ -4331,13 +4331,13 @@ var require_race$1 = /* @__PURE__ */ __commonJSMin(((exports) => {
4331
4331
  var innerFrom_1$28 = require_innerFrom();
4332
4332
  var argsOrArgArray_1$4 = require_argsOrArgArray();
4333
4333
  var OperatorSubscriber_1$48 = require_OperatorSubscriber();
4334
- function race$4() {
4334
+ function race$5() {
4335
4335
  var sources = [];
4336
4336
  for (var _i = 0; _i < arguments.length; _i++) sources[_i] = arguments[_i];
4337
4337
  sources = argsOrArgArray_1$4.argsOrArgArray(sources);
4338
4338
  return sources.length === 1 ? innerFrom_1$28.innerFrom(sources[0]) : new Observable_1$6.Observable(raceInit(sources));
4339
4339
  }
4340
- exports.race = race$4;
4340
+ exports.race = race$5;
4341
4341
  function raceInit(sources) {
4342
4342
  return function(subscriber) {
4343
4343
  var subscriptions = [];
@@ -6829,12 +6829,12 @@ var require_skip = /* @__PURE__ */ __commonJSMin(((exports) => {
6829
6829
  Object.defineProperty(exports, "__esModule", { value: true });
6830
6830
  exports.skip = void 0;
6831
6831
  var filter_1$3 = require_filter();
6832
- function skip$3(count$1) {
6832
+ function skip$5(count$1) {
6833
6833
  return filter_1$3.filter(function(_, index) {
6834
6834
  return count$1 <= index;
6835
6835
  });
6836
6836
  }
6837
- exports.skip = skip$3;
6837
+ exports.skip = skip$5;
6838
6838
  }));
6839
6839
 
6840
6840
  //#endregion
@@ -9029,6 +9029,18 @@ const DEFAULT_ICE_GATHERING_TIMEOUT_MS = 6e3;
9029
9029
  const DEFAULT_RECONNECT_CALLS_TIMEOUT_MS = 300 * 1e3;
9030
9030
  const DEFAULT_CONNECTION_TIMEOUT_MS = 1e4;
9031
9031
  /**
9032
+ * The server sends `signalwire.ping` every 10s. A socket that stays silent
9033
+ * longer than this is half-open: the server has dropped the session but the
9034
+ * browser never fired `close`.
9035
+ */
9036
+ const SERVER_PING_TIMEOUT_MS = 15e3;
9037
+ /**
9038
+ * How long a `signalwire.ping` the client sends after the server's pings
9039
+ * stopped may take to be answered. The server defers its own pings while its
9040
+ * threadpool is congested, so silence alone does not mean the socket is dead.
9041
+ */
9042
+ const SERVER_PING_PROBE_TIMEOUT_MS = 1e4;
9043
+ /**
9032
9044
  * How long call setup may wait for the network, measured from local media
9033
9045
  * settling until the member id arrives.
9034
9046
  *
@@ -12359,6 +12371,12 @@ const RPCReauthenticate = (params) => {
12359
12371
 
12360
12372
  //#endregion
12361
12373
  //#region src/core/RPCMessages/RPCPing.ts
12374
+ const RPCPing = () => {
12375
+ return buildRPCRequest({
12376
+ method: "signalwire.ping",
12377
+ params: { timestamp: Date.now() / 1e3 }
12378
+ });
12379
+ };
12362
12380
  const RPCPingResponse = (id, timestamp$1) => {
12363
12381
  return makeRPCResponse({
12364
12382
  id,
@@ -12742,12 +12760,12 @@ var require_race = /* @__PURE__ */ __commonJSMin(((exports) => {
12742
12760
  exports.race = void 0;
12743
12761
  var argsOrArgArray_1 = require_argsOrArgArray();
12744
12762
  var raceWith_1$1 = require_raceWith();
12745
- function race$3() {
12763
+ function race$4() {
12746
12764
  var args = [];
12747
12765
  for (var _i = 0; _i < arguments.length; _i++) args[_i] = arguments[_i];
12748
12766
  return raceWith_1$1.raceWith.apply(void 0, __spreadArray([], __read(argsOrArgArray_1.argsOrArgArray(args))));
12749
12767
  }
12750
- exports.race = race$3;
12768
+ exports.race = race$4;
12751
12769
  }));
12752
12770
 
12753
12771
  //#endregion
@@ -17213,6 +17231,7 @@ var WebRTCVertoManager = class extends VertoManager {
17213
17231
  this._signalingStatus$ = this.createReplaySubject(1);
17214
17232
  this._screenShareStatus$ = this.createBehaviorSubject("none");
17215
17233
  this._rtcPeerConnectionsMap = /* @__PURE__ */ new Map();
17234
+ this._lastVertoPings = /* @__PURE__ */ new Map();
17216
17235
  this._legErrors$ = this.createSubject();
17217
17236
  this._nodeId$ = this.createBehaviorSubject(options.nodeId ?? null);
17218
17237
  this.onError = options.onError;
@@ -17328,9 +17347,13 @@ var WebRTCVertoManager = class extends VertoManager {
17328
17347
  this.applyServerMediaParams(rtcPeerConnController, audio, video);
17329
17348
  });
17330
17349
  this.subscribeTo(this.vertoPing$, (vertoPing) => {
17350
+ this._lastVertoPings.set(vertoPing.callID, vertoPing);
17331
17351
  this.attachManager.refresh(this.buildAttachableCall());
17332
17352
  this.sendVertoPong(vertoPing);
17333
17353
  });
17354
+ this.subscribeTo(this.webRtcCallSession.clientSession.authenticated$.pipe((0, import_cjs$15.skip)(1), (0, import_cjs$15.filter)(Boolean)), () => {
17355
+ this._lastVertoPings.forEach((vertoPing) => void this.sendVertoPong(vertoPing));
17356
+ });
17334
17357
  }
17335
17358
  /**
17336
17359
  * An auxiliary-leg failure must never destroy the call; main-leg and
@@ -20672,6 +20695,8 @@ var ClientSessionManager = class extends Destroyable {
20672
20695
  this._errors$ = this.createReplaySubject(1);
20673
20696
  this._authState$ = this.createBehaviorSubject({ kind: "unauthenticated" });
20674
20697
  this._wasClientBound = false;
20698
+ this._lastFreshConnectReminted = false;
20699
+ this._remintBeforeNextFreshConnect = false;
20675
20700
  this._userInfo$ = this.createBehaviorSubject(null);
20676
20701
  this._calls$ = this.createBehaviorSubject({});
20677
20702
  this._iceServers$ = this.createBehaviorSubject([]);
@@ -20867,13 +20892,27 @@ var ClientSessionManager = class extends Destroyable {
20867
20892
  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 })));
20868
20893
  }
20869
20894
  async handleAuthenticationError(error) {
20895
+ if (error instanceof TransportConnectionError) {
20896
+ logger$9.debug("[Session] Socket closed during authentication, waiting for reconnect");
20897
+ return;
20898
+ }
20870
20899
  logger$9.error("Authentication error:", error);
20871
20900
  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);
20872
20901
  const hasStoredState = await (0, import_cjs$7.firstValueFrom)(this.authorizationState$.pipe((0, import_cjs$7.take)(1))) !== void 0;
20873
20902
  if (isRecoverableAuthError$1 && hasStoredState) {
20874
20903
  logger$9.debug("[Session] Recoverable auth error — cleaning up stored state and reconnecting fresh");
20875
20904
  await this.discardResumeStateAndReconnect();
20876
- } else this._errors$.next(error);
20905
+ } else if (isRecoverableAuthError$1 && this.onBeforeReconnect && !this._lastFreshConnectReminted) {
20906
+ logger$9.debug("[Session] Fresh connect refused — re-minting the credential and retrying once");
20907
+ this._remintBeforeNextFreshConnect = true;
20908
+ this.transport.reconnect();
20909
+ } else {
20910
+ this._errors$.next(error);
20911
+ if (isRecoverableAuthError$1) {
20912
+ this.transport.disconnect();
20913
+ this.onAuthenticationRejected?.();
20914
+ }
20915
+ }
20877
20916
  }
20878
20917
  /**
20879
20918
  * Clear the resume state (authorization_state + protocol) and ask the
@@ -20965,6 +21004,7 @@ var ClientSessionManager = class extends Destroyable {
20965
21004
  }
20966
21005
  async authenticate() {
20967
21006
  logger$9.debug("[Session] Starting authentication process");
21007
+ this._lastFreshConnectReminted = false;
20968
21008
  const persistedParams = await (0, import_cjs$7.firstValueFrom)((0, import_cjs$7.combineLatest)({
20969
21009
  protocol: this.transport.protocol$,
20970
21010
  authorization_state: this.authorizationState$
@@ -20981,8 +21021,11 @@ var ClientSessionManager = class extends Destroyable {
20981
21021
  else {
20982
21022
  const credential = this.getCredential();
20983
21023
  const credentialExpired = credential.expiry_at !== void 0 && credential.expiry_at <= Date.now() + CREDENTIAL_EXPIRY_SKEW_MS;
20984
- if (this.onBeforeReconnect && (this.clientBound || credentialExpired)) {
21024
+ const remint = this.clientBound || credentialExpired || this._remintBeforeNextFreshConnect;
21025
+ this._remintBeforeNextFreshConnect = false;
21026
+ if (this.onBeforeReconnect && remint) {
20985
21027
  logger$9.debug("[Session] Refreshing credentials before fresh connect");
21028
+ this._lastFreshConnectReminted = true;
20986
21029
  await this.onBeforeReconnect();
20987
21030
  }
20988
21031
  }
@@ -21006,7 +21049,9 @@ var ClientSessionManager = class extends Destroyable {
21006
21049
  protocol: persistedParams.protocol
21007
21050
  } : {}
21008
21051
  });
21009
- 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)(() => {
21052
+ if (this.transport.connectionStatus !== "connected") throw new TransportConnectionError("WebSocket closed during authentication");
21053
+ 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"))));
21054
+ 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)(() => {
21010
21055
  logger$9.debug("[Session] Response passed filter, processing authentication result");
21011
21056
  }), (0, import_cjs$7.take)(1), (0, import_cjs$7.catchError)((err) => {
21012
21057
  logger$9.error("[Session] Authentication RPC failed:", err);
@@ -21021,6 +21066,7 @@ var ClientSessionManager = class extends Destroyable {
21021
21066
  this._authorization$.next(response.authorization);
21022
21067
  if (response.authorization.cnf?.jkt) this._wasClientBound = true;
21023
21068
  this._iceServers$.next(response.ice_servers ?? []);
21069
+ this.transport.setAuthenticated();
21024
21070
  this._authState$.next({ kind: "authenticated" });
21025
21071
  logger$9.debug("[Session] Authentication completed successfully");
21026
21072
  }
@@ -22217,6 +22263,7 @@ var WebSocketController = class WebSocketController extends Destroyable {
22217
22263
  }
22218
22264
  handleClose(_event) {
22219
22265
  this.clearConnectionTimeout();
22266
+ this.messageQueue = [];
22220
22267
  if (this.shouldReconnect) {
22221
22268
  this._status$.next("reconnecting");
22222
22269
  this.scheduleReconnection();
@@ -22286,6 +22333,9 @@ var WebSocketController = class WebSocketController extends Destroyable {
22286
22333
  function isSignalwirePingRequest(value) {
22287
22334
  return isJSONRPCRequest(value) && value.method === "signalwire.ping";
22288
22335
  }
22336
+ function isSignalwireConnectRequest(value) {
22337
+ return isJSONRPCRequest(value) && value.method === "signalwire.connect";
22338
+ }
22289
22339
 
22290
22340
  //#endregion
22291
22341
  //#region src/managers/TransportManager.ts
@@ -22300,6 +22350,10 @@ var TransportManager = class extends Destroyable {
22300
22350
  this.protocol$ = this.createReplaySubject(1);
22301
22351
  this.isConnecting = false;
22302
22352
  this.isConnected = false;
22353
+ this.socketStatus = "disconnected";
22354
+ this.sessionAuthenticated = false;
22355
+ this.heldRequests = [];
22356
+ this.serverPingWatch = 0;
22303
22357
  this.ackEvent = () => {
22304
22358
  return (0, import_cjs$2.tap)((message) => {
22305
22359
  if (isSignalwireRequest(message)) try {
@@ -22315,6 +22369,7 @@ var TransportManager = class extends Destroyable {
22315
22369
  if (isSignalwirePingRequest(message)) {
22316
22370
  try {
22317
22371
  logger$2.debug("[Transport] Received ping, sending pong", { pingId: message.id });
22372
+ if (this.sessionAuthenticated) this.armServerPingWatchdog();
22318
22373
  this.send(RPCPingResponse(message.id));
22319
22374
  } catch (error) {
22320
22375
  logger$2.error("[Transport] Failed to send ping response:", error);
@@ -22352,6 +22407,14 @@ var TransportManager = class extends Destroyable {
22352
22407
  this.subscribeTo(this._webSocketConnections.errors$, (error) => {
22353
22408
  this.onError?.(error);
22354
22409
  });
22410
+ this.subscribeTo(this._webSocketConnections.status$, (status) => {
22411
+ this.socketStatus = status;
22412
+ if (status !== "connected") {
22413
+ this.sessionAuthenticated = false;
22414
+ this.clearServerPingWatchdog();
22415
+ }
22416
+ if (status === "disconnected") this.heldRequests = [];
22417
+ });
22355
22418
  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$));
22356
22419
  this._jsonRPCMessage$ = this._webSocketConnections.incomingMessages$.pipe((0, import_cjs$2.map)((event) => {
22357
22420
  try {
@@ -22380,6 +22443,9 @@ var TransportManager = class extends Destroyable {
22380
22443
  get connectionStatus$() {
22381
22444
  return this._webSocketConnections.status$;
22382
22445
  }
22446
+ get connectionStatus() {
22447
+ return this.socketStatus;
22448
+ }
22383
22449
  async connect() {
22384
22450
  if (this.isConnecting || this.isConnected) {
22385
22451
  logger$2.warn("[Transport] Already connecting or connected");
@@ -22424,16 +22490,45 @@ var TransportManager = class extends Destroyable {
22424
22490
  }
22425
22491
  async execute(request, options) {
22426
22492
  this.send(request);
22427
- return new PendingRPC(request, this._jsonRPCResponse$, options).promise;
22493
+ const pending = new PendingRPC(request, this._jsonRPCResponse$, options).promise;
22494
+ pending.catch(() => {
22495
+ this.heldRequests = this.heldRequests.filter((held) => held.id !== request.id);
22496
+ });
22497
+ return pending;
22428
22498
  }
22429
22499
  send(message) {
22430
22500
  const payload = JSON.stringify(message);
22501
+ if (isJSONRPCResponse(message)) {
22502
+ if (this.socketStatus !== "connected") {
22503
+ logger$2.debug("[Transport] Dropping response while disconnected", { id: message.id });
22504
+ return;
22505
+ }
22506
+ } else if (!this.sessionAuthenticated && !isSignalwireConnectRequest(message)) {
22507
+ this.heldRequests.push({
22508
+ id: message.id,
22509
+ payload
22510
+ });
22511
+ return;
22512
+ }
22431
22513
  this._outgoingMessages$.next(payload);
22432
22514
  }
22515
+ /**
22516
+ * Called by the session when signalwire.connect succeeds on the current
22517
+ * socket. Sends the requests held since the socket opened, in order.
22518
+ */
22519
+ setAuthenticated() {
22520
+ if (this.socketStatus !== "connected") return;
22521
+ this.sessionAuthenticated = true;
22522
+ this.armServerPingWatchdog();
22523
+ const held = this.heldRequests;
22524
+ this.heldRequests = [];
22525
+ held.forEach(({ payload }) => this._outgoingMessages$.next(payload));
22526
+ }
22433
22527
  disconnect() {
22434
22528
  logger$2.debug("[Transport] Disconnecting");
22435
22529
  this.isConnected = false;
22436
22530
  this.isConnecting = false;
22531
+ this.clearServerPingWatchdog();
22437
22532
  this._webSocketConnections.disconnect();
22438
22533
  }
22439
22534
  destroy() {
@@ -22442,6 +22537,28 @@ var TransportManager = class extends Destroyable {
22442
22537
  super.destroy();
22443
22538
  this._webSocketConnections.destroy();
22444
22539
  }
22540
+ armServerPingWatchdog() {
22541
+ this.clearServerPingWatchdog();
22542
+ const watch = this.serverPingWatch;
22543
+ this.serverPingTimer = setTimeout(() => void this.probeServer(watch), SERVER_PING_TIMEOUT_MS);
22544
+ }
22545
+ /** Bumps the watch so a probe still in flight no longer acts on its result. */
22546
+ clearServerPingWatchdog() {
22547
+ clearTimeout(this.serverPingTimer);
22548
+ this.serverPingTimer = void 0;
22549
+ this.serverPingWatch += 1;
22550
+ }
22551
+ async probeServer(watch) {
22552
+ logger$2.debug(`[Transport] No signalwire.ping for ${SERVER_PING_TIMEOUT_MS}ms, probing the server`);
22553
+ try {
22554
+ await this.execute(RPCPing(), { timeoutMs: SERVER_PING_PROBE_TIMEOUT_MS });
22555
+ if (watch === this.serverPingWatch) this.armServerPingWatchdog();
22556
+ } catch (error) {
22557
+ if (watch !== this.serverPingWatch) return;
22558
+ logger$2.warn("[Transport] Server did not answer the ping probe, replacing the socket", error);
22559
+ this.reconnect();
22560
+ }
22561
+ }
22445
22562
  async _loadProtocolFromStorage() {
22446
22563
  try {
22447
22564
  const storedProtocol = await this.storage.getItem(this.protocolKey);
@@ -22968,6 +23085,7 @@ var SignalWire = class extends Destroyable {
22968
23085
  this.persistClientBoundMarker(Boolean(authorization?.cnf?.jkt));
22969
23086
  });
22970
23087
  await this._clientSession.connect();
23088
+ this._clientSession.onAuthenticationRejected = () => void this.disconnect();
22971
23089
  await this._refreshCoordinator?.activate(this._deps.user, this._clientSession);
22972
23090
  this.subscribeTo(this._clientSession.authenticated$.pipe((0, import_cjs$1.skip)(1), (0, import_cjs$1.filter)(Boolean)), async () => {
22973
23091
  try {
@@ -23147,7 +23265,7 @@ var SignalWire = class extends Destroyable {
23147
23265
  logger$1.warn("[SignalWire] Failed to initialize VisibilityController:", error);
23148
23266
  }
23149
23267
  try {
23150
- this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "4.0.0-rc.3" });
23268
+ this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "4.0.0-rc.5" });
23151
23269
  } catch (error) {
23152
23270
  logger$1.warn("[SignalWire] Failed to initialize DiagnosticsCollector:", error);
23153
23271
  }
@@ -23702,7 +23820,7 @@ var StaticCredentialProvider = class {
23702
23820
  /**
23703
23821
  * Library version from package.json, injected at build time.
23704
23822
  */
23705
- const version = "4.0.0-rc.3";
23823
+ const version = "4.0.0-rc.5";
23706
23824
  /**
23707
23825
  * Flag indicating the library has been loaded and is ready to use.
23708
23826
  * For UMD builds: `window.SignalWire.ready`
@@ -23724,7 +23842,7 @@ const ready = true;
23724
23842
  */
23725
23843
  const emitReadyEvent = () => {
23726
23844
  if (typeof window !== "undefined") {
23727
- const event = new CustomEvent("signalwire:js:ready", { detail: { version: "4.0.0-rc.3" } });
23845
+ const event = new CustomEvent("signalwire:js:ready", { detail: { version: "4.0.0-rc.5" } });
23728
23846
  window.dispatchEvent(event);
23729
23847
  }
23730
23848
  };