@0xmonaco/core 1.0.64 → 1.0.66
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/api/positions/api.d.ts +12 -0
- package/dist/api/positions/api.js +22 -0
- package/dist/api/profile/api.d.ts +4 -3
- package/dist/api/profile/api.js +4 -3
- package/dist/api/trading/api.d.ts +24 -1
- package/dist/api/trading/api.js +34 -0
- package/dist/api/websocket/types.d.ts +1 -1
- package/dist/api/websocket/websocket.js +103 -3
- package/package.json +3 -3
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
import type { AttachPositionTpSlRequest, AttachPositionTpSlResponse, BatchCloseAllRequest, BatchCloseAllResponse, ClosePositionRequest, ClosePositionResponse, GetPositionPnlHistoryParams, GetPositionPnlHistoryResponse, GetPositionResponse, ListPositionHistoryParams, ListPositionHistoryResponse, ListPositionsParams, ListPositionsResponse, PositionRisk, PositionsAPI } from "@0xmonaco/types";
|
|
2
2
|
import { BaseAPI } from "../base";
|
|
3
3
|
export declare class PositionsAPIImpl extends BaseAPI implements PositionsAPI {
|
|
4
|
+
/**
|
|
5
|
+
* List the caller's positions with page-number pagination. The response
|
|
6
|
+
* `total` is bounded by the listing's pagination reach: exact up to
|
|
7
|
+
* `pageSize` x 10,000 rows and saturating there, so a `page` above 10,000 is
|
|
8
|
+
* rejected rather than served past the last page `total` can report.
|
|
9
|
+
*/
|
|
4
10
|
listPositions(params?: ListPositionsParams): Promise<ListPositionsResponse>;
|
|
5
11
|
getPosition(positionId: string): Promise<GetPositionResponse>;
|
|
6
12
|
closePosition(positionId: string, request: ClosePositionRequest): Promise<ClosePositionResponse>;
|
|
7
13
|
batchCloseAllPositions(request?: BatchCloseAllRequest): Promise<BatchCloseAllResponse>;
|
|
8
14
|
getPositionRisk(positionId: string): Promise<PositionRisk>;
|
|
9
15
|
attachPositionTpSl(positionId: string, request: AttachPositionTpSlRequest): Promise<AttachPositionTpSlResponse>;
|
|
16
|
+
/**
|
|
17
|
+
* List the caller's position-history events with page-number pagination. The
|
|
18
|
+
* response `total` is bounded by the listing's pagination reach: exact up to
|
|
19
|
+
* `pageSize` x 10,000 rows and saturating there, so a `page` above 10,000 is
|
|
20
|
+
* rejected rather than served past the last page `total` can report.
|
|
21
|
+
*/
|
|
10
22
|
listPositionHistory(params?: ListPositionHistoryParams): Promise<ListPositionHistoryResponse>;
|
|
11
23
|
getPositionPnlHistory(positionId: string, params: GetPositionPnlHistoryParams): Promise<GetPositionPnlHistoryResponse>;
|
|
12
24
|
}
|
|
@@ -2,6 +2,12 @@ import { AttachPositionTpSlSchema, BatchCloseAllSchema, ClosePositionSchema, Get
|
|
|
2
2
|
import { BaseAPI } from "../base";
|
|
3
3
|
import { perpRoutes } from "../perp";
|
|
4
4
|
export class PositionsAPIImpl extends BaseAPI {
|
|
5
|
+
/**
|
|
6
|
+
* List the caller's positions with page-number pagination. The response
|
|
7
|
+
* `total` is bounded by the listing's pagination reach: exact up to
|
|
8
|
+
* `pageSize` x 10,000 rows and saturating there, so a `page` above 10,000 is
|
|
9
|
+
* rejected rather than served past the last page `total` can report.
|
|
10
|
+
*/
|
|
5
11
|
async listPositions(params) {
|
|
6
12
|
if (params) {
|
|
7
13
|
validate(ListPositionsSchema, params);
|
|
@@ -69,10 +75,26 @@ export class PositionsAPIImpl extends BaseAPI {
|
|
|
69
75
|
expiresAt: request.stopLoss.expiresAt,
|
|
70
76
|
}
|
|
71
77
|
: undefined,
|
|
78
|
+
trailingStop: request.trailingStop
|
|
79
|
+
? {
|
|
80
|
+
trailBps: request.trailingStop.trailBps,
|
|
81
|
+
activationPrice: request.trailingStop.activationPrice,
|
|
82
|
+
quantity: request.trailingStop.quantity,
|
|
83
|
+
closePosition: request.trailingStop.closePosition,
|
|
84
|
+
slippageToleranceBps: request.trailingStop.slippageToleranceBps,
|
|
85
|
+
expiresAt: request.trailingStop.expiresAt,
|
|
86
|
+
}
|
|
87
|
+
: undefined,
|
|
72
88
|
oco: request.oco,
|
|
73
89
|
}),
|
|
74
90
|
});
|
|
75
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* List the caller's position-history events with page-number pagination. The
|
|
94
|
+
* response `total` is bounded by the listing's pagination reach: exact up to
|
|
95
|
+
* `pageSize` x 10,000 rows and saturating there, so a `page` above 10,000 is
|
|
96
|
+
* rejected rather than served past the last page `total` can report.
|
|
97
|
+
*/
|
|
76
98
|
async listPositionHistory(params) {
|
|
77
99
|
if (params) {
|
|
78
100
|
validate(ListPositionHistorySchema, params);
|
|
@@ -176,9 +176,10 @@ export declare class ProfileAPIImpl extends BaseAPI implements ProfileAPI {
|
|
|
176
176
|
*
|
|
177
177
|
* @param params - Optional query parameters for pagination and filtering
|
|
178
178
|
* @param params.page - Deprecated: page number for legacy offset pagination (starts
|
|
179
|
-
* from 1). Passing it selects legacy page-number mode, which only sees
|
|
180
|
-
* history and populates `total`/`totalPages
|
|
181
|
-
*
|
|
179
|
+
* from 1, at most 10,000). Passing it selects legacy page-number mode, which only sees
|
|
180
|
+
* recent (hot) history and populates `total`/`totalPages` — bounded at the pagination
|
|
181
|
+
* reach (`total` saturates at `pageSize` × 10,000 rows, `totalPages` at 10,000 pages).
|
|
182
|
+
* Use cursor pagination (`pageToken`) instead.
|
|
182
183
|
* @param params.pageSize - Number of items per page (default: 20; max 100
|
|
183
184
|
* in page-number mode, up to 1000 when pageToken is present)
|
|
184
185
|
* @param params.tradingPairId - Filter by trading pair ID (UUID)
|
package/dist/api/profile/api.js
CHANGED
|
@@ -227,9 +227,10 @@ export class ProfileAPIImpl extends BaseAPI {
|
|
|
227
227
|
*
|
|
228
228
|
* @param params - Optional query parameters for pagination and filtering
|
|
229
229
|
* @param params.page - Deprecated: page number for legacy offset pagination (starts
|
|
230
|
-
* from 1). Passing it selects legacy page-number mode, which only sees
|
|
231
|
-
* history and populates `total`/`totalPages
|
|
232
|
-
*
|
|
230
|
+
* from 1, at most 10,000). Passing it selects legacy page-number mode, which only sees
|
|
231
|
+
* recent (hot) history and populates `total`/`totalPages` — bounded at the pagination
|
|
232
|
+
* reach (`total` saturates at `pageSize` × 10,000 rows, `totalPages` at 10,000 pages).
|
|
233
|
+
* Use cursor pagination (`pageToken`) instead.
|
|
233
234
|
* @param params.pageSize - Number of items per page (default: 20; max 100
|
|
234
235
|
* in page-number mode, up to 1000 when pageToken is present)
|
|
235
236
|
* @param params.tradingPairId - Filter by trading pair ID (UUID)
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* );
|
|
22
22
|
* ```
|
|
23
23
|
*/
|
|
24
|
-
import type { BatchCancelOrdersResponse, BatchCreateOrderParams, BatchCreateOrdersResponse, BatchReplaceOrderParams, BatchReplaceOrdersResponse, CancelConditionalOrderResponse, CancelOrderResponse, CancelTwapOrderResponse, ConditionalOrder, CreateOrderResponse, CreateTwapOrderOptions, CreateTwapOrderResponse, GetOrderResponse, GetPaginatedOrdersParams, GetPaginatedOrdersResponse, GetTwapOrderTcaResponse, ListConditionalOrdersParams, ListConditionalOrdersResponse, ListTwapOrdersParams, ListTwapOrdersResponse, MarginMode, OrderSide, ParentTpSlLegParams, PositionSide, ReplaceOrderResponse, SelfTradePreventionMode, TimeInForce, TradingAPI, TradingMode, TwapOrder } from "@0xmonaco/types";
|
|
24
|
+
import type { BatchCancelOrdersResponse, BatchCreateOrderParams, BatchCreateOrdersResponse, BatchReplaceOrderParams, BatchReplaceOrdersResponse, CancelConditionalOrderResponse, CancelOrderResponse, CancelTwapOrderResponse, ConditionalOrder, CreateOrderResponse, CreateTwapOrderOptions, CreateTwapOrderResponse, GetOrderResponse, GetPaginatedOrdersParams, GetPaginatedOrdersResponse, GetTwapOrderTcaResponse, ListConditionalOrdersParams, ListConditionalOrdersResponse, ListTwapOrdersParams, ListTwapOrdersResponse, MarginMode, OrderSide, ParentTpSlLegParams, ParentTrailingStopLegParams, PositionSide, ReplaceOrderResponse, SelfTradePreventionMode, TimeInForce, TradingAPI, TradingMode, TwapOrder } from "@0xmonaco/types";
|
|
25
25
|
import { BaseAPI } from "../base";
|
|
26
26
|
export declare class TradingAPIImpl extends BaseAPI implements TradingAPI {
|
|
27
27
|
/**
|
|
@@ -94,6 +94,8 @@ export declare class TradingAPIImpl extends BaseAPI implements TradingAPI {
|
|
|
94
94
|
idempotencyKey?: string;
|
|
95
95
|
takeProfit?: ParentTpSlLegParams;
|
|
96
96
|
stopLoss?: ParentTpSlLegParams;
|
|
97
|
+
/** Trailing stop that arms when this order fully fills; margin entry orders only. */
|
|
98
|
+
trailingStop?: ParentTrailingStopLegParams;
|
|
97
99
|
}): Promise<CreateOrderResponse>;
|
|
98
100
|
/**
|
|
99
101
|
* Places a market order for immediate execution.
|
|
@@ -152,6 +154,8 @@ export declare class TradingAPIImpl extends BaseAPI implements TradingAPI {
|
|
|
152
154
|
selfTradePreventionMode?: SelfTradePreventionMode;
|
|
153
155
|
takeProfit?: ParentTpSlLegParams;
|
|
154
156
|
stopLoss?: ParentTpSlLegParams;
|
|
157
|
+
/** Trailing stop that arms when this order fully fills; margin entry orders only. */
|
|
158
|
+
trailingStop?: ParentTrailingStopLegParams;
|
|
155
159
|
clientOrderId?: string;
|
|
156
160
|
/** Generated once per call when omitted. Supply the same key to retry a logical order within 24 hours. */
|
|
157
161
|
idempotencyKey?: string;
|
|
@@ -184,6 +188,19 @@ export declare class TradingAPIImpl extends BaseAPI implements TradingAPI {
|
|
|
184
188
|
}): Promise<CancelOrderResponse>;
|
|
185
189
|
cancelConditionalOrder(conditionalOrderId: string): Promise<CancelConditionalOrderResponse>;
|
|
186
190
|
getConditionalOrder(conditionalOrderId: string): Promise<ConditionalOrder>;
|
|
191
|
+
/**
|
|
192
|
+
* Lists conditional orders for the authenticated user.
|
|
193
|
+
*
|
|
194
|
+
* The response `total` is bounded by the listing's pagination reach: exact up
|
|
195
|
+
* to `pageSize` x 10,000 rows and saturating there, so a caller with more
|
|
196
|
+
* matching conditional orders than page-number mode can page through reads the
|
|
197
|
+
* cap, not its lifetime total; `page` is capped at 10,000 and a larger page is
|
|
198
|
+
* rejected.
|
|
199
|
+
*
|
|
200
|
+
* @param params - Optional pagination and margin-account / trading-pair / state filters
|
|
201
|
+
* @returns Promise resolving to ListConditionalOrdersResponse with orders and pagination info
|
|
202
|
+
* @throws {APIError} When API communication fails
|
|
203
|
+
*/
|
|
187
204
|
listConditionalOrders(params?: ListConditionalOrdersParams): Promise<ListConditionalOrdersResponse>;
|
|
188
205
|
/**
|
|
189
206
|
* Batch cancels specific orders by their IDs.
|
|
@@ -505,6 +522,12 @@ export declare class TradingAPIImpl extends BaseAPI implements TradingAPI {
|
|
|
505
522
|
/**
|
|
506
523
|
* Lists TWAP parent orders for the authenticated user.
|
|
507
524
|
*
|
|
525
|
+
* The response `total` is bounded by the listing's pagination reach: exact up
|
|
526
|
+
* to `pageSize` x 10,000 rows and saturating there, so a caller with more
|
|
527
|
+
* matching TWAP orders than page-number mode can page through reads the cap,
|
|
528
|
+
* not its lifetime total; `page` is capped at 10,000 and a larger page is
|
|
529
|
+
* rejected.
|
|
530
|
+
*
|
|
508
531
|
* @param params - Optional pagination and state / trading-pair filters
|
|
509
532
|
* @returns Promise resolving to ListTwapOrdersResponse with orders and pagination info
|
|
510
533
|
* @throws {APIError} When API communication fails
|
package/dist/api/trading/api.js
CHANGED
|
@@ -41,6 +41,19 @@ function parentTpSlLegToRequest(leg) {
|
|
|
41
41
|
closePosition: leg.closePosition,
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
|
+
function parentTrailingStopLegToRequest(leg) {
|
|
45
|
+
if (!leg)
|
|
46
|
+
return undefined;
|
|
47
|
+
return {
|
|
48
|
+
trailBps: leg.trailBps,
|
|
49
|
+
activationPrice: leg.activationPrice,
|
|
50
|
+
// Same sizing contract as the TP/SL legs above — see the note there.
|
|
51
|
+
quantity: leg.quantity,
|
|
52
|
+
closePosition: leg.closePosition,
|
|
53
|
+
slippageToleranceBps: leg.slippageToleranceBps,
|
|
54
|
+
expiresAt: leg.expiresAt,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
44
57
|
export class TradingAPIImpl extends BaseAPI {
|
|
45
58
|
/**
|
|
46
59
|
* Places a limit order on the order book.
|
|
@@ -125,6 +138,7 @@ export class TradingAPIImpl extends BaseAPI {
|
|
|
125
138
|
idempotencyKey: options?.idempotencyKey === undefined ? crypto.randomUUID() : options.idempotencyKey,
|
|
126
139
|
takeProfit: parentTpSlLegToRequest(options?.takeProfit),
|
|
127
140
|
stopLoss: parentTpSlLegToRequest(options?.stopLoss),
|
|
141
|
+
trailingStop: parentTrailingStopLegToRequest(options?.trailingStop),
|
|
128
142
|
};
|
|
129
143
|
return await this.makeAuthenticatedRequest(perpRoutes.orders.create(), {
|
|
130
144
|
method: "POST",
|
|
@@ -201,6 +215,7 @@ export class TradingAPIImpl extends BaseAPI {
|
|
|
201
215
|
selfTradePreventionMode: options?.selfTradePreventionMode,
|
|
202
216
|
takeProfit: parentTpSlLegToRequest(options?.takeProfit),
|
|
203
217
|
stopLoss: parentTpSlLegToRequest(options?.stopLoss),
|
|
218
|
+
trailingStop: parentTrailingStopLegToRequest(options?.trailingStop),
|
|
204
219
|
clientOrderId: options?.clientOrderId,
|
|
205
220
|
idempotencyKey: options?.idempotencyKey === undefined ? crypto.randomUUID() : options.idempotencyKey,
|
|
206
221
|
};
|
|
@@ -258,6 +273,19 @@ export class TradingAPIImpl extends BaseAPI {
|
|
|
258
273
|
method: "GET",
|
|
259
274
|
});
|
|
260
275
|
}
|
|
276
|
+
/**
|
|
277
|
+
* Lists conditional orders for the authenticated user.
|
|
278
|
+
*
|
|
279
|
+
* The response `total` is bounded by the listing's pagination reach: exact up
|
|
280
|
+
* to `pageSize` x 10,000 rows and saturating there, so a caller with more
|
|
281
|
+
* matching conditional orders than page-number mode can page through reads the
|
|
282
|
+
* cap, not its lifetime total; `page` is capped at 10,000 and a larger page is
|
|
283
|
+
* rejected.
|
|
284
|
+
*
|
|
285
|
+
* @param params - Optional pagination and margin-account / trading-pair / state filters
|
|
286
|
+
* @returns Promise resolving to ListConditionalOrdersResponse with orders and pagination info
|
|
287
|
+
* @throws {APIError} When API communication fails
|
|
288
|
+
*/
|
|
261
289
|
async listConditionalOrders(params) {
|
|
262
290
|
if (params) {
|
|
263
291
|
validate(ListConditionalOrdersSchema, params);
|
|
@@ -793,6 +821,12 @@ export class TradingAPIImpl extends BaseAPI {
|
|
|
793
821
|
/**
|
|
794
822
|
* Lists TWAP parent orders for the authenticated user.
|
|
795
823
|
*
|
|
824
|
+
* The response `total` is bounded by the listing's pagination reach: exact up
|
|
825
|
+
* to `pageSize` x 10,000 rows and saturating there, so a caller with more
|
|
826
|
+
* matching TWAP orders than page-number mode can page through reads the cap,
|
|
827
|
+
* not its lifetime total; `page` is capped at 10,000 and a larger page is
|
|
828
|
+
* rejected.
|
|
829
|
+
*
|
|
796
830
|
* @param params - Optional pagination and state / trading-pair filters
|
|
797
831
|
* @returns Promise resolving to ListTwapOrdersResponse with orders and pagination info
|
|
798
832
|
* @throws {APIError} When API communication fails
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MonacoWebSocketConfig, SessionCredentials } from "@0xmonaco/types";
|
|
2
|
-
export type { MessageHandler, MonacoWebSocket, ResyncHandler, StatusHandler, WebSocketResyncInfo } from "@0xmonaco/types";
|
|
2
|
+
export type { MessageHandler, MonacoWebSocket, ReauthenticationRequiredHandler, ResyncHandler, StatusHandler, WebSocketReauthenticationInfo, WebSocketResyncInfo, } from "@0xmonaco/types";
|
|
3
3
|
/**
|
|
4
4
|
* Options for {@link createMonacoWebSocket}. The reconnect/resync knobs and
|
|
5
5
|
* callbacks are shared with the main SDK config via {@link MonacoWebSocketConfig}
|
|
@@ -155,6 +155,13 @@ function parseConditionalOrderEventData(raw) {
|
|
|
155
155
|
cancelledAt: readOptionalString(data, "cancelledAt"),
|
|
156
156
|
expiresAt: readOptionalString(data, "expiresAt"),
|
|
157
157
|
failureReason: readOptionalString(data, "failureReason"),
|
|
158
|
+
// Trailing-stop fields (0XM-2827), explicit `null` on TP/SL rows. A live
|
|
159
|
+
// `ratcheted` frame carries the moved `triggerPrice` together with the
|
|
160
|
+
// `watermarkPrice` it was derived from; none of these is validated.
|
|
161
|
+
trailBps: readOptionalNumber(data, "trailBps"),
|
|
162
|
+
activationPrice: readOptionalString(data, "activationPrice"),
|
|
163
|
+
watermarkPrice: readOptionalString(data, "watermarkPrice"),
|
|
164
|
+
trailArmedAt: readOptionalString(data, "trailArmedAt"),
|
|
158
165
|
// Free-form on the wire (the server field is a plain string), so it is
|
|
159
166
|
// passed through untouched rather than matched against a value list.
|
|
160
167
|
reason: readString(data, "reason"),
|
|
@@ -416,6 +423,15 @@ function parseInstrumentEventData(raw) {
|
|
|
416
423
|
maxOrderSize: readString(data, "maxOrderSize"),
|
|
417
424
|
marketType: readString(data, "marketType"),
|
|
418
425
|
category: readString(data, "category"),
|
|
426
|
+
// Present-as-null on the wire: the server always sends both keys, carrying an
|
|
427
|
+
// explicit `null` when an asset has no icon. Read tolerantly — a missing key
|
|
428
|
+
// maps to `null` rather than dropping the frame — because the instrument
|
|
429
|
+
// channel must never drop a frame (a dropped frame can hide a market going
|
|
430
|
+
// inactive, 0XM-2628), and a cosmetic icon is safe to treat as absent. Unlike
|
|
431
|
+
// `readNullableString`, this does not reject a missing key; a present
|
|
432
|
+
// non-string value still throws.
|
|
433
|
+
baseIconUrl: readOptionalString(data, "baseIconUrl") ?? null,
|
|
434
|
+
quoteIconUrl: readOptionalString(data, "quoteIconUrl") ?? null,
|
|
419
435
|
maxLeverage: readOptionalString(data, "maxLeverage"),
|
|
420
436
|
initialMarginRate: readOptionalString(data, "initialMarginRate"),
|
|
421
437
|
maintenanceMarginRate: readOptionalString(data, "maintenanceMarginRate"),
|
|
@@ -674,6 +690,15 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
|
|
|
674
690
|
// Close code of the connection loss the next successful reopen recovers
|
|
675
691
|
// from; null when no resync is owed (first connect, clean disconnect).
|
|
676
692
|
let pendingResyncCode = null;
|
|
693
|
+
// Authentication-ending Error frame received on the current socket, tagged
|
|
694
|
+
// with the session it rejected. The server answers handshakes in the order
|
|
695
|
+
// they were sent, so each Authenticated / AUTH_FAILED reply belongs to the
|
|
696
|
+
// oldest pending handshake, and SESSION_INVALID revokes the session the
|
|
697
|
+
// server last confirmed. All of it resets per socket so a frame from one
|
|
698
|
+
// connection never decides how a later connection's close is read.
|
|
699
|
+
let lastAuthError = null;
|
|
700
|
+
let pendingAuthSessions = [];
|
|
701
|
+
let confirmedSession;
|
|
677
702
|
// Handler storage: channel -> set of handlers (supports multiple subscribers per channel)
|
|
678
703
|
const handlers = new Map();
|
|
679
704
|
// Subscribe-time Snapshot frame handlers, keyed by the RAW subscribed
|
|
@@ -723,6 +748,7 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
|
|
|
723
748
|
return;
|
|
724
749
|
const timestamp = Date.now();
|
|
725
750
|
const signature = signMessage(hexToBytes(session.privateKey), composeWsSigningString(session.publicKey, timestamp));
|
|
751
|
+
pendingAuthSessions.push(session);
|
|
726
752
|
ws.send(JSON.stringify({
|
|
727
753
|
type: "Authenticate",
|
|
728
754
|
public_key: session.publicKey,
|
|
@@ -822,8 +848,8 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
|
|
|
822
848
|
};
|
|
823
849
|
const routeMessage = (msg) => {
|
|
824
850
|
if (msg.type === "Error") {
|
|
825
|
-
//
|
|
826
|
-
//
|
|
851
|
+
// An Error frame matches no subscription even when it names a channel,
|
|
852
|
+
// so without this it reached no consumer at all and a
|
|
827
853
|
// failed subscribe / invalidated session / unreadable subscribe-time
|
|
828
854
|
// snapshot was silent. `code` is passed through whatever its value —
|
|
829
855
|
// a code added server-side after this SDK shipped must still reach the
|
|
@@ -832,10 +858,31 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
|
|
|
832
858
|
// and synthesizing the missing half would send the app down a branch on
|
|
833
859
|
// data the server never sent.
|
|
834
860
|
if (typeof msg.code === "string" && typeof msg.message === "string") {
|
|
835
|
-
|
|
861
|
+
// The server sends the reason a connection is ending as an Error
|
|
862
|
+
// frame just before the close; the close event decides from it.
|
|
863
|
+
// Recorded before any application code runs, so a throwing onError
|
|
864
|
+
// cannot make the close look transient.
|
|
865
|
+
if (msg.code === "AUTH_FAILED") {
|
|
866
|
+
lastAuthError = { code: msg.code, message: msg.message, session: pendingAuthSessions.shift() };
|
|
867
|
+
}
|
|
868
|
+
else if (msg.code === "SESSION_INVALID") {
|
|
869
|
+
lastAuthError = { code: msg.code, message: msg.message, session: confirmedSession ?? pendingAuthSessions[0] };
|
|
870
|
+
}
|
|
871
|
+
// A per-channel failure names the channel it concerns; the key is
|
|
872
|
+
// left off entirely otherwise, so a connection-wide failure reaches
|
|
873
|
+
// the app exactly as the server sent it. A non-string channel is
|
|
874
|
+
// dropped rather than the frame: the code and message are still
|
|
875
|
+
// actionable without it. (The frame is untyped JSON, so the declared
|
|
876
|
+
// `string` type of `channel` is not trusted here.)
|
|
877
|
+
const channel = msg.channel;
|
|
878
|
+
options.onError?.(typeof channel === "string" ? { code: msg.code, message: msg.message, channel } : { code: msg.code, message: msg.message });
|
|
836
879
|
}
|
|
837
880
|
return;
|
|
838
881
|
}
|
|
882
|
+
if (msg.type === "Authenticated") {
|
|
883
|
+
confirmedSession = pendingAuthSessions.shift();
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
839
886
|
if (!msg.channel)
|
|
840
887
|
return;
|
|
841
888
|
if (msg.type === "Snapshot") {
|
|
@@ -867,6 +914,9 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
|
|
|
867
914
|
try {
|
|
868
915
|
const socket = new WebSocket(getUrl());
|
|
869
916
|
ws = socket;
|
|
917
|
+
lastAuthError = null;
|
|
918
|
+
pendingAuthSessions = [];
|
|
919
|
+
confirmedSession = undefined;
|
|
870
920
|
const timeout = setTimeout(() => {
|
|
871
921
|
if (ws !== socket) {
|
|
872
922
|
resolve();
|
|
@@ -938,6 +988,56 @@ export function createMonacoWebSocket(baseUrl, options = {}) {
|
|
|
938
988
|
}
|
|
939
989
|
ws = null;
|
|
940
990
|
stopHeartbeat();
|
|
991
|
+
// Terminal authentication loss: the server revoked the session
|
|
992
|
+
// (SESSION_INVALID, then 1008) or rejected this connection's
|
|
993
|
+
// handshake (AUTH_FAILED, then a close). Reconnecting would replay
|
|
994
|
+
// the same rejected credentials into the same rejection forever, so
|
|
995
|
+
// drop them, skip the reconnect, and let the app supply a fresh
|
|
996
|
+
// session. Handlers stay registered so that session resubscribes
|
|
997
|
+
// them. A 1008 without SESSION_INVALID (the message-rate cap) is a
|
|
998
|
+
// back-off-and-retry and falls through to the reconnect below, as
|
|
999
|
+
// does a close after the app already replaced the rejected session
|
|
1000
|
+
// (e.g. re-authenticating from onError): the fresh session is what
|
|
1001
|
+
// the reconnect uses.
|
|
1002
|
+
const authError = lastAuthError;
|
|
1003
|
+
const authLoss = authError !== null && authError.session !== undefined && (authError.code === "AUTH_FAILED" || event.code === 1008);
|
|
1004
|
+
const terminalAuthLoss = authLoss && session === authError.session;
|
|
1005
|
+
if (terminalAuthLoss) {
|
|
1006
|
+
session = undefined;
|
|
1007
|
+
// The reopen with a fresh session still owes the app a resync —
|
|
1008
|
+
// nothing was delivered while the session was dead — whether or
|
|
1009
|
+
// not automatic reconnect is on, since that reopen is driven by
|
|
1010
|
+
// the new session, not by the reconnect loop.
|
|
1011
|
+
if (pendingResyncCode === null) {
|
|
1012
|
+
pendingResyncCode = event.code;
|
|
1013
|
+
}
|
|
1014
|
+
try {
|
|
1015
|
+
options.onStatusChange?.("disconnected");
|
|
1016
|
+
}
|
|
1017
|
+
finally {
|
|
1018
|
+
// Recovery must not hinge on the status observer not throwing.
|
|
1019
|
+
options.onReauthenticationRequired?.({ code: authError.code, message: authError.message, closeCode: event.code });
|
|
1020
|
+
}
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
// The app already handed over a replacement session on this socket.
|
|
1024
|
+
// Had it arrived after the close, setSessionKeypair would have
|
|
1025
|
+
// connected with it; honour that even with automatic reconnect off
|
|
1026
|
+
// (with it on, the reconnect below uses the new session).
|
|
1027
|
+
if (authLoss && session !== undefined && !autoReconnect) {
|
|
1028
|
+
if (pendingResyncCode === null) {
|
|
1029
|
+
pendingResyncCode = event.code;
|
|
1030
|
+
}
|
|
1031
|
+
try {
|
|
1032
|
+
options.onStatusChange?.("disconnected");
|
|
1033
|
+
}
|
|
1034
|
+
finally {
|
|
1035
|
+
connect().catch((err) => {
|
|
1036
|
+
console.warn("WebSocket: Failed to connect with the replacement session:", err);
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
941
1041
|
// Reconnect on abnormal close. 1013 is the server's slow-client
|
|
942
1042
|
// shed: the stream has a gap, so the reopen owes the app a resync.
|
|
943
1043
|
// Keep the first owed code (upgrading to 1013 if it arrives later):
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@0xmonaco/core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.66",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"homepage": "https://docs.0xmonaco.com/sdk/typescript",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"viem": "^2.45.2"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@0xmonaco/contracts": "1.0.
|
|
23
|
-
"@0xmonaco/types": "1.0.
|
|
22
|
+
"@0xmonaco/contracts": "1.0.66",
|
|
23
|
+
"@0xmonaco/types": "1.0.66",
|
|
24
24
|
"@noble/curves": "^1.9.1",
|
|
25
25
|
"@noble/hashes": "^2.4.0",
|
|
26
26
|
"http-status-codes": "^2.3.0"
|