nsekit 0.3.0 → 0.3.2

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
@@ -19448,8 +19448,9 @@ class InstrumentMaster {
19448
19448
  if (local.ok)
19449
19449
  return local;
19450
19450
  if (brokerInstruments.capabilities.searchAPI && brokerInstruments.searchAPI) {
19451
- const { symbol } = this.parseInput(input);
19452
- const apiResult = await brokerInstruments.searchAPI(symbol);
19451
+ const { exchange, symbol } = this.parseInput(input);
19452
+ const inferredExchange = exchange ?? (/-(?:CE|PE|FUT)-/.test(symbol) ? "NFO" : undefined);
19453
+ const apiResult = await brokerInstruments.searchAPI(symbol, inferredExchange);
19453
19454
  if (apiResult.ok && apiResult.value.length > 0) {
19454
19455
  for (const entry of apiResult.value)
19455
19456
  this.mergeEntry(brokerId, entry);
@@ -20192,8 +20193,8 @@ function safeSide(raw) {
20192
20193
  }
20193
20194
  function parseTimestamp(ts) {
20194
20195
  if (!ts)
20195
- return Date.now();
20196
- return new Date(ts).getTime();
20196
+ return 0;
20197
+ return new Date(ts).getTime() || 0;
20197
20198
  }
20198
20199
  function inferInstrumentType(symbol, exchange) {
20199
20200
  if (exchange === "NSE" || exchange === "BSE")
@@ -21012,7 +21013,7 @@ var VALIDITY_FROM_SHOONYA = {
21012
21013
  IOC: "IOC"
21013
21014
  };
21014
21015
  var SHOONYA_API_BASE = "https://api.shoonya.com/NorenWClientTP";
21015
- var SHOONYA_WS_URL = "wss://api.shoonya.com/NorenWSTP";
21016
+ var SHOONYA_WS_URL = "wss://api.shoonya.com/NorenWSTP/";
21016
21017
  var SHOONYA_INSTRUMENTS_BASE = "https://api.shoonya.com/";
21017
21018
  var SHOONYA_INSTRUMENT_FILES = {
21018
21019
  NSE: "NSE_symbols.txt.zip",
@@ -21169,13 +21170,18 @@ function inferSide2(qty) {
21169
21170
  }
21170
21171
  function parseTimestamp2(ts) {
21171
21172
  if (!ts)
21172
- return Date.now();
21173
+ return 0;
21173
21174
  const asNum = Number(ts);
21174
21175
  if (!isNaN(asNum) && asNum > 1000000000000)
21175
21176
  return asNum;
21176
21177
  if (!isNaN(asNum) && asNum > 1e9)
21177
21178
  return asNum * 1000;
21178
- return new Date(ts).getTime() || Date.now();
21179
+ const shoonya = ts.match(/^(\d{2}:\d{2}:\d{2})\s+(\d{2})-(\d{2})-(\d{4})$/);
21180
+ if (shoonya) {
21181
+ const [, time, dd, mm, yyyy] = shoonya;
21182
+ return new Date(`${yyyy}-${mm}-${dd}T${time}`).getTime() || 0;
21183
+ }
21184
+ return new Date(ts).getTime() || 0;
21179
21185
  }
21180
21186
  function toOrderParams2(unified, uid, actid) {
21181
21187
  const params = {
@@ -21203,17 +21209,21 @@ function fromPosition2(pos) {
21203
21209
  const buyQty = num(pos.daybuyqty);
21204
21210
  const sellQty = num(pos.daysellqty);
21205
21211
  const realizedPnl = num(pos.rpnl);
21206
- const unrealizedPnl = num(pos.urmtom);
21212
+ const ltp = num(pos.lp);
21213
+ const entryPrice = num(pos.upldprc);
21214
+ const avgPrice = entryPrice > 0 ? entryPrice : num(pos.netavgprc);
21215
+ const unrealizedPnl = entryPrice > 0 ? (ltp - entryPrice) * netQty : num(pos.urmtom);
21216
+ const tradingSymbol = pos.tsym.replace(/-EQ$|-BE$|-BL$|-IL$|-IQ$/, "");
21207
21217
  return {
21208
21218
  broker: "finvasia",
21209
- tradingSymbol: pos.tsym,
21219
+ tradingSymbol,
21210
21220
  exchange: safeExchange2(pos.exch),
21211
21221
  side: inferSide2(netQty),
21212
21222
  quantity: Math.abs(netQty),
21213
21223
  buyQty,
21214
21224
  sellQty,
21215
- avgPrice: num(pos.netavgprc),
21216
- ltp: num(pos.lp),
21225
+ avgPrice,
21226
+ ltp,
21217
21227
  pnl: realizedPnl + unrealizedPnl,
21218
21228
  realizedPnl,
21219
21229
  unrealizedPnl,
@@ -21250,7 +21260,7 @@ function fromTick2(tick, symbolLookup) {
21250
21260
  return {
21251
21261
  broker: "finvasia",
21252
21262
  token: tick.tk,
21253
- tradingSymbol: symbolLookup ?? tick.tk,
21263
+ tradingSymbol: symbolLookup ?? tick.ts ?? tick.tk,
21254
21264
  ltp: num(tick.lp),
21255
21265
  open: num(tick.o),
21256
21266
  high: num(tick.h),
@@ -21265,15 +21275,17 @@ function fromTick2(tick, symbolLookup) {
21265
21275
  timestamp: parseTimestamp2(tick.ft)
21266
21276
  };
21267
21277
  }
21268
- function fromHolding2(holding) {
21278
+ function fromHolding2(holding, ltp = 0) {
21269
21279
  const first = holding.exch_tsym?.[0];
21270
21280
  const qty = num(holding.holdqty) + num(holding.dpqty) + num(holding.npoadqty) + num(holding.btstqty) - num(holding.usedqty);
21271
21281
  const avgPrice = num(holding.upldprc);
21272
- const ltp = num(holding.lp);
21282
+ ltp = num(holding.lp) || num(holding.c) || ltp;
21273
21283
  const pnl = (ltp - avgPrice) * qty;
21284
+ const rawSymbol = first?.tsym ?? "";
21285
+ const tradingSymbol = rawSymbol.replace(/-EQ$|-BE$|-BL$|-IL$|-IQ$/, "");
21274
21286
  return {
21275
21287
  broker: "finvasia",
21276
- tradingSymbol: first?.tsym ?? "",
21288
+ tradingSymbol,
21277
21289
  exchange: safeExchange2(first?.exch ?? "NSE"),
21278
21290
  isin: holding.isin ?? "",
21279
21291
  quantity: qty,
@@ -21312,6 +21324,7 @@ function fromQuote2(quote) {
21312
21324
  }
21313
21325
 
21314
21326
  // src/brokers/finvasia/finvasia-socket.ts
21327
+ import WS from "ws";
21315
21328
  var subIdCounter3 = 0;
21316
21329
  function nextSubId3() {
21317
21330
  subIdCounter3 += 1;
@@ -21329,21 +21342,24 @@ class FinvasiaSocket {
21329
21342
  reconnectAttempts = 0;
21330
21343
  maxReconnects = 5;
21331
21344
  reconnectTimer = null;
21332
- constructor(userId, accessToken) {
21345
+ onAuthFailed;
21346
+ constructor(userId, accessToken, onAuthFailed) {
21333
21347
  this.userId = userId;
21334
21348
  this.accessToken = accessToken;
21349
+ this.onAuthFailed = onAuthFailed;
21335
21350
  }
21336
21351
  connect() {
21337
21352
  if (this.state === "CONNECTED" || this.state === "CONNECTING")
21338
21353
  return;
21339
21354
  this.state = "CONNECTING";
21340
21355
  try {
21341
- this.ws = new WebSocket(SHOONYA_WS_URL);
21356
+ this.ws = new WS(SHOONYA_WS_URL);
21342
21357
  } catch {
21343
21358
  this.state = "DISCONNECTED";
21344
21359
  return;
21345
21360
  }
21346
- this.ws.onopen = () => {
21361
+ this.ws.on("open", () => {
21362
+ console.log(`[FinvasiaSocket] WS open, sending auth for uid=${this.userId}`);
21347
21363
  const authMsg = JSON.stringify({
21348
21364
  t: "c",
21349
21365
  uid: this.userId,
@@ -21352,18 +21368,23 @@ class FinvasiaSocket {
21352
21368
  source: "API"
21353
21369
  });
21354
21370
  this.ws.send(authMsg);
21355
- };
21356
- this.ws.onmessage = (event) => {
21371
+ });
21372
+ this.ws.on("message", (raw) => {
21357
21373
  try {
21358
- const data = JSON.parse(String(event.data));
21374
+ const text = raw.toString();
21375
+ console.log(`[FinvasiaSocket] WS msg: ${text.slice(0, 200)}`);
21376
+ const data = JSON.parse(text);
21359
21377
  this.handleMessage(data);
21360
21378
  } catch {}
21361
- };
21362
- this.ws.onclose = () => {
21379
+ });
21380
+ this.ws.on("close", (code, reason) => {
21381
+ console.error(`[FinvasiaSocket] WS closed: code=${code}, reason=${reason.toString()}`);
21363
21382
  this.state = "DISCONNECTED";
21364
21383
  this.attemptReconnect();
21365
- };
21366
- this.ws.onerror = () => {};
21384
+ });
21385
+ this.ws.on("error", (err) => {
21386
+ console.error(`[FinvasiaSocket] WS error:`, err.message);
21387
+ });
21367
21388
  }
21368
21389
  disconnect() {
21369
21390
  this.clearReconnectTimer();
@@ -21375,6 +21396,18 @@ class FinvasiaSocket {
21375
21396
  this.state = "DISCONNECTED";
21376
21397
  this.subscribedTokens.clear();
21377
21398
  }
21399
+ reconnect(newToken) {
21400
+ this.clearReconnectTimer();
21401
+ if (newToken)
21402
+ this.accessToken = newToken;
21403
+ this.reconnectAttempts = 0;
21404
+ if (this.ws) {
21405
+ this.ws.close();
21406
+ this.ws = null;
21407
+ }
21408
+ this.state = "DISCONNECTED";
21409
+ this.connect();
21410
+ }
21378
21411
  subscribe(tokens, callback, symbolMap) {
21379
21412
  const sub = {
21380
21413
  id: nextSubId3(),
@@ -21385,7 +21418,11 @@ class FinvasiaSocket {
21385
21418
  for (const t of tokens) {
21386
21419
  this.subscribedTokens.add(t);
21387
21420
  if (symbolMap?.has(t)) {
21388
- this.tokenToSymbol.set(t, symbolMap.get(t));
21421
+ const sym = symbolMap.get(t);
21422
+ this.tokenToSymbol.set(t, sym);
21423
+ const sep = t.indexOf("|");
21424
+ if (sep !== -1)
21425
+ this.tokenToSymbol.set(t.slice(sep + 1), sym);
21389
21426
  }
21390
21427
  }
21391
21428
  if (this.ws && this.state === "CONNECTED") {
@@ -21418,6 +21455,10 @@ class FinvasiaSocket {
21418
21455
  setSymbolMap(map) {
21419
21456
  for (const [token, symbol] of map) {
21420
21457
  this.tokenToSymbol.set(token, symbol);
21458
+ const sep = token.indexOf("|");
21459
+ if (sep !== -1) {
21460
+ this.tokenToSymbol.set(token.slice(sep + 1), symbol);
21461
+ }
21421
21462
  }
21422
21463
  }
21423
21464
  handleMessage(data) {
@@ -21429,14 +21470,26 @@ class FinvasiaSocket {
21429
21470
  if (this.subscribedTokens.size > 0) {
21430
21471
  this.sendSubscribe(Array.from(this.subscribedTokens));
21431
21472
  }
21473
+ } else {
21474
+ console.error(`[FinvasiaSocket] Auth rejected (ck NOT_OK) \u2014 token invalid, stopping reconnect`);
21475
+ this.reconnectAttempts = this.maxReconnects;
21476
+ this.state = "DISCONNECTED";
21477
+ this.onAuthFailed?.();
21432
21478
  }
21433
21479
  return;
21434
21480
  }
21435
21481
  if (type === "tf" || type === "tk" || type === "dk" || type === "df") {
21436
21482
  const tick = data;
21437
- const tokenKey = tick.tk;
21438
- const symbol = this.tokenToSymbol.get(tokenKey);
21483
+ const rawToken = tick.tk;
21484
+ const exch = data["e"] ?? "";
21485
+ const fullKey = exch ? `${exch}|${rawToken}` : rawToken;
21486
+ if (tick.ts && !this.tokenToSymbol.has(fullKey)) {
21487
+ this.tokenToSymbol.set(fullKey, tick.ts);
21488
+ this.tokenToSymbol.set(rawToken, tick.ts);
21489
+ }
21490
+ const symbol = this.tokenToSymbol.get(fullKey) ?? this.tokenToSymbol.get(rawToken);
21439
21491
  const unified = fromTick2(tick, symbol);
21492
+ unified.token = fullKey;
21440
21493
  this.dispatchTick(unified);
21441
21494
  return;
21442
21495
  }
@@ -21487,6 +21540,27 @@ class FinvasiaSocket {
21487
21540
  }
21488
21541
 
21489
21542
  // src/brokers/finvasia/finvasia-instruments.ts
21543
+ import { inflateRawSync } from "zlib";
21544
+ function extractTextFromZip(buffer) {
21545
+ const bytes = new Uint8Array(buffer);
21546
+ if (bytes.length < 30 || bytes[0] !== 80 || bytes[1] !== 75 || bytes[2] !== 3 || bytes[3] !== 4) {
21547
+ return new TextDecoder().decode(buffer);
21548
+ }
21549
+ const view = new DataView(buffer);
21550
+ const compressionMethod = view.getUint16(8, true);
21551
+ const compressedSize = view.getUint32(18, true);
21552
+ const filenameLength = view.getUint16(26, true);
21553
+ const extraFieldLength = view.getUint16(28, true);
21554
+ const dataOffset = 30 + filenameLength + extraFieldLength;
21555
+ const compressedData = new Uint8Array(buffer, dataOffset, compressedSize);
21556
+ if (compressionMethod === 0) {
21557
+ return new TextDecoder().decode(compressedData);
21558
+ }
21559
+ if (compressionMethod === 8) {
21560
+ return new TextDecoder().decode(inflateRawSync(compressedData));
21561
+ }
21562
+ throw new Error(`Unsupported ZIP compression method: ${compressionMethod}`);
21563
+ }
21490
21564
  var COL2 = {
21491
21565
  EXCHANGE: 0,
21492
21566
  TOKEN: 1,
@@ -21499,6 +21573,18 @@ var COL2 = {
21499
21573
  STRIKE: 8,
21500
21574
  OPTION_TYPE: 9
21501
21575
  };
21576
+ var HEADER_MAP = {
21577
+ Exchange: "exchange",
21578
+ Token: "token",
21579
+ LotSize: "lot_size",
21580
+ Symbol: "symbol",
21581
+ TradingSymbol: "trading_symbol",
21582
+ Expiry: "expiry",
21583
+ Instrument: "instrument_type",
21584
+ OptionType: "option_type",
21585
+ StrikePrice: "strike",
21586
+ TickSize: "tick_size"
21587
+ };
21502
21588
  var EXCHANGE_MAP2 = {
21503
21589
  NSE: "NSE",
21504
21590
  BSE: "BSE",
@@ -21558,7 +21644,7 @@ class FinvasiaInstruments {
21558
21644
  const response = await fetch(url);
21559
21645
  if (!response.ok)
21560
21646
  continue;
21561
- const text = await response.text();
21647
+ const text = extractTextFromZip(await response.arrayBuffer());
21562
21648
  const lines = text.split(`
21563
21649
  `);
21564
21650
  let headers = null;
@@ -21566,10 +21652,10 @@ class FinvasiaInstruments {
21566
21652
  const trimmed = line.trim();
21567
21653
  if (!trimmed)
21568
21654
  continue;
21569
- const fields = trimmed.split("|");
21655
+ const fields = trimmed.split(",");
21570
21656
  if (!headers) {
21571
21657
  if (fields[0] === "Exchange" || fields[0] === "exch") {
21572
- headers = fields;
21658
+ headers = fields.map((h) => HEADER_MAP[h] ?? h.toLowerCase());
21573
21659
  continue;
21574
21660
  }
21575
21661
  headers = [];
@@ -21685,6 +21771,7 @@ class FinvasiaBroker {
21685
21771
  };
21686
21772
  session = null;
21687
21773
  socket = null;
21774
+ credentials = null;
21688
21775
  instrumentsImpl;
21689
21776
  _iMaster;
21690
21777
  constructor(options) {
@@ -21696,6 +21783,7 @@ class FinvasiaBroker {
21696
21783
  const result = await authenticate2(creds);
21697
21784
  if (!result.ok)
21698
21785
  return result;
21786
+ this.credentials = creds;
21699
21787
  this.session = result.value;
21700
21788
  this.instrumentsImpl.setCredentials(this.session.userId, this.session.accessToken);
21701
21789
  return result;
@@ -21756,20 +21844,28 @@ class FinvasiaBroker {
21756
21844
  async modifyOrder(orderId, params) {
21757
21845
  try {
21758
21846
  const uid = this.session?.userId ?? "";
21759
- const payload = {
21847
+ const histRaw = await this.post("SingleOrdHist", {
21760
21848
  uid,
21761
21849
  norenordno: orderId
21850
+ });
21851
+ if (!Array.isArray(histRaw) || histRaw.length === 0) {
21852
+ return Err(new Error(`Cannot modify order ${orderId}: order not found`));
21853
+ }
21854
+ const latestOrder = histRaw[histRaw.length - 1];
21855
+ const payload = {
21856
+ uid,
21857
+ norenordno: orderId,
21858
+ exch: latestOrder.exch,
21859
+ tsym: latestOrder.tsym,
21860
+ prctyp: latestOrder.prctyp,
21861
+ prc: String(params.price ?? latestOrder.prc),
21862
+ qty: String(params.quantity ?? latestOrder.qty),
21863
+ ret: params.validity ?? latestOrder.ret
21762
21864
  };
21763
- if (params.price !== undefined)
21764
- payload["prc"] = String(params.price);
21765
- if (params.quantity !== undefined)
21766
- payload["qty"] = String(params.quantity);
21767
21865
  if (params.triggerPrice !== undefined)
21768
21866
  payload["trgprc"] = String(params.triggerPrice);
21769
21867
  if (params.type !== undefined)
21770
21868
  payload["prctyp"] = params.type;
21771
- if (params.validity !== undefined)
21772
- payload["ret"] = params.validity;
21773
21869
  const res = await this.post("ModifyOrder", payload);
21774
21870
  if (res.stat !== "Ok") {
21775
21871
  return Err(new Error(`Order modification failed: ${res.emsg ?? "Unknown error"}`));
@@ -21839,7 +21935,7 @@ class FinvasiaBroker {
21839
21935
  async getPositions() {
21840
21936
  try {
21841
21937
  const uid = this.session?.userId ?? "";
21842
- const raw = await this.post("PositionBook", { uid });
21938
+ const raw = await this.post("PositionBook", { uid, actid: uid });
21843
21939
  if (!Array.isArray(raw))
21844
21940
  return Ok([]);
21845
21941
  return Ok(raw.map(fromPosition2));
@@ -21984,6 +22080,10 @@ class FinvasiaBroker {
21984
22080
  this.ensureSocket();
21985
22081
  return this.socket.subscribe(tokens, cb);
21986
22082
  }
22083
+ setSymbolMap(map) {
22084
+ this.ensureSocket();
22085
+ this.socket.setSymbolMap(map);
22086
+ }
21987
22087
  unsubscribeTicks(sub) {
21988
22088
  if (this.socket) {
21989
22089
  this.socket.unsubscribe(sub);
@@ -22091,9 +22191,30 @@ class FinvasiaBroker {
22091
22191
  return;
22092
22192
  if (!this.session)
22093
22193
  throw new Error("Cannot create socket without an active session");
22094
- this.socket = new FinvasiaSocket(this.session.userId, this.session.accessToken);
22194
+ this.socket = new FinvasiaSocket(this.session.userId, this.session.accessToken, () => {
22195
+ this.handleSocketAuthFailed();
22196
+ });
22095
22197
  this.socket.connect();
22096
22198
  }
22199
+ handleSocketAuthFailed() {
22200
+ if (!this.credentials) {
22201
+ console.error("[FinvasiaBroker] Socket auth failed but no credentials stored \u2014 cannot re-authenticate");
22202
+ return;
22203
+ }
22204
+ console.log("[FinvasiaBroker] Socket auth failed, re-authenticating...");
22205
+ authenticate2(this.credentials).then((result) => {
22206
+ if (!result.ok) {
22207
+ console.error("[FinvasiaBroker] Re-authentication failed:", result.error instanceof Error ? result.error.message : result.error);
22208
+ return;
22209
+ }
22210
+ this.session = result.value;
22211
+ this.instrumentsImpl.setCredentials(this.session.userId, this.session.accessToken);
22212
+ console.log("[FinvasiaBroker] Re-authenticated successfully, reconnecting socket");
22213
+ this.socket?.reconnect(this.session.accessToken);
22214
+ }).catch((err) => {
22215
+ console.error("[FinvasiaBroker] Re-authentication exception:", err instanceof Error ? err.message : err);
22216
+ });
22217
+ }
22097
22218
  }
22098
22219
  // src/brokers/dhan/dhan-constants.ts
22099
22220
  var EXCHANGE_TO_DHAN = {
@@ -22113,7 +22234,7 @@ var EXCHANGE_FROM_DHAN = {
22113
22234
  NSE_CURRENCY: "CDS"
22114
22235
  };
22115
22236
  var PRODUCT_TO_DHAN = {
22116
- INTRADAY: "INTRA",
22237
+ INTRADAY: "INTRADAY",
22117
22238
  DELIVERY: "CNC",
22118
22239
  NORMAL: "MARGIN"
22119
22240
  };
@@ -22163,6 +22284,7 @@ var VALIDITY_FROM_DHAN = {
22163
22284
  IOC: "IOC"
22164
22285
  };
22165
22286
  var DHAN_API_BASE = "https://api.dhan.co/v2";
22287
+ var DHAN_AUTH_BASE = "https://auth.dhan.co";
22166
22288
  var DHAN_WS_URL = "wss://api-feed.dhan.co";
22167
22289
  var DHAN_INSTRUMENTS_URL = "https://images.dhan.co/api-data/api-scrip-master.csv";
22168
22290
  var CANDLE_INTERVAL_TO_DHAN = {
@@ -22208,6 +22330,79 @@ async function authenticate3(creds) {
22208
22330
  return Err(err instanceof Error ? err : new Error(String(err)));
22209
22331
  }
22210
22332
  }
22333
+ async function generateConsent(creds) {
22334
+ const { apiKey: appId, apiSecret: appSecret, userId: clientId } = creds;
22335
+ if (!appId)
22336
+ return Err(new Error("Missing apiKey (App ID) in credentials"));
22337
+ if (!appSecret)
22338
+ return Err(new Error("Missing apiSecret (App Secret) in credentials"));
22339
+ if (!clientId)
22340
+ return Err(new Error("Missing userId (Dhan Client ID) in credentials"));
22341
+ try {
22342
+ const response = await fetch(`${DHAN_AUTH_BASE}/app/generate-consent?client_id=${clientId}`, {
22343
+ method: "POST",
22344
+ headers: {
22345
+ "Content-Type": "application/json",
22346
+ app_id: appId,
22347
+ app_secret: appSecret
22348
+ },
22349
+ body: JSON.stringify({})
22350
+ });
22351
+ if (!response.ok) {
22352
+ const body = await response.text();
22353
+ return Err(new Error(`Dhan generate-consent failed (${response.status}): ${body}`));
22354
+ }
22355
+ const data = await response.json();
22356
+ if (!data.consentAppId) {
22357
+ return Err(new Error(data.message ?? "Failed to generate consent \u2014 no consentAppId returned"));
22358
+ }
22359
+ const loginUrl = `${DHAN_AUTH_BASE}/login/consentApp-login?consentAppId=${data.consentAppId}`;
22360
+ return Ok({ consentAppId: data.consentAppId, loginUrl });
22361
+ } catch (err) {
22362
+ return Err(err instanceof Error ? err : new Error(String(err)));
22363
+ }
22364
+ }
22365
+ async function consumeConsent(creds, tokenId) {
22366
+ const { apiKey: appId, apiSecret: appSecret, userId: clientId } = creds;
22367
+ if (!appId)
22368
+ return Err(new Error("Missing apiKey (App ID) in credentials"));
22369
+ if (!appSecret)
22370
+ return Err(new Error("Missing apiSecret (App Secret) in credentials"));
22371
+ if (!tokenId)
22372
+ return Err(new Error("Missing tokenId from callback"));
22373
+ try {
22374
+ const response = await fetch(`${DHAN_AUTH_BASE}/app/consumeApp-consent?tokenId=${encodeURIComponent(tokenId)}`, {
22375
+ method: "POST",
22376
+ headers: {
22377
+ "Content-Type": "application/json",
22378
+ app_id: appId,
22379
+ app_secret: appSecret
22380
+ },
22381
+ body: JSON.stringify({})
22382
+ });
22383
+ if (!response.ok) {
22384
+ const body = await response.text();
22385
+ return Err(new Error(`Dhan consumeApp-consent failed (${response.status}): ${body}`));
22386
+ }
22387
+ const data = await response.json();
22388
+ if (!data.accessToken) {
22389
+ return Err(new Error(data.message ?? "Failed to get access token \u2014 no accessToken returned"));
22390
+ }
22391
+ const thirtyDaysMs = 30 * 24 * 60 * 60 * 1000;
22392
+ const session = {
22393
+ accessToken: data.accessToken,
22394
+ refreshToken: undefined,
22395
+ expiresAt: Date.now() + thirtyDaysMs,
22396
+ ttlSeconds: 30 * 24 * 60 * 60,
22397
+ brokerId: "dhan",
22398
+ userId: clientId ?? "",
22399
+ metadata: { authMode: "oauth" }
22400
+ };
22401
+ return Ok(session);
22402
+ } catch (err) {
22403
+ return Err(err instanceof Error ? err : new Error(String(err)));
22404
+ }
22405
+ }
22211
22406
  async function refreshSession3(_session) {
22212
22407
  return Err(new Error("Dhan does not support token refresh. Generate a new access token from the Dhan developer portal."));
22213
22408
  }
@@ -22238,10 +22433,10 @@ function safeSide3(raw) {
22238
22433
  }
22239
22434
  function parseTimestamp3(ts) {
22240
22435
  if (!ts)
22241
- return Date.now();
22436
+ return 0;
22242
22437
  if (typeof ts === "number")
22243
22438
  return ts > 1000000000000 ? ts : ts * 1000;
22244
- return new Date(ts).getTime() || Date.now();
22439
+ return new Date(ts).getTime() || 0;
22245
22440
  }
22246
22441
  function inferInstrumentType3(exchangeSegment, tradingSymbol) {
22247
22442
  if (exchangeSegment === "NSE_EQ" || exchangeSegment === "BSE_EQ")
@@ -22350,6 +22545,7 @@ function fromTick3(tick, symbolLookup) {
22350
22545
  function fromHolding3(holding, ltp = 0) {
22351
22546
  const qty = holding.totalQty ?? holding.availableQty;
22352
22547
  const avgPrice = holding.avgCostPrice;
22548
+ ltp = holding.lastTradedPrice ?? ltp;
22353
22549
  const pnl = (ltp - avgPrice) * qty;
22354
22550
  return {
22355
22551
  broker: "dhan",
@@ -22391,11 +22587,29 @@ function fromQuote3(quote, symbol, exchange) {
22391
22587
  }
22392
22588
 
22393
22589
  // src/brokers/dhan/dhan-socket.ts
22590
+ import WS2 from "ws";
22394
22591
  var subIdCounter4 = 0;
22395
22592
  function nextSubId4() {
22396
22593
  subIdCounter4 += 1;
22397
22594
  return `dsub_${subIdCounter4}`;
22398
22595
  }
22596
+ var EXCH_SEG_FROM_BYTE = {
22597
+ 0: "IDX_I",
22598
+ 1: "NSE_EQ",
22599
+ 2: "NSE_FNO",
22600
+ 3: "NSE_CURRENCY",
22601
+ 4: "BSE_EQ",
22602
+ 5: "MCX_COMM",
22603
+ 7: "BSE_CURRENCY",
22604
+ 8: "BSE_FNO"
22605
+ };
22606
+ function splitToken(token) {
22607
+ const sep = token.includes("|") ? "|" : ":";
22608
+ const idx = token.indexOf(sep);
22609
+ if (idx === -1)
22610
+ return ["NSE_EQ", token];
22611
+ return [token.slice(0, idx), token.slice(idx + 1)];
22612
+ }
22399
22613
 
22400
22614
  class DhanSocket {
22401
22615
  ws = null;
@@ -22419,36 +22633,36 @@ class DhanSocket {
22419
22633
  this.state = "CONNECTING";
22420
22634
  try {
22421
22635
  const url = `${DHAN_WS_URL}?version=2&token=${this.accessToken}&clientId=${this.clientId}&authType=2`;
22422
- this.ws = new WebSocket(url);
22423
- this.ws.binaryType = "arraybuffer";
22636
+ this.ws = new WS2(url);
22424
22637
  } catch {
22425
22638
  this.state = "DISCONNECTED";
22426
22639
  return;
22427
22640
  }
22428
- this.ws.onopen = () => {
22641
+ this.ws.on("open", () => {
22429
22642
  this.state = "CONNECTED";
22430
22643
  this.reconnectAttempts = 0;
22431
22644
  this.startHeartbeat();
22432
22645
  if (this.subscribedTokens.size > 0) {
22433
22646
  this.sendSubscribe(Array.from(this.subscribedTokens));
22434
22647
  }
22435
- };
22436
- this.ws.onmessage = (event) => {
22648
+ });
22649
+ this.ws.on("message", (raw, isBinary) => {
22437
22650
  try {
22438
- if (event.data instanceof ArrayBuffer) {
22439
- this.handleBinaryMessage(event.data);
22651
+ if (isBinary) {
22652
+ const buf = raw instanceof ArrayBuffer ? Buffer.from(raw) : Buffer.isBuffer(raw) ? raw : Array.isArray(raw) ? Buffer.concat(raw) : Buffer.from(raw);
22653
+ this.handleBinaryMessage(buf);
22440
22654
  } else {
22441
- const data = JSON.parse(String(event.data));
22655
+ const data = JSON.parse(raw.toString());
22442
22656
  this.handleJsonMessage(data);
22443
22657
  }
22444
22658
  } catch {}
22445
- };
22446
- this.ws.onclose = () => {
22659
+ });
22660
+ this.ws.on("close", () => {
22447
22661
  this.state = "DISCONNECTED";
22448
22662
  this.stopHeartbeat();
22449
22663
  this.attemptReconnect();
22450
- };
22451
- this.ws.onerror = () => {};
22664
+ });
22665
+ this.ws.on("error", () => {});
22452
22666
  }
22453
22667
  disconnect() {
22454
22668
  this.clearReconnectTimer();
@@ -22506,39 +22720,100 @@ class DhanSocket {
22506
22720
  this.tokenToSymbol.set(token, symbol);
22507
22721
  }
22508
22722
  }
22509
- handleBinaryMessage(buffer) {
22510
- const view = new DataView(buffer);
22511
- if (buffer.byteLength < 48)
22723
+ handleBinaryMessage(data) {
22724
+ if (data.length < 8)
22512
22725
  return;
22513
- try {
22726
+ const responseCode = data.readUInt8(0);
22727
+ const exchSegByte = data.readUInt8(3);
22728
+ const securityId = data.readInt32LE(4);
22729
+ const exchangeSegment = EXCH_SEG_FROM_BYTE[exchSegByte] ?? "NSE_EQ";
22730
+ const fullKey = `${exchangeSegment}:${securityId}`;
22731
+ if (responseCode === 1 && data.length >= 32) {
22732
+ const ltp = data.readFloatLE(8);
22733
+ const ltt = data.readInt32LE(12);
22734
+ const open = data.readFloatLE(16);
22735
+ const close = data.readFloatLE(20);
22736
+ const high = data.readFloatLE(24);
22737
+ const low = data.readFloatLE(28);
22514
22738
  const tick = {
22515
- securityId: view.getInt32(0, true),
22516
- LTP: view.getFloat32(4, true),
22517
- open: view.getFloat32(8, true),
22518
- high: view.getFloat32(12, true),
22519
- low: view.getFloat32(16, true),
22520
- close: view.getFloat32(20, true),
22521
- volume: view.getInt32(24, true),
22522
- OI: view.getInt32(28, true),
22523
- bestBidPrice: view.getFloat32(32, true),
22524
- bestAskPrice: view.getFloat32(36, true),
22525
- bestBidQty: view.getInt32(40, true),
22526
- bestAskQty: view.getInt32(44, true),
22527
- exchangeSegment: "",
22528
- timestamp: Date.now()
22739
+ securityId,
22740
+ LTP: ltp,
22741
+ open,
22742
+ high,
22743
+ low,
22744
+ close,
22745
+ volume: 0,
22746
+ bestBidPrice: 0,
22747
+ bestAskPrice: 0,
22748
+ bestBidQty: 0,
22749
+ bestAskQty: 0,
22750
+ exchangeSegment,
22751
+ timestamp: ltt > 0 ? ltt * 1000 : Date.now()
22529
22752
  };
22530
- const tokenStr = String(tick.securityId);
22531
- const symbol = this.tokenToSymbol.get(tokenStr);
22753
+ const symbol = this.tokenToSymbol.get(fullKey) ?? this.tokenToSymbol.get(String(securityId));
22532
22754
  const unified = fromTick3(tick, symbol);
22755
+ unified.token = fullKey;
22533
22756
  this.dispatchTick(unified);
22534
- } catch {}
22757
+ } else if (responseCode === 2 && data.length >= 16) {
22758
+ const ltp = data.readFloatLE(8);
22759
+ const ltt = data.readInt32LE(12);
22760
+ const tick = {
22761
+ securityId,
22762
+ LTP: ltp,
22763
+ open: 0,
22764
+ high: 0,
22765
+ low: 0,
22766
+ close: 0,
22767
+ volume: 0,
22768
+ bestBidPrice: 0,
22769
+ bestAskPrice: 0,
22770
+ bestBidQty: 0,
22771
+ bestAskQty: 0,
22772
+ exchangeSegment,
22773
+ timestamp: ltt > 0 ? ltt * 1000 : Date.now()
22774
+ };
22775
+ const symbol = this.tokenToSymbol.get(fullKey) ?? this.tokenToSymbol.get(String(securityId));
22776
+ const unified = fromTick3(tick, symbol);
22777
+ unified.token = fullKey;
22778
+ this.dispatchTick(unified);
22779
+ } else if (responseCode === 4 && data.length >= 50) {
22780
+ const ltp = data.readFloatLE(8);
22781
+ const ltt = data.readInt32LE(14);
22782
+ const volume = data.readInt32LE(22);
22783
+ const totalSellQty = data.readInt32LE(26);
22784
+ const totalBuyQty = data.readInt32LE(30);
22785
+ const open = data.readFloatLE(34);
22786
+ const close = data.readFloatLE(38);
22787
+ const high = data.readFloatLE(42);
22788
+ const low = data.readFloatLE(46);
22789
+ const tick = {
22790
+ securityId,
22791
+ LTP: ltp,
22792
+ open,
22793
+ high,
22794
+ low,
22795
+ close,
22796
+ volume,
22797
+ bestBidPrice: 0,
22798
+ bestAskPrice: 0,
22799
+ bestBidQty: totalBuyQty,
22800
+ bestAskQty: totalSellQty,
22801
+ exchangeSegment,
22802
+ timestamp: ltt > 0 ? ltt * 1000 : Date.now()
22803
+ };
22804
+ const symbol = this.tokenToSymbol.get(fullKey) ?? this.tokenToSymbol.get(String(securityId));
22805
+ const unified = fromTick3(tick, symbol);
22806
+ unified.token = fullKey;
22807
+ this.dispatchTick(unified);
22808
+ }
22535
22809
  }
22536
22810
  handleJsonMessage(data) {
22537
22811
  if (data["type"] === "ticker_data") {
22538
22812
  const tick = data;
22539
- const tokenStr = String(tick.securityId);
22540
- const symbol = this.tokenToSymbol.get(tokenStr);
22813
+ const fullKey = tick.exchangeSegment ? `${tick.exchangeSegment}:${tick.securityId}` : String(tick.securityId);
22814
+ const symbol = this.tokenToSymbol.get(fullKey) ?? this.tokenToSymbol.get(String(tick.securityId));
22541
22815
  const unified = fromTick3(tick, symbol);
22816
+ unified.token = fullKey;
22542
22817
  this.dispatchTick(unified);
22543
22818
  }
22544
22819
  }
@@ -22546,10 +22821,10 @@ class DhanSocket {
22546
22821
  if (!this.ws || this.state !== "CONNECTED")
22547
22822
  return;
22548
22823
  const instruments = tokens.map((t) => {
22549
- const parts = t.split(":");
22824
+ const [segment, id] = splitToken(t);
22550
22825
  return {
22551
- ExchangeSegment: parts[0] ?? "NSE_EQ",
22552
- SecurityId: parts[1] ?? parts[0]
22826
+ ExchangeSegment: segment,
22827
+ SecurityId: id
22553
22828
  };
22554
22829
  });
22555
22830
  const request = {
@@ -22563,10 +22838,10 @@ class DhanSocket {
22563
22838
  if (!this.ws || this.state !== "CONNECTED")
22564
22839
  return;
22565
22840
  const instruments = tokens.map((t) => {
22566
- const parts = t.split(":");
22841
+ const [segment, id] = splitToken(t);
22567
22842
  return {
22568
- ExchangeSegment: parts[0] ?? "NSE_EQ",
22569
- SecurityId: parts[1] ?? parts[0]
22843
+ ExchangeSegment: segment,
22844
+ SecurityId: id
22570
22845
  };
22571
22846
  });
22572
22847
  const request = {
@@ -22590,7 +22865,7 @@ class DhanSocket {
22590
22865
  this.heartbeatTimer = setInterval(() => {
22591
22866
  if (this.ws && this.state === "CONNECTED") {
22592
22867
  try {
22593
- this.ws.send(JSON.stringify({ RequestCode: 12 }));
22868
+ this.ws.ping();
22594
22869
  } catch {}
22595
22870
  }
22596
22871
  }, 30000);
@@ -22632,6 +22907,15 @@ var DHAN_EXCHANGE_MAP = {
22632
22907
  NFO: "NFO",
22633
22908
  MCX: "MCX"
22634
22909
  };
22910
+ function resolveDhanExchange(exchId, segment) {
22911
+ if (segment === "D")
22912
+ return exchId === "BSE" ? "BFO" : "NFO";
22913
+ if (segment === "C")
22914
+ return "CDS";
22915
+ if (segment === "M")
22916
+ return "MCX";
22917
+ return exchId === "BSE" ? "BSE" : "NSE";
22918
+ }
22635
22919
  function mapInstrumentType3(instName, optionType) {
22636
22920
  const opt = optionType?.toUpperCase();
22637
22921
  if (opt === "CE")
@@ -22773,14 +23057,16 @@ class DhanInstruments {
22773
23057
  const securityId = fields["SEM_SMST_SECURITY_ID"] ?? fields["securityId"] ?? "";
22774
23058
  const tradingSymbol = fields["SEM_TRADING_SYMBOL"] ?? fields["tradingSymbol"] ?? "";
22775
23059
  const customSymbol = fields["SEM_CUSTOM_SYMBOL"] ?? fields["customSymbol"] ?? tradingSymbol;
22776
- const exchangeRaw = fields["SEM_EXM_EXCH_ID"] ?? fields["SEM_SEGMENT"] ?? fields["exchangeSegment"] ?? "NSE_EQ";
23060
+ const exchId = fields["SEM_EXM_EXCH_ID"] ?? "";
23061
+ const segment = fields["SEM_SEGMENT"] ?? "";
23062
+ const exchangeSegment = fields["exchangeSegment"] ?? "";
22777
23063
  const instNameRaw = fields["SEM_INSTRUMENT_NAME"] ?? fields["instrumentType"] ?? "EQUITY";
22778
23064
  const optionType = fields["SEM_OPTION_TYPE"] ?? fields["optionType"] ?? "";
22779
23065
  const lotSizeRaw = fields["SEM_LOT_UNITS"] ?? fields["lotSize"] ?? "1";
22780
23066
  const tickSizeRaw = fields["SEM_TICK_SIZE"] ?? fields["tickSize"] ?? "0.05";
22781
23067
  const expiryRaw = fields["SEM_EXPIRY_DATE"] ?? fields["expiryDate"] ?? "";
22782
23068
  const strikeRaw = fields["SEM_STRIKE_PRICE"] ?? fields["strikePrice"] ?? "";
22783
- const exchange = DHAN_EXCHANGE_MAP[exchangeRaw] ?? EXCHANGE_FROM_DHAN[exchangeRaw] ?? "NSE";
23069
+ const exchange = exchId && segment ? resolveDhanExchange(exchId, segment) : DHAN_EXCHANGE_MAP[exchangeSegment] ?? EXCHANGE_FROM_DHAN[exchangeSegment] ?? "NSE";
22784
23070
  const instrumentType = mapInstrumentType3(instNameRaw, optionType);
22785
23071
  return {
22786
23072
  tradingSymbol,
@@ -22837,6 +23123,7 @@ class DhanBroker {
22837
23123
  session = null;
22838
23124
  socket = null;
22839
23125
  clientId = "";
23126
+ oauthCreds = null;
22840
23127
  instrumentsImpl;
22841
23128
  _iMaster;
22842
23129
  constructor(options) {
@@ -22864,6 +23151,24 @@ class DhanBroker {
22864
23151
  return null;
22865
23152
  return new Date(this.session.expiresAt);
22866
23153
  }
23154
+ async generateConsent(creds) {
23155
+ this.oauthCreds = creds;
23156
+ this.clientId = creds.userId ?? "";
23157
+ return generateConsent(creds);
23158
+ }
23159
+ async consumeConsent(tokenId) {
23160
+ if (!this.oauthCreds) {
23161
+ return Err(new Error("Call generateConsent() before consumeConsent()"));
23162
+ }
23163
+ const result = await consumeConsent(this.oauthCreds, tokenId);
23164
+ if (!result.ok)
23165
+ return result;
23166
+ this.session = result.value;
23167
+ this.clientId = this.oauthCreds.userId ?? "";
23168
+ this.instrumentsImpl.setCredentials(this.clientId, this.session.accessToken);
23169
+ this.oauthCreds = null;
23170
+ return result;
23171
+ }
22867
23172
  async sync(force) {
22868
23173
  return this._iMaster.syncBroker(this.id, this.instruments, force);
22869
23174
  }
@@ -22883,9 +23188,11 @@ class DhanBroker {
22883
23188
  if (!asyncResolved.ok) {
22884
23189
  return Err(new Error(`Cannot resolve "${params.tradingSymbol}". Either run broker.sync() first to use unified nsekit symbols, or provide exchange and the broker-native tradingSymbol directly.`));
22885
23190
  }
22886
- resolvedParams = { ...params, exchange: asyncResolved.value.exchange };
23191
+ const nativeId = asyncResolved.value.brokerTokens.dhan?.securityId ?? params.tradingSymbol;
23192
+ resolvedParams = { ...params, exchange: asyncResolved.value.exchange, tradingSymbol: nativeId };
22887
23193
  } else {
22888
- resolvedParams = { ...params, exchange: resolved.value.exchange };
23194
+ const nativeId = resolved.value.brokerTokens.dhan?.securityId ?? params.tradingSymbol;
23195
+ resolvedParams = { ...params, exchange: resolved.value.exchange, tradingSymbol: nativeId };
22889
23196
  }
22890
23197
  }
22891
23198
  const dhanParams = toOrderParams3(resolvedParams, this.clientId);
@@ -22902,20 +23209,18 @@ class DhanBroker {
22902
23209
  }
22903
23210
  async modifyOrder(orderId, params) {
22904
23211
  try {
23212
+ const rawOrder = await this.request("GET", `/orders/${orderId}`);
23213
+ const original = Array.isArray(rawOrder) ? rawOrder[0] : rawOrder;
22905
23214
  const payload = {
22906
23215
  dhanClientId: this.clientId,
22907
- orderId
23216
+ orderId,
23217
+ orderType: params.type ? ORDER_TYPE_TO_DHAN[params.type] ?? original.orderType : original.orderType,
23218
+ validity: params.validity ?? original.validity ?? "DAY",
23219
+ quantity: params.quantity ?? original.quantity,
23220
+ price: params.price ?? original.price
22908
23221
  };
22909
- if (params.price !== undefined)
22910
- payload["price"] = params.price;
22911
- if (params.quantity !== undefined)
22912
- payload["quantity"] = params.quantity;
22913
23222
  if (params.triggerPrice !== undefined)
22914
23223
  payload["triggerPrice"] = params.triggerPrice;
22915
- if (params.type !== undefined)
22916
- payload["orderType"] = params.type;
22917
- if (params.validity !== undefined)
22918
- payload["validity"] = params.validity;
22919
23224
  const res = await this.request("PUT", `/orders/${orderId}`, payload);
22920
23225
  return Ok({
22921
23226
  orderId: res.orderId ?? orderId,
@@ -22982,7 +23287,11 @@ class DhanBroker {
22982
23287
  return Ok([]);
22983
23288
  return Ok(raw.map((h) => fromHolding3(h)));
22984
23289
  } catch (err) {
22985
- return Err(err instanceof Error ? err : new Error(String(err)));
23290
+ const msg = err instanceof Error ? err.message : String(err);
23291
+ if (msg.includes("No holdings available") || msg.includes("DH-1111")) {
23292
+ return Ok([]);
23293
+ }
23294
+ return Err(err instanceof Error ? err : new Error(msg));
22986
23295
  }
22987
23296
  }
22988
23297
  async getLTP(symbols) {
@@ -23124,6 +23433,10 @@ class DhanBroker {
23124
23433
  this.ensureSocket();
23125
23434
  return this.socket.subscribe(tokens, cb);
23126
23435
  }
23436
+ setSymbolMap(map) {
23437
+ this.ensureSocket();
23438
+ this.socket.setSymbolMap(map);
23439
+ }
23127
23440
  unsubscribeTicks(sub) {
23128
23441
  if (this.socket) {
23129
23442
  this.socket.unsubscribe(sub);