starknet 10.6.5 → 10.6.7

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/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## [10.6.7](https://github.com/starknet-io/starknet.js/compare/v10.6.6...v10.6.7) (2026-08-06)
2
+
3
+ ### Bug Fixes
4
+
5
+ - **ws:** settle in-flight requests when the connection drops ([8eae9c9](https://github.com/starknet-io/starknet.js/commit/8eae9c9457befe6e0c3f4fd30a56025fe5455fd8))
6
+
7
+ ## [10.6.6](https://github.com/starknet-io/starknet.js/compare/v10.6.5...v10.6.6) (2026-08-05)
8
+
9
+ ### Bug Fixes
10
+
11
+ - **ws:** reject queued requests instead of hanging ([82ced97](https://github.com/starknet-io/starknet.js/commit/82ced97dc6dc409131bc5650db076efedf467101))
12
+
1
13
  ## [10.6.5](https://github.com/starknet-io/starknet.js/compare/v10.6.4...v10.6.5) (2026-08-05)
2
14
 
3
15
  ### Bug Fixes
package/dist/index.d.ts CHANGED
@@ -5151,6 +5151,8 @@ declare class WebSocketChannel {
5151
5151
  private reconnectTimeoutId;
5152
5152
  private reconnectStabilityTimeoutId;
5153
5153
  private requestQueue;
5154
+ /** Abort handles for the requests currently on the wire, one per pending `sendReceive`. */
5155
+ private inFlight;
5154
5156
  private events;
5155
5157
  private openListener;
5156
5158
  private closeListener;
@@ -5243,6 +5245,25 @@ declare class WebSocketChannel {
5243
5245
  */
5244
5246
  reconnect(): void;
5245
5247
  private _processRequestQueue;
5248
+ /**
5249
+ * Reject every request still waiting in the queue.
5250
+ *
5251
+ * A queued request carries no timeout of its own: the `requestTimeout` timer is only
5252
+ * armed once the request is actually put on the wire. So once the channel reaches a state
5253
+ * where the queue can never be flushed — reconnection gave up, or the user closed the
5254
+ * connection — the queued promises would stay pending forever, and no caller-side timeout
5255
+ * could rescue them. Settling them here is the only way out.
5256
+ */
5257
+ private _rejectRequestQueue;
5258
+ /**
5259
+ * Settle every request already on the wire.
5260
+ *
5261
+ * The counterpart of `_rejectRequestQueue`, for requests past the queue. Their only other
5262
+ * exit is the `requestTimeout` timer, so without this the caller waits the whole timeout —
5263
+ * 60s by default — for a reply that can no longer arrive, and that pending timer keeps the
5264
+ * Node event loop alive for just as long.
5265
+ */
5266
+ private _rejectInFlight;
5246
5267
  private _restoreSubscriptions;
5247
5268
  /**
5248
5269
  * Reset the reconnection attempt counter, but only once the current connection has
@@ -12618,6 +12618,8 @@ ${indent}}` : "}";
12618
12618
  // resetting the reconnection attempt counter.
12619
12619
  reconnectStabilityTimeoutId = null;
12620
12620
  requestQueue = [];
12621
+ /** Abort handles for the requests currently on the wire, one per pending `sendReceive`. */
12622
+ inFlight = /* @__PURE__ */ new Set();
12621
12623
  events = new EventEmitter();
12622
12624
  openListener = (ev) => {
12623
12625
  this.scheduleReconnectAttemptsReset();
@@ -12701,12 +12703,19 @@ ${indent}}` : "}";
12701
12703
  });
12702
12704
  }
12703
12705
  const sendId = this.send(method, params);
12706
+ const socket = this.websocket;
12704
12707
  return new Promise((resolve, reject) => {
12705
- let timeoutId;
12706
- if (!this.websocket || this.websocket.readyState !== ws_default.OPEN) {
12708
+ if (socket.readyState !== ws_default.OPEN) {
12707
12709
  reject(new WebSocketNotConnectedError("WebSocket not available or not connected."));
12708
12710
  return;
12709
12711
  }
12712
+ let timeoutId;
12713
+ const settle = () => {
12714
+ clearTimeout(timeoutId);
12715
+ socket.removeEventListener("message", messageHandler);
12716
+ socket.removeEventListener("error", errorHandler);
12717
+ this.inFlight.delete(abort);
12718
+ };
12710
12719
  const messageHandler = (event) => {
12711
12720
  if (!isString(event.data)) {
12712
12721
  logger.warn("WebSocket received non-string message data:", event.data);
@@ -12721,34 +12730,35 @@ ${indent}}` : "}";
12721
12730
  );
12722
12731
  return;
12723
12732
  }
12724
- if (message.id === sendId) {
12725
- clearTimeout(timeoutId);
12726
- this.websocket.removeEventListener("message", messageHandler);
12727
- this.websocket.removeEventListener("error", errorHandler);
12728
- if ("result" in message) {
12729
- resolve(message.result);
12730
- } else {
12731
- reject(
12732
- new Error(`Error on ${method} (id: ${sendId}): ${JSON.stringify(message.error)}`)
12733
- );
12734
- }
12733
+ if (message.id !== sendId) return;
12734
+ settle();
12735
+ if ("result" in message) {
12736
+ resolve(message.result);
12737
+ } else {
12738
+ reject(new Error(`Error on ${method} (id: ${sendId}): ${JSON.stringify(message.error)}`));
12735
12739
  }
12736
12740
  };
12737
12741
  const errorHandler = (event) => {
12738
- clearTimeout(timeoutId);
12739
- this.websocket.removeEventListener("message", messageHandler);
12740
- this.websocket.removeEventListener("error", errorHandler);
12742
+ settle();
12741
12743
  reject(
12742
12744
  new Error(
12743
12745
  `WebSocket error during ${method} (id: ${sendId}): ${event.type || "Unknown error"}`
12744
12746
  )
12745
12747
  );
12746
12748
  };
12747
- this.websocket.addEventListener("message", messageHandler);
12748
- this.websocket.addEventListener("error", errorHandler);
12749
+ const abort = (reason) => {
12750
+ settle();
12751
+ reject(
12752
+ new WebSocketNotConnectedError(
12753
+ `Request ${method} (id: ${sendId}) went unanswered: ${reason}`
12754
+ )
12755
+ );
12756
+ };
12757
+ socket.addEventListener("message", messageHandler);
12758
+ socket.addEventListener("error", errorHandler);
12759
+ this.inFlight.add(abort);
12749
12760
  timeoutId = setTimeout(() => {
12750
- this.websocket.removeEventListener("message", messageHandler);
12751
- this.websocket.removeEventListener("error", errorHandler);
12761
+ settle();
12752
12762
  reject(
12753
12763
  new TimeoutError(
12754
12764
  `Request ${method} (id: ${sendId}) timed out after ${this.requestTimeout}ms`
@@ -12803,6 +12813,9 @@ ${indent}}` : "}";
12803
12813
  this.reconnectStabilityTimeoutId = null;
12804
12814
  }
12805
12815
  this.userInitiatedClose = true;
12816
+ this.isReconnecting = false;
12817
+ this._rejectRequestQueue("the connection was closed by the user");
12818
+ this._rejectInFlight("the connection was closed by the user");
12806
12819
  this.websocket.close(code, reason);
12807
12820
  }
12808
12821
  /**
@@ -12876,6 +12889,39 @@ ${indent}}` : "}";
12876
12889
  this.sendReceive(method, params).then(resolve).catch(reject);
12877
12890
  });
12878
12891
  }
12892
+ /**
12893
+ * Reject every request still waiting in the queue.
12894
+ *
12895
+ * A queued request carries no timeout of its own: the `requestTimeout` timer is only
12896
+ * armed once the request is actually put on the wire. So once the channel reaches a state
12897
+ * where the queue can never be flushed — reconnection gave up, or the user closed the
12898
+ * connection — the queued promises would stay pending forever, and no caller-side timeout
12899
+ * could rescue them. Settling them here is the only way out.
12900
+ */
12901
+ _rejectRequestQueue(reason) {
12902
+ if (this.requestQueue.length === 0) return;
12903
+ const pending = this.requestQueue;
12904
+ this.requestQueue = [];
12905
+ logger.info(`WebSocket: Rejecting ${pending.length} queued request(s). Reason: ${reason}.`);
12906
+ pending.forEach(({ method, reject }) => {
12907
+ reject(new WebSocketNotConnectedError(`Request ${method} was never sent: ${reason}`));
12908
+ });
12909
+ }
12910
+ /**
12911
+ * Settle every request already on the wire.
12912
+ *
12913
+ * The counterpart of `_rejectRequestQueue`, for requests past the queue. Their only other
12914
+ * exit is the `requestTimeout` timer, so without this the caller waits the whole timeout —
12915
+ * 60s by default — for a reply that can no longer arrive, and that pending timer keeps the
12916
+ * Node event loop alive for just as long.
12917
+ */
12918
+ _rejectInFlight(reason) {
12919
+ if (this.inFlight.size === 0) return;
12920
+ const pending = Array.from(this.inFlight);
12921
+ this.inFlight.clear();
12922
+ logger.info(`WebSocket: Rejecting ${pending.length} in-flight request(s). Reason: ${reason}.`);
12923
+ pending.forEach((abort) => abort(reason));
12924
+ }
12879
12925
  async _restoreSubscriptions() {
12880
12926
  const oldSubscriptions = Array.from(this.activeSubscriptions.values());
12881
12927
  this.activeSubscriptions.clear();
@@ -12919,6 +12965,9 @@ ${indent}}` : "}";
12919
12965
  if (this.reconnectAttempts >= this.reconnectOptions.retries) {
12920
12966
  logger.error("WebSocket: Maximum reconnection retries reached. Giving up.");
12921
12967
  this.isReconnecting = false;
12968
+ this._rejectRequestQueue(
12969
+ `reconnection gave up after ${this.reconnectOptions.retries} attempts`
12970
+ );
12922
12971
  return;
12923
12972
  }
12924
12973
  this.reconnectAttempts += 1;
@@ -12926,18 +12975,24 @@ ${indent}}` : "}";
12926
12975
  `WebSocket: Connection lost. Attempting to reconnect... (${this.reconnectAttempts}/${this.reconnectOptions.retries})`
12927
12976
  );
12928
12977
  this.reconnect();
12978
+ let attemptSettled = false;
12979
+ const scheduleRetry = () => {
12980
+ if (attemptSettled || !this.isReconnecting) return;
12981
+ attemptSettled = true;
12982
+ const delay = this.reconnectOptions.exponential ? this.reconnectOptions.delay * 2 ** (this.reconnectAttempts - 1) : this.reconnectOptions.delay;
12983
+ logger.info(`WebSocket: Reconnect attempt failed. Retrying in ${delay}ms.`);
12984
+ this.reconnectTimeoutId = setTimeout(tryReconnect, delay);
12985
+ };
12929
12986
  this.websocket.onopen = async () => {
12930
12987
  logger.info("WebSocket: Reconnection successful.");
12988
+ attemptSettled = true;
12931
12989
  this.isReconnecting = false;
12932
12990
  await this._restoreSubscriptions();
12933
12991
  this._processRequestQueue();
12934
12992
  this.events.emit("open", new Event("open"));
12935
12993
  };
12936
- this.websocket.onerror = () => {
12937
- const delay = this.reconnectOptions.exponential ? this.reconnectOptions.delay * 2 ** (this.reconnectAttempts - 1) : this.reconnectOptions.delay;
12938
- logger.info(`WebSocket: Reconnect attempt failed. Retrying in ${delay}ms.`);
12939
- this.reconnectTimeoutId = setTimeout(tryReconnect, delay);
12940
- };
12994
+ this.websocket.onerror = scheduleRetry;
12995
+ this.websocket.addEventListener("close", scheduleRetry);
12941
12996
  };
12942
12997
  tryReconnect();
12943
12998
  }
@@ -12946,6 +13001,7 @@ ${indent}}` : "}";
12946
13001
  this.websocket.removeEventListener("close", this.closeListener);
12947
13002
  this.websocket.removeEventListener("message", this.messageListener);
12948
13003
  this.websocket.removeEventListener("error", this.errorListener);
13004
+ this._rejectInFlight("the connection was closed");
12949
13005
  this.events.emit("close", ev);
12950
13006
  if (!this.userInitiatedClose) {
12951
13007
  this._startReconnect();