@signalwire/js 4.0.0-dev-20260915135732 → 4.0.0-dev-20261001133709

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,
@@ -12536,8 +12554,36 @@ var AttachManager = class {
12536
12554
  logger$26.warn("[AttachManager] Skip attach for calls with no destination");
12537
12555
  return;
12538
12556
  }
12557
+ const attachment = this.buildAttachment(call, call.to);
12558
+ await this.mutate((attached) => ({
12559
+ ...attached,
12560
+ [call.id]: attachment
12561
+ }));
12562
+ }
12563
+ /**
12564
+ * Keep an already-stored call's reference alive and current — the periodic
12565
+ * refresh the `verto.ping` keepalive drives.
12566
+ *
12567
+ * Only ever updates: a call with no record is one nothing wants reattached,
12568
+ * and re-creating it here would undo a `detach`. That matters because a ping
12569
+ * can land in the window between `bye()` detaching and the call being torn
12570
+ * down, and a record revived there survives the hangup — so the next page
12571
+ * load dials a call nobody is on. The existence check and the write share
12572
+ * one {@link mutate} turn, so a concurrent detach cannot slip between them.
12573
+ */
12574
+ async refresh(call) {
12575
+ if (!call.to) return;
12539
12576
  const destination = call.to;
12540
- const attachment = {
12577
+ await this.mutate((attached) => {
12578
+ if (!Object.hasOwn(attached, call.id)) return attached;
12579
+ return {
12580
+ ...attached,
12581
+ [call.id]: this.buildAttachment(call, destination)
12582
+ };
12583
+ });
12584
+ }
12585
+ buildAttachment(call, destination) {
12586
+ return {
12541
12587
  nodeId: call.nodeId,
12542
12588
  destination,
12543
12589
  mediaDirections: call.mediaDirections,
@@ -12545,10 +12591,6 @@ var AttachManager = class {
12545
12591
  videoInputDevice: call.mediaDirections.video !== "inactive" ? this.deviceController.selectedVideoInputDevice : null,
12546
12592
  attachedAt: Date.now()
12547
12593
  };
12548
- await this.mutate((attached) => ({
12549
- ...attached,
12550
- [call.id]: attachment
12551
- }));
12552
12594
  }
12553
12595
  async detach(call) {
12554
12596
  await this.mutate((attached) => {
@@ -12712,12 +12754,12 @@ var require_race = /* @__PURE__ */ __commonJSMin(((exports) => {
12712
12754
  exports.race = void 0;
12713
12755
  var argsOrArgArray_1 = require_argsOrArgArray();
12714
12756
  var raceWith_1$1 = require_raceWith();
12715
- function race$3() {
12757
+ function race$4() {
12716
12758
  var args = [];
12717
12759
  for (var _i = 0; _i < arguments.length; _i++) args[_i] = arguments[_i];
12718
12760
  return raceWith_1$1.raceWith.apply(void 0, __spreadArray([], __read(argsOrArgArray_1.argsOrArgArray(args))));
12719
12761
  }
12720
- exports.race = race$3;
12762
+ exports.race = race$4;
12721
12763
  }));
12722
12764
 
12723
12765
  //#endregion
@@ -17183,6 +17225,7 @@ var WebRTCVertoManager = class extends VertoManager {
17183
17225
  this._signalingStatus$ = this.createReplaySubject(1);
17184
17226
  this._screenShareStatus$ = this.createBehaviorSubject("none");
17185
17227
  this._rtcPeerConnectionsMap = /* @__PURE__ */ new Map();
17228
+ this._lastVertoPings = /* @__PURE__ */ new Map();
17186
17229
  this._legErrors$ = this.createSubject();
17187
17230
  this._nodeId$ = this.createBehaviorSubject(options.nodeId ?? null);
17188
17231
  this.onError = options.onError;
@@ -17298,9 +17341,13 @@ var WebRTCVertoManager = class extends VertoManager {
17298
17341
  this.applyServerMediaParams(rtcPeerConnController, audio, video);
17299
17342
  });
17300
17343
  this.subscribeTo(this.vertoPing$, (vertoPing) => {
17301
- this.attachManager.attach(this.buildAttachableCall());
17344
+ this._lastVertoPings.set(vertoPing.callID, vertoPing);
17345
+ this.attachManager.refresh(this.buildAttachableCall());
17302
17346
  this.sendVertoPong(vertoPing);
17303
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
+ });
17304
17351
  }
17305
17352
  /**
17306
17353
  * An auxiliary-leg failure must never destroy the call; main-leg and
@@ -17660,7 +17707,7 @@ var WebRTCVertoManager = class extends VertoManager {
17660
17707
  this.emitMainSignalingStatus(rtcPeerConnController.id, "trying");
17661
17708
  const nodeId = getValueFrom(response, "result.node_id") ?? null;
17662
17709
  const memberId = getValueFrom(response, "result.result.result.memberID") ?? null;
17663
- const callId = getValueFrom(response, "result.result.result.callID") ?? null;
17710
+ const callId = getValueFrom(response, "result.result.result.callID");
17664
17711
  logger$16.debug("[WebRTCManager] Verto invite response:", {
17665
17712
  callId,
17666
17713
  memberId,
@@ -17671,14 +17718,9 @@ var WebRTCVertoManager = class extends VertoManager {
17671
17718
  if (rtcPeerConnController.isMainDevice) {
17672
17719
  this._selfId$.next(memberId);
17673
17720
  this._nodeId$.next(nodeId);
17674
- }
17675
- if (callId) {
17676
- this.webRtcCallSession.addCallId(callId);
17677
17721
  this.attachManager.attach(this.buildAttachableCall(callId, nodeId ?? void 0));
17678
- } else logger$16.warn("[WebRTCManager] Cannot attach call, missing callId:", {
17679
- nodeId: this.nodeId,
17680
- callId
17681
- });
17722
+ }
17723
+ if (callId) this.webRtcCallSession.addCallId(callId);
17682
17724
  logger$16.info("[WebRTCManager] Verto invite successful");
17683
17725
  logger$16.debug(`[WebRTCManager] nodeid: ${this._nodeId$.value}, selfId: ${this._selfId$.value}`);
17684
17726
  } else {
@@ -20842,6 +20884,10 @@ var ClientSessionManager = class extends Destroyable {
20842
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 })));
20843
20885
  }
20844
20886
  async handleAuthenticationError(error) {
20887
+ if (error instanceof TransportConnectionError) {
20888
+ logger$9.debug("[Session] Socket closed during authentication, waiting for reconnect");
20889
+ return;
20890
+ }
20845
20891
  logger$9.error("Authentication error:", error);
20846
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);
20847
20893
  const hasStoredState = await (0, import_cjs$7.firstValueFrom)(this.authorizationState$.pipe((0, import_cjs$7.take)(1))) !== void 0;
@@ -20981,7 +21027,9 @@ var ClientSessionManager = class extends Destroyable {
20981
21027
  protocol: persistedParams.protocol
20982
21028
  } : {}
20983
21029
  });
20984
- 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)(() => {
20985
21033
  logger$9.debug("[Session] Response passed filter, processing authentication result");
20986
21034
  }), (0, import_cjs$7.take)(1), (0, import_cjs$7.catchError)((err) => {
20987
21035
  logger$9.error("[Session] Authentication RPC failed:", err);
@@ -20996,6 +21044,7 @@ var ClientSessionManager = class extends Destroyable {
20996
21044
  this._authorization$.next(response.authorization);
20997
21045
  if (response.authorization.cnf?.jkt) this._wasClientBound = true;
20998
21046
  this._iceServers$.next(response.ice_servers ?? []);
21047
+ this.transport.setAuthenticated();
20999
21048
  this._authState$.next({ kind: "authenticated" });
21000
21049
  logger$9.debug("[Session] Authentication completed successfully");
21001
21050
  }
@@ -22192,6 +22241,7 @@ var WebSocketController = class WebSocketController extends Destroyable {
22192
22241
  }
22193
22242
  handleClose(_event) {
22194
22243
  this.clearConnectionTimeout();
22244
+ this.messageQueue = [];
22195
22245
  if (this.shouldReconnect) {
22196
22246
  this._status$.next("reconnecting");
22197
22247
  this.scheduleReconnection();
@@ -22261,6 +22311,9 @@ var WebSocketController = class WebSocketController extends Destroyable {
22261
22311
  function isSignalwirePingRequest(value) {
22262
22312
  return isJSONRPCRequest(value) && value.method === "signalwire.ping";
22263
22313
  }
22314
+ function isSignalwireConnectRequest(value) {
22315
+ return isJSONRPCRequest(value) && value.method === "signalwire.connect";
22316
+ }
22264
22317
 
22265
22318
  //#endregion
22266
22319
  //#region src/managers/TransportManager.ts
@@ -22275,6 +22328,10 @@ var TransportManager = class extends Destroyable {
22275
22328
  this.protocol$ = this.createReplaySubject(1);
22276
22329
  this.isConnecting = false;
22277
22330
  this.isConnected = false;
22331
+ this.socketStatus = "disconnected";
22332
+ this.sessionAuthenticated = false;
22333
+ this.heldRequests = [];
22334
+ this.serverPingWatch = 0;
22278
22335
  this.ackEvent = () => {
22279
22336
  return (0, import_cjs$2.tap)((message) => {
22280
22337
  if (isSignalwireRequest(message)) try {
@@ -22290,6 +22347,7 @@ var TransportManager = class extends Destroyable {
22290
22347
  if (isSignalwirePingRequest(message)) {
22291
22348
  try {
22292
22349
  logger$2.debug("[Transport] Received ping, sending pong", { pingId: message.id });
22350
+ if (this.sessionAuthenticated) this.armServerPingWatchdog();
22293
22351
  this.send(RPCPingResponse(message.id));
22294
22352
  } catch (error) {
22295
22353
  logger$2.error("[Transport] Failed to send ping response:", error);
@@ -22327,6 +22385,14 @@ var TransportManager = class extends Destroyable {
22327
22385
  this.subscribeTo(this._webSocketConnections.errors$, (error) => {
22328
22386
  this.onError?.(error);
22329
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
+ });
22330
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$));
22331
22397
  this._jsonRPCMessage$ = this._webSocketConnections.incomingMessages$.pipe((0, import_cjs$2.map)((event) => {
22332
22398
  try {
@@ -22355,6 +22421,9 @@ var TransportManager = class extends Destroyable {
22355
22421
  get connectionStatus$() {
22356
22422
  return this._webSocketConnections.status$;
22357
22423
  }
22424
+ get connectionStatus() {
22425
+ return this.socketStatus;
22426
+ }
22358
22427
  async connect() {
22359
22428
  if (this.isConnecting || this.isConnected) {
22360
22429
  logger$2.warn("[Transport] Already connecting or connected");
@@ -22399,16 +22468,45 @@ var TransportManager = class extends Destroyable {
22399
22468
  }
22400
22469
  async execute(request, options) {
22401
22470
  this.send(request);
22402
- 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;
22403
22476
  }
22404
22477
  send(message) {
22405
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
+ }
22406
22491
  this._outgoingMessages$.next(payload);
22407
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
+ }
22408
22505
  disconnect() {
22409
22506
  logger$2.debug("[Transport] Disconnecting");
22410
22507
  this.isConnected = false;
22411
22508
  this.isConnecting = false;
22509
+ this.clearServerPingWatchdog();
22412
22510
  this._webSocketConnections.disconnect();
22413
22511
  }
22414
22512
  destroy() {
@@ -22417,6 +22515,28 @@ var TransportManager = class extends Destroyable {
22417
22515
  super.destroy();
22418
22516
  this._webSocketConnections.destroy();
22419
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
+ }
22420
22540
  async _loadProtocolFromStorage() {
22421
22541
  try {
22422
22542
  const storedProtocol = await this.storage.getItem(this.protocolKey);
@@ -23122,7 +23242,7 @@ var SignalWire = class extends Destroyable {
23122
23242
  logger$1.warn("[SignalWire] Failed to initialize VisibilityController:", error);
23123
23243
  }
23124
23244
  try {
23125
- this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "4.0.0-dev-20260915135732" });
23245
+ this._diagnosticsCollector = new DiagnosticsCollector({ sdkVersion: "4.0.0-dev-20261001133709" });
23126
23246
  } catch (error) {
23127
23247
  logger$1.warn("[SignalWire] Failed to initialize DiagnosticsCollector:", error);
23128
23248
  }
@@ -23677,7 +23797,7 @@ var StaticCredentialProvider = class {
23677
23797
  /**
23678
23798
  * Library version from package.json, injected at build time.
23679
23799
  */
23680
- const version = "4.0.0-dev-20260915135732";
23800
+ const version = "4.0.0-dev-20261001133709";
23681
23801
  /**
23682
23802
  * Flag indicating the library has been loaded and is ready to use.
23683
23803
  * For UMD builds: `window.SignalWire.ready`
@@ -23699,7 +23819,7 @@ const ready = true;
23699
23819
  */
23700
23820
  const emitReadyEvent = () => {
23701
23821
  if (typeof window !== "undefined") {
23702
- const event = new CustomEvent("signalwire:js:ready", { detail: { version: "4.0.0-dev-20260915135732" } });
23822
+ const event = new CustomEvent("signalwire:js:ready", { detail: { version: "4.0.0-dev-20261001133709" } });
23703
23823
  window.dispatchEvent(event);
23704
23824
  }
23705
23825
  };