@imbingox/acex 0.1.0 → 0.2.0
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/README.md +92 -285
- package/index.ts +1 -0
- package/package.json +40 -23
- package/src/adapters/binance/adapter.ts +80 -0
- package/src/adapters/binance/book-ticker.ts +123 -0
- package/src/adapters/binance/mark-price.ts +126 -0
- package/src/adapters/binance/market-catalog.ts +258 -0
- package/src/adapters/binance/private-adapter.ts +833 -0
- package/src/adapters/types.ts +219 -0
- package/src/client/context.ts +123 -0
- package/src/client/create-client.ts +6 -0
- package/src/client/private-subscription-coordinator.ts +512 -0
- package/src/client/runtime.ts +410 -0
- package/src/errors.ts +27 -0
- package/src/index.ts +5 -0
- package/src/internal/async-event-bus.ts +100 -0
- package/src/internal/filters.ts +117 -0
- package/src/internal/managed-websocket.ts +280 -0
- package/src/managers/account-manager.ts +609 -0
- package/src/managers/market-manager.ts +889 -0
- package/src/managers/order-manager.ts +685 -0
- package/src/types/account.ts +157 -0
- package/src/types/client.ts +79 -0
- package/src/types/index.ts +5 -0
- package/src/types/market.ts +150 -0
- package/src/types/order.ts +177 -0
- package/src/types/shared.ts +93 -0
- package/dist/adapters/binance/composite-adapter.d.ts +0 -116
- package/dist/adapters/binance/composite-adapter.js +0 -121
- package/dist/adapters/binance/market-types.d.ts +0 -63
- package/dist/adapters/binance/market-types.js +0 -1
- package/dist/adapters/binance/native-market-adapter.d.ts +0 -102
- package/dist/adapters/binance/native-market-adapter.js +0 -455
- package/dist/adapters/binance/normalizers.d.ts +0 -8
- package/dist/adapters/binance/normalizers.js +0 -123
- package/dist/adapters/binance/rest-client.d.ts +0 -17
- package/dist/adapters/binance/rest-client.js +0 -66
- package/dist/adapters/binance/symbol-router.d.ts +0 -9
- package/dist/adapters/binance/symbol-router.js +0 -174
- package/dist/adapters/binance/ws-client.d.ts +0 -24
- package/dist/adapters/binance/ws-client.js +0 -261
- package/dist/adapters/ccxt/aster-ccxt-adapter.d.ts +0 -157
- package/dist/adapters/ccxt/aster-ccxt-adapter.js +0 -272
- package/dist/adapters/ccxt/binance-usdm-ccxt-adapter.d.ts +0 -180
- package/dist/adapters/ccxt/binance-usdm-ccxt-adapter.js +0 -539
- package/dist/adapters/ccxt/binance-usdm-exchange.d.ts +0 -22
- package/dist/adapters/ccxt/binance-usdm-exchange.js +0 -23
- package/dist/adapters/fake/fake-aster-adapter.d.ts +0 -130
- package/dist/adapters/fake/fake-aster-adapter.js +0 -283
- package/dist/adapters/types.d.ts +0 -210
- package/dist/adapters/types.js +0 -1
- package/dist/core/client.d.ts +0 -50
- package/dist/core/client.js +0 -403
- package/dist/core/recovery.d.ts +0 -22
- package/dist/core/recovery.js +0 -18
- package/dist/core/runtime.d.ts +0 -26
- package/dist/core/runtime.js +0 -150
- package/dist/errors/acex-error.d.ts +0 -25
- package/dist/errors/acex-error.js +0 -54
- package/dist/index.d.ts +0 -6
- package/dist/index.js +0 -3
- package/dist/managers/account-manager.d.ts +0 -41
- package/dist/managers/account-manager.js +0 -80
- package/dist/managers/market-manager.d.ts +0 -16
- package/dist/managers/market-manager.js +0 -28
- package/dist/managers/order-manager.d.ts +0 -87
- package/dist/managers/order-manager.js +0 -122
- package/dist/runtime/async-queue.d.ts +0 -8
- package/dist/runtime/async-queue.js +0 -88
- package/dist/runtime/request-id.d.ts +0 -1
- package/dist/runtime/request-id.js +0 -5
- package/dist/runtime/ws-connection-supervisor.d.ts +0 -76
- package/dist/runtime/ws-connection-supervisor.js +0 -522
- package/dist/store/account-store.d.ts +0 -52
- package/dist/store/account-store.js +0 -18
- package/dist/store/health-store.d.ts +0 -16
- package/dist/store/health-store.js +0 -29
- package/dist/store/market-store.d.ts +0 -42
- package/dist/store/market-store.js +0 -51
- package/dist/store/order-store.d.ts +0 -38
- package/dist/store/order-store.js +0 -49
- package/dist/testing/create-fake-runtime.d.ts +0 -5
- package/dist/testing/create-fake-runtime.js +0 -7
- package/dist/types/public.d.ts +0 -5
- package/dist/types/public.js +0 -1
|
@@ -1,522 +0,0 @@
|
|
|
1
|
-
import { createAcexError } from "../errors/acex-error.js";
|
|
2
|
-
const SOCKET_CONNECTING = 0;
|
|
3
|
-
const SOCKET_OPEN = 1;
|
|
4
|
-
const SOCKET_CLOSING = 2;
|
|
5
|
-
function createDeferred() {
|
|
6
|
-
let resolve;
|
|
7
|
-
const promise = new Promise((innerResolve) => {
|
|
8
|
-
resolve = innerResolve;
|
|
9
|
-
});
|
|
10
|
-
return {
|
|
11
|
-
promise,
|
|
12
|
-
resolve() {
|
|
13
|
-
resolve?.();
|
|
14
|
-
},
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
function createTransportError(message, cause) {
|
|
18
|
-
return createAcexError({
|
|
19
|
-
code: "TRANSPORT_UNAVAILABLE",
|
|
20
|
-
message,
|
|
21
|
-
retryable: true,
|
|
22
|
-
...(cause === undefined ? {} : { cause }),
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
function toError(value) {
|
|
26
|
-
if (value instanceof Error) {
|
|
27
|
-
return value;
|
|
28
|
-
}
|
|
29
|
-
if (typeof value === "string") {
|
|
30
|
-
return new Error(value);
|
|
31
|
-
}
|
|
32
|
-
return new Error("websocket error");
|
|
33
|
-
}
|
|
34
|
-
export function createWsConnectionSupervisor(input) {
|
|
35
|
-
const now = input.now ?? (() => Date.now());
|
|
36
|
-
const sleep = input.sleep ??
|
|
37
|
-
((ms) => {
|
|
38
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
39
|
-
});
|
|
40
|
-
let state = "idle";
|
|
41
|
-
let socket;
|
|
42
|
-
let connectPromise;
|
|
43
|
-
let resolveConnect;
|
|
44
|
-
let rejectConnect;
|
|
45
|
-
let reconnectTask;
|
|
46
|
-
let manualClose = false;
|
|
47
|
-
let lastActivityAt;
|
|
48
|
-
let openedAt;
|
|
49
|
-
let loopGeneration = 0;
|
|
50
|
-
let reconnectAttempts = 0;
|
|
51
|
-
let reconnectGeneration = 0;
|
|
52
|
-
let customHeartbeatCleanup;
|
|
53
|
-
let customHeartbeatStartTask;
|
|
54
|
-
let connectionLoopsStopping = false;
|
|
55
|
-
let connectionLoopStopTask = Promise.resolve();
|
|
56
|
-
let pendingClose;
|
|
57
|
-
const resetPendingConnect = () => {
|
|
58
|
-
connectPromise = undefined;
|
|
59
|
-
resolveConnect = undefined;
|
|
60
|
-
rejectConnect = undefined;
|
|
61
|
-
};
|
|
62
|
-
const markActivity = () => {
|
|
63
|
-
lastActivityAt = now();
|
|
64
|
-
};
|
|
65
|
-
const resetActivityWindow = () => {
|
|
66
|
-
lastActivityAt = undefined;
|
|
67
|
-
openedAt = now();
|
|
68
|
-
};
|
|
69
|
-
const toInactiveState = () => {
|
|
70
|
-
if (manualClose) {
|
|
71
|
-
state = "closed";
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
state = "idle";
|
|
75
|
-
};
|
|
76
|
-
const reportError = (error) => {
|
|
77
|
-
try {
|
|
78
|
-
input.onError?.(toError(error));
|
|
79
|
-
}
|
|
80
|
-
catch { }
|
|
81
|
-
};
|
|
82
|
-
const runSyncCallback = (callback) => {
|
|
83
|
-
try {
|
|
84
|
-
callback?.();
|
|
85
|
-
}
|
|
86
|
-
catch (error) {
|
|
87
|
-
reportError(error);
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
const runMessageCallback = (callback, event) => {
|
|
91
|
-
try {
|
|
92
|
-
callback?.(event);
|
|
93
|
-
}
|
|
94
|
-
catch (error) {
|
|
95
|
-
reportError(error);
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
const runOnReconnect = () => {
|
|
99
|
-
void Promise.resolve()
|
|
100
|
-
.then(async () => {
|
|
101
|
-
await input.onReconnect?.();
|
|
102
|
-
})
|
|
103
|
-
.catch((error) => {
|
|
104
|
-
reportError(error);
|
|
105
|
-
});
|
|
106
|
-
};
|
|
107
|
-
const stopConnectionLoops = () => {
|
|
108
|
-
const cleanup = customHeartbeatCleanup;
|
|
109
|
-
const startTask = customHeartbeatStartTask;
|
|
110
|
-
customHeartbeatCleanup = undefined;
|
|
111
|
-
customHeartbeatStartTask = undefined;
|
|
112
|
-
loopGeneration += 1;
|
|
113
|
-
openedAt = undefined;
|
|
114
|
-
connectionLoopsStopping = true;
|
|
115
|
-
connectionLoopStopTask = Promise.resolve(startTask)
|
|
116
|
-
.then(async () => {
|
|
117
|
-
if (typeof cleanup === "function") {
|
|
118
|
-
await cleanup();
|
|
119
|
-
}
|
|
120
|
-
})
|
|
121
|
-
.catch((error) => {
|
|
122
|
-
reportError(error);
|
|
123
|
-
})
|
|
124
|
-
.finally(() => {
|
|
125
|
-
connectionLoopsStopping = false;
|
|
126
|
-
});
|
|
127
|
-
return connectionLoopStopTask;
|
|
128
|
-
};
|
|
129
|
-
const resolvePendingClose = (currentSocket) => {
|
|
130
|
-
if (pendingClose?.socket !== currentSocket) {
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
const { resolve } = pendingClose;
|
|
134
|
-
pendingClose = undefined;
|
|
135
|
-
void connectionLoopStopTask.finally(() => {
|
|
136
|
-
resolve();
|
|
137
|
-
});
|
|
138
|
-
};
|
|
139
|
-
const watchdogIntervalMs = () => {
|
|
140
|
-
if (input.heartbeat.kind === "passive_read_timeout") {
|
|
141
|
-
return input.heartbeat.idleTimeoutMs;
|
|
142
|
-
}
|
|
143
|
-
if (input.heartbeat.kind === "custom") {
|
|
144
|
-
return Math.min(input.heartbeat.heartbeatIntervalMs ?? input.heartbeat.idleTimeoutMs, input.heartbeat.idleTimeoutMs);
|
|
145
|
-
}
|
|
146
|
-
return Math.min(input.heartbeat.heartbeatIntervalMs, input.heartbeat.idleTimeoutMs);
|
|
147
|
-
};
|
|
148
|
-
const backoffDelayMs = () => {
|
|
149
|
-
const uncappedDelay = input.backoff.baseDelayMs * 2 ** reconnectAttempts;
|
|
150
|
-
const cappedDelay = Math.min(uncappedDelay, input.backoff.maxDelayMs);
|
|
151
|
-
if (input.backoff.jitter === false) {
|
|
152
|
-
return cappedDelay;
|
|
153
|
-
}
|
|
154
|
-
const spread = Math.floor(cappedDelay * 0.2);
|
|
155
|
-
const jitterDelta = Math.floor(Math.random() * (spread * 2 + 1)) - spread;
|
|
156
|
-
return Math.max(0, cappedDelay + jitterDelta);
|
|
157
|
-
};
|
|
158
|
-
const isCurrentOpenSocket = (generation, currentSocket) => {
|
|
159
|
-
return (generation === loopGeneration &&
|
|
160
|
-
socket === currentSocket &&
|
|
161
|
-
currentSocket.readyState === SOCKET_OPEN &&
|
|
162
|
-
state === "open");
|
|
163
|
-
};
|
|
164
|
-
const disconnectSocketForTimeout = (currentSocket) => {
|
|
165
|
-
if (socket !== currentSocket) {
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
socket = undefined;
|
|
169
|
-
stopConnectionLoops();
|
|
170
|
-
try {
|
|
171
|
-
if (typeof currentSocket.terminate === "function") {
|
|
172
|
-
currentSocket.terminate();
|
|
173
|
-
}
|
|
174
|
-
else if (currentSocket.readyState < SOCKET_CLOSING) {
|
|
175
|
-
currentSocket.close();
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
catch (error) {
|
|
179
|
-
reportError(error);
|
|
180
|
-
}
|
|
181
|
-
if (manualClose) {
|
|
182
|
-
state = "closed";
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
runSyncCallback(input.onClose);
|
|
186
|
-
void reconnect();
|
|
187
|
-
};
|
|
188
|
-
const startHeartbeatLoop = (generation, currentSocket) => {
|
|
189
|
-
const heartbeat = input.heartbeat;
|
|
190
|
-
if (heartbeat.kind === "passive_read_timeout" || heartbeat.kind === "custom") {
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
void (async () => {
|
|
194
|
-
while (isCurrentOpenSocket(generation, currentSocket) && !manualClose) {
|
|
195
|
-
await sleep(heartbeat.heartbeatIntervalMs);
|
|
196
|
-
if (!isCurrentOpenSocket(generation, currentSocket) || manualClose) {
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
try {
|
|
200
|
-
if (heartbeat.kind === "native_ping_pong") {
|
|
201
|
-
currentSocket.ping?.();
|
|
202
|
-
}
|
|
203
|
-
else {
|
|
204
|
-
currentSocket.send(heartbeat.buildPingMessage());
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
catch (error) {
|
|
208
|
-
reportError(error);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
})().catch((error) => {
|
|
212
|
-
reportError(error);
|
|
213
|
-
});
|
|
214
|
-
};
|
|
215
|
-
const startCustomHeartbeatDriver = (generation, currentSocket) => {
|
|
216
|
-
const heartbeat = input.heartbeat;
|
|
217
|
-
if (heartbeat.kind !== "custom") {
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
customHeartbeatStartTask = Promise.resolve()
|
|
221
|
-
.then(async () => {
|
|
222
|
-
return await heartbeat.start({
|
|
223
|
-
socket: currentSocket,
|
|
224
|
-
markActivity: () => {
|
|
225
|
-
if (socket === currentSocket) {
|
|
226
|
-
markActivity();
|
|
227
|
-
}
|
|
228
|
-
},
|
|
229
|
-
send: (data) => {
|
|
230
|
-
if (socket !== currentSocket || currentSocket.readyState !== SOCKET_OPEN) {
|
|
231
|
-
throw createTransportError("websocket is not open");
|
|
232
|
-
}
|
|
233
|
-
currentSocket.send(data);
|
|
234
|
-
},
|
|
235
|
-
sleep,
|
|
236
|
-
now,
|
|
237
|
-
reportError,
|
|
238
|
-
});
|
|
239
|
-
})
|
|
240
|
-
.then(async (cleanup) => {
|
|
241
|
-
if (typeof cleanup !== "function") {
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
if (generation !== loopGeneration ||
|
|
245
|
-
socket !== currentSocket ||
|
|
246
|
-
currentSocket.readyState !== SOCKET_OPEN ||
|
|
247
|
-
state !== "open") {
|
|
248
|
-
await cleanup();
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
customHeartbeatCleanup = cleanup;
|
|
252
|
-
})
|
|
253
|
-
.catch((error) => {
|
|
254
|
-
reportError(error);
|
|
255
|
-
});
|
|
256
|
-
};
|
|
257
|
-
const startWatchdogLoop = (generation, currentSocket) => {
|
|
258
|
-
const watchdogInterval = watchdogIntervalMs();
|
|
259
|
-
void (async () => {
|
|
260
|
-
while (generation === loopGeneration && socket === currentSocket && !manualClose) {
|
|
261
|
-
const activityReferenceAt = lastActivityAt ?? openedAt;
|
|
262
|
-
const remainingTimeoutMs = activityReferenceAt === undefined
|
|
263
|
-
? input.heartbeat.idleTimeoutMs
|
|
264
|
-
: Math.max(0, input.heartbeat.idleTimeoutMs - (now() - activityReferenceAt));
|
|
265
|
-
if (remainingTimeoutMs === 0) {
|
|
266
|
-
disconnectSocketForTimeout(currentSocket);
|
|
267
|
-
return;
|
|
268
|
-
}
|
|
269
|
-
await sleep(Math.min(watchdogInterval, remainingTimeoutMs));
|
|
270
|
-
if (generation !== loopGeneration || socket !== currentSocket || manualClose) {
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
})().catch((error) => {
|
|
275
|
-
reportError(error);
|
|
276
|
-
});
|
|
277
|
-
};
|
|
278
|
-
const beginConnect = (mode, manualConnectGeneration) => {
|
|
279
|
-
const reconnecting = mode === "reconnect";
|
|
280
|
-
const connectGeneration = mode === "connect" ? (manualConnectGeneration ?? reconnectGeneration + 1) : undefined;
|
|
281
|
-
if (mode === "connect") {
|
|
282
|
-
reconnectGeneration = connectGeneration ?? reconnectGeneration;
|
|
283
|
-
reconnectTask = undefined;
|
|
284
|
-
}
|
|
285
|
-
if (socket !== undefined) {
|
|
286
|
-
if (socket.readyState === SOCKET_OPEN) {
|
|
287
|
-
state = "open";
|
|
288
|
-
return Promise.resolve();
|
|
289
|
-
}
|
|
290
|
-
if (socket.readyState === SOCKET_CONNECTING && connectPromise !== undefined) {
|
|
291
|
-
return connectPromise;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
if (mode === "connect" && (pendingClose !== undefined || connectionLoopsStopping)) {
|
|
295
|
-
return Promise.all([
|
|
296
|
-
pendingClose?.promise,
|
|
297
|
-
connectionLoopsStopping ? connectionLoopStopTask : undefined,
|
|
298
|
-
]).then(() => beginConnect(mode, connectGeneration));
|
|
299
|
-
}
|
|
300
|
-
if (mode === "connect") {
|
|
301
|
-
manualClose = false;
|
|
302
|
-
}
|
|
303
|
-
state = reconnecting ? "reconnecting" : "connecting";
|
|
304
|
-
let currentSocket;
|
|
305
|
-
try {
|
|
306
|
-
currentSocket = input.createSocket();
|
|
307
|
-
}
|
|
308
|
-
catch (error) {
|
|
309
|
-
toInactiveState();
|
|
310
|
-
return Promise.reject(error);
|
|
311
|
-
}
|
|
312
|
-
socket = currentSocket;
|
|
313
|
-
connectPromise = new Promise((resolve, reject) => {
|
|
314
|
-
resolveConnect = resolve;
|
|
315
|
-
rejectConnect = reject;
|
|
316
|
-
});
|
|
317
|
-
currentSocket.onopen = () => {
|
|
318
|
-
if (socket !== currentSocket) {
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
state = "open";
|
|
322
|
-
resetActivityWindow();
|
|
323
|
-
loopGeneration += 1;
|
|
324
|
-
reconnectAttempts = 0;
|
|
325
|
-
resolveConnect?.();
|
|
326
|
-
resetPendingConnect();
|
|
327
|
-
startHeartbeatLoop(loopGeneration, currentSocket);
|
|
328
|
-
startCustomHeartbeatDriver(loopGeneration, currentSocket);
|
|
329
|
-
startWatchdogLoop(loopGeneration, currentSocket);
|
|
330
|
-
if (!reconnecting) {
|
|
331
|
-
runSyncCallback(input.onOpen);
|
|
332
|
-
}
|
|
333
|
-
};
|
|
334
|
-
currentSocket.onmessage = (event) => {
|
|
335
|
-
if (socket !== currentSocket) {
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
|
-
markActivity();
|
|
339
|
-
runMessageCallback(input.onMessage, event);
|
|
340
|
-
};
|
|
341
|
-
currentSocket.onping = () => {
|
|
342
|
-
if (socket !== currentSocket) {
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
markActivity();
|
|
346
|
-
};
|
|
347
|
-
currentSocket.onpong = () => {
|
|
348
|
-
if (socket !== currentSocket) {
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
markActivity();
|
|
352
|
-
};
|
|
353
|
-
currentSocket.onclose = () => {
|
|
354
|
-
if (socket !== currentSocket) {
|
|
355
|
-
resolvePendingClose(currentSocket);
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
socket = undefined;
|
|
359
|
-
stopConnectionLoops();
|
|
360
|
-
if (rejectConnect !== undefined) {
|
|
361
|
-
rejectConnect(createTransportError("websocket connection closed before opening"));
|
|
362
|
-
toInactiveState();
|
|
363
|
-
resetPendingConnect();
|
|
364
|
-
resolvePendingClose(currentSocket);
|
|
365
|
-
return;
|
|
366
|
-
}
|
|
367
|
-
if (manualClose) {
|
|
368
|
-
state = "closed";
|
|
369
|
-
resolvePendingClose(currentSocket);
|
|
370
|
-
return;
|
|
371
|
-
}
|
|
372
|
-
runSyncCallback(input.onClose);
|
|
373
|
-
void reconnect();
|
|
374
|
-
};
|
|
375
|
-
currentSocket.onerror = (event) => {
|
|
376
|
-
if (socket !== currentSocket) {
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
reportError(event);
|
|
380
|
-
socket = undefined;
|
|
381
|
-
stopConnectionLoops();
|
|
382
|
-
if (rejectConnect !== undefined) {
|
|
383
|
-
rejectConnect(createTransportError("failed to connect websocket", event));
|
|
384
|
-
toInactiveState();
|
|
385
|
-
resetPendingConnect();
|
|
386
|
-
return;
|
|
387
|
-
}
|
|
388
|
-
if (currentSocket.readyState < SOCKET_CLOSING) {
|
|
389
|
-
try {
|
|
390
|
-
currentSocket.close();
|
|
391
|
-
}
|
|
392
|
-
catch { }
|
|
393
|
-
}
|
|
394
|
-
if (manualClose) {
|
|
395
|
-
state = "closed";
|
|
396
|
-
return;
|
|
397
|
-
}
|
|
398
|
-
void reconnect();
|
|
399
|
-
};
|
|
400
|
-
return connectPromise;
|
|
401
|
-
};
|
|
402
|
-
const reconnect = async () => {
|
|
403
|
-
if (reconnectTask !== undefined) {
|
|
404
|
-
return reconnectTask;
|
|
405
|
-
}
|
|
406
|
-
const generation = reconnectGeneration + 1;
|
|
407
|
-
reconnectGeneration = generation;
|
|
408
|
-
state = "reconnecting";
|
|
409
|
-
try {
|
|
410
|
-
input.onDisconnect?.();
|
|
411
|
-
}
|
|
412
|
-
catch (error) {
|
|
413
|
-
reportError(error);
|
|
414
|
-
}
|
|
415
|
-
const taskRef = { current: undefined };
|
|
416
|
-
const currentTask = (async () => {
|
|
417
|
-
try {
|
|
418
|
-
try {
|
|
419
|
-
await connectionLoopStopTask;
|
|
420
|
-
await sleep(backoffDelayMs());
|
|
421
|
-
if (manualClose || generation !== reconnectGeneration) {
|
|
422
|
-
return;
|
|
423
|
-
}
|
|
424
|
-
reconnectAttempts += 1;
|
|
425
|
-
await beginConnect("reconnect");
|
|
426
|
-
if (manualClose || generation !== reconnectGeneration) {
|
|
427
|
-
return;
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
catch (error) {
|
|
431
|
-
if (!manualClose) {
|
|
432
|
-
toInactiveState();
|
|
433
|
-
reportError(error);
|
|
434
|
-
}
|
|
435
|
-
return;
|
|
436
|
-
}
|
|
437
|
-
runOnReconnect();
|
|
438
|
-
}
|
|
439
|
-
finally {
|
|
440
|
-
if (reconnectTask === taskRef.current) {
|
|
441
|
-
reconnectTask = undefined;
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
})();
|
|
445
|
-
taskRef.current = currentTask;
|
|
446
|
-
reconnectTask = currentTask;
|
|
447
|
-
return reconnectTask;
|
|
448
|
-
};
|
|
449
|
-
return {
|
|
450
|
-
async connect() {
|
|
451
|
-
await beginConnect("connect");
|
|
452
|
-
},
|
|
453
|
-
async close() {
|
|
454
|
-
const currentSocket = socket;
|
|
455
|
-
const previousManualClose = manualClose;
|
|
456
|
-
const previousState = state;
|
|
457
|
-
manualClose = true;
|
|
458
|
-
if (currentSocket === undefined) {
|
|
459
|
-
stopConnectionLoops();
|
|
460
|
-
state = "closed";
|
|
461
|
-
if (rejectConnect !== undefined) {
|
|
462
|
-
rejectConnect(createTransportError("websocket connection closed by supervisor"));
|
|
463
|
-
resetPendingConnect();
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
else if (currentSocket.readyState < SOCKET_CLOSING) {
|
|
467
|
-
if (pendingClose?.socket !== currentSocket) {
|
|
468
|
-
const deferred = createDeferred();
|
|
469
|
-
pendingClose = {
|
|
470
|
-
socket: currentSocket,
|
|
471
|
-
promise: deferred.promise,
|
|
472
|
-
resolve: deferred.resolve,
|
|
473
|
-
};
|
|
474
|
-
}
|
|
475
|
-
const closePromise = pendingClose.promise;
|
|
476
|
-
try {
|
|
477
|
-
currentSocket.close();
|
|
478
|
-
}
|
|
479
|
-
catch (error) {
|
|
480
|
-
manualClose = previousManualClose;
|
|
481
|
-
state = previousState;
|
|
482
|
-
pendingClose = undefined;
|
|
483
|
-
throw createTransportError("failed to close websocket", error);
|
|
484
|
-
}
|
|
485
|
-
await closePromise;
|
|
486
|
-
}
|
|
487
|
-
else if (pendingClose?.socket === currentSocket) {
|
|
488
|
-
await pendingClose.promise;
|
|
489
|
-
}
|
|
490
|
-
else {
|
|
491
|
-
const deferred = createDeferred();
|
|
492
|
-
pendingClose = {
|
|
493
|
-
socket: currentSocket,
|
|
494
|
-
promise: deferred.promise,
|
|
495
|
-
resolve: deferred.resolve,
|
|
496
|
-
};
|
|
497
|
-
await pendingClose.promise;
|
|
498
|
-
}
|
|
499
|
-
try {
|
|
500
|
-
await reconnectTask;
|
|
501
|
-
}
|
|
502
|
-
catch {
|
|
503
|
-
if (!manualClose) {
|
|
504
|
-
throw createTransportError("failed to close websocket supervisor");
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
await connectionLoopStopTask;
|
|
508
|
-
},
|
|
509
|
-
send(data) {
|
|
510
|
-
if (socket === undefined || socket.readyState !== SOCKET_OPEN) {
|
|
511
|
-
throw createTransportError("websocket is not open");
|
|
512
|
-
}
|
|
513
|
-
socket.send(data);
|
|
514
|
-
},
|
|
515
|
-
getState() {
|
|
516
|
-
return state;
|
|
517
|
-
},
|
|
518
|
-
getLastActivityAt() {
|
|
519
|
-
return lastActivityAt;
|
|
520
|
-
},
|
|
521
|
-
};
|
|
522
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
export interface AccountBalanceSnapshot {
|
|
2
|
-
accountId: string;
|
|
3
|
-
exchange: string;
|
|
4
|
-
asset: string;
|
|
5
|
-
free: string;
|
|
6
|
-
used: string;
|
|
7
|
-
total: string;
|
|
8
|
-
seq: number;
|
|
9
|
-
receivedAt: number;
|
|
10
|
-
updatedAt: number;
|
|
11
|
-
}
|
|
12
|
-
export interface AccountRiskSnapshot {
|
|
13
|
-
accountId: string;
|
|
14
|
-
exchange: string;
|
|
15
|
-
seq: number;
|
|
16
|
-
receivedAt: number;
|
|
17
|
-
updatedAt: number;
|
|
18
|
-
equity: string;
|
|
19
|
-
}
|
|
20
|
-
export interface AccountPositionSnapshot {
|
|
21
|
-
accountId: string;
|
|
22
|
-
exchange: string;
|
|
23
|
-
symbol: string;
|
|
24
|
-
side: string;
|
|
25
|
-
size: string;
|
|
26
|
-
seq: number;
|
|
27
|
-
receivedAt: number;
|
|
28
|
-
updatedAt: number;
|
|
29
|
-
}
|
|
30
|
-
export interface AccountSnapshot {
|
|
31
|
-
accountId: string;
|
|
32
|
-
exchange: string;
|
|
33
|
-
balances: Record<string, AccountBalanceSnapshot>;
|
|
34
|
-
positions: AccountPositionSnapshot[];
|
|
35
|
-
risk?: AccountRiskSnapshot;
|
|
36
|
-
receivedAt: number;
|
|
37
|
-
updatedAt: number;
|
|
38
|
-
}
|
|
39
|
-
export interface AccountStatus {
|
|
40
|
-
accountId: string;
|
|
41
|
-
exchange: string;
|
|
42
|
-
status: "healthy" | "degraded" | "reconciling";
|
|
43
|
-
bootstrapCompleted: boolean;
|
|
44
|
-
unresolvedOrdersCount?: number;
|
|
45
|
-
}
|
|
46
|
-
export interface AccountStore {
|
|
47
|
-
replaceSnapshot(snapshot: AccountSnapshot): void;
|
|
48
|
-
getSnapshot(accountId: string): AccountSnapshot | undefined;
|
|
49
|
-
setStatus(status: AccountStatus): void;
|
|
50
|
-
getStatus(accountId: string): AccountStatus | undefined;
|
|
51
|
-
}
|
|
52
|
-
export declare function createAccountStore(): AccountStore;
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export function createAccountStore() {
|
|
2
|
-
const snapshots = new Map();
|
|
3
|
-
const statuses = new Map();
|
|
4
|
-
return {
|
|
5
|
-
replaceSnapshot(snapshot) {
|
|
6
|
-
snapshots.set(snapshot.accountId, snapshot);
|
|
7
|
-
},
|
|
8
|
-
getSnapshot(accountId) {
|
|
9
|
-
return snapshots.get(accountId);
|
|
10
|
-
},
|
|
11
|
-
setStatus(status) {
|
|
12
|
-
statuses.set(status.accountId, status);
|
|
13
|
-
},
|
|
14
|
-
getStatus(accountId) {
|
|
15
|
-
return statuses.get(accountId);
|
|
16
|
-
},
|
|
17
|
-
};
|
|
18
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { ClientStatus } from "../types/public.js";
|
|
2
|
-
export interface HealthSnapshot {
|
|
3
|
-
clientStatus: ClientStatus;
|
|
4
|
-
exchanges: Record<string, unknown>;
|
|
5
|
-
accounts: Record<string, unknown>;
|
|
6
|
-
orders: Record<string, unknown>;
|
|
7
|
-
updatedAt: number;
|
|
8
|
-
}
|
|
9
|
-
export interface HealthStore {
|
|
10
|
-
setClientStatus(status: ClientStatus): void;
|
|
11
|
-
setExchangeHealth(exchange: string, health: unknown): void;
|
|
12
|
-
setAccountStatus(accountId: string, status: unknown): void;
|
|
13
|
-
setOrderStatus(accountId: string, status: unknown): void;
|
|
14
|
-
snapshot(): HealthSnapshot;
|
|
15
|
-
}
|
|
16
|
-
export declare function createHealthStore(): HealthStore;
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
export function createHealthStore() {
|
|
2
|
-
let clientStatus = "idle";
|
|
3
|
-
const exchanges = new Map();
|
|
4
|
-
const accounts = new Map();
|
|
5
|
-
const orders = new Map();
|
|
6
|
-
return {
|
|
7
|
-
setClientStatus(status) {
|
|
8
|
-
clientStatus = status;
|
|
9
|
-
},
|
|
10
|
-
setExchangeHealth(exchange, health) {
|
|
11
|
-
exchanges.set(exchange, health);
|
|
12
|
-
},
|
|
13
|
-
setAccountStatus(accountId, status) {
|
|
14
|
-
accounts.set(accountId, status);
|
|
15
|
-
},
|
|
16
|
-
setOrderStatus(accountId, status) {
|
|
17
|
-
orders.set(accountId, status);
|
|
18
|
-
},
|
|
19
|
-
snapshot() {
|
|
20
|
-
return {
|
|
21
|
-
clientStatus,
|
|
22
|
-
exchanges: Object.fromEntries(exchanges),
|
|
23
|
-
accounts: Object.fromEntries(accounts),
|
|
24
|
-
orders: Object.fromEntries(orders),
|
|
25
|
-
updatedAt: Date.now(),
|
|
26
|
-
};
|
|
27
|
-
},
|
|
28
|
-
};
|
|
29
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
export interface MarketStoreKey {
|
|
2
|
-
exchange: string;
|
|
3
|
-
symbol: string;
|
|
4
|
-
}
|
|
5
|
-
export interface MarketDataStatus extends MarketStoreKey {
|
|
6
|
-
freshness: "fresh" | "stale" | "reconciling";
|
|
7
|
-
lastStreamReceivedAt?: number;
|
|
8
|
-
lastBaselineSyncAt?: number;
|
|
9
|
-
staleSince?: number;
|
|
10
|
-
reason?: "bootstrap_pending" | "ws_disconnected" | "heartbeat_timeout" | "sequence_gap" | "reconciling";
|
|
11
|
-
}
|
|
12
|
-
export interface L1BookSnapshot extends MarketStoreKey {
|
|
13
|
-
bidPrice: string;
|
|
14
|
-
bidSize: string;
|
|
15
|
-
askPrice: string;
|
|
16
|
-
askSize: string;
|
|
17
|
-
exchangeTs?: number;
|
|
18
|
-
receivedAt: number;
|
|
19
|
-
updatedAt: number;
|
|
20
|
-
version: number;
|
|
21
|
-
}
|
|
22
|
-
export interface FundingRateSnapshot extends MarketStoreKey {
|
|
23
|
-
fundingRate: string;
|
|
24
|
-
exchangeTs?: number;
|
|
25
|
-
receivedAt: number;
|
|
26
|
-
updatedAt: number;
|
|
27
|
-
version: number;
|
|
28
|
-
}
|
|
29
|
-
export interface MarketSnapshot {
|
|
30
|
-
l1Book?: L1BookSnapshot;
|
|
31
|
-
fundingRate?: FundingRateSnapshot;
|
|
32
|
-
}
|
|
33
|
-
export interface MarketStore {
|
|
34
|
-
upsertL1Book(input: Omit<L1BookSnapshot, "updatedAt" | "version">): L1BookSnapshot;
|
|
35
|
-
getL1Book(key: MarketStoreKey): L1BookSnapshot | undefined;
|
|
36
|
-
upsertFundingRate(input: Omit<FundingRateSnapshot, "updatedAt" | "version">): FundingRateSnapshot;
|
|
37
|
-
getFundingRate(key: MarketStoreKey): FundingRateSnapshot | undefined;
|
|
38
|
-
getSnapshot(key: MarketStoreKey): MarketSnapshot;
|
|
39
|
-
setStatus(status: MarketDataStatus): void;
|
|
40
|
-
getStatus(key: MarketStoreKey): MarketDataStatus | undefined;
|
|
41
|
-
}
|
|
42
|
-
export declare function createMarketStore(): MarketStore;
|