shoonya-sdk 0.3.6 → 0.4.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.
@@ -204,6 +204,11 @@ interface OrderBookFail {
204
204
  }
205
205
  type OrderBook = OrderBookSuccess[] | OrderBookFail;
206
206
 
207
+ type ShoonyaWSEvents = "connect" | "open" | "priceUpdate" | "orderUpdate" | "stopped" | "close" | "reconnect" | "error";
208
+ declare interface Shoonya {
209
+ on(eventName: ShoonyaWSEvents, listener: (...args: any[]) => void): this;
210
+ emit(eventName: ShoonyaWSEvents, ...args: any[]): boolean;
211
+ }
207
212
  declare class Shoonya extends EventEmitter {
208
213
  accessToken: string;
209
214
  userId: string;
@@ -220,8 +225,21 @@ declare class Shoonya extends EventEmitter {
220
225
  private apiKey;
221
226
  private cronJobRunning;
222
227
  private scripList;
228
+ private reconnectTimeout;
229
+ private heartbeatTimeout;
230
+ private lastWsMsgAt;
223
231
  constructor(options?: {
224
232
  logging: boolean;
233
+ /**
234
+ * check for connection every x ms
235
+ * @default 30000
236
+ */
237
+ reconnectTimeout?: number;
238
+ /**
239
+ * send heartbeat msg in every x ms
240
+ * @default 3000
241
+ */
242
+ heartbeatTimeout?: number;
225
243
  });
226
244
  request<T>(path: Path, body: {
227
245
  data: RequestDataType;
@@ -319,6 +337,13 @@ declare class Shoonya extends EventEmitter {
319
337
  }>;
320
338
  private getFormattedExpiry;
321
339
  private getNextFormattedDates;
340
+ getLastWSMessage(): Date;
341
+ getWSState(): {
342
+ OPEN: boolean;
343
+ CLOSED: boolean;
344
+ CLOSING: boolean;
345
+ CONNECTING: boolean;
346
+ };
322
347
  connectWS(): void;
323
348
  /**
324
349
  *
@@ -204,6 +204,11 @@ interface OrderBookFail {
204
204
  }
205
205
  type OrderBook = OrderBookSuccess[] | OrderBookFail;
206
206
 
207
+ type ShoonyaWSEvents = "connect" | "open" | "priceUpdate" | "orderUpdate" | "stopped" | "close" | "reconnect" | "error";
208
+ declare interface Shoonya {
209
+ on(eventName: ShoonyaWSEvents, listener: (...args: any[]) => void): this;
210
+ emit(eventName: ShoonyaWSEvents, ...args: any[]): boolean;
211
+ }
207
212
  declare class Shoonya extends EventEmitter {
208
213
  accessToken: string;
209
214
  userId: string;
@@ -220,8 +225,21 @@ declare class Shoonya extends EventEmitter {
220
225
  private apiKey;
221
226
  private cronJobRunning;
222
227
  private scripList;
228
+ private reconnectTimeout;
229
+ private heartbeatTimeout;
230
+ private lastWsMsgAt;
223
231
  constructor(options?: {
224
232
  logging: boolean;
233
+ /**
234
+ * check for connection every x ms
235
+ * @default 30000
236
+ */
237
+ reconnectTimeout?: number;
238
+ /**
239
+ * send heartbeat msg in every x ms
240
+ * @default 3000
241
+ */
242
+ heartbeatTimeout?: number;
225
243
  });
226
244
  request<T>(path: Path, body: {
227
245
  data: RequestDataType;
@@ -319,6 +337,13 @@ declare class Shoonya extends EventEmitter {
319
337
  }>;
320
338
  private getFormattedExpiry;
321
339
  private getNextFormattedDates;
340
+ getLastWSMessage(): Date;
341
+ getWSState(): {
342
+ OPEN: boolean;
343
+ CLOSED: boolean;
344
+ CLOSING: boolean;
345
+ CONNECTING: boolean;
346
+ };
322
347
  connectWS(): void;
323
348
  /**
324
349
  *
@@ -158,13 +158,22 @@ var Shoonya = class extends import_events.EventEmitter {
158
158
  apiKey;
159
159
  cronJobRunning = false;
160
160
  scripList;
161
+ reconnectTimeout;
162
+ heartbeatTimeout;
163
+ lastWsMsgAt;
161
164
  constructor(options) {
162
- const { logging = false } = options || {};
165
+ const {
166
+ logging = false,
167
+ reconnectTimeout = 3e4,
168
+ heartbeatTimeout = 3e3
169
+ } = options || {};
163
170
  super({ captureRejections: true });
164
171
  this.userId = "";
165
172
  this.accessToken = "";
166
173
  this.accountId = this.userId;
167
174
  this.logging = logging;
175
+ this.reconnectTimeout = { timer: null, timeout: reconnectTimeout };
176
+ this.heartbeatTimeout = { timer: null, timeout: heartbeatTimeout };
168
177
  if (logging) {
169
178
  this.logger = new logger_default(
170
179
  `./logs/log_${Date.now().toString()}.log`,
@@ -472,23 +481,34 @@ var Shoonya = class extends import_events.EventEmitter {
472
481
  return `${day}${newMonth}${newYear}`;
473
482
  }
474
483
  getNextFormattedDates(expiry) {
475
- let multipler = 1;
484
+ let multiplier = 1;
476
485
  if (expiry === "next" || expiry === "more-than-1-day")
477
- multipler = 2;
486
+ multiplier = 2;
478
487
  if (expiry === "next-next")
479
- multipler = 3;
488
+ multiplier = 3;
480
489
  const dates = [];
481
490
  let currentDate = /* @__PURE__ */ new Date();
482
- for (let i = 0; i <= 7 * multipler; i++) {
491
+ for (let i = 0; i <= 7 * multiplier; i++) {
483
492
  const formattedDate = this.getFormattedExpiry(currentDate);
484
493
  dates.push([formattedDate, currentDate]);
485
494
  currentDate = new Date(currentDate.setDate(currentDate.getDate() + 1));
486
495
  }
487
496
  return dates;
488
497
  }
498
+ getLastWSMessage() {
499
+ return this.lastWsMsgAt;
500
+ }
501
+ getWSState() {
502
+ return {
503
+ OPEN: this.ws && this.ws.readyState === this.ws.OPEN,
504
+ CLOSED: this.ws && this.ws.readyState === this.ws.CLOSED,
505
+ CLOSING: this.ws && this.ws.readyState === this.ws.CLOSING,
506
+ CONNECTING: this.ws && this.ws.readyState === this.ws.CONNECTING
507
+ };
508
+ }
489
509
  // WebSocket API
490
510
  connectWS() {
491
- if (this.ws && (this.ws.readyState === 0 || this.ws.readyState === 1)) {
511
+ if (this.getWSState().OPEN || this.getWSState().CONNECTING) {
492
512
  return;
493
513
  }
494
514
  this.ws = new import_ws.WebSocket(this.wsBaseUrl);
@@ -501,15 +521,21 @@ var Shoonya = class extends import_events.EventEmitter {
501
521
  susertoken: this.accessToken
502
522
  };
503
523
  this.ws.send(JSON.stringify(msg));
524
+ this.emit("open", "opened ws connection");
525
+ clearInterval(this.heartbeatTimeout.timer);
526
+ this.heartbeatTimeout.timer = setInterval(() => {
527
+ const heartbeatMsg = '{"t":"h"}';
528
+ this.getWSState().OPEN && this.ws.send(heartbeatMsg);
529
+ this.logging && this.logger.log(`sent heartbeat message: ${heartbeatMsg}`);
530
+ }, this.heartbeatTimeout.timeout);
504
531
  if (this.cronJobRunning) {
505
532
  this.subscribe(this.scripList);
506
533
  return;
507
534
  }
508
- import_node_cron.default.schedule("43,00 3,10 * * 1-5", async () => {
509
- this.cronJobRunning = true;
510
- const hour = (/* @__PURE__ */ new Date()).getUTCHours();
511
- const min = (/* @__PURE__ */ new Date()).getUTCMinutes();
512
- if (hour === 3 && min === 43) {
535
+ import_node_cron.default.schedule(
536
+ "13 9 * * 1-5",
537
+ async () => {
538
+ this.cronJobRunning = true;
513
539
  try {
514
540
  const cred = {
515
541
  apiKey: this.apiKey,
@@ -518,21 +544,21 @@ var Shoonya = class extends import_events.EventEmitter {
518
544
  userId: this.userId
519
545
  };
520
546
  await this.login(cred);
521
- this.connectWS();
547
+ this.logging && this.logger.log("Token Refreshed");
522
548
  } catch (err) {
523
549
  console.error(err);
524
550
  const errMessage = `Token refreshing failed. ERR: ${err.message}`;
525
551
  this.logging && this.logger.log(errMessage, "ERROR");
552
+ this.getWSState().OPEN && this.ws.close();
526
553
  this.emit("stopped", errMessage);
527
554
  }
528
- }
529
- if (hour === 10 && min === 0) {
530
- this.disconnect();
531
- }
532
- });
555
+ },
556
+ { timezone: "Asia/Kolkata" }
557
+ );
533
558
  });
534
559
  this.ws.addEventListener("message", (e) => {
535
560
  const result = JSON.parse(e.data.toString());
561
+ this.lastWsMsgAt = /* @__PURE__ */ new Date();
536
562
  if (result.t === "dk") {
537
563
  this.logging && this.logger.log(`Subscribed to ${result.ts}. Listening...`);
538
564
  }
@@ -550,6 +576,19 @@ var Shoonya = class extends import_events.EventEmitter {
550
576
  }, 3e3);
551
577
  return;
552
578
  }
579
+ clearTimeout(this.reconnectTimeout.timer);
580
+ this.reconnectTimeout.timer = setTimeout(() => {
581
+ let msg = {
582
+ t: "c",
583
+ uid: this.userId,
584
+ actid: this.accountId,
585
+ source: "API",
586
+ susertoken: this.accessToken
587
+ };
588
+ this.subscribe(this.scripList);
589
+ this.getWSState().OPEN && this.ws.send(JSON.stringify(msg));
590
+ this.emit("reconnect", "reconnected with shoonya ws");
591
+ }, this.reconnectTimeout.timeout);
553
592
  clearTimeout(this.disconnectTimer);
554
593
  this.disconnectTimer = setTimeout(() => {
555
594
  const day = (/* @__PURE__ */ new Date()).getDay();
@@ -558,7 +597,16 @@ var Shoonya = class extends import_events.EventEmitter {
558
597
  const err = "API have stopped to respond! there could be a problem with API or market has closed";
559
598
  this.logging && this.logger.log(err, "WARN");
560
599
  this.emit("stopped", err);
561
- }, 6e4 * 60 * 18 - 15e3);
600
+ this.getWSState().OPEN && this.ws.close();
601
+ }, 6e4 * 60 * 18 - 1e4);
602
+ });
603
+ this.ws.addEventListener("error", (e) => {
604
+ this.logging && this.logger.log("some error in ws");
605
+ this.emit("error", e);
606
+ this.getWSState().OPEN && this.ws.close();
607
+ });
608
+ this.ws.addEventListener("close", (e) => {
609
+ this.emit("close", e);
562
610
  });
563
611
  }
564
612
  /**
@@ -609,7 +657,8 @@ var Shoonya = class extends import_events.EventEmitter {
609
657
  }
610
658
  }
611
659
  disconnect() {
612
- if (this.ws && this.ws.readyState !== 2 && this.ws.readyState !== 3) {
660
+ if (this.getWSState().OPEN || this.getWSState().CONNECTING) {
661
+ clearInterval(this.heartbeatTimeout.timer);
613
662
  this.ws.close();
614
663
  this.logging && this.logger.log("Websocket Connection with Shoonya API is now closed!");
615
664
  this.emit("close");
@@ -132,13 +132,22 @@ var Shoonya = class extends EventEmitter {
132
132
  apiKey;
133
133
  cronJobRunning = false;
134
134
  scripList;
135
+ reconnectTimeout;
136
+ heartbeatTimeout;
137
+ lastWsMsgAt;
135
138
  constructor(options) {
136
- const { logging = false } = options || {};
139
+ const {
140
+ logging = false,
141
+ reconnectTimeout = 3e4,
142
+ heartbeatTimeout = 3e3
143
+ } = options || {};
137
144
  super({ captureRejections: true });
138
145
  this.userId = "";
139
146
  this.accessToken = "";
140
147
  this.accountId = this.userId;
141
148
  this.logging = logging;
149
+ this.reconnectTimeout = { timer: null, timeout: reconnectTimeout };
150
+ this.heartbeatTimeout = { timer: null, timeout: heartbeatTimeout };
142
151
  if (logging) {
143
152
  this.logger = new logger_default(
144
153
  `./logs/log_${Date.now().toString()}.log`,
@@ -446,23 +455,34 @@ var Shoonya = class extends EventEmitter {
446
455
  return `${day}${newMonth}${newYear}`;
447
456
  }
448
457
  getNextFormattedDates(expiry) {
449
- let multipler = 1;
458
+ let multiplier = 1;
450
459
  if (expiry === "next" || expiry === "more-than-1-day")
451
- multipler = 2;
460
+ multiplier = 2;
452
461
  if (expiry === "next-next")
453
- multipler = 3;
462
+ multiplier = 3;
454
463
  const dates = [];
455
464
  let currentDate = /* @__PURE__ */ new Date();
456
- for (let i = 0; i <= 7 * multipler; i++) {
465
+ for (let i = 0; i <= 7 * multiplier; i++) {
457
466
  const formattedDate = this.getFormattedExpiry(currentDate);
458
467
  dates.push([formattedDate, currentDate]);
459
468
  currentDate = new Date(currentDate.setDate(currentDate.getDate() + 1));
460
469
  }
461
470
  return dates;
462
471
  }
472
+ getLastWSMessage() {
473
+ return this.lastWsMsgAt;
474
+ }
475
+ getWSState() {
476
+ return {
477
+ OPEN: this.ws && this.ws.readyState === this.ws.OPEN,
478
+ CLOSED: this.ws && this.ws.readyState === this.ws.CLOSED,
479
+ CLOSING: this.ws && this.ws.readyState === this.ws.CLOSING,
480
+ CONNECTING: this.ws && this.ws.readyState === this.ws.CONNECTING
481
+ };
482
+ }
463
483
  // WebSocket API
464
484
  connectWS() {
465
- if (this.ws && (this.ws.readyState === 0 || this.ws.readyState === 1)) {
485
+ if (this.getWSState().OPEN || this.getWSState().CONNECTING) {
466
486
  return;
467
487
  }
468
488
  this.ws = new WS(this.wsBaseUrl);
@@ -475,15 +495,21 @@ var Shoonya = class extends EventEmitter {
475
495
  susertoken: this.accessToken
476
496
  };
477
497
  this.ws.send(JSON.stringify(msg));
498
+ this.emit("open", "opened ws connection");
499
+ clearInterval(this.heartbeatTimeout.timer);
500
+ this.heartbeatTimeout.timer = setInterval(() => {
501
+ const heartbeatMsg = '{"t":"h"}';
502
+ this.getWSState().OPEN && this.ws.send(heartbeatMsg);
503
+ this.logging && this.logger.log(`sent heartbeat message: ${heartbeatMsg}`);
504
+ }, this.heartbeatTimeout.timeout);
478
505
  if (this.cronJobRunning) {
479
506
  this.subscribe(this.scripList);
480
507
  return;
481
508
  }
482
- cron.schedule("43,00 3,10 * * 1-5", async () => {
483
- this.cronJobRunning = true;
484
- const hour = (/* @__PURE__ */ new Date()).getUTCHours();
485
- const min = (/* @__PURE__ */ new Date()).getUTCMinutes();
486
- if (hour === 3 && min === 43) {
509
+ cron.schedule(
510
+ "13 9 * * 1-5",
511
+ async () => {
512
+ this.cronJobRunning = true;
487
513
  try {
488
514
  const cred = {
489
515
  apiKey: this.apiKey,
@@ -492,21 +518,21 @@ var Shoonya = class extends EventEmitter {
492
518
  userId: this.userId
493
519
  };
494
520
  await this.login(cred);
495
- this.connectWS();
521
+ this.logging && this.logger.log("Token Refreshed");
496
522
  } catch (err) {
497
523
  console.error(err);
498
524
  const errMessage = `Token refreshing failed. ERR: ${err.message}`;
499
525
  this.logging && this.logger.log(errMessage, "ERROR");
526
+ this.getWSState().OPEN && this.ws.close();
500
527
  this.emit("stopped", errMessage);
501
528
  }
502
- }
503
- if (hour === 10 && min === 0) {
504
- this.disconnect();
505
- }
506
- });
529
+ },
530
+ { timezone: "Asia/Kolkata" }
531
+ );
507
532
  });
508
533
  this.ws.addEventListener("message", (e) => {
509
534
  const result = JSON.parse(e.data.toString());
535
+ this.lastWsMsgAt = /* @__PURE__ */ new Date();
510
536
  if (result.t === "dk") {
511
537
  this.logging && this.logger.log(`Subscribed to ${result.ts}. Listening...`);
512
538
  }
@@ -524,6 +550,19 @@ var Shoonya = class extends EventEmitter {
524
550
  }, 3e3);
525
551
  return;
526
552
  }
553
+ clearTimeout(this.reconnectTimeout.timer);
554
+ this.reconnectTimeout.timer = setTimeout(() => {
555
+ let msg = {
556
+ t: "c",
557
+ uid: this.userId,
558
+ actid: this.accountId,
559
+ source: "API",
560
+ susertoken: this.accessToken
561
+ };
562
+ this.subscribe(this.scripList);
563
+ this.getWSState().OPEN && this.ws.send(JSON.stringify(msg));
564
+ this.emit("reconnect", "reconnected with shoonya ws");
565
+ }, this.reconnectTimeout.timeout);
527
566
  clearTimeout(this.disconnectTimer);
528
567
  this.disconnectTimer = setTimeout(() => {
529
568
  const day = (/* @__PURE__ */ new Date()).getDay();
@@ -532,7 +571,16 @@ var Shoonya = class extends EventEmitter {
532
571
  const err = "API have stopped to respond! there could be a problem with API or market has closed";
533
572
  this.logging && this.logger.log(err, "WARN");
534
573
  this.emit("stopped", err);
535
- }, 6e4 * 60 * 18 - 15e3);
574
+ this.getWSState().OPEN && this.ws.close();
575
+ }, 6e4 * 60 * 18 - 1e4);
576
+ });
577
+ this.ws.addEventListener("error", (e) => {
578
+ this.logging && this.logger.log("some error in ws");
579
+ this.emit("error", e);
580
+ this.getWSState().OPEN && this.ws.close();
581
+ });
582
+ this.ws.addEventListener("close", (e) => {
583
+ this.emit("close", e);
536
584
  });
537
585
  }
538
586
  /**
@@ -583,7 +631,8 @@ var Shoonya = class extends EventEmitter {
583
631
  }
584
632
  }
585
633
  disconnect() {
586
- if (this.ws && this.ws.readyState !== 2 && this.ws.readyState !== 3) {
634
+ if (this.getWSState().OPEN || this.getWSState().CONNECTING) {
635
+ clearInterval(this.heartbeatTimeout.timer);
587
636
  this.ws.close();
588
637
  this.logging && this.logger.log("Websocket Connection with Shoonya API is now closed!");
589
638
  this.emit("close");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shoonya-sdk",
3
- "version": "0.3.6",
3
+ "version": "0.4.0",
4
4
  "description": "Wrapper around Shoonya API",
5
5
  "main": "dist/shoonya-sdk.js",
6
6
  "module": "dist/shoonya-sdk.mjs",