@binance/common 1.0.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/dist/index.mjs ADDED
@@ -0,0 +1,1496 @@
1
+ // src/configuration.ts
2
+ var ConfigurationRestAPI = class {
3
+ constructor(param = { apiKey: "" }) {
4
+ this.apiKey = param.apiKey;
5
+ this.apiSecret = param.apiSecret;
6
+ this.basePath = param.basePath;
7
+ this.keepAlive = param.keepAlive ?? true;
8
+ this.compression = param.compression ?? true;
9
+ this.retries = param.retries ?? 3;
10
+ this.backoff = param.backoff ?? 1e3;
11
+ this.privateKey = param.privateKey;
12
+ this.privateKeyPassphrase = param.privateKeyPassphrase;
13
+ this.timeUnit = param.timeUnit;
14
+ this.baseOptions = {
15
+ timeout: this.timeout ?? 1e3,
16
+ proxy: this.proxy && {
17
+ host: this.proxy.host,
18
+ port: this.proxy.port,
19
+ auth: this.proxy.auth
20
+ },
21
+ httpsAgent: this.httpsAgent ?? false,
22
+ headers: {
23
+ "Content-Type": "application/json",
24
+ "X-MBX-APIKEY": this.apiKey
25
+ }
26
+ };
27
+ }
28
+ };
29
+ var ConfigurationWebsocketAPI = class {
30
+ constructor(param = { apiKey: "" }) {
31
+ this.apiKey = param.apiKey;
32
+ this.apiSecret = param.apiSecret;
33
+ this.wsURL = param.wsURL;
34
+ this.timeout = param.timeout ?? 5e3;
35
+ this.reconnectDelay = param.reconnectDelay ?? 5e3;
36
+ this.compression = param.compression ?? true;
37
+ this.agent = param.agent ?? false;
38
+ this.mode = param.mode ?? "single";
39
+ this.poolSize = param.poolSize ?? 1;
40
+ this.privateKey = param.privateKey;
41
+ this.privateKeyPassphrase = param.privateKeyPassphrase;
42
+ this.timeUnit = param.timeUnit;
43
+ }
44
+ };
45
+ var ConfigurationWebsocketStreams = class {
46
+ constructor(param = {}) {
47
+ this.wsURL = param.wsURL;
48
+ this.reconnectDelay = param.reconnectDelay ?? 5e3;
49
+ this.compression = param.compression ?? true;
50
+ this.agent = param.agent ?? false;
51
+ this.mode = param.mode ?? "single";
52
+ this.poolSize = param.poolSize ?? 1;
53
+ this.timeUnit = param.timeUnit;
54
+ }
55
+ };
56
+
57
+ // src/constants.ts
58
+ var TimeUnit = {
59
+ MILLISECOND: "MILLISECOND",
60
+ millisecond: "millisecond",
61
+ MICROSECOND: "MICROSECOND",
62
+ microsecond: "microsecond"
63
+ };
64
+ var SPOT_REST_API_PROD_URL = "https://api.binance.com";
65
+ var SPOT_REST_API_TESTNET_URL = "https://testnet.binance.vision";
66
+ var SPOT_WS_API_PROD_URL = "wss://ws-api.binance.com:443/ws-api/v3";
67
+ var SPOT_WS_API_TESTNET_URL = "wss://ws-api.testnet.binance.vision/ws-api/v3";
68
+ var SPOT_WS_STREAMS_PROD_URL = "wss://stream.binance.com:9443";
69
+ var SPOT_WS_STREAMS_TESTNET_URL = "wss://stream.testnet.binance.vision";
70
+ var SPOT_REST_API_MARKET_URL = "https://data-api.binance.vision";
71
+ var SPOT_WS_STREAMS_MARKET_URL = "wss://data-stream.binance.vision";
72
+ var ALGO_REST_API_PROD_URL = "https://api.binance.com";
73
+ var ALGO_REST_API_TESTNET_URL = "https://testnet.binance.vision";
74
+ var AUTO_INVEST_REST_API_PROD_URL = "https://api.binance.com";
75
+ var AUTO_INVEST_REST_API_TESTNET_URL = "https://testnet.binance.vision";
76
+ var C2C_REST_API_PROD_URL = "https://api.binance.com";
77
+ var C2C_REST_API_TESTNET_URL = "https://testnet.binance.vision";
78
+ var CONVERT_REST_API_PROD_URL = "https://api.binance.com";
79
+ var CONVERT_REST_API_TESTNET_URL = "https://testnet.binance.vision";
80
+ var COPY_TRADING_REST_API_PROD_URL = "https://api.binance.com";
81
+ var COPY_TRADING_REST_API_TESTNET_URL = "https://testnet.binance.vision";
82
+ var CRYPTO_LOAN_REST_API_PROD_URL = "https://api.binance.com";
83
+ var CRYPTO_LOAN_REST_API_TESTNET_URL = "https://testnet.binance.vision";
84
+ var DERIVATIVES_TRADING_COIN_FUTURES_REST_API_PROD_URL = "https://dapi.binance.com";
85
+ var DERIVATIVES_TRADING_COIN_FUTURES_REST_API_TESTNET_URL = "https://testnet.binancefuture.com";
86
+ var DERIVATIVES_TRADING_COIN_FUTURES_WS_API_PROD_URL = "wss://ws-dapi.binance.com/ws-dapi/v1";
87
+ var DERIVATIVES_TRADING_COIN_FUTURES_WS_API_TESTNET_URL = "wss://testnet.binancefuture.com/ws-dapi/v1";
88
+ var DERIVATIVES_TRADING_COIN_FUTURES_WS_STREAMS_PROD_URL = "wss://dstream.binance.com";
89
+ var DERIVATIVES_TRADING_COIN_FUTURES_WS_STREAMS_TESTNET_URL = "wss://dstream.binancefuture.com";
90
+ var DERIVATIVES_TRADING_USDS_FUTURES_REST_API_PROD_URL = "https://fapi.binance.com";
91
+ var DERIVATIVES_TRADING_USDS_FUTURES_REST_API_TESTNET_URL = "https://testnet.binancefuture.com";
92
+ var DERIVATIVES_TRADING_USDS_FUTURES_WS_API_PROD_URL = "wss://ws-fapi.binance.com/ws-fapi/v1";
93
+ var DERIVATIVES_TRADING_USDS_FUTURES_WS_API_TESTNET_URL = "wss://testnet.binancefuture.com/ws-fapi/v1";
94
+ var DERIVATIVES_TRADING_USDS_FUTURES_WS_STREAMS_PROD_URL = "wss://fstream.binance.com";
95
+ var DERIVATIVES_TRADING_USDS_FUTURES_WS_STREAMS_TESTNET_URL = "wss://stream.binancefuture.com";
96
+ var DERIVATIVES_TRADING_OPTIONS_REST_API_PROD_URL = "https://eapi.binance.com";
97
+ var DERIVATIVES_TRADING_OPTIONS_WS_STREAMS_PROD_URL = "wss://nbstream.binance.com/eoptions";
98
+ var DERIVATIVES_TRADING_PORTFOLIO_MARGIN_REST_API_PROD_URL = "https://papi.binance.com";
99
+ var DERIVATIVES_TRADING_PORTFOLIO_MARGIN_REST_API_TESTNET_URL = "https://testnet.binancefuture.com";
100
+ var DERIVATIVES_TRADING_PORTFOLIO_MARGIN_PRO_REST_API_PROD_URL = "https://fapi.binance.com";
101
+ var DERIVATIVES_TRADING_PORTFOLIO_MARGIN_PRO_REST_API_TESTNET_URL = "https://testnet.binancefuture.com";
102
+ var DUAL_INVESTMENT_REST_API_PROD_URL = "https://api.binance.com";
103
+ var DUAL_INVESTMENT_REST_API_TESTNET_URL = "https://testnet.binance.vision";
104
+ var FIAT_REST_API_PROD_URL = "https://api.binance.com";
105
+ var FIAT_REST_API_TESTNET_URL = "https://testnet.binance.vision";
106
+ var GIFT_CARD_REST_API_PROD_URL = "https://api.binance.com";
107
+ var GIFT_CARD_REST_API_TESTNET_URL = "https://testnet.binance.vision";
108
+ var MARGIN_TRADING_REST_API_PROD_URL = "https://api.binance.com";
109
+ var MARGIN_TRADING_REST_API_TESTNET_URL = "https://testnet.binance.vision";
110
+ var MINING_REST_API_PROD_URL = "https://api.binance.com";
111
+ var MINING_REST_API_TESTNET_URL = "https://testnet.binance.vision";
112
+ var NFT_REST_API_PROD_URL = "https://api.binance.com";
113
+ var NFT_REST_API_TESTNET_URL = "https://testnet.binance.vision";
114
+ var PAY_REST_API_PROD_URL = "https://api.binance.com";
115
+ var PAY_REST_API_TESTNET_URL = "https://testnet.binance.vision";
116
+ var REBATE_REST_API_PROD_URL = "https://api.binance.com";
117
+ var REBATE_REST_API_TESTNET_URL = "https://testnet.binance.vision";
118
+ var SIMPLE_EARN_REST_API_PROD_URL = "https://api.binance.com";
119
+ var SIMPLE_EARN_REST_API_TESTNET_URL = "https://testnet.binance.vision";
120
+ var STAKING_REST_API_PROD_URL = "https://api.binance.com";
121
+ var STAKING_REST_API_TESTNET_URL = "https://testnet.binance.vision";
122
+ var SUB_ACCOUNT_REST_API_PROD_URL = "https://api.binance.com";
123
+ var SUB_ACCOUNT_REST_API_TESTNET_URL = "https://testnet.binance.vision";
124
+ var VIP_LOAN_REST_API_PROD_URL = "https://api.binance.com";
125
+ var VIP_LOAN_REST_API_TESTNET_URL = "https://testnet.binance.vision";
126
+ var WALLET_REST_API_PROD_URL = "https://api.binance.com";
127
+ var WALLET_REST_API_TESTNET_URL = "https://testnet.binance.vision";
128
+
129
+ // src/errors.ts
130
+ var ConnectorClientError = class _ConnectorClientError extends Error {
131
+ constructor(msg) {
132
+ super(msg || "An unexpected error occurred.");
133
+ Object.setPrototypeOf(this, _ConnectorClientError.prototype);
134
+ this.name = "ConnectorClientError";
135
+ }
136
+ };
137
+ var RequiredError = class _RequiredError extends Error {
138
+ constructor(field, msg) {
139
+ super(msg || `Required parameter ${field} was null or undefined.`);
140
+ this.field = field;
141
+ Object.setPrototypeOf(this, _RequiredError.prototype);
142
+ this.name = "RequiredError";
143
+ }
144
+ };
145
+ var UnauthorizedError = class _UnauthorizedError extends Error {
146
+ constructor(msg) {
147
+ super(msg || "Unauthorized access. Authentication required.");
148
+ Object.setPrototypeOf(this, _UnauthorizedError.prototype);
149
+ this.name = "UnauthorizedError";
150
+ }
151
+ };
152
+ var ForbiddenError = class _ForbiddenError extends Error {
153
+ constructor(msg) {
154
+ super(msg || "Access to the requested resource is forbidden.");
155
+ Object.setPrototypeOf(this, _ForbiddenError.prototype);
156
+ this.name = "ForbiddenError";
157
+ }
158
+ };
159
+ var TooManyRequestsError = class _TooManyRequestsError extends Error {
160
+ constructor(msg) {
161
+ super(msg || "Too many requests. You are being rate-limited.");
162
+ Object.setPrototypeOf(this, _TooManyRequestsError.prototype);
163
+ this.name = "TooManyRequestsError";
164
+ }
165
+ };
166
+ var RateLimitBanError = class _RateLimitBanError extends Error {
167
+ constructor(msg) {
168
+ super(msg || "The IP address has been banned for exceeding rate limits.");
169
+ Object.setPrototypeOf(this, _RateLimitBanError.prototype);
170
+ this.name = "RateLimitBanError";
171
+ }
172
+ };
173
+ var ServerError = class _ServerError extends Error {
174
+ constructor(msg, statusCode) {
175
+ super(msg || "An internal server error occurred.");
176
+ this.statusCode = statusCode;
177
+ Object.setPrototypeOf(this, _ServerError.prototype);
178
+ this.name = "ServerError";
179
+ }
180
+ };
181
+ var NetworkError = class _NetworkError extends Error {
182
+ constructor(msg) {
183
+ super(msg || "A network error occurred.");
184
+ Object.setPrototypeOf(this, _NetworkError.prototype);
185
+ this.name = "NetworkError";
186
+ }
187
+ };
188
+ var NotFoundError = class _NotFoundError extends Error {
189
+ constructor(msg) {
190
+ super(msg || "The requested resource was not found.");
191
+ Object.setPrototypeOf(this, _NotFoundError.prototype);
192
+ this.name = "NotFoundError";
193
+ }
194
+ };
195
+ var BadRequestError = class _BadRequestError extends Error {
196
+ constructor(msg) {
197
+ super(msg || "The request was invalid or cannot be otherwise served.");
198
+ Object.setPrototypeOf(this, _BadRequestError.prototype);
199
+ this.name = "BadRequestError";
200
+ }
201
+ };
202
+
203
+ // src/logger.ts
204
+ var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
205
+ LogLevel2["NONE"] = "";
206
+ LogLevel2["DEBUG"] = "debug";
207
+ LogLevel2["INFO"] = "info";
208
+ LogLevel2["WARN"] = "warn";
209
+ LogLevel2["ERROR"] = "error";
210
+ return LogLevel2;
211
+ })(LogLevel || {});
212
+ var Logger = class _Logger {
213
+ constructor() {
214
+ this.minLogLevel = "info" /* INFO */;
215
+ this.levelsOrder = [
216
+ "" /* NONE */,
217
+ "debug" /* DEBUG */,
218
+ "info" /* INFO */,
219
+ "warn" /* WARN */,
220
+ "error" /* ERROR */
221
+ ];
222
+ }
223
+ static getInstance() {
224
+ if (!_Logger.instance) {
225
+ _Logger.instance = new _Logger();
226
+ }
227
+ return _Logger.instance;
228
+ }
229
+ setMinLogLevel(level) {
230
+ if (!this.isValidLogLevel(level)) {
231
+ throw new Error(`Invalid log level: ${level}`);
232
+ }
233
+ this.minLogLevel = level;
234
+ }
235
+ isValidLogLevel(level) {
236
+ return this.levelsOrder.includes(level);
237
+ }
238
+ log(level, ...message) {
239
+ if (level === "" /* NONE */ || !this.allowLevelLog(level)) {
240
+ return;
241
+ }
242
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
243
+ console[level](`[${timestamp}] [${level.toLowerCase()}]`, ...message);
244
+ }
245
+ allowLevelLog(level) {
246
+ if (!this.isValidLogLevel(level)) {
247
+ throw new Error(`Invalid log level: ${level}`);
248
+ }
249
+ const currentLevelIndex = this.levelsOrder.indexOf(level);
250
+ const minLevelIndex = this.levelsOrder.indexOf(this.minLogLevel);
251
+ return currentLevelIndex >= minLevelIndex;
252
+ }
253
+ debug(...message) {
254
+ this.log("debug" /* DEBUG */, ...message);
255
+ }
256
+ info(...message) {
257
+ this.log("info" /* INFO */, ...message);
258
+ }
259
+ warn(...message) {
260
+ this.log("warn" /* WARN */, ...message);
261
+ }
262
+ error(...message) {
263
+ this.log("error" /* ERROR */, ...message);
264
+ }
265
+ };
266
+
267
+ // src/utils.ts
268
+ import crypto from "crypto";
269
+ import fs from "fs";
270
+ import https from "https";
271
+ import globalAxios from "axios";
272
+ function buildQueryString(params) {
273
+ if (!params)
274
+ return "";
275
+ return Object.entries(params).map(stringifyKeyValuePair).join("&");
276
+ }
277
+ function stringifyKeyValuePair([key, value]) {
278
+ const valueString = Array.isArray(value) ? `["${value.join('","')}"]` : value;
279
+ return `${key}=${encodeURIComponent(valueString)}`;
280
+ }
281
+ function randomString() {
282
+ return crypto.randomBytes(16).toString("hex");
283
+ }
284
+ function validateTimeUnit(timeUnit) {
285
+ if (!timeUnit) {
286
+ return;
287
+ } else if (timeUnit !== TimeUnit.MILLISECOND && timeUnit !== TimeUnit.MICROSECOND && timeUnit !== TimeUnit.millisecond && timeUnit !== TimeUnit.microsecond) {
288
+ throw new Error("timeUnit must be either 'MILLISECOND' or 'MICROSECOND'");
289
+ }
290
+ return timeUnit;
291
+ }
292
+ async function delay(ms) {
293
+ return new Promise((resolve) => setTimeout(resolve, ms));
294
+ }
295
+ function getTimestamp() {
296
+ return Date.now();
297
+ }
298
+ var getSignature = function(configuration, queryParams) {
299
+ const params = buildQueryString(queryParams);
300
+ let signature = "";
301
+ if (configuration?.apiSecret && !configuration?.privateKey) {
302
+ signature = crypto.createHmac("sha256", configuration.apiSecret).update(params).digest("hex");
303
+ } else if (configuration?.privateKey) {
304
+ let privateKey = configuration.privateKey;
305
+ if (typeof privateKey === "string" && fs.existsSync(privateKey)) {
306
+ privateKey = fs.readFileSync(privateKey, "utf-8");
307
+ }
308
+ let keyObject;
309
+ try {
310
+ const privateKeyObj = { key: privateKey };
311
+ if (configuration.privateKeyPassphrase && typeof configuration.privateKeyPassphrase === "string") {
312
+ privateKeyObj.passphrase = configuration.privateKeyPassphrase;
313
+ }
314
+ keyObject = crypto.createPrivateKey(privateKeyObj);
315
+ } catch {
316
+ throw new Error(
317
+ "Invalid private key. Please provide a valid RSA or ED25519 private key."
318
+ );
319
+ }
320
+ const keyType = keyObject.asymmetricKeyType;
321
+ if (keyType === "rsa") {
322
+ signature = crypto.sign("RSA-SHA256", Buffer.from(params), keyObject).toString("base64");
323
+ } else if (keyType === "ed25519") {
324
+ signature = crypto.sign(null, Buffer.from(params), keyObject).toString("base64");
325
+ } else {
326
+ throw new Error("Unsupported private key type. Must be RSA or ED25519.");
327
+ }
328
+ } else {
329
+ throw new Error("Either 'apiSecret' or 'privateKey' must be provided for signed requests.");
330
+ }
331
+ return signature;
332
+ };
333
+ var assertParamExists = function(functionName, paramName, paramValue) {
334
+ if (paramValue === null || paramValue === void 0) {
335
+ throw new RequiredError(
336
+ paramName,
337
+ `Required parameter ${paramName} was null or undefined when calling ${functionName}.`
338
+ );
339
+ }
340
+ };
341
+ function setFlattenedQueryParams(urlSearchParams, parameter, key = "") {
342
+ if (parameter == null)
343
+ return;
344
+ if (typeof parameter === "object") {
345
+ if (Array.isArray(parameter)) {
346
+ parameter.forEach((item) => setFlattenedQueryParams(urlSearchParams, item, key));
347
+ } else {
348
+ Object.keys(parameter).forEach(
349
+ (currentKey) => setFlattenedQueryParams(
350
+ urlSearchParams,
351
+ parameter[currentKey],
352
+ `${key}${key !== "" ? "." : ""}${currentKey}`
353
+ )
354
+ );
355
+ }
356
+ } else {
357
+ if (urlSearchParams.has(key)) {
358
+ urlSearchParams.append(key, String(parameter));
359
+ } else {
360
+ urlSearchParams.set(key, String(parameter));
361
+ }
362
+ }
363
+ }
364
+ var setSearchParams = function(url, ...objects) {
365
+ const searchParams = new URLSearchParams(url.search);
366
+ setFlattenedQueryParams(searchParams, objects);
367
+ url.search = searchParams.toString();
368
+ };
369
+ var toPathString = function(url) {
370
+ return url.pathname + url.search + url.hash;
371
+ };
372
+ var shouldRetryRequest = function(error, method, retriesLeft) {
373
+ const isRetriableMethod = ["GET", "DELETE"].includes(method ?? "");
374
+ const isRetriableStatus = [500, 502, 503, 504].includes(
375
+ error?.response?.status ?? 0
376
+ );
377
+ return (retriesLeft ?? 0) > 0 && isRetriableMethod && (isRetriableStatus || !error?.response);
378
+ };
379
+ var httpRequestFunction = async function(axiosArgs, configuration) {
380
+ const axiosRequestArgs = {
381
+ ...axiosArgs.options,
382
+ url: (globalAxios.defaults?.baseURL ? "" : configuration?.basePath ?? "") + axiosArgs.url
383
+ };
384
+ if (configuration?.keepAlive) {
385
+ axiosRequestArgs.httpsAgent = new https.Agent({
386
+ ...configuration?.httpsAgent instanceof https.Agent ? configuration.httpsAgent.options : {},
387
+ keepAlive: true
388
+ });
389
+ }
390
+ if (configuration?.compression) {
391
+ axiosRequestArgs.headers = {
392
+ ...axiosRequestArgs.headers,
393
+ "Accept-Encoding": "gzip, deflate, br"
394
+ };
395
+ }
396
+ const retries = configuration?.retries ?? 0;
397
+ const backoff = configuration?.backoff ?? 0;
398
+ let attempt = 0;
399
+ while (attempt <= retries) {
400
+ try {
401
+ const response = await globalAxios.request({
402
+ ...axiosRequestArgs,
403
+ responseType: "text"
404
+ });
405
+ const rateLimits = parseRateLimitHeaders(response.headers);
406
+ return {
407
+ data: async () => JSON.parse(response.data),
408
+ status: response.status,
409
+ headers: response.headers,
410
+ rateLimits
411
+ };
412
+ } catch (error) {
413
+ attempt++;
414
+ if (shouldRetryRequest(
415
+ error,
416
+ axiosRequestArgs?.method?.toUpperCase(),
417
+ retries - attempt
418
+ )) {
419
+ await delay(backoff * attempt);
420
+ } else {
421
+ if (error.response) {
422
+ const status = error.response?.status;
423
+ const data = JSON.parse(
424
+ error.response?.data ?? "{}"
425
+ );
426
+ switch (status) {
427
+ case 400:
428
+ throw new BadRequestError(data?.msg);
429
+ case 401:
430
+ throw new UnauthorizedError(data?.msg);
431
+ case 403:
432
+ throw new ForbiddenError(data?.msg);
433
+ case 404:
434
+ throw new NotFoundError(data?.msg);
435
+ case 418:
436
+ throw new RateLimitBanError(data?.msg);
437
+ case 429:
438
+ throw new TooManyRequestsError(data?.msg);
439
+ default:
440
+ if (status >= 500 && status < 600)
441
+ throw new ServerError(`Server error: ${status}`, status);
442
+ throw new ConnectorClientError(data?.msg);
443
+ }
444
+ } else {
445
+ throw new NetworkError("Network error or request timeout.");
446
+ }
447
+ }
448
+ }
449
+ }
450
+ throw new Error(`Request failed after ${retries} retries`);
451
+ };
452
+ var parseRateLimitHeaders = function(headers) {
453
+ const rateLimits = [];
454
+ const parseIntervalDetails = (key) => {
455
+ const match = key.match(/x-mbx-used-weight-(\d+)([smhd])|x-mbx-order-count-(\d+)([smhd])/i);
456
+ if (!match)
457
+ return null;
458
+ const intervalNum = parseInt(match[1] || match[3], 10);
459
+ const intervalLetter = (match[2] || match[4])?.toUpperCase();
460
+ let interval;
461
+ switch (intervalLetter) {
462
+ case "S":
463
+ interval = "SECOND";
464
+ break;
465
+ case "M":
466
+ interval = "MINUTE";
467
+ break;
468
+ case "H":
469
+ interval = "HOUR";
470
+ break;
471
+ case "D":
472
+ interval = "DAY";
473
+ break;
474
+ default:
475
+ return null;
476
+ }
477
+ return { interval, intervalNum };
478
+ };
479
+ for (const [key, value] of Object.entries(headers)) {
480
+ const normalizedKey = key.toLowerCase();
481
+ if (value === void 0)
482
+ continue;
483
+ if (normalizedKey.startsWith("x-mbx-used-weight-")) {
484
+ const details = parseIntervalDetails(normalizedKey);
485
+ if (details) {
486
+ rateLimits.push({
487
+ rateLimitType: "REQUEST_WEIGHT",
488
+ interval: details.interval,
489
+ intervalNum: details.intervalNum,
490
+ count: parseInt(value, 10)
491
+ });
492
+ }
493
+ } else if (normalizedKey.startsWith("x-mbx-order-count-")) {
494
+ const details = parseIntervalDetails(normalizedKey);
495
+ if (details) {
496
+ rateLimits.push({
497
+ rateLimitType: "ORDERS",
498
+ interval: details.interval,
499
+ intervalNum: details.intervalNum,
500
+ count: parseInt(value, 10)
501
+ });
502
+ }
503
+ }
504
+ }
505
+ if (headers["retry-after"]) {
506
+ const retryAfter = parseInt(headers["retry-after"], 10);
507
+ for (const limit of rateLimits) {
508
+ limit.retryAfter = retryAfter;
509
+ }
510
+ }
511
+ return rateLimits;
512
+ };
513
+ var sendRequest = function(configuration, endpoint, method, params = {}, timeUnit, options = {}) {
514
+ const localVarUrlObj = new URL(endpoint, configuration?.basePath);
515
+ const localVarRequestOptions = {
516
+ method,
517
+ ...configuration?.baseOptions
518
+ };
519
+ const localVarQueryParameter = { ...params };
520
+ if (options.isSigned) {
521
+ const timestamp = getTimestamp();
522
+ localVarQueryParameter["timestamp"] = timestamp;
523
+ const signature = getSignature(configuration, localVarQueryParameter);
524
+ if (signature) {
525
+ localVarQueryParameter["signature"] = signature;
526
+ }
527
+ }
528
+ setSearchParams(localVarUrlObj, localVarQueryParameter);
529
+ if (timeUnit && localVarRequestOptions.headers) {
530
+ const _timeUnit = validateTimeUnit(timeUnit);
531
+ localVarRequestOptions.headers = {
532
+ ...localVarRequestOptions.headers,
533
+ "X-MBX-TIME-UNIT": _timeUnit
534
+ };
535
+ }
536
+ return httpRequestFunction(
537
+ {
538
+ url: toPathString(localVarUrlObj),
539
+ options: localVarRequestOptions
540
+ },
541
+ configuration
542
+ );
543
+ };
544
+ function removeEmptyValue(obj) {
545
+ if (!(obj instanceof Object))
546
+ return {};
547
+ return Object.fromEntries(
548
+ Object.entries(obj).filter(
549
+ ([, value]) => value !== null && value !== void 0 && value !== ""
550
+ )
551
+ );
552
+ }
553
+ function sortObject(obj) {
554
+ return Object.keys(obj).sort().reduce((res, key) => {
555
+ res[key] = obj[key];
556
+ return res;
557
+ }, {});
558
+ }
559
+ function replaceWebsocketStreamsPlaceholders(str, variables) {
560
+ const normalizedVariables = Object.keys(variables).reduce(
561
+ (acc, key) => {
562
+ acc[key.toLowerCase().replace(/[-_]/g, "")] = variables[key];
563
+ return acc;
564
+ },
565
+ {}
566
+ );
567
+ return str.replace(/<([^>]+)>/g, (match, fieldName) => {
568
+ const normalizedFieldName = fieldName.toLowerCase().replace(/[-_]/g, "");
569
+ if (Object.prototype.hasOwnProperty.call(normalizedVariables, normalizedFieldName) && normalizedVariables[normalizedFieldName] != null) {
570
+ const value = normalizedVariables[normalizedFieldName];
571
+ switch (normalizedFieldName) {
572
+ case "symbol":
573
+ case "windowsize":
574
+ return value.toLowerCase();
575
+ case "updatespeed":
576
+ return `@${value}`;
577
+ default:
578
+ return value;
579
+ }
580
+ }
581
+ return "";
582
+ });
583
+ }
584
+ function createStreamHandler(websocketBase, stream, id) {
585
+ websocketBase.subscribe(stream, id);
586
+ let registeredCallback;
587
+ return {
588
+ on: (event, callback) => {
589
+ if (event === "message") {
590
+ registeredCallback = (data) => callback(data);
591
+ const callbackSet = websocketBase.streamCallbackMap.get(stream) ?? /* @__PURE__ */ new Set();
592
+ callbackSet.add(registeredCallback);
593
+ websocketBase.streamCallbackMap.set(stream, callbackSet);
594
+ }
595
+ },
596
+ unsubscribe: () => {
597
+ if (registeredCallback)
598
+ websocketBase.streamCallbackMap.get(stream)?.delete(registeredCallback);
599
+ websocketBase.unsubscribe(stream, id);
600
+ }
601
+ };
602
+ }
603
+
604
+ // src/websocket.ts
605
+ import { EventEmitter } from "events";
606
+ import WebSocketClient from "ws";
607
+ var WebsocketEventEmitter = class {
608
+ constructor() {
609
+ this.eventEmitter = new EventEmitter();
610
+ }
611
+ /* eslint-disable @typescript-eslint/no-explicit-any */
612
+ on(event, listener) {
613
+ this.eventEmitter.on(event, listener);
614
+ }
615
+ /* eslint-disable @typescript-eslint/no-explicit-any */
616
+ off(event, listener) {
617
+ this.eventEmitter.off(event, listener);
618
+ }
619
+ /* eslint-disable @typescript-eslint/no-explicit-any */
620
+ emit(event, ...args) {
621
+ this.eventEmitter.emit(event, ...args);
622
+ }
623
+ };
624
+ var _WebsocketCommon = class _WebsocketCommon extends WebsocketEventEmitter {
625
+ constructor(configuration, connectionPool = []) {
626
+ super();
627
+ this.configuration = configuration;
628
+ this.connectionQueue = [];
629
+ this.queueProcessing = false;
630
+ this.connectionTimers = /* @__PURE__ */ new Map();
631
+ this.roundRobinIndex = 0;
632
+ this.logger = Logger.getInstance();
633
+ this.connectionPool = connectionPool;
634
+ this.mode = this.configuration?.mode ?? "single";
635
+ this.poolSize = this.mode === "pool" && this.configuration?.poolSize ? this.configuration.poolSize : 1;
636
+ if (!connectionPool || connectionPool.length === 0)
637
+ this.initializePool(this.poolSize);
638
+ }
639
+ /**
640
+ * Initializes the WebSocket connection pool by creating a specified number of connection objects
641
+ * and adding them to the `connectionPool` array. Each connection object has the following properties:
642
+ * - `closeInitiated`: a boolean indicating whether the connection has been closed
643
+ * - `reconnectionPending`: a boolean indicating whether a reconnection is pending
644
+ * - `pendingRequests`: a Map that tracks pending requests for the connection
645
+ * @param size - The number of connection objects to create and add to the pool.
646
+ * @returns void
647
+ */
648
+ initializePool(size) {
649
+ for (let i = 0; i < size; i++) {
650
+ this.connectionPool.push({
651
+ id: randomString(),
652
+ closeInitiated: false,
653
+ reconnectionPending: false,
654
+ renewalPending: false,
655
+ pendingRequests: /* @__PURE__ */ new Map(),
656
+ pendingSubscriptions: []
657
+ });
658
+ }
659
+ }
660
+ /**
661
+ * Gets a WebSocket connection from the pool or single connection.
662
+ * If the connection mode is 'single', it returns the first connection in the pool.
663
+ * If the connection mode is 'pool', it returns an available connection from the pool,
664
+ * using a round-robin selection strategy. If no available connections are found, it throws an error.
665
+ * @param allowNonEstablishedWebsockets - A boolean indicating whether to allow connections that are not established.
666
+ * @returns {WebsocketConnection} The selected WebSocket connection.
667
+ */
668
+ getConnection(allowNonEstablishedWebsockets = false) {
669
+ if (this.mode === "single")
670
+ return this.connectionPool[0];
671
+ const availableConnections = this.connectionPool.filter(
672
+ (connection) => this.isConnectionReady(connection, allowNonEstablishedWebsockets)
673
+ );
674
+ if (availableConnections.length === 0) {
675
+ throw new Error("No available Websocket connections are ready.");
676
+ }
677
+ const selectedConnection = availableConnections[this.roundRobinIndex % availableConnections.length];
678
+ this.roundRobinIndex = (this.roundRobinIndex + 1) % availableConnections.length;
679
+ return selectedConnection;
680
+ }
681
+ /**
682
+ * Checks if the provided WebSocket connection is ready for use.
683
+ * A connection is considered ready if it is open, has no pending reconnection, and has not been closed.
684
+ * @param connection - The WebSocket connection to check.
685
+ * @param allowNonEstablishedWebsockets - An optional flag to allow non-established WebSocket connections.
686
+ * @returns `true` if the connection is ready, `false` otherwise.
687
+ */
688
+ isConnectionReady(connection, allowNonEstablishedWebsockets = false) {
689
+ return (allowNonEstablishedWebsockets || connection.ws?.readyState === WebSocketClient.OPEN) && !connection.reconnectionPending && !connection.closeInitiated;
690
+ }
691
+ /**
692
+ * Schedules a timer for a WebSocket connection and tracks it
693
+ * @param connection WebSocket client instance
694
+ * @param callback Function to execute when timer triggers
695
+ * @param delay Time in milliseconds before callback execution
696
+ * @param type Timer type ('timeout' or 'interval')
697
+ * @returns Timer handle
698
+ */
699
+ scheduleTimer(connection, callback, delay2, type = "timeout") {
700
+ const timer = type === "timeout" ? setTimeout(callback, delay2) : setInterval(callback, delay2);
701
+ if (!this.connectionTimers.has(connection))
702
+ this.connectionTimers.set(connection, /* @__PURE__ */ new Set());
703
+ this.connectionTimers.get(connection)?.add({ timer, type });
704
+ return timer;
705
+ }
706
+ /**
707
+ * Clears all timers associated with a WebSocket connection.
708
+ * @param connection - The WebSocket client instance to clear timers for.
709
+ * @returns void
710
+ */
711
+ clearTimers(connection) {
712
+ const timers = this.connectionTimers.get(connection);
713
+ if (timers) {
714
+ timers.forEach(({ timer, type }) => {
715
+ if (type === "timeout")
716
+ clearTimeout(timer);
717
+ else if (type === "interval")
718
+ clearInterval(timer);
719
+ });
720
+ this.connectionTimers.delete(connection);
721
+ }
722
+ }
723
+ /**
724
+ * Processes the connection queue, reconnecting or renewing connections as needed.
725
+ * This method is responsible for iterating through the connection queue and initiating
726
+ * the reconnection or renewal process for each connection in the queue. It throttles
727
+ * the queue processing to avoid overwhelming the server with too many connection
728
+ * requests at once.
729
+ * @param throttleRate - The time in milliseconds to wait between processing each
730
+ * connection in the queue.
731
+ * @returns A Promise that resolves when the queue has been fully processed.
732
+ */
733
+ async processQueue(throttleRate = 1e3) {
734
+ if (this.queueProcessing)
735
+ return;
736
+ this.queueProcessing = true;
737
+ while (this.connectionQueue.length > 0) {
738
+ const { connection, url, isRenewal } = this.connectionQueue.shift();
739
+ this.initConnect(url, isRenewal, connection);
740
+ await delay(throttleRate);
741
+ }
742
+ this.queueProcessing = false;
743
+ }
744
+ /**
745
+ * Enqueues a reconnection or renewal for a WebSocket connection.
746
+ * This method adds the connection, URL, and renewal flag to the connection queue,
747
+ * and then calls the `processQueue` method to initiate the reconnection or renewal
748
+ * process.
749
+ * @param connection - The WebSocket connection to reconnect or renew.
750
+ * @param url - The URL to use for the reconnection or renewal.
751
+ * @param isRenewal - A flag indicating whether this is a renewal (true) or a reconnection (false).
752
+ */
753
+ enqueueReconnection(connection, url, isRenewal) {
754
+ this.connectionQueue.push({ connection, url, isRenewal });
755
+ this.processQueue();
756
+ }
757
+ /**
758
+ * Gracefully closes a WebSocket connection after pending requests complete.
759
+ * This method waits for any pending requests to complete before closing the connection.
760
+ * It sets up a timeout to force-close the connection after 30 seconds if the pending requests
761
+ * do not complete. Once all pending requests are completed, the connection is closed.
762
+ * @param connectionToClose - The WebSocket client instance to close.
763
+ * @param WebsocketConnectionToClose - The WebSocket connection to close.
764
+ * @param connection - The WebSocket connection to close.
765
+ * @returns Promise that resolves when the connection is closed.
766
+ */
767
+ async closeConnectionGracefully(WebsocketConnectionToClose, connection) {
768
+ if (!WebsocketConnectionToClose || !connection)
769
+ return;
770
+ this.logger.debug("Waiting for pending requests to complete before disconnecting.");
771
+ const closePromise = new Promise((resolve) => {
772
+ this.scheduleTimer(
773
+ WebsocketConnectionToClose,
774
+ () => {
775
+ this.logger.warn("Force-closing connection after 30 seconds.");
776
+ resolve();
777
+ },
778
+ 3e4
779
+ );
780
+ this.scheduleTimer(
781
+ WebsocketConnectionToClose,
782
+ () => {
783
+ if (connection.pendingRequests.size === 0) {
784
+ this.logger.debug("All pending requests completed, closing connection.");
785
+ resolve();
786
+ }
787
+ },
788
+ 1e3,
789
+ "interval"
790
+ );
791
+ });
792
+ await closePromise;
793
+ this.logger.info("Closing Websocket connection.");
794
+ WebsocketConnectionToClose.close();
795
+ this.cleanup(WebsocketConnectionToClose);
796
+ }
797
+ /**
798
+ * Cleans up WebSocket connection resources.
799
+ * Removes all listeners and clears any associated timers for the provided WebSocket client.
800
+ * @param ws - The WebSocket client to clean up.
801
+ * @returns void
802
+ */
803
+ cleanup(ws) {
804
+ if (ws) {
805
+ ws.removeAllListeners();
806
+ this.clearTimers(ws);
807
+ }
808
+ }
809
+ /**
810
+ * Handles incoming WebSocket messages
811
+ * @param data Raw message data received
812
+ * @param connection Websocket connection
813
+ */
814
+ onMessage(data, connection) {
815
+ this.emit("message", data.toString(), connection);
816
+ }
817
+ /**
818
+ * Handles the opening of a WebSocket connection.
819
+ * @param url - The URL of the WebSocket server.
820
+ * @param targetConnection - The WebSocket connection being opened.
821
+ * @param oldConnection - The previous WebSocket connection, if this is a renewal.
822
+ * @param isRenewal - Indicates whether this is a connection renewal.
823
+ * @param oldWSConnection - The WebSocket client instance associated with the old connection.
824
+ */
825
+ onOpen(url, targetConnection, oldWSConnection) {
826
+ this.logger.info(
827
+ `Connected to the Websocket Server with id ${targetConnection.id}: ${url}`
828
+ );
829
+ if (targetConnection.renewalPending) {
830
+ targetConnection.renewalPending = false;
831
+ this.closeConnectionGracefully(oldWSConnection, targetConnection);
832
+ } else if (targetConnection.closeInitiated) {
833
+ this.closeConnectionGracefully(targetConnection.ws, targetConnection);
834
+ } else {
835
+ this.emit("open", this);
836
+ }
837
+ }
838
+ /**
839
+ * Returns the URL to use when reconnecting.
840
+ * Derived classes should override this to provide dynamic URLs.
841
+ * @param defaultURL The URL originally passed during the first connection.
842
+ * @param targetConnection The WebSocket connection being connected.
843
+ * @returns The URL to reconnect to.
844
+ */
845
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
846
+ getReconnectURL(defaultURL, targetConnection) {
847
+ return defaultURL;
848
+ }
849
+ /**
850
+ * Connects all WebSocket connections in the pool
851
+ * @param url - The Websocket server URL.
852
+ * @returns A promise that resolves when all connections are established.
853
+ */
854
+ async connectPool(url) {
855
+ const connectPromises = this.connectionPool.map(
856
+ (connection) => new Promise((resolve, reject) => {
857
+ this.initConnect(url, false, connection);
858
+ connection.ws?.on("open", () => resolve());
859
+ connection.ws?.on("error", (err) => reject(err));
860
+ connection.ws?.on(
861
+ "close",
862
+ () => reject(new Error("Connection closed unexpectedly."))
863
+ );
864
+ })
865
+ );
866
+ await Promise.all(connectPromises);
867
+ }
868
+ /**
869
+ * Creates a new WebSocket client instance.
870
+ * @param url - The URL to connect to.
871
+ * @returns A new WebSocket client instance.
872
+ */
873
+ createWebSocket(url) {
874
+ return new WebSocketClient(url, {
875
+ perMessageDeflate: this.configuration?.compression,
876
+ agent: this.configuration?.agent
877
+ });
878
+ }
879
+ /**
880
+ * Initializes a WebSocket connection.
881
+ * @param url - The Websocket server URL.
882
+ * @param isRenewal - Whether this is a connection renewal.
883
+ * @param connection - An optional WebSocket connection to use.
884
+ * @returns The WebSocket connection.
885
+ */
886
+ initConnect(url, isRenewal = false, connection) {
887
+ const targetConnection = connection || this.getConnection();
888
+ if (targetConnection.renewalPending && isRenewal) {
889
+ this.logger.warn(
890
+ `Connection renewal with id ${targetConnection.id} is already in progress`
891
+ );
892
+ return;
893
+ }
894
+ if (targetConnection.ws && targetConnection.ws.readyState === WebSocketClient.OPEN && !isRenewal) {
895
+ this.logger.warn(`Connection with id ${targetConnection.id} already exists`);
896
+ return;
897
+ }
898
+ const ws = this.createWebSocket(url);
899
+ this.logger.info(
900
+ `Establishing Websocket connection with id ${targetConnection.id} to: ${url}`
901
+ );
902
+ if (isRenewal)
903
+ targetConnection.renewalPending = true;
904
+ else
905
+ targetConnection.ws = ws;
906
+ this.scheduleTimer(
907
+ ws,
908
+ () => {
909
+ this.logger.info(`Renewing Websocket connection with id ${targetConnection.id}`);
910
+ this.enqueueReconnection(
911
+ targetConnection,
912
+ this.getReconnectURL(url, targetConnection),
913
+ true
914
+ );
915
+ },
916
+ _WebsocketCommon.MAX_CONNECTION_DURATION
917
+ );
918
+ ws.on("open", () => {
919
+ const oldWSConnection = targetConnection.ws;
920
+ if (targetConnection.renewalPending)
921
+ targetConnection.ws = ws;
922
+ this.onOpen(url, targetConnection, oldWSConnection);
923
+ });
924
+ ws.on("message", (data) => {
925
+ this.onMessage(data.toString(), targetConnection);
926
+ });
927
+ ws.on("ping", () => {
928
+ this.logger.info("Received PING from server");
929
+ this.emit("ping");
930
+ ws.pong();
931
+ this.logger.info("Responded PONG to server's PING message");
932
+ });
933
+ ws.on("pong", () => {
934
+ this.logger.info("Received PONG from server");
935
+ this.emit("pong");
936
+ });
937
+ ws.on("error", (err) => {
938
+ this.logger.error("Received error from server");
939
+ this.logger.error(err);
940
+ this.emit("error", err);
941
+ });
942
+ ws.on("close", (closeEventCode, reason) => {
943
+ this.emit("close", closeEventCode, reason);
944
+ if (!targetConnection.closeInitiated && !isRenewal) {
945
+ this.logger.warn(
946
+ `Connection with id ${targetConnection.id} closed due to ${closeEventCode}: ${reason}`
947
+ );
948
+ this.scheduleTimer(
949
+ ws,
950
+ () => {
951
+ this.logger.info(
952
+ `Reconnecting conection with id ${targetConnection.id} to the server.`
953
+ );
954
+ targetConnection.reconnectionPending = true;
955
+ this.enqueueReconnection(
956
+ targetConnection,
957
+ this.getReconnectURL(url, targetConnection),
958
+ false
959
+ );
960
+ },
961
+ this.configuration?.reconnectDelay ?? 5e3
962
+ );
963
+ }
964
+ });
965
+ return targetConnection;
966
+ }
967
+ /**
968
+ * Checks if the WebSocket connection is currently open.
969
+ * @param connection - An optional WebSocket connection to check. If not provided, the entire connection pool is checked.
970
+ * @returns `true` if the connection is open, `false` otherwise.
971
+ */
972
+ isConnected(connection) {
973
+ const connectionPool = connection ? [connection] : this.connectionPool;
974
+ return connectionPool.some((connection2) => this.isConnectionReady(connection2));
975
+ }
976
+ /**
977
+ * Disconnects from the WebSocket server.
978
+ * If there is no active connection, a warning is logged.
979
+ * Otherwise, all connections in the connection pool are closed gracefully,
980
+ * and a message is logged indicating that the connection has been disconnected.
981
+ * @returns A Promise that resolves when all connections have been closed.
982
+ * @throws Error if the WebSocket client is not set.
983
+ */
984
+ async disconnect() {
985
+ if (!this.isConnected())
986
+ this.logger.warn("No connection to close.");
987
+ else {
988
+ this.connectionPool.forEach((connection) => {
989
+ connection.closeInitiated = true;
990
+ });
991
+ const disconnectPromises = this.connectionPool.map(
992
+ (connection) => this.closeConnectionGracefully(connection.ws, connection)
993
+ );
994
+ await Promise.all(disconnectPromises);
995
+ this.logger.info("Disconnected with Binance Websocket Server");
996
+ }
997
+ }
998
+ /**
999
+ * Sends a ping message to all connected Websocket servers in the pool.
1000
+ * If no connections are ready, a warning is logged.
1001
+ * For each active connection, the ping message is sent, and debug logs provide details.
1002
+ * @throws Error if a Websocket client is not set for a connection.
1003
+ */
1004
+ pingServer() {
1005
+ const connectedConnections = this.connectionPool.filter(
1006
+ (connection) => this.isConnected(connection)
1007
+ );
1008
+ if (connectedConnections.length === 0) {
1009
+ this.logger.warn("Ping only can be sent when connection is ready.");
1010
+ return;
1011
+ }
1012
+ this.logger.info("Sending PING to all connected Websocket servers.");
1013
+ connectedConnections.forEach((connection) => {
1014
+ if (connection.ws) {
1015
+ connection.ws.ping();
1016
+ this.logger.debug(`PING sent to connection with id ${connection.id}`);
1017
+ } else {
1018
+ this.logger.error("WebSocket Client not set for a connection.");
1019
+ }
1020
+ });
1021
+ }
1022
+ /**
1023
+ * Sends a payload through the WebSocket connection.
1024
+ * @param payload - Message to send.
1025
+ * @param id - Optional request identifier.
1026
+ * @param promiseBased - Whether to return a promise.
1027
+ * @param timeout - Timeout duration in milliseconds.
1028
+ * @param connection - The WebSocket connection to use.
1029
+ * @returns A promise if `promiseBased` is true, void otherwise.
1030
+ * @throws Error if not connected or WebSocket client is not set.
1031
+ */
1032
+ send(payload, id, promiseBased = true, timeout = 5e3, connection) {
1033
+ if (!this.isConnected(connection)) {
1034
+ const errorMsg = "Send can only be sent when connection is ready.";
1035
+ this.logger.warn(errorMsg);
1036
+ if (promiseBased)
1037
+ return Promise.reject(new Error(errorMsg));
1038
+ else
1039
+ throw new Error(errorMsg);
1040
+ }
1041
+ const connectionToUse = connection ?? this.getConnection();
1042
+ if (!connectionToUse.ws) {
1043
+ const errorMsg = "Websocket Client not set";
1044
+ this.logger.error(errorMsg);
1045
+ if (promiseBased)
1046
+ return Promise.reject(new Error(errorMsg));
1047
+ else
1048
+ throw new Error(errorMsg);
1049
+ }
1050
+ connectionToUse.ws.send(payload);
1051
+ if (promiseBased) {
1052
+ return new Promise((resolve, reject) => {
1053
+ if (!id)
1054
+ return reject(new Error("id is required for promise-based sending."));
1055
+ connectionToUse.pendingRequests.set(id, { resolve, reject });
1056
+ this.scheduleTimer(
1057
+ connectionToUse.ws,
1058
+ () => {
1059
+ if (connectionToUse.pendingRequests.has(id)) {
1060
+ connectionToUse.pendingRequests.delete(id);
1061
+ reject(new Error(`Request timeout for id: ${id}`));
1062
+ }
1063
+ },
1064
+ timeout
1065
+ );
1066
+ });
1067
+ }
1068
+ }
1069
+ };
1070
+ _WebsocketCommon.MAX_CONNECTION_DURATION = 23 * 60 * 60 * 1e3;
1071
+ var WebsocketCommon = _WebsocketCommon;
1072
+ var WebsocketAPIBase = class extends WebsocketCommon {
1073
+ constructor(configuration, connectionPool = []) {
1074
+ super(configuration, connectionPool);
1075
+ this.isConnecting = false;
1076
+ this.logger = Logger.getInstance();
1077
+ this.configuration = configuration;
1078
+ }
1079
+ /**
1080
+ * Prepares the WebSocket URL by adding optional timeUnit parameter
1081
+ * @param wsUrl The base WebSocket URL
1082
+ * @returns The formatted WebSocket URL with parameters
1083
+ */
1084
+ prepareURL(wsUrl) {
1085
+ let url = wsUrl;
1086
+ if (this?.configuration.timeUnit) {
1087
+ try {
1088
+ const _timeUnit = validateTimeUnit(this.configuration.timeUnit);
1089
+ url = `${url}${url.includes("?") ? "&" : "?"}timeUnit=${_timeUnit}`;
1090
+ } catch (err) {
1091
+ this.logger.error(err);
1092
+ }
1093
+ }
1094
+ return url;
1095
+ }
1096
+ /**
1097
+ * Processes incoming WebSocket messages
1098
+ * @param data The raw message data received
1099
+ */
1100
+ onMessage(data, connection) {
1101
+ try {
1102
+ const message = JSON.parse(data);
1103
+ const { id, status } = message;
1104
+ if (id && connection.pendingRequests.has(id)) {
1105
+ const request = connection.pendingRequests.get(id);
1106
+ connection.pendingRequests.delete(id);
1107
+ if (status && status >= 400) {
1108
+ request?.reject(message.error);
1109
+ } else {
1110
+ const response = { data: message.result };
1111
+ if (message?.rateLimits)
1112
+ response.rateLimits = message.rateLimits;
1113
+ request?.resolve(response);
1114
+ }
1115
+ } else {
1116
+ this.logger.warn("Received response for unknown or timed-out request:", message);
1117
+ }
1118
+ } catch (error) {
1119
+ this.logger.error("Failed to parse WebSocket message:", data, error);
1120
+ }
1121
+ super.onMessage(data, connection);
1122
+ }
1123
+ /**
1124
+ * Establishes a WebSocket connection to Binance
1125
+ * @returns Promise that resolves when connection is established
1126
+ * @throws Error if connection times out
1127
+ */
1128
+ connect() {
1129
+ if (this.isConnected()) {
1130
+ this.logger.info("WebSocket connection already established");
1131
+ return Promise.resolve();
1132
+ }
1133
+ return new Promise((resolve, reject) => {
1134
+ if (this.isConnecting)
1135
+ return;
1136
+ this.isConnecting = true;
1137
+ const timeout = setTimeout(() => {
1138
+ this.isConnecting = false;
1139
+ reject(new Error("Websocket connection timed out"));
1140
+ }, 1e4);
1141
+ this.connectPool(this.prepareURL(this.configuration.wsURL)).then(() => {
1142
+ clearTimeout(timeout);
1143
+ this.isConnecting = false;
1144
+ resolve();
1145
+ }).catch((error) => {
1146
+ clearTimeout(timeout);
1147
+ this.isConnecting = false;
1148
+ reject(error);
1149
+ });
1150
+ });
1151
+ }
1152
+ /**
1153
+ * Sends a message to the WebSocket API Server.
1154
+ * Supports both signed and unsigned messages.
1155
+ * @param method The API method to call
1156
+ * @param payload Message parameters and options
1157
+ * @param options Additional requests options (withApiKey, isSigned)
1158
+ * @returns Promise that resolves with the server response
1159
+ * @throws Error if not connected
1160
+ */
1161
+ sendMessage(method, payload = {}, options = {}) {
1162
+ if (!this.isConnected()) {
1163
+ return Promise.reject(new Error("Not connected"));
1164
+ }
1165
+ const id = payload.id && /^[0-9a-f]{32}$/.test(payload.id) ? payload.id : randomString();
1166
+ delete payload.id;
1167
+ let params = removeEmptyValue(payload);
1168
+ if (options.withApiKey || options.isSigned) {
1169
+ params.apiKey = this.configuration.apiKey;
1170
+ }
1171
+ if (options.isSigned) {
1172
+ params.timestamp = getTimestamp();
1173
+ params = sortObject(params);
1174
+ params.signature = getSignature(this.configuration, params);
1175
+ }
1176
+ const data = {
1177
+ id,
1178
+ method,
1179
+ params
1180
+ };
1181
+ this.logger.debug("Send message to Binance WebSocket API Server:", data);
1182
+ return this.send(JSON.stringify(data), id, true, this.configuration?.timeout);
1183
+ }
1184
+ };
1185
+ var WebsocketStreamsBase2 = class extends WebsocketCommon {
1186
+ constructor(configuration, connectionPool = []) {
1187
+ super(configuration, connectionPool);
1188
+ this.streamConnectionMap = /* @__PURE__ */ new Map();
1189
+ this.streamCallbackMap = /* @__PURE__ */ new Map();
1190
+ this.logger = Logger.getInstance();
1191
+ this.configuration = configuration;
1192
+ this.wsURL = configuration.wsURL;
1193
+ }
1194
+ /**
1195
+ * Formats the WebSocket URL for a given stream or streams.
1196
+ * @param streams - Array of stream names to include in the URL.
1197
+ * @returns The formatted WebSocket URL with the provided streams.
1198
+ */
1199
+ prepareURL(streams = []) {
1200
+ let url = `${this.wsURL}/stream?streams=${streams.join("/")}`;
1201
+ if (this.configuration?.timeUnit) {
1202
+ try {
1203
+ const _timeUnit = validateTimeUnit(this.configuration.timeUnit);
1204
+ url = `${url}${url.includes("?") ? "&" : "?"}timeUnit=${_timeUnit}`;
1205
+ } catch (err) {
1206
+ this.logger.error(err);
1207
+ }
1208
+ }
1209
+ return url;
1210
+ }
1211
+ /**
1212
+ * Formats the WebSocket URL with stream and configuration parameters to be used for reconnection.
1213
+ * @param url - The base WebSocket URL.
1214
+ * @param targetConnection - The target WebSocket connection.
1215
+ * @returns The formatted WebSocket URL with streams and optional parameters.
1216
+ */
1217
+ getReconnectURL(url, targetConnection) {
1218
+ const streams = Array.from(this.streamConnectionMap.keys()).filter(
1219
+ (stream) => this.streamConnectionMap.get(stream) === targetConnection
1220
+ );
1221
+ return this.prepareURL(streams);
1222
+ }
1223
+ /**
1224
+ * Handles subscription to streams and assigns them to specific connections
1225
+ * @param streams Array of stream names to subscribe to
1226
+ * @returns Map of connections to streams
1227
+ */
1228
+ handleStreamAssignment(streams) {
1229
+ const connectionStreamMap = /* @__PURE__ */ new Map();
1230
+ streams.forEach((stream) => {
1231
+ if (!this.streamCallbackMap.has(stream))
1232
+ this.streamCallbackMap.set(stream, /* @__PURE__ */ new Set());
1233
+ let connection = this.streamConnectionMap.get(stream);
1234
+ if (!connection || connection.closeInitiated || connection.reconnectionPending) {
1235
+ connection = this.getConnection(true);
1236
+ this.streamConnectionMap.set(stream, connection);
1237
+ }
1238
+ if (!connectionStreamMap.has(connection))
1239
+ connectionStreamMap.set(connection, []);
1240
+ connectionStreamMap.get(connection)?.push(stream);
1241
+ });
1242
+ return connectionStreamMap;
1243
+ }
1244
+ /**
1245
+ * Sends a subscription payload for specified streams on a given connection.
1246
+ * @param connection The WebSocket connection to use for sending the subscription.
1247
+ * @param streams The streams to subscribe to.
1248
+ * @param id Optional ID for the subscription.
1249
+ */
1250
+ sendSubscriptionPayload(connection, streams, id) {
1251
+ const payload = {
1252
+ method: "SUBSCRIBE",
1253
+ params: streams,
1254
+ id: id && /^[0-9a-f]{32}$/.test(id) ? id : randomString()
1255
+ };
1256
+ this.logger.info("SUBSCRIBE", payload);
1257
+ this.send(JSON.stringify(payload), void 0, false, 0, connection);
1258
+ }
1259
+ /**
1260
+ * Processes pending subscriptions for a given connection.
1261
+ * Sends all queued subscriptions in a single payload.
1262
+ * @param connection The WebSocket connection to process.
1263
+ */
1264
+ processPendingSubscriptions(connection) {
1265
+ if (connection.pendingSubscriptions && connection.pendingSubscriptions.length > 0) {
1266
+ this.logger.info("Processing queued subscriptions for connection");
1267
+ this.sendSubscriptionPayload(connection, connection.pendingSubscriptions);
1268
+ connection.pendingSubscriptions = [];
1269
+ }
1270
+ }
1271
+ /**
1272
+ * Handles incoming WebSocket messages, parsing the data and invoking the appropriate callback function.
1273
+ * If the message contains a stream name that is registered in the `streamCallbackMap`, the corresponding
1274
+ * callback function is called with the message data.
1275
+ * If the message cannot be parsed, an error is logged.
1276
+ * @param data The raw WebSocket message data.
1277
+ * @param connection The WebSocket connection that received the message.
1278
+ */
1279
+ onMessage(data, connection) {
1280
+ try {
1281
+ const parsedData = JSON.parse(data);
1282
+ const streamName = parsedData?.stream;
1283
+ if (streamName && this.streamCallbackMap.has(streamName))
1284
+ this.streamCallbackMap.get(streamName)?.forEach((callback) => callback(parsedData.data));
1285
+ } catch (error) {
1286
+ this.logger.error("Failed to parse WebSocket message:", data, error);
1287
+ }
1288
+ super.onMessage(data, connection);
1289
+ }
1290
+ /**
1291
+ * Called when the WebSocket connection is opened.
1292
+ * Processes any pending subscriptions for the target connection.
1293
+ * @param url The URL of the WebSocket connection.
1294
+ * @param targetConnection The WebSocket connection that was opened.
1295
+ * @param oldConnection The previous WebSocket connection, if any.
1296
+ * @param isRenewal Whether the connection is a renewal of an existing connection.
1297
+ */
1298
+ onOpen(url, targetConnection, oldWSConnection) {
1299
+ this.processPendingSubscriptions(targetConnection);
1300
+ super.onOpen(url, targetConnection, oldWSConnection);
1301
+ }
1302
+ /**
1303
+ * Connects to the WebSocket server and subscribes to the specified streams.
1304
+ * This method returns a Promise that resolves when the connection is established,
1305
+ * or rejects with an error if the connection fails to be established within 10 seconds.
1306
+ * @param stream - A single stream name or an array of stream names to subscribe to.
1307
+ * @returns A Promise that resolves when the connection is established.
1308
+ */
1309
+ connect(stream = []) {
1310
+ const streams = Array.isArray(stream) ? stream : [stream];
1311
+ return new Promise((resolve, reject) => {
1312
+ const timeout = setTimeout(() => {
1313
+ reject(new Error("Websocket connection timed out"));
1314
+ }, 1e4);
1315
+ this.connectPool(this.prepareURL(streams)).then(() => {
1316
+ clearTimeout(timeout);
1317
+ resolve();
1318
+ }).catch((error) => {
1319
+ clearTimeout(timeout);
1320
+ reject(error);
1321
+ });
1322
+ });
1323
+ }
1324
+ /**
1325
+ * Disconnects the WebSocket connection and clears the stream callback map.
1326
+ * This method is called to clean up the connection and associated resources.
1327
+ */
1328
+ async disconnect() {
1329
+ this.streamCallbackMap.clear();
1330
+ this.streamConnectionMap.clear();
1331
+ super.disconnect();
1332
+ }
1333
+ /**
1334
+ * Subscribes to one or multiple WebSocket streams
1335
+ * Handles both single and pool modes
1336
+ * @param stream Single stream name or array of stream names to subscribe to
1337
+ * @param id Optional subscription ID
1338
+ * @returns void
1339
+ */
1340
+ subscribe(stream, id) {
1341
+ const streams = (Array.isArray(stream) ? stream : [stream]).filter(
1342
+ (stream2) => !this.streamConnectionMap.has(stream2)
1343
+ );
1344
+ const connectionStreamMap = this.handleStreamAssignment(streams);
1345
+ connectionStreamMap.forEach((streams2, connection) => {
1346
+ if (!this.isConnected(connection)) {
1347
+ this.logger.info(
1348
+ `Connection is not ready. Queuing subscription for streams: ${streams2}`
1349
+ );
1350
+ connection.pendingSubscriptions?.push(...streams2);
1351
+ return;
1352
+ }
1353
+ this.sendSubscriptionPayload(connection, streams2, id);
1354
+ });
1355
+ }
1356
+ /**
1357
+ * Unsubscribes from one or multiple WebSocket streams
1358
+ * Handles both single and pool modes
1359
+ * @param stream Single stream name or array of stream names to unsubscribe from
1360
+ * @param id Optional unsubscription ID
1361
+ * @returns void
1362
+ */
1363
+ unsubscribe(stream, id) {
1364
+ const streams = Array.isArray(stream) ? stream : [stream];
1365
+ streams.forEach((stream2) => {
1366
+ const connection = this.streamConnectionMap.get(stream2);
1367
+ if (!connection || !connection.ws || !this.isConnected(connection)) {
1368
+ this.logger.warn(`Stream ${stream2} not associated with an active connection.`);
1369
+ return;
1370
+ }
1371
+ if (!this.streamCallbackMap.has(stream2) || this.streamCallbackMap.get(stream2)?.size === 0) {
1372
+ const payload = {
1373
+ method: "UNSUBSCRIBE",
1374
+ params: [stream2],
1375
+ id: id && /^[0-9a-f]{32}$/.test(id) ? id : randomString()
1376
+ };
1377
+ this.logger.info("UNSUBSCRIBE", payload);
1378
+ this.send(JSON.stringify(payload), void 0, false, 0, connection);
1379
+ this.streamConnectionMap.delete(stream2);
1380
+ this.streamCallbackMap.delete(stream2);
1381
+ }
1382
+ });
1383
+ }
1384
+ /**
1385
+ * Checks if the specified stream is currently subscribed.
1386
+ * @param stream - The name of the stream to check.
1387
+ * @returns `true` if the stream is currently subscribed, `false` otherwise.
1388
+ */
1389
+ isSubscribed(stream) {
1390
+ return this.streamConnectionMap.has(stream);
1391
+ }
1392
+ };
1393
+ export {
1394
+ ALGO_REST_API_PROD_URL,
1395
+ ALGO_REST_API_TESTNET_URL,
1396
+ AUTO_INVEST_REST_API_PROD_URL,
1397
+ AUTO_INVEST_REST_API_TESTNET_URL,
1398
+ BadRequestError,
1399
+ C2C_REST_API_PROD_URL,
1400
+ C2C_REST_API_TESTNET_URL,
1401
+ CONVERT_REST_API_PROD_URL,
1402
+ CONVERT_REST_API_TESTNET_URL,
1403
+ COPY_TRADING_REST_API_PROD_URL,
1404
+ COPY_TRADING_REST_API_TESTNET_URL,
1405
+ CRYPTO_LOAN_REST_API_PROD_URL,
1406
+ CRYPTO_LOAN_REST_API_TESTNET_URL,
1407
+ ConfigurationRestAPI,
1408
+ ConfigurationWebsocketAPI,
1409
+ ConfigurationWebsocketStreams,
1410
+ ConnectorClientError,
1411
+ DERIVATIVES_TRADING_COIN_FUTURES_REST_API_PROD_URL,
1412
+ DERIVATIVES_TRADING_COIN_FUTURES_REST_API_TESTNET_URL,
1413
+ DERIVATIVES_TRADING_COIN_FUTURES_WS_API_PROD_URL,
1414
+ DERIVATIVES_TRADING_COIN_FUTURES_WS_API_TESTNET_URL,
1415
+ DERIVATIVES_TRADING_COIN_FUTURES_WS_STREAMS_PROD_URL,
1416
+ DERIVATIVES_TRADING_COIN_FUTURES_WS_STREAMS_TESTNET_URL,
1417
+ DERIVATIVES_TRADING_OPTIONS_REST_API_PROD_URL,
1418
+ DERIVATIVES_TRADING_OPTIONS_WS_STREAMS_PROD_URL,
1419
+ DERIVATIVES_TRADING_PORTFOLIO_MARGIN_PRO_REST_API_PROD_URL,
1420
+ DERIVATIVES_TRADING_PORTFOLIO_MARGIN_PRO_REST_API_TESTNET_URL,
1421
+ DERIVATIVES_TRADING_PORTFOLIO_MARGIN_REST_API_PROD_URL,
1422
+ DERIVATIVES_TRADING_PORTFOLIO_MARGIN_REST_API_TESTNET_URL,
1423
+ DERIVATIVES_TRADING_USDS_FUTURES_REST_API_PROD_URL,
1424
+ DERIVATIVES_TRADING_USDS_FUTURES_REST_API_TESTNET_URL,
1425
+ DERIVATIVES_TRADING_USDS_FUTURES_WS_API_PROD_URL,
1426
+ DERIVATIVES_TRADING_USDS_FUTURES_WS_API_TESTNET_URL,
1427
+ DERIVATIVES_TRADING_USDS_FUTURES_WS_STREAMS_PROD_URL,
1428
+ DERIVATIVES_TRADING_USDS_FUTURES_WS_STREAMS_TESTNET_URL,
1429
+ DUAL_INVESTMENT_REST_API_PROD_URL,
1430
+ DUAL_INVESTMENT_REST_API_TESTNET_URL,
1431
+ FIAT_REST_API_PROD_URL,
1432
+ FIAT_REST_API_TESTNET_URL,
1433
+ ForbiddenError,
1434
+ GIFT_CARD_REST_API_PROD_URL,
1435
+ GIFT_CARD_REST_API_TESTNET_URL,
1436
+ LogLevel,
1437
+ Logger,
1438
+ MARGIN_TRADING_REST_API_PROD_URL,
1439
+ MARGIN_TRADING_REST_API_TESTNET_URL,
1440
+ MINING_REST_API_PROD_URL,
1441
+ MINING_REST_API_TESTNET_URL,
1442
+ NFT_REST_API_PROD_URL,
1443
+ NFT_REST_API_TESTNET_URL,
1444
+ NetworkError,
1445
+ NotFoundError,
1446
+ PAY_REST_API_PROD_URL,
1447
+ PAY_REST_API_TESTNET_URL,
1448
+ REBATE_REST_API_PROD_URL,
1449
+ REBATE_REST_API_TESTNET_URL,
1450
+ RateLimitBanError,
1451
+ RequiredError,
1452
+ SIMPLE_EARN_REST_API_PROD_URL,
1453
+ SIMPLE_EARN_REST_API_TESTNET_URL,
1454
+ SPOT_REST_API_MARKET_URL,
1455
+ SPOT_REST_API_PROD_URL,
1456
+ SPOT_REST_API_TESTNET_URL,
1457
+ SPOT_WS_API_PROD_URL,
1458
+ SPOT_WS_API_TESTNET_URL,
1459
+ SPOT_WS_STREAMS_MARKET_URL,
1460
+ SPOT_WS_STREAMS_PROD_URL,
1461
+ SPOT_WS_STREAMS_TESTNET_URL,
1462
+ STAKING_REST_API_PROD_URL,
1463
+ STAKING_REST_API_TESTNET_URL,
1464
+ SUB_ACCOUNT_REST_API_PROD_URL,
1465
+ SUB_ACCOUNT_REST_API_TESTNET_URL,
1466
+ ServerError,
1467
+ TimeUnit,
1468
+ TooManyRequestsError,
1469
+ UnauthorizedError,
1470
+ VIP_LOAN_REST_API_PROD_URL,
1471
+ VIP_LOAN_REST_API_TESTNET_URL,
1472
+ WALLET_REST_API_PROD_URL,
1473
+ WALLET_REST_API_TESTNET_URL,
1474
+ WebsocketAPIBase,
1475
+ WebsocketCommon,
1476
+ WebsocketEventEmitter,
1477
+ WebsocketStreamsBase2 as WebsocketStreamsBase,
1478
+ assertParamExists,
1479
+ buildQueryString,
1480
+ createStreamHandler,
1481
+ delay,
1482
+ getSignature,
1483
+ getTimestamp,
1484
+ httpRequestFunction,
1485
+ parseRateLimitHeaders,
1486
+ randomString,
1487
+ removeEmptyValue,
1488
+ replaceWebsocketStreamsPlaceholders,
1489
+ sendRequest,
1490
+ setSearchParams,
1491
+ shouldRetryRequest,
1492
+ sortObject,
1493
+ toPathString,
1494
+ validateTimeUnit
1495
+ };
1496
+ //# sourceMappingURL=index.mjs.map