starknet 10.6.6 → 10.6.8
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 +12 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.global.js +154 -46
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +154 -46
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +154 -46
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -7292,6 +7292,8 @@ var Subscription = class {
|
|
|
7292
7292
|
maxBufferSize;
|
|
7293
7293
|
handler = null;
|
|
7294
7294
|
_isClosed = false;
|
|
7295
|
+
// The unsubscribe request currently on the wire, shared by concurrent callers.
|
|
7296
|
+
pendingUnsubscribe = null;
|
|
7295
7297
|
/**
|
|
7296
7298
|
* @internal
|
|
7297
7299
|
* @param options - Subscription configuration options
|
|
@@ -7310,6 +7312,20 @@ var Subscription = class {
|
|
|
7310
7312
|
get isClosed() {
|
|
7311
7313
|
return this._isClosed;
|
|
7312
7314
|
}
|
|
7315
|
+
/**
|
|
7316
|
+
* Closes the subscription locally, without contacting the node.
|
|
7317
|
+
*
|
|
7318
|
+
* Used when the channel knows the subscription is gone and cannot be recovered — a
|
|
7319
|
+
* re-subscribe refused after a reconnection, for instance. Without it the object would keep
|
|
7320
|
+
* reporting itself as open while no event could ever reach its handler again.
|
|
7321
|
+
* @internal
|
|
7322
|
+
*/
|
|
7323
|
+
_markClosed() {
|
|
7324
|
+
if (this._isClosed) return;
|
|
7325
|
+
this._isClosed = true;
|
|
7326
|
+
this.events.emit("unsubscribe", void 0);
|
|
7327
|
+
this.events.clear();
|
|
7328
|
+
}
|
|
7313
7329
|
/**
|
|
7314
7330
|
* Internal method to handle incoming events from the WebSocket channel.
|
|
7315
7331
|
* If a handler is attached, it's invoked immediately. Otherwise, the event is buffered.
|
|
@@ -7356,14 +7372,24 @@ var Subscription = class {
|
|
|
7356
7372
|
if (this._isClosed) {
|
|
7357
7373
|
return true;
|
|
7358
7374
|
}
|
|
7359
|
-
|
|
7360
|
-
|
|
7361
|
-
this._isClosed = true;
|
|
7362
|
-
this.channel.removeSubscription(this.id);
|
|
7363
|
-
this.events.emit("unsubscribe", void 0);
|
|
7364
|
-
this.events.clear();
|
|
7375
|
+
if (this.pendingUnsubscribe) {
|
|
7376
|
+
return this.pendingUnsubscribe;
|
|
7365
7377
|
}
|
|
7366
|
-
|
|
7378
|
+
this.pendingUnsubscribe = (async () => {
|
|
7379
|
+
try {
|
|
7380
|
+
const success = await this.channel.unsubscribe(this.id);
|
|
7381
|
+
if (success) {
|
|
7382
|
+
this._isClosed = true;
|
|
7383
|
+
this.channel.removeSubscription(this.id);
|
|
7384
|
+
this.events.emit("unsubscribe", void 0);
|
|
7385
|
+
this.events.clear();
|
|
7386
|
+
}
|
|
7387
|
+
return success;
|
|
7388
|
+
} finally {
|
|
7389
|
+
this.pendingUnsubscribe = null;
|
|
7390
|
+
}
|
|
7391
|
+
})();
|
|
7392
|
+
return this.pendingUnsubscribe;
|
|
7367
7393
|
}
|
|
7368
7394
|
};
|
|
7369
7395
|
|
|
@@ -7394,6 +7420,17 @@ var WebSocketChannel = class {
|
|
|
7394
7420
|
// resetting the reconnection attempt counter.
|
|
7395
7421
|
reconnectStabilityTimeoutId = null;
|
|
7396
7422
|
requestQueue = [];
|
|
7423
|
+
/** Abort handles for the requests currently on the wire, one per pending `sendReceive`. */
|
|
7424
|
+
inFlight = /* @__PURE__ */ new Set();
|
|
7425
|
+
/**
|
|
7426
|
+
* Callers blocked in `waitForUnsubscription`, keyed by subscription id.
|
|
7427
|
+
*
|
|
7428
|
+
* Held here rather than as `unsubscribe` event listeners because that event only ever
|
|
7429
|
+
* announces success: a waiter attached to it cannot learn that the node refused the
|
|
7430
|
+
* unsubscribe or that the connection went away, and would wait forever with no timeout of
|
|
7431
|
+
* its own to fall back on.
|
|
7432
|
+
*/
|
|
7433
|
+
unsubscribeWaiters = /* @__PURE__ */ new Map();
|
|
7397
7434
|
events = new EventEmitter();
|
|
7398
7435
|
openListener = (ev) => {
|
|
7399
7436
|
this.scheduleReconnectAttemptsReset();
|
|
@@ -7477,12 +7514,19 @@ var WebSocketChannel = class {
|
|
|
7477
7514
|
});
|
|
7478
7515
|
}
|
|
7479
7516
|
const sendId = this.send(method, params);
|
|
7517
|
+
const socket = this.websocket;
|
|
7480
7518
|
return new Promise((resolve, reject) => {
|
|
7481
|
-
|
|
7482
|
-
if (!this.websocket || this.websocket.readyState !== ws_default.OPEN) {
|
|
7519
|
+
if (socket.readyState !== ws_default.OPEN) {
|
|
7483
7520
|
reject(new WebSocketNotConnectedError("WebSocket not available or not connected."));
|
|
7484
7521
|
return;
|
|
7485
7522
|
}
|
|
7523
|
+
let timeoutId;
|
|
7524
|
+
const settle = () => {
|
|
7525
|
+
clearTimeout(timeoutId);
|
|
7526
|
+
socket.removeEventListener("message", messageHandler);
|
|
7527
|
+
socket.removeEventListener("error", errorHandler);
|
|
7528
|
+
this.inFlight.delete(abort);
|
|
7529
|
+
};
|
|
7486
7530
|
const messageHandler = (event) => {
|
|
7487
7531
|
if (!isString(event.data)) {
|
|
7488
7532
|
logger.warn("WebSocket received non-string message data:", event.data);
|
|
@@ -7497,34 +7541,35 @@ var WebSocketChannel = class {
|
|
|
7497
7541
|
);
|
|
7498
7542
|
return;
|
|
7499
7543
|
}
|
|
7500
|
-
if (message.id
|
|
7501
|
-
|
|
7502
|
-
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
|
|
7506
|
-
} else {
|
|
7507
|
-
reject(
|
|
7508
|
-
new Error(`Error on ${method} (id: ${sendId}): ${JSON.stringify(message.error)}`)
|
|
7509
|
-
);
|
|
7510
|
-
}
|
|
7544
|
+
if (message.id !== sendId) return;
|
|
7545
|
+
settle();
|
|
7546
|
+
if ("result" in message) {
|
|
7547
|
+
resolve(message.result);
|
|
7548
|
+
} else {
|
|
7549
|
+
reject(new Error(`Error on ${method} (id: ${sendId}): ${JSON.stringify(message.error)}`));
|
|
7511
7550
|
}
|
|
7512
7551
|
};
|
|
7513
7552
|
const errorHandler = (event) => {
|
|
7514
|
-
|
|
7515
|
-
this.websocket.removeEventListener("message", messageHandler);
|
|
7516
|
-
this.websocket.removeEventListener("error", errorHandler);
|
|
7553
|
+
settle();
|
|
7517
7554
|
reject(
|
|
7518
7555
|
new Error(
|
|
7519
7556
|
`WebSocket error during ${method} (id: ${sendId}): ${event.type || "Unknown error"}`
|
|
7520
7557
|
)
|
|
7521
7558
|
);
|
|
7522
7559
|
};
|
|
7523
|
-
|
|
7524
|
-
|
|
7560
|
+
const abort = (reason) => {
|
|
7561
|
+
settle();
|
|
7562
|
+
reject(
|
|
7563
|
+
new WebSocketNotConnectedError(
|
|
7564
|
+
`Request ${method} (id: ${sendId}) went unanswered: ${reason}`
|
|
7565
|
+
)
|
|
7566
|
+
);
|
|
7567
|
+
};
|
|
7568
|
+
socket.addEventListener("message", messageHandler);
|
|
7569
|
+
socket.addEventListener("error", errorHandler);
|
|
7570
|
+
this.inFlight.add(abort);
|
|
7525
7571
|
timeoutId = setTimeout(() => {
|
|
7526
|
-
|
|
7527
|
-
this.websocket.removeEventListener("error", errorHandler);
|
|
7572
|
+
settle();
|
|
7528
7573
|
reject(
|
|
7529
7574
|
new TimeoutError(
|
|
7530
7575
|
`Request ${method} (id: ${sendId}) timed out after ${this.requestTimeout}ms`
|
|
@@ -7581,6 +7626,8 @@ var WebSocketChannel = class {
|
|
|
7581
7626
|
this.userInitiatedClose = true;
|
|
7582
7627
|
this.isReconnecting = false;
|
|
7583
7628
|
this._rejectRequestQueue("the connection was closed by the user");
|
|
7629
|
+
this._rejectInFlight("the connection was closed by the user");
|
|
7630
|
+
this._rejectUnsubscribeWaiters("the connection was closed by the user");
|
|
7584
7631
|
this.websocket.close(code, reason);
|
|
7585
7632
|
}
|
|
7586
7633
|
/**
|
|
@@ -7605,14 +7652,48 @@ var WebSocketChannel = class {
|
|
|
7605
7652
|
* @returns {Promise<boolean>} A Promise that resolves with `true` if the unsubscription was successful.
|
|
7606
7653
|
*/
|
|
7607
7654
|
async unsubscribe(subscriptionId) {
|
|
7608
|
-
|
|
7609
|
-
|
|
7610
|
-
|
|
7655
|
+
let status;
|
|
7656
|
+
try {
|
|
7657
|
+
status = await this.sendReceive("starknet_unsubscribe", {
|
|
7658
|
+
subscription_id: subscriptionId
|
|
7659
|
+
});
|
|
7660
|
+
} catch (error) {
|
|
7661
|
+
this._settleUnsubscribeWaiters(subscriptionId, error);
|
|
7662
|
+
throw error;
|
|
7663
|
+
}
|
|
7611
7664
|
if (status) {
|
|
7612
7665
|
this.events.emit("unsubscribe", subscriptionId);
|
|
7666
|
+
this._settleUnsubscribeWaiters(subscriptionId);
|
|
7667
|
+
} else {
|
|
7668
|
+
this._settleUnsubscribeWaiters(
|
|
7669
|
+
subscriptionId,
|
|
7670
|
+
new Error(`Node refused to unsubscribe subscription ${subscriptionId}`)
|
|
7671
|
+
);
|
|
7613
7672
|
}
|
|
7614
7673
|
return status;
|
|
7615
7674
|
}
|
|
7675
|
+
/** Settles every caller waiting on one subscription id: resolved, or rejected with `error`. */
|
|
7676
|
+
_settleUnsubscribeWaiters(subscriptionId, error) {
|
|
7677
|
+
const waiters = this.unsubscribeWaiters.get(subscriptionId);
|
|
7678
|
+
if (!waiters) return;
|
|
7679
|
+
this.unsubscribeWaiters.delete(subscriptionId);
|
|
7680
|
+
waiters.forEach((waiter) => error ? waiter.reject(error) : waiter.resolve());
|
|
7681
|
+
}
|
|
7682
|
+
/**
|
|
7683
|
+
* Rejects every caller still waiting on any subscription.
|
|
7684
|
+
*
|
|
7685
|
+
* Once the connection is gone, no unsubscribe can be observed on it: a reconnection restores
|
|
7686
|
+
* each subscription under a fresh id, so the id being waited on will never be announced.
|
|
7687
|
+
*/
|
|
7688
|
+
_rejectUnsubscribeWaiters(reason) {
|
|
7689
|
+
if (this.unsubscribeWaiters.size === 0) return;
|
|
7690
|
+
Array.from(this.unsubscribeWaiters.keys()).forEach(
|
|
7691
|
+
(id) => this._settleUnsubscribeWaiters(
|
|
7692
|
+
id,
|
|
7693
|
+
new WebSocketNotConnectedError(`Subscription ${id} was never unsubscribed: ${reason}`)
|
|
7694
|
+
)
|
|
7695
|
+
);
|
|
7696
|
+
}
|
|
7616
7697
|
/**
|
|
7617
7698
|
* Returns a Promise that resolves when a specific subscription is successfully unsubscribed.
|
|
7618
7699
|
* @param {SUBSCRIPTION_ID} targetId - The ID of the subscription to wait for.
|
|
@@ -7624,14 +7705,10 @@ var WebSocketChannel = class {
|
|
|
7624
7705
|
* ```
|
|
7625
7706
|
*/
|
|
7626
7707
|
waitForUnsubscription(targetId) {
|
|
7627
|
-
return new Promise((resolve) => {
|
|
7628
|
-
const
|
|
7629
|
-
|
|
7630
|
-
|
|
7631
|
-
resolve();
|
|
7632
|
-
}
|
|
7633
|
-
};
|
|
7634
|
-
this.events.on("unsubscribe", listener);
|
|
7708
|
+
return new Promise((resolve, reject) => {
|
|
7709
|
+
const waiters = this.unsubscribeWaiters.get(targetId) ?? /* @__PURE__ */ new Set();
|
|
7710
|
+
waiters.add({ resolve, reject });
|
|
7711
|
+
this.unsubscribeWaiters.set(targetId, waiters);
|
|
7635
7712
|
});
|
|
7636
7713
|
}
|
|
7637
7714
|
/**
|
|
@@ -7672,6 +7749,21 @@ var WebSocketChannel = class {
|
|
|
7672
7749
|
reject(new WebSocketNotConnectedError(`Request ${method} was never sent: ${reason}`));
|
|
7673
7750
|
});
|
|
7674
7751
|
}
|
|
7752
|
+
/**
|
|
7753
|
+
* Settle every request already on the wire.
|
|
7754
|
+
*
|
|
7755
|
+
* The counterpart of `_rejectRequestQueue`, for requests past the queue. Their only other
|
|
7756
|
+
* exit is the `requestTimeout` timer, so without this the caller waits the whole timeout —
|
|
7757
|
+
* 60s by default — for a reply that can no longer arrive, and that pending timer keeps the
|
|
7758
|
+
* Node event loop alive for just as long.
|
|
7759
|
+
*/
|
|
7760
|
+
_rejectInFlight(reason) {
|
|
7761
|
+
if (this.inFlight.size === 0) return;
|
|
7762
|
+
const pending = Array.from(this.inFlight);
|
|
7763
|
+
this.inFlight.clear();
|
|
7764
|
+
logger.info(`WebSocket: Rejecting ${pending.length} in-flight request(s). Reason: ${reason}.`);
|
|
7765
|
+
pending.forEach((abort) => abort(reason));
|
|
7766
|
+
}
|
|
7675
7767
|
async _restoreSubscriptions() {
|
|
7676
7768
|
const oldSubscriptions = Array.from(this.activeSubscriptions.values());
|
|
7677
7769
|
this.activeSubscriptions.clear();
|
|
@@ -7683,6 +7775,7 @@ var WebSocketChannel = class {
|
|
|
7683
7775
|
logger.info(`Subscription ${sub.method} restored with new ID: ${newSubId}`);
|
|
7684
7776
|
} catch (error) {
|
|
7685
7777
|
logger.error(`Failed to restore subscription ${sub.method}:`, error);
|
|
7778
|
+
sub._markClosed();
|
|
7686
7779
|
}
|
|
7687
7780
|
});
|
|
7688
7781
|
await Promise.all(restorePromises);
|
|
@@ -7725,18 +7818,24 @@ var WebSocketChannel = class {
|
|
|
7725
7818
|
`WebSocket: Connection lost. Attempting to reconnect... (${this.reconnectAttempts}/${this.reconnectOptions.retries})`
|
|
7726
7819
|
);
|
|
7727
7820
|
this.reconnect();
|
|
7821
|
+
let attemptSettled = false;
|
|
7822
|
+
const scheduleRetry = () => {
|
|
7823
|
+
if (attemptSettled || !this.isReconnecting) return;
|
|
7824
|
+
attemptSettled = true;
|
|
7825
|
+
const delay = this.reconnectOptions.exponential ? this.reconnectOptions.delay * 2 ** (this.reconnectAttempts - 1) : this.reconnectOptions.delay;
|
|
7826
|
+
logger.info(`WebSocket: Reconnect attempt failed. Retrying in ${delay}ms.`);
|
|
7827
|
+
this.reconnectTimeoutId = setTimeout(tryReconnect, delay);
|
|
7828
|
+
};
|
|
7728
7829
|
this.websocket.onopen = async () => {
|
|
7729
7830
|
logger.info("WebSocket: Reconnection successful.");
|
|
7831
|
+
attemptSettled = true;
|
|
7730
7832
|
this.isReconnecting = false;
|
|
7731
7833
|
await this._restoreSubscriptions();
|
|
7732
7834
|
this._processRequestQueue();
|
|
7733
7835
|
this.events.emit("open", new Event("open"));
|
|
7734
7836
|
};
|
|
7735
|
-
this.websocket.onerror =
|
|
7736
|
-
|
|
7737
|
-
logger.info(`WebSocket: Reconnect attempt failed. Retrying in ${delay}ms.`);
|
|
7738
|
-
this.reconnectTimeoutId = setTimeout(tryReconnect, delay);
|
|
7739
|
-
};
|
|
7837
|
+
this.websocket.onerror = scheduleRetry;
|
|
7838
|
+
this.websocket.addEventListener("close", scheduleRetry);
|
|
7740
7839
|
};
|
|
7741
7840
|
tryReconnect();
|
|
7742
7841
|
}
|
|
@@ -7745,6 +7844,8 @@ var WebSocketChannel = class {
|
|
|
7745
7844
|
this.websocket.removeEventListener("close", this.closeListener);
|
|
7746
7845
|
this.websocket.removeEventListener("message", this.messageListener);
|
|
7747
7846
|
this.websocket.removeEventListener("error", this.errorListener);
|
|
7847
|
+
this._rejectInFlight("the connection was closed");
|
|
7848
|
+
this._rejectUnsubscribeWaiters("the connection was closed");
|
|
7748
7849
|
this.events.emit("close", ev);
|
|
7749
7850
|
if (!this.userInitiatedClose) {
|
|
7750
7851
|
this._startReconnect();
|
|
@@ -7766,9 +7867,16 @@ var WebSocketChannel = class {
|
|
|
7766
7867
|
if (subscription) {
|
|
7767
7868
|
subscription._handleEvent(result);
|
|
7768
7869
|
} else {
|
|
7769
|
-
|
|
7770
|
-
|
|
7771
|
-
|
|
7870
|
+
queueMicrotask(() => {
|
|
7871
|
+
const registered = this.activeSubscriptions.get(subscription_id);
|
|
7872
|
+
if (registered) {
|
|
7873
|
+
registered._handleEvent(result);
|
|
7874
|
+
} else {
|
|
7875
|
+
logger.warn(
|
|
7876
|
+
`WebSocketChannel: Received event for untracked subscription ID: ${subscription_id}.`
|
|
7877
|
+
);
|
|
7878
|
+
}
|
|
7879
|
+
});
|
|
7772
7880
|
}
|
|
7773
7881
|
}
|
|
7774
7882
|
logger.debug("onMessageProxy:", event.data);
|