@usherlabs/cex-broker 0.2.13 → 0.2.14

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.js CHANGED
@@ -293238,15 +293238,12 @@ function createExecuteActionHandler(deps) {
293238
293238
  import * as grpc13 from "@grpc/grpc-js";
293239
293239
 
293240
293240
  // src/helpers/binance-user-data-stream.ts
293241
+ import { Buffer as Buffer2 } from "buffer";
293241
293242
  import { createHmac } from "crypto";
293243
+ import WebSocket2 from "ws";
293242
293244
  var BINANCE_SPOT_WS_API_URL = "wss://ws-api.binance.com:443/ws-api/v3";
293243
- function getWebSocketConstructor() {
293244
- const WebSocketCtor = globalThis.WebSocket;
293245
- if (!WebSocketCtor) {
293246
- throw new Error("WebSocket is not available in this runtime");
293247
- }
293248
- return WebSocketCtor;
293249
- }
293245
+ var DEFAULT_BINANCE_USER_DATA_MAX_BUFFERED_EVENTS = 16;
293246
+ var createWebSocket = (url3) => new WebSocket2(url3);
293250
293247
  function getExchangeString(exchange, key) {
293251
293248
  const value = exchange[key];
293252
293249
  if (typeof value !== "string" || value.length === 0) {
@@ -293272,24 +293269,106 @@ function getBinanceSpotWsApiUrl(exchange) {
293272
293269
  const urls = exchange.urls;
293273
293270
  return urls?.api?.ws?.["ws-api"]?.spot ?? BINANCE_SPOT_WS_API_URL;
293274
293271
  }
293272
+ function getRecord(value) {
293273
+ return typeof value === "object" && value !== null ? value : null;
293274
+ }
293275
+ function getMessage(value) {
293276
+ if (value instanceof Error) {
293277
+ return value.message;
293278
+ }
293279
+ if (typeof value === "string" && value.length > 0) {
293280
+ return value;
293281
+ }
293282
+ const record2 = getRecord(value);
293283
+ const message = record2?.message;
293284
+ return typeof message === "string" && message.length > 0 ? message : null;
293285
+ }
293286
+ function getOptionalExchangeString(exchange, key) {
293287
+ const value = exchange[key];
293288
+ return typeof value === "string" && value.length > 0 ? value : null;
293289
+ }
293290
+ function redactDiagnosticMessage(message, secretValues) {
293291
+ let redacted = message;
293292
+ for (const value of secretValues) {
293293
+ if (value.length > 0) {
293294
+ redacted = redacted.split(value).join("[redacted]");
293295
+ }
293296
+ }
293297
+ return redacted.replace(/(\b(?:apiKey|secret|signature)\b\s*=\s*)[^\s&,;)]+/gi, "$1[redacted]").replace(/("(?:apiKey|secret|signature)"\s*:\s*")[^"]*(")/gi, "$1[redacted]$2");
293298
+ }
293299
+ function formatBinanceUserDataWebSocketError(event, secretValues) {
293300
+ const record2 = getRecord(event);
293301
+ const message = getMessage(record2?.error) ?? getMessage(record2?.message) ?? getMessage(event);
293302
+ const safeMessage = message === null ? null : redactDiagnosticMessage(message, secretValues);
293303
+ return new Error(safeMessage ? `Binance user-data WebSocket error: ${safeMessage}` : "Binance user-data WebSocket error");
293304
+ }
293305
+ function getCloseReason(value) {
293306
+ if (typeof value === "string") {
293307
+ return value.length > 0 ? value : null;
293308
+ }
293309
+ if (Buffer2.isBuffer(value)) {
293310
+ const reason = value.toString("utf8");
293311
+ return reason.length > 0 ? reason : null;
293312
+ }
293313
+ if (value instanceof Uint8Array) {
293314
+ const reason = Buffer2.from(value).toString("utf8");
293315
+ return reason.length > 0 ? reason : null;
293316
+ }
293317
+ return null;
293318
+ }
293319
+ function formatBinanceUserDataWebSocketClose(codeOrEvent, reasonOrUndefined, secretValues) {
293320
+ const record2 = getRecord(codeOrEvent);
293321
+ const code = record2 ? record2.code : codeOrEvent;
293322
+ const reason = getCloseReason(record2 ? record2.reason : reasonOrUndefined);
293323
+ const safeReason = reason === null ? null : redactDiagnosticMessage(reason, secretValues);
293324
+ const details = [
293325
+ typeof code === "number" || typeof code === "string" ? `code=${code}` : null,
293326
+ safeReason ? `reason=${safeReason}` : null
293327
+ ].filter((detail) => detail !== null);
293328
+ return new Error(details.length > 0 ? `Binance user-data WebSocket closed unexpectedly (${details.join(", ")})` : "Binance user-data WebSocket closed unexpectedly");
293329
+ }
293330
+ function decodeMessageData(data) {
293331
+ if (typeof data === "string") {
293332
+ return data;
293333
+ }
293334
+ if (Buffer2.isBuffer(data)) {
293335
+ return data.toString("utf8");
293336
+ }
293337
+ if (data instanceof ArrayBuffer) {
293338
+ return Buffer2.from(data).toString("utf8");
293339
+ }
293340
+ if (ArrayBuffer.isView(data)) {
293341
+ return Buffer2.from(data.buffer, data.byteOffset, data.byteLength).toString("utf8");
293342
+ }
293343
+ if (Array.isArray(data) && data.every((item) => Buffer2.isBuffer(item))) {
293344
+ return Buffer2.concat(data).toString("utf8");
293345
+ }
293346
+ return data;
293347
+ }
293275
293348
 
293276
293349
  class BinanceSpotUserDataStream {
293277
293350
  exchange;
293278
293351
  ws;
293352
+ secretValues;
293279
293353
  requestId = `user-data-${Date.now()}-${Math.random()}`;
293354
+ maxBufferedEvents;
293280
293355
  queue = [];
293281
293356
  waiters = [];
293282
293357
  closed = false;
293283
293358
  closeError = null;
293284
293359
  subscriptionId = null;
293285
- constructor(exchange) {
293360
+ constructor(exchange, options = {}) {
293286
293361
  this.exchange = exchange;
293287
- const WebSocketCtor = getWebSocketConstructor();
293288
- this.ws = new WebSocketCtor(getBinanceSpotWsApiUrl(exchange));
293289
- this.ws.onopen = () => this.subscribe();
293290
- this.ws.onmessage = (message) => this.handleMessage(message.data);
293291
- this.ws.onerror = (error48) => this.fail(error48 instanceof Error ? error48 : new Error("Binance user-data WebSocket error"));
293292
- this.ws.onclose = () => this.close();
293362
+ this.maxBufferedEvents = options.maxBufferedEvents ?? DEFAULT_BINANCE_USER_DATA_MAX_BUFFERED_EVENTS;
293363
+ this.secretValues = [
293364
+ getOptionalExchangeString(exchange, "apiKey"),
293365
+ getOptionalExchangeString(exchange, "secret")
293366
+ ].filter((value) => value !== null);
293367
+ this.ws = createWebSocket(getBinanceSpotWsApiUrl(exchange));
293368
+ this.ws.on("open", () => this.subscribe());
293369
+ this.ws.on("message", (data) => this.handleMessage(data));
293370
+ this.ws.on("error", (error48) => this.fail(formatBinanceUserDataWebSocketError(error48, this.secretValues)));
293371
+ this.ws.on("close", (code, reason) => this.handleClose(code, reason));
293293
293372
  }
293294
293373
  async* [Symbol.asyncIterator]() {
293295
293374
  while (true) {
@@ -293305,11 +293384,18 @@ class BinanceSpotUserDataStream {
293305
293384
  return;
293306
293385
  }
293307
293386
  this.closed = true;
293387
+ this.queue.length = 0;
293308
293388
  try {
293309
293389
  this.ws.close();
293310
293390
  } catch {}
293311
293391
  this.flushWaiters();
293312
293392
  }
293393
+ handleClose(code, reason) {
293394
+ if (this.closed) {
293395
+ return;
293396
+ }
293397
+ this.fail(formatBinanceUserDataWebSocketClose(code, reason, this.secretValues));
293398
+ }
293313
293399
  subscribe() {
293314
293400
  const apiKey = getExchangeString(this.exchange, "apiKey");
293315
293401
  const signedParams = signUserDataStreamParams(this.exchange, {
@@ -293323,9 +293409,13 @@ class BinanceSpotUserDataStream {
293323
293409
  }));
293324
293410
  }
293325
293411
  handleMessage(data) {
293412
+ if (this.closed) {
293413
+ return;
293414
+ }
293326
293415
  let message;
293327
293416
  try {
293328
- message = typeof data === "string" ? JSON.parse(data) : data;
293417
+ const decodedData = decodeMessageData(data);
293418
+ message = typeof decodedData === "string" ? JSON.parse(decodedData) : decodedData;
293329
293419
  } catch (error48) {
293330
293420
  this.fail(error48 instanceof Error ? error48 : new Error("Invalid Binance user-data message"));
293331
293421
  return;
@@ -293348,11 +293438,18 @@ class BinanceSpotUserDataStream {
293348
293438
  this.push({ subscriptionId, event: message.event });
293349
293439
  }
293350
293440
  push(event) {
293441
+ if (this.closed) {
293442
+ return;
293443
+ }
293351
293444
  const waiter = this.waiters.shift();
293352
293445
  if (waiter) {
293353
293446
  waiter.resolve(event);
293354
293447
  return;
293355
293448
  }
293449
+ if (this.queue.length >= this.maxBufferedEvents) {
293450
+ this.fail(new Error(`Binance user-data stream buffered event limit exceeded (${this.maxBufferedEvents}); downstream consumer is not keeping up`));
293451
+ return;
293452
+ }
293356
293453
  this.queue.push(event);
293357
293454
  }
293358
293455
  nextEvent() {
@@ -293376,6 +293473,7 @@ class BinanceSpotUserDataStream {
293376
293473
  }
293377
293474
  this.closeError = error48;
293378
293475
  this.closed = true;
293476
+ this.queue.length = 0;
293379
293477
  this.flushWaiters();
293380
293478
  try {
293381
293479
  this.ws.close();
@@ -293403,16 +293501,49 @@ function isBinanceOrderUserDataEvent(event) {
293403
293501
  function isBinanceSpotAccountSubscription(cex3, subscriptionType, marketTypeInput) {
293404
293502
  return cex3 === "binance" && parseMarketType(marketTypeInput) === "spot" && (subscriptionType === SubscriptionType.BALANCE || subscriptionType === SubscriptionType.ORDERS);
293405
293503
  }
293406
- function writeSubscribeFrame(call, isClosed, frame) {
293504
+ function waitForSubscribeDrain(call, isClosed) {
293505
+ if (isClosed() || call.destroyed) {
293506
+ return Promise.resolve(false);
293507
+ }
293508
+ return new Promise((resolve) => {
293509
+ let settled = false;
293510
+ const cleanup = () => {
293511
+ call.off("drain", onDrain);
293512
+ call.off("close", onClosed);
293513
+ call.off("cancelled", onClosed);
293514
+ call.off("end", onClosed);
293515
+ call.off("error", onClosed);
293516
+ };
293517
+ const settle2 = (drained) => {
293518
+ if (settled) {
293519
+ return;
293520
+ }
293521
+ settled = true;
293522
+ cleanup();
293523
+ resolve(drained && !isClosed() && !call.destroyed);
293524
+ };
293525
+ const onDrain = () => settle2(true);
293526
+ const onClosed = () => settle2(false);
293527
+ call.once("drain", onDrain);
293528
+ call.once("close", onClosed);
293529
+ call.once("cancelled", onClosed);
293530
+ call.once("end", onClosed);
293531
+ call.once("error", onClosed);
293532
+ });
293533
+ }
293534
+ async function writeSubscribeFrame(call, isClosed, frame) {
293407
293535
  if (isClosed() || call.destroyed) {
293408
293536
  return false;
293409
293537
  }
293410
- call.write(frame);
293411
- return true;
293538
+ const canContinue = call.write(frame);
293539
+ if (canContinue) {
293540
+ return true;
293541
+ }
293542
+ return waitForSubscribeDrain(call, isClosed);
293412
293543
  }
293413
- function writeSubscribeError(call, isClosed, frame) {
293414
- writeSubscribeFrame(call, isClosed, frame);
293415
- if (!isClosed() && !call.destroyed) {
293544
+ async function writeSubscribeError(call, isClosed, frame) {
293545
+ const frameWritten = await writeSubscribeFrame(call, isClosed, frame);
293546
+ if (frameWritten && !isClosed() && !call.destroyed) {
293416
293547
  call.end();
293417
293548
  }
293418
293549
  }
@@ -293459,7 +293590,7 @@ async function streamBinanceUserData(call, broker, symbol2, subscriptionType, is
293459
293590
  continue;
293460
293591
  }
293461
293592
  }
293462
- if (!writeSubscribeFrame(call, isClosed, {
293593
+ if (!await writeSubscribeFrame(call, isClosed, {
293463
293594
  data: JSON.stringify({
293464
293595
  subscriptionId: message.subscriptionId,
293465
293596
  event
@@ -293478,7 +293609,7 @@ async function streamBinanceUserData(call, broker, symbol2, subscriptionType, is
293478
293609
  async function runCcxtSubscribeLoop(call, isClosed, symbol2, subscriptionType, watch) {
293479
293610
  while (!isClosed()) {
293480
293611
  const data = await watch();
293481
- if (!writeSubscribeFrame(call, isClosed, {
293612
+ if (!await writeSubscribeFrame(call, isClosed, {
293482
293613
  data: JSON.stringify(data),
293483
293614
  timestamp: Date.now(),
293484
293615
  symbol: symbol2,
@@ -293544,7 +293675,7 @@ function createSubscribeHandler(deps) {
293544
293675
  type: subscriptionTypeName
293545
293676
  });
293546
293677
  if (!cex3 || !symbol2) {
293547
- writeSubscribeError(call, isStreamClosed, {
293678
+ await writeSubscribeError(call, isStreamClosed, {
293548
293679
  data: JSON.stringify({
293549
293680
  error: "cex, symbol, and type are required"
293550
293681
  }),
@@ -293558,7 +293689,7 @@ function createSubscribeHandler(deps) {
293558
293689
  const selectedBroker = selectBroker(brokers[normalizedCex], metadata) ?? createBroker(normalizedCex, metadata);
293559
293690
  const broker = selectedBroker ?? createPublicBroker(normalizedCex);
293560
293691
  if (!broker) {
293561
- writeSubscribeError(call, isStreamClosed, {
293692
+ await writeSubscribeError(call, isStreamClosed, {
293562
293693
  data: JSON.stringify({
293563
293694
  error: "Exchange not registered and no API metadata found"
293564
293695
  }),
@@ -293571,7 +293702,7 @@ function createSubscribeHandler(deps) {
293571
293702
  const resolvedSymbol = await resolveSubscriptionSymbol(broker, symbol2, options?.marketType);
293572
293703
  if (isBinanceSpotAccountSubscription(normalizedCex, subscriptionType, options?.marketType)) {
293573
293704
  if (!selectedBroker) {
293574
- writeSubscribeError(call, isStreamClosed, {
293705
+ await writeSubscribeError(call, isStreamClosed, {
293575
293706
  data: JSON.stringify({
293576
293707
  error: "Binance account subscriptions require API credentials"
293577
293708
  }),
@@ -293591,7 +293722,7 @@ function createSubscribeHandler(deps) {
293591
293722
  const depthLimit = parseOptionalDepthLimit(options?.depthLimit ?? options?.limit);
293592
293723
  const orderbook = depthLimit === undefined ? await broker.watchOrderBook(resolvedSymbol) : await broker.watchOrderBook(resolvedSymbol, depthLimit);
293593
293724
  const receivedTimestamp = Date.now();
293594
- if (!writeSubscribeFrame(call, isStreamClosed, {
293725
+ if (!await writeSubscribeFrame(call, isStreamClosed, {
293595
293726
  data: JSON.stringify(normalizeOrderBookSnapshot(orderbook, {
293596
293727
  exchange: normalizedCex,
293597
293728
  symbol: resolvedSymbol,
@@ -293608,7 +293739,7 @@ function createSubscribeHandler(deps) {
293608
293739
  } catch (error48) {
293609
293740
  log.error(`Error fetching orderbook for ${resolvedSymbol} on ${cex3}:`, error48);
293610
293741
  const message = getErrorMessage(error48);
293611
- writeSubscribeError(call, isStreamClosed, {
293742
+ await writeSubscribeError(call, isStreamClosed, {
293612
293743
  data: JSON.stringify({
293613
293744
  error: `Failed to fetch orderbook: ${message}`
293614
293745
  }),
@@ -293624,7 +293755,7 @@ function createSubscribeHandler(deps) {
293624
293755
  } catch (error48) {
293625
293756
  const message = getErrorMessage(error48);
293626
293757
  log.error(`Error fetching trades for ${resolvedSymbol} on ${cex3}:`, error48);
293627
- writeSubscribeError(call, isStreamClosed, {
293758
+ await writeSubscribeError(call, isStreamClosed, {
293628
293759
  data: JSON.stringify({
293629
293760
  error: `Failed to fetch trades: ${message}`
293630
293761
  }),
@@ -293640,7 +293771,7 @@ function createSubscribeHandler(deps) {
293640
293771
  } catch (error48) {
293641
293772
  const message = getErrorMessage(error48);
293642
293773
  log.error(`Error fetching ticker for ${resolvedSymbol} on ${cex3}:`, error48);
293643
- writeSubscribeError(call, isStreamClosed, {
293774
+ await writeSubscribeError(call, isStreamClosed, {
293644
293775
  data: JSON.stringify({
293645
293776
  error: `Failed to fetch ticker: ${message}`
293646
293777
  }),
@@ -293657,7 +293788,7 @@ function createSubscribeHandler(deps) {
293657
293788
  } catch (error48) {
293658
293789
  log.error(`Error fetching OHLCV for ${resolvedSymbol} on ${cex3}:`, error48);
293659
293790
  const message = getErrorMessage(error48);
293660
- writeSubscribeError(call, isStreamClosed, {
293791
+ await writeSubscribeError(call, isStreamClosed, {
293661
293792
  data: JSON.stringify({
293662
293793
  error: `Failed to fetch OHLCV: ${message}`
293663
293794
  }),
@@ -293673,7 +293804,7 @@ function createSubscribeHandler(deps) {
293673
293804
  } catch (error48) {
293674
293805
  const message = getErrorMessage(error48);
293675
293806
  log.error(`Error fetching balance for ${cex3}:`, error48);
293676
- writeSubscribeError(call, isStreamClosed, {
293807
+ await writeSubscribeError(call, isStreamClosed, {
293677
293808
  data: JSON.stringify({
293678
293809
  error: `Failed to fetch balance: ${message}`
293679
293810
  }),
@@ -293689,7 +293820,7 @@ function createSubscribeHandler(deps) {
293689
293820
  } catch (error48) {
293690
293821
  log.error(`Error fetching orders for ${resolvedSymbol} on ${cex3}:`, error48);
293691
293822
  const message = getErrorMessage(error48);
293692
- writeSubscribeError(call, isStreamClosed, {
293823
+ await writeSubscribeError(call, isStreamClosed, {
293693
293824
  data: JSON.stringify({
293694
293825
  error: `Failed to fetch orders: ${message}`
293695
293826
  }),
@@ -293700,7 +293831,7 @@ function createSubscribeHandler(deps) {
293700
293831
  }
293701
293832
  break;
293702
293833
  default:
293703
- writeSubscribeError(call, isStreamClosed, {
293834
+ await writeSubscribeError(call, isStreamClosed, {
293704
293835
  data: JSON.stringify({ error: "Invalid subscription type" }),
293705
293836
  timestamp: Date.now(),
293706
293837
  symbol: symbol2,
@@ -293710,7 +293841,7 @@ function createSubscribeHandler(deps) {
293710
293841
  } catch (error48) {
293711
293842
  log.error("Error in Subscribe stream:", error48);
293712
293843
  const message = getErrorMessage(error48);
293713
- writeSubscribeError(call, isStreamClosed, {
293844
+ await writeSubscribeError(call, isStreamClosed, {
293714
293845
  data: JSON.stringify({ error: `Internal server error: ${message}` }),
293715
293846
  timestamp: Date.now(),
293716
293847
  symbol: "",
@@ -294185,4 +294316,4 @@ export {
294185
294316
  CEXBroker as default
294186
294317
  };
294187
294318
 
294188
- //# debugId=18334E4E9BE8A34F64756E2164756E21
294319
+ //# debugId=6D0E11DC0BFB633164756E2164756E21