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

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
@@ -20861,6 +20884,10 @@ var ClientSessionManager = class extends Destroyable {
20861
20884
  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
20885
  }
20863
20886
  async handleAuthenticationError(error) {
20887
+ if (error instanceof TransportConnectionError) {
20888
+ logger$9.debug("[Session] Socket closed during authentication, waiting for reconnect");
20889
+ return;
20890
+ }
20864
20891
  logger$9.error("Authentication error:", error);
20865
20892
  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
20893
  const hasStoredState = await (0, import_cjs$7.firstValueFrom)(this.authorizationState$.pipe((0, import_cjs$7.take)(1))) !== void 0;
@@ -21000,7 +21027,9 @@ var ClientSessionManager = class extends Destroyable {
21000
21027
  protocol: persistedParams.protocol
21001
21028
  } : {}
21002
21029
  });
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)(() => {
21030
+ if (this.transport.connectionStatus !== "connected") throw new TransportConnectionError("WebSocket closed during authentication");
21031
+ 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"))));
21032
+ 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
21033
  logger$9.debug("[Session] Response passed filter, processing authentication result");
21005
21034
  }), (0, import_cjs$7.take)(1), (0, import_cjs$7.catchError)((err) => {
21006
21035
  logger$9.error("[Session] Authentication RPC failed:", err);
@@ -21015,6 +21044,7 @@ var ClientSessionManager = class extends Destroyable {
21015
21044
  this._authorization$.next(response.authorization);
21016
21045
  if (response.authorization.cnf?.jkt) this._wasClientBound = true;
21017
21046
  this._iceServers$.next(response.ice_servers ?? []);
21047
+ this.transport.setAuthenticated();
21018
21048
  this._authState$.next({ kind: "authenticated" });
21019
21049
  logger$9.debug("[Session] Authentication completed successfully");
21020
21050
  }
@@ -22211,6 +22241,7 @@ var WebSocketController = class WebSocketController extends Destroyable {
22211
22241
  }
22212
22242
  handleClose(_event) {
22213
22243
  this.clearConnectionTimeout();
22244
+ this.messageQueue = [];
22214
22245
  if (this.shouldReconnect) {
22215
22246
  this._status$.next("reconnecting");
22216
22247
  this.scheduleReconnection();
@@ -22280,6 +22311,9 @@ var WebSocketController = class WebSocketController extends Destroyable {
22280
22311
  function isSignalwirePingRequest(value) {
22281
22312
  return isJSONRPCRequest(value) && value.method === "signalwire.ping";
22282
22313
  }
22314
+ function isSignalwireConnectRequest(value) {
22315
+ return isJSONRPCRequest(value) && value.method === "signalwire.connect";
22316
+ }
22283
22317
 
22284
22318
  //#endregion
22285
22319
  //#region src/managers/TransportManager.ts
@@ -22294,6 +22328,10 @@ var TransportManager = class extends Destroyable {
22294
22328
  this.protocol$ = this.createReplaySubject(1);
22295
22329
  this.isConnecting = false;
22296
22330
  this.isConnected = false;
22331
+ this.socketStatus = "disconnected";
22332
+ this.sessionAuthenticated = false;
22333
+ this.heldRequests = [];
22334
+ this.serverPingWatch = 0;
22297
22335
  this.ackEvent = () => {
22298
22336
  return (0, import_cjs$2.tap)((message) => {
22299
22337
  if (isSignalwireRequest(message)) try {
@@ -22309,6 +22347,7 @@ var TransportManager = class extends Destroyable {
22309
22347
  if (isSignalwirePingRequest(message)) {
22310
22348
  try {
22311
22349
  logger$2.debug("[Transport] Received ping, sending pong", { pingId: message.id });
22350
+ if (this.sessionAuthenticated) this.armServerPingWatchdog();
22312
22351
  this.send(RPCPingResponse(message.id));
22313
22352
  } catch (error) {
22314
22353
  logger$2.error("[Transport] Failed to send ping response:", error);
@@ -22346,6 +22385,14 @@ var TransportManager = class extends Destroyable {
22346
22385
  this.subscribeTo(this._webSocketConnections.errors$, (error) => {
22347
22386
  this.onError?.(error);
22348
22387
  });
22388
+ this.subscribeTo(this._webSocketConnections.status$, (status) => {
22389
+ this.socketStatus = status;
22390
+ if (status !== "connected") {
22391
+ this.sessionAuthenticated = false;
22392
+ this.clearServerPingWatchdog();
22393
+ }
22394
+ if (status === "disconnected") this.heldRequests = [];
22395
+ });
22349
22396
  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
22397
  this._jsonRPCMessage$ = this._webSocketConnections.incomingMessages$.pipe((0, import_cjs$2.map)((event) => {
22351
22398
  try {
@@ -22374,6 +22421,9 @@ var TransportManager = class extends Destroyable {
22374
22421
  get connectionStatus$() {
22375
22422
  return this._webSocketConnections.status$;
22376
22423
  }
22424
+ get connectionStatus() {
22425
+ return this.socketStatus;
22426
+ }
22377
22427
  async connect() {
22378
22428
  if (this.isConnecting || this.isConnected) {
22379
22429
  logger$2.warn("[Transport] Already connecting or connected");
@@ -22418,16 +22468,45 @@ var TransportManager = class extends Destroyable {
22418
22468
  }
22419
22469
  async execute(request, options) {
22420
22470
  this.send(request);
22421
- return new PendingRPC(request, this._jsonRPCResponse$, options).promise;
22471
+ const pending = new PendingRPC(request, this._jsonRPCResponse$, options).promise;
22472
+ pending.catch(() => {
22473
+ this.heldRequests = this.heldRequests.filter((held) => held.id !== request.id);
22474
+ });
22475
+ return pending;
22422
22476
  }
22423
22477
  send(message) {
22424
22478
  const payload = JSON.stringify(message);
22479
+ if (isJSONRPCResponse(message)) {
22480
+ if (this.socketStatus !== "connected") {
22481
+ logger$2.debug("[Transport] Dropping response while disconnected", { id: message.id });
22482
+ return;
22483
+ }
22484
+ } else if (!this.sessionAuthenticated && !isSignalwireConnectRequest(message)) {
22485
+ this.heldRequests.push({
22486
+ id: message.id,
22487
+ payload
22488
+ });
22489
+ return;
22490
+ }
22425
22491
  this._outgoingMessages$.next(payload);
22426
22492
  }
22493
+ /**
22494
+ * Called by the session when signalwire.connect succeeds on the current
22495
+ * socket. Sends the requests held since the socket opened, in order.
22496
+ */
22497
+ setAuthenticated() {
22498
+ if (this.socketStatus !== "connected") return;
22499
+ this.sessionAuthenticated = true;
22500
+ this.armServerPingWatchdog();
22501
+ const held = this.heldRequests;
22502
+ this.heldRequests = [];
22503
+ held.forEach(({ payload }) => this._outgoingMessages$.next(payload));
22504
+ }
22427
22505
  disconnect() {
22428
22506
  logger$2.debug("[Transport] Disconnecting");
22429
22507
  this.isConnected = false;
22430
22508
  this.isConnecting = false;
22509
+ this.clearServerPingWatchdog();
22431
22510
  this._webSocketConnections.disconnect();
22432
22511
  }
22433
22512
  destroy() {
@@ -22436,6 +22515,28 @@ var TransportManager = class extends Destroyable {
22436
22515
  super.destroy();
22437
22516
  this._webSocketConnections.destroy();
22438
22517
  }
22518
+ armServerPingWatchdog() {
22519
+ this.clearServerPingWatchdog();
22520
+ const watch = this.serverPingWatch;
22521
+ this.serverPingTimer = setTimeout(() => void this.probeServer(watch), SERVER_PING_TIMEOUT_MS);
22522
+ }
22523
+ /** Bumps the watch so a probe still in flight no longer acts on its result. */
22524
+ clearServerPingWatchdog() {
22525
+ clearTimeout(this.serverPingTimer);
22526
+ this.serverPingTimer = void 0;
22527
+ this.serverPingWatch += 1;
22528
+ }
22529
+ async probeServer(watch) {
22530
+ logger$2.debug(`[Transport] No signalwire.ping for ${SERVER_PING_TIMEOUT_MS}ms, probing the server`);
22531
+ try {
22532
+ await this.execute(RPCPing(), { timeoutMs: SERVER_PING_PROBE_TIMEOUT_MS });
22533
+ if (watch === this.serverPingWatch) this.armServerPingWatchdog();
22534
+ } catch (error) {
22535
+ if (watch !== this.serverPingWatch) return;
22536
+ logger$2.warn("[Transport] Server did not answer the ping probe, replacing the socket", error);
22537
+ this.reconnect();
22538
+ }
22539
+ }
22439
22540
  async _loadProtocolFromStorage() {
22440
22541
  try {
22441
22542
  const storedProtocol = await this.storage.getItem(this.protocolKey);
@@ -23141,7 +23242,7 @@ var SignalWire = class extends Destroyable {
23141
23242
  logger$1.warn("[SignalWire] Failed to initialize VisibilityController:", error);
23142
23243
  }
23143
23244
  try {
23144
- this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "4.0.0-rc.3" });
23245
+ this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "4.0.0-rc.4" });
23145
23246
  } catch (error) {
23146
23247
  logger$1.warn("[SignalWire] Failed to initialize DiagnosticsCollector:", error);
23147
23248
  }
@@ -23696,7 +23797,7 @@ var StaticCredentialProvider = class {
23696
23797
  /**
23697
23798
  * Library version from package.json, injected at build time.
23698
23799
  */
23699
- const version = "4.0.0-rc.3";
23800
+ const version = "4.0.0-rc.4";
23700
23801
  /**
23701
23802
  * Flag indicating the library has been loaded and is ready to use.
23702
23803
  * For UMD builds: `window.SignalWire.ready`
@@ -23718,7 +23819,7 @@ const ready = true;
23718
23819
  */
23719
23820
  const emitReadyEvent = () => {
23720
23821
  if (typeof window !== "undefined") {
23721
- const event = new CustomEvent("signalwire:js:ready", { detail: { version: "4.0.0-rc.3" } });
23822
+ const event = new CustomEvent("signalwire:js:ready", { detail: { version: "4.0.0-rc.4" } });
23722
23823
  window.dispatchEvent(event);
23723
23824
  }
23724
23825
  };