firstock 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Readme.md +144 -1
- package/dist/index.js +6 -1
- package/dist/test.js +17 -20
- package/dist/websockets/websocket_functions.js +668 -0
- package/dist/websockets/websockets.js +213 -0
- package/package.json +5 -2
- package/websockets/test_websockets.js +187 -0
package/Readme.md
CHANGED
|
@@ -5,7 +5,7 @@ To communicate with the Firstock Developer API using Nodejs, you can use the off
|
|
|
5
5
|
Licensed under the MIT License.
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
[Version - 1.0.
|
|
8
|
+
[Version - 1.0.5](https://www.npmjs.com/package/firstock)
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
## Documentation
|
|
@@ -277,6 +277,149 @@ firstock.timePriceSeries({
|
|
|
277
277
|
});
|
|
278
278
|
```
|
|
279
279
|
|
|
280
|
+
```javascript
|
|
281
|
+
WebSockets
|
|
282
|
+
|
|
283
|
+
Order Update Feed
|
|
284
|
+
|
|
285
|
+
const { Firstock, FirstockWebSocket } = require('./websockets');
|
|
286
|
+
|
|
287
|
+
// Define callback method
|
|
288
|
+
function orderBookData(data) {
|
|
289
|
+
console.log(data);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const userId = 'YOUR_USER_ID';
|
|
293
|
+
|
|
294
|
+
const model = new FirstockWebSocket({
|
|
295
|
+
order_data: orderBookData
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
async function main() {
|
|
299
|
+
const [conn, err] = await Firstock.initializeWebsockets(userId, model);
|
|
300
|
+
console.log("Error:", err);
|
|
301
|
+
|
|
302
|
+
await new Promise(resolve => setTimeout(resolve, 25000));
|
|
303
|
+
|
|
304
|
+
// Close WebSocket connection
|
|
305
|
+
const closeErr = await Firstock.closeWebsocket(conn);
|
|
306
|
+
console.log("Close Error:", closeErr);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
main();
|
|
310
|
+
|
|
311
|
+
Position Update Feed
|
|
312
|
+
|
|
313
|
+
const { Firstock, FirstockWebSocket } = require('./websockets');
|
|
314
|
+
|
|
315
|
+
// Define callback method
|
|
316
|
+
function positionBookData(data) {
|
|
317
|
+
console.log(data);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const userId = 'YOUR_USER_ID';
|
|
321
|
+
|
|
322
|
+
const model = new FirstockWebSocket({
|
|
323
|
+
position_data: positionBookData
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
async function main() {
|
|
327
|
+
const [conn, err] = await Firstock.initializeWebsockets(userId, model);
|
|
328
|
+
console.log("Error:", err);
|
|
329
|
+
|
|
330
|
+
await new Promise(resolve => setTimeout(resolve, 25000));
|
|
331
|
+
|
|
332
|
+
// Close WebSocket connection
|
|
333
|
+
const closeErr = await Firstock.closeWebsocket(conn);
|
|
334
|
+
console.log("Close Error:", closeErr);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
main();
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
Subscribe Feed
|
|
341
|
+
|
|
342
|
+
const { Firstock, FirstockWebSocket } = require('./websockets');
|
|
343
|
+
|
|
344
|
+
// Define callback method
|
|
345
|
+
function subscribeFeedData(data) {
|
|
346
|
+
console.log(data);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const userId = 'YOUR_USER_ID';
|
|
350
|
+
|
|
351
|
+
const model = new FirstockWebSocket({
|
|
352
|
+
subscribe_feed_data: subscribeFeedData,
|
|
353
|
+
tokens: ["NSE:26000"]
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
async function main() {
|
|
357
|
+
const [conn, err] = await Firstock.initializeWebsockets(userId, model);
|
|
358
|
+
console.log("Error:", err);
|
|
359
|
+
|
|
360
|
+
if (conn) {
|
|
361
|
+
const subscribeErr = await Firstock.subscribe(conn, ["BSE:1"]);
|
|
362
|
+
console.log("Subscribe Error:", subscribeErr);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
await new Promise(resolve => setTimeout(resolve, 25000));
|
|
366
|
+
|
|
367
|
+
// Unsubscribe
|
|
368
|
+
const unsubErr = await Firstock.unsubscribe(conn, ["NSE:26000", "BSE:1"]);
|
|
369
|
+
console.log("Unsubscribe Error:", unsubErr);
|
|
370
|
+
|
|
371
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
372
|
+
|
|
373
|
+
// Close WebSocket connection
|
|
374
|
+
const closeErr = await Firstock.closeWebsocket(conn);
|
|
375
|
+
console.log("Close Error:", closeErr);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
main();
|
|
379
|
+
|
|
380
|
+
Subscribe Option Greeks Feed
|
|
381
|
+
|
|
382
|
+
const { Firstock, FirstockWebSocket } = require('./websockets');
|
|
383
|
+
|
|
384
|
+
// Define callback method
|
|
385
|
+
function subscribeOptionGreeksData(data) {
|
|
386
|
+
console.log(data);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const userId = 'YOUR_USER_ID';
|
|
390
|
+
|
|
391
|
+
const model = new FirstockWebSocket({
|
|
392
|
+
subscribe_option_greeks_data: subscribeOptionGreeksData,
|
|
393
|
+
option_greeks_tokens: ["NFO:44297"]
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
async function main() {
|
|
397
|
+
const [conn, err] = await Firstock.initializeWebsockets(userId, model);
|
|
398
|
+
console.log("Error:", err);
|
|
399
|
+
|
|
400
|
+
if (conn) {
|
|
401
|
+
const subscribeErr = await Firstock.subscribeOptionGreeks(conn, ["NFO:44298"]);
|
|
402
|
+
console.log("Subscribe Error:", subscribeErr);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
await new Promise(resolve => setTimeout(resolve, 25000));
|
|
406
|
+
|
|
407
|
+
// Unsubscribe
|
|
408
|
+
const unsubErr = await Firstock.unsubscribeOptionGreeks(conn, ["NFO:44297", "NFO:44298"]);
|
|
409
|
+
console.log("Unsubscribe Error:", unsubErr);
|
|
410
|
+
|
|
411
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
412
|
+
|
|
413
|
+
// Close WebSocket connection
|
|
414
|
+
const closeErr = await Firstock.closeWebsocket(conn);
|
|
415
|
+
console.log("Close Error:", closeErr);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
main();
|
|
419
|
+
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
|
|
280
423
|
Refer to the [Firstock Connect Documentation](https://connect.thefirstock.com/) for the complete list of supported methods.
|
|
281
424
|
|
|
282
425
|
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FirstockWebSocket = exports.Firstock = void 0;
|
|
2
4
|
const Firstock_1 = require("./Classes/Firstock");
|
|
3
|
-
|
|
5
|
+
Object.defineProperty(exports, "Firstock", { enumerable: true, get: function () { return Firstock_1.Firstock; } });
|
|
6
|
+
const websockets_1 = require("./websockets/websockets");
|
|
7
|
+
Object.defineProperty(exports, "FirstockWebSocket", { enumerable: true, get: function () { return websockets_1.FirstockWebSocket; } });
|
|
8
|
+
exports.default = Firstock_1.Firstock;
|
package/dist/test.js
CHANGED
|
@@ -6,13 +6,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const index_1 = __importDefault(require("./index"));
|
|
7
7
|
const firstock = new index_1.default();
|
|
8
8
|
let orderNumber = "";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
const userDetails = {
|
|
10
|
+
userId: "NP2997",
|
|
11
|
+
password: "Skanda@2025",
|
|
12
|
+
TOTP: "1997",
|
|
13
|
+
vendorCode: "NP2997_API",
|
|
14
|
+
apiKey: "e55eb28e18ee1337fc0b2705f9b82465",
|
|
15
|
+
};
|
|
16
16
|
// const userDetails = {
|
|
17
17
|
// userId: "",
|
|
18
18
|
// password: "",
|
|
@@ -21,19 +21,16 @@ let orderNumber = "";
|
|
|
21
21
|
// apiKey: "",
|
|
22
22
|
// };
|
|
23
23
|
// // Login and user Details start
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
// console.log("Result: ", result);
|
|
35
|
-
// }
|
|
36
|
-
// );
|
|
24
|
+
firstock.login({
|
|
25
|
+
userId: userDetails.userId,
|
|
26
|
+
password: userDetails.password,
|
|
27
|
+
TOTP: userDetails.TOTP,
|
|
28
|
+
vendorCode: userDetails.vendorCode,
|
|
29
|
+
apiKey: userDetails.apiKey,
|
|
30
|
+
}, (err, result) => {
|
|
31
|
+
console.log("Error: ", err);
|
|
32
|
+
console.log("Result: ", result);
|
|
33
|
+
});
|
|
37
34
|
// // Order and report start
|
|
38
35
|
// firstock.placeOrder(
|
|
39
36
|
// {
|
|
@@ -0,0 +1,668 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.connections = exports.UpdateType = void 0;
|
|
16
|
+
exports.getUrlAndHeaderData = getUrlAndHeaderData;
|
|
17
|
+
exports.readMessage = readMessage;
|
|
18
|
+
exports.subscribe = subscribe;
|
|
19
|
+
exports.unsubscribe = unsubscribe;
|
|
20
|
+
exports.subscribeOptionGreeks = subscribeOptionGreeks;
|
|
21
|
+
exports.unsubscribeOptionGreeks = unsubscribeOptionGreeks;
|
|
22
|
+
const ws_1 = __importDefault(require("ws"));
|
|
23
|
+
const fs_1 = require("fs");
|
|
24
|
+
// Configure logging
|
|
25
|
+
const logger = {
|
|
26
|
+
debug: (msg) => console.debug(`${new Date().toISOString()} - ${msg}`),
|
|
27
|
+
info: (msg) => console.info(`${new Date().toISOString()} - ${msg}`),
|
|
28
|
+
warning: (msg) => console.warn(`${new Date().toISOString()} - ${msg}`),
|
|
29
|
+
error: (msg, err) => {
|
|
30
|
+
console.error(`[ERROR] ${new Date().toISOString()} - ${msg}`);
|
|
31
|
+
if (err)
|
|
32
|
+
console.error(err);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
var UpdateType;
|
|
36
|
+
(function (UpdateType) {
|
|
37
|
+
UpdateType["ORDER"] = "order";
|
|
38
|
+
UpdateType["POSITION"] = "position";
|
|
39
|
+
UpdateType["MARKET_FEED"] = "market_feed";
|
|
40
|
+
UpdateType["OPTION_GREEKS"] = "option_greeks";
|
|
41
|
+
})(UpdateType || (exports.UpdateType = UpdateType = {}));
|
|
42
|
+
class SafeConn {
|
|
43
|
+
constructor(ws) {
|
|
44
|
+
this.lock = false;
|
|
45
|
+
this.ws = ws;
|
|
46
|
+
}
|
|
47
|
+
acquireLock() {
|
|
48
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
49
|
+
while (this.lock) {
|
|
50
|
+
yield new Promise(resolve => setTimeout(resolve, 10));
|
|
51
|
+
}
|
|
52
|
+
this.lock = true;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
releaseLock() {
|
|
56
|
+
this.lock = false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
class ConnectionManager {
|
|
60
|
+
constructor() {
|
|
61
|
+
this.connMap = new Map();
|
|
62
|
+
this.indexMap = new Map();
|
|
63
|
+
this.lock = false;
|
|
64
|
+
}
|
|
65
|
+
acquireLock() {
|
|
66
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
67
|
+
while (this.lock) {
|
|
68
|
+
yield new Promise(resolve => setTimeout(resolve, 10));
|
|
69
|
+
}
|
|
70
|
+
this.lock = true;
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
releaseLock() {
|
|
74
|
+
this.lock = false;
|
|
75
|
+
}
|
|
76
|
+
countConnections() {
|
|
77
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
78
|
+
yield this.acquireLock();
|
|
79
|
+
const count = this.connMap.size;
|
|
80
|
+
this.releaseLock();
|
|
81
|
+
return count;
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
addConnection(ws) {
|
|
85
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
86
|
+
yield this.acquireLock();
|
|
87
|
+
if (this.indexMap.has(ws)) {
|
|
88
|
+
logger.info("Connection already exists");
|
|
89
|
+
this.releaseLock();
|
|
90
|
+
return this.indexMap.get(ws);
|
|
91
|
+
}
|
|
92
|
+
const safe = new SafeConn(ws);
|
|
93
|
+
this.connMap.set(safe, true);
|
|
94
|
+
this.indexMap.set(ws, safe);
|
|
95
|
+
logger.info("Connection added");
|
|
96
|
+
this.releaseLock();
|
|
97
|
+
return safe;
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
checkIfConnectionExists(ws) {
|
|
101
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
102
|
+
yield this.acquireLock();
|
|
103
|
+
const exists = this.indexMap.has(ws);
|
|
104
|
+
this.releaseLock();
|
|
105
|
+
return exists;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
writeMessage(ws, data) {
|
|
109
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
110
|
+
yield this.acquireLock();
|
|
111
|
+
const safe = this.indexMap.get(ws);
|
|
112
|
+
this.releaseLock();
|
|
113
|
+
if (!safe) {
|
|
114
|
+
return "connection not found";
|
|
115
|
+
}
|
|
116
|
+
yield safe.acquireLock();
|
|
117
|
+
try {
|
|
118
|
+
if (safe.ws.readyState === ws_1.default.OPEN) {
|
|
119
|
+
safe.ws.send(data);
|
|
120
|
+
safe.releaseLock();
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
safe.releaseLock();
|
|
125
|
+
return "WebSocket is not open";
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch (e) {
|
|
129
|
+
safe.releaseLock();
|
|
130
|
+
return e.message;
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
deleteConnection(ws) {
|
|
135
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
136
|
+
yield this.acquireLock();
|
|
137
|
+
if (this.indexMap.has(ws)) {
|
|
138
|
+
// Set shutdown flag BEFORE closing
|
|
139
|
+
_setShutdownFlag(ws);
|
|
140
|
+
const safe = this.indexMap.get(ws);
|
|
141
|
+
this.connMap.delete(safe);
|
|
142
|
+
this.indexMap.delete(ws);
|
|
143
|
+
try {
|
|
144
|
+
if (ws.readyState === ws_1.default.OPEN || ws.readyState === ws_1.default.CONNECTING) {
|
|
145
|
+
ws.close();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch (e) {
|
|
149
|
+
// Ignore errors during close
|
|
150
|
+
}
|
|
151
|
+
logger.info("Connection deleted");
|
|
152
|
+
// Clear subscription tracking
|
|
153
|
+
_clearTrackedSubscriptions(ws);
|
|
154
|
+
this.releaseLock();
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
logger.info("Connection not found");
|
|
158
|
+
this.releaseLock();
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
exports.connections = new ConnectionManager();
|
|
163
|
+
const subscriptionTracker = new Map();
|
|
164
|
+
function _getWsId(ws) {
|
|
165
|
+
return ws.__wsId || (ws.__wsId = Math.random());
|
|
166
|
+
}
|
|
167
|
+
function _trackSubscription(ws, tokens, subscriptionType) {
|
|
168
|
+
const wsId = _getWsId(ws);
|
|
169
|
+
if (!subscriptionTracker.has(wsId)) {
|
|
170
|
+
subscriptionTracker.set(wsId, {
|
|
171
|
+
tokens: [],
|
|
172
|
+
option_greeks_tokens: []
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
const tracker = subscriptionTracker.get(wsId);
|
|
176
|
+
for (const token of tokens) {
|
|
177
|
+
if (!tracker[subscriptionType].includes(token)) {
|
|
178
|
+
tracker[subscriptionType].push(token);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
logger.debug(`Tracked subscription: ${subscriptionType} - ${tokens.join(', ')}`);
|
|
182
|
+
}
|
|
183
|
+
function _untrackSubscription(ws, tokens, subscriptionType) {
|
|
184
|
+
const wsId = _getWsId(ws);
|
|
185
|
+
if (subscriptionTracker.has(wsId)) {
|
|
186
|
+
const tracker = subscriptionTracker.get(wsId);
|
|
187
|
+
for (const token of tokens) {
|
|
188
|
+
const index = tracker[subscriptionType].indexOf(token);
|
|
189
|
+
if (index > -1) {
|
|
190
|
+
tracker[subscriptionType].splice(index, 1);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
logger.debug(`Untracked subscription: ${subscriptionType} - ${tokens.join(', ')}`);
|
|
195
|
+
}
|
|
196
|
+
function _getTrackedSubscriptions(ws) {
|
|
197
|
+
const wsId = _getWsId(ws);
|
|
198
|
+
return subscriptionTracker.get(wsId) || { tokens: [], option_greeks_tokens: [] };
|
|
199
|
+
}
|
|
200
|
+
function _clearTrackedSubscriptions(ws) {
|
|
201
|
+
const wsId = _getWsId(ws);
|
|
202
|
+
if (subscriptionTracker.has(wsId)) {
|
|
203
|
+
subscriptionTracker.delete(wsId);
|
|
204
|
+
logger.debug(`Cleared subscription tracking for connection ${wsId}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// Shutdown flags
|
|
208
|
+
const shutdownFlags = new Map();
|
|
209
|
+
function _setShutdownFlag(ws) {
|
|
210
|
+
const wsId = _getWsId(ws);
|
|
211
|
+
shutdownFlags.set(wsId, true);
|
|
212
|
+
logger.debug(`Shutdown flag set for connection ${wsId}`);
|
|
213
|
+
}
|
|
214
|
+
function _isShutdownRequested(ws) {
|
|
215
|
+
const wsId = _getWsId(ws);
|
|
216
|
+
return shutdownFlags.get(wsId) || false;
|
|
217
|
+
}
|
|
218
|
+
function _clearShutdownFlag(ws) {
|
|
219
|
+
const wsId = _getWsId(ws);
|
|
220
|
+
if (shutdownFlags.has(wsId)) {
|
|
221
|
+
shutdownFlags.delete(wsId);
|
|
222
|
+
logger.debug(`Shutdown flag cleared for connection ${wsId}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function getUrlAndHeaderData(userId, config) {
|
|
226
|
+
const scheme = config.scheme || 'wss';
|
|
227
|
+
const host = config.host || 'socket.firstock.in';
|
|
228
|
+
const path = config.path || '/ws';
|
|
229
|
+
const srcVal = config.source || 'API';
|
|
230
|
+
const acceptEncoding = config.accept_encoding || 'gzip, deflate, br';
|
|
231
|
+
const acceptLanguage = config.accept_language || 'en-US,en;q=0.9';
|
|
232
|
+
const origin = config.origin || '';
|
|
233
|
+
const baseUrl = `${scheme}://${host}${path}`;
|
|
234
|
+
logger.info(`Connecting to ${baseUrl}`);
|
|
235
|
+
let configJson;
|
|
236
|
+
try {
|
|
237
|
+
const configFile = (0, fs_1.readFileSync)('config.json', 'utf-8');
|
|
238
|
+
configJson = JSON.parse(configFile);
|
|
239
|
+
}
|
|
240
|
+
catch (e) {
|
|
241
|
+
return ['', null, new Error('Failed to read config.json')];
|
|
242
|
+
}
|
|
243
|
+
// Use the userId parameter to get the jKey
|
|
244
|
+
if (!configJson[userId]) {
|
|
245
|
+
return ['', null, new Error(`User ID '${userId}' not found in config.json`)];
|
|
246
|
+
}
|
|
247
|
+
const jkey = configJson[userId].jKey;
|
|
248
|
+
if (!jkey) {
|
|
249
|
+
return ['', null, new Error(`jKey not found for user '${userId}' in config.json`)];
|
|
250
|
+
}
|
|
251
|
+
const queryParams = new URLSearchParams({
|
|
252
|
+
userId: userId,
|
|
253
|
+
jKey: jkey,
|
|
254
|
+
source: 'developer-api'
|
|
255
|
+
});
|
|
256
|
+
const urlWithParams = `${baseUrl}?${queryParams.toString()}`;
|
|
257
|
+
const headers = {
|
|
258
|
+
'accept-encoding': acceptEncoding,
|
|
259
|
+
'accept-language': acceptLanguage,
|
|
260
|
+
'cache-control': 'no-cache',
|
|
261
|
+
'origin': origin,
|
|
262
|
+
'pragma': 'no-cache'
|
|
263
|
+
};
|
|
264
|
+
return [urlWithParams, headers, null];
|
|
265
|
+
}
|
|
266
|
+
function _identifyUpdateType(data) {
|
|
267
|
+
if (data.norenordno) {
|
|
268
|
+
return UpdateType.ORDER;
|
|
269
|
+
}
|
|
270
|
+
else if (data.brkname) {
|
|
271
|
+
return UpdateType.POSITION;
|
|
272
|
+
}
|
|
273
|
+
else if (data.gamma) {
|
|
274
|
+
return UpdateType.OPTION_GREEKS;
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
return UpdateType.MARKET_FEED;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function _handleAuthenticationResponse(message) {
|
|
281
|
+
if (message.includes("Authentication successful")) {
|
|
282
|
+
logger.info("Authentication successful");
|
|
283
|
+
return true;
|
|
284
|
+
}
|
|
285
|
+
else if (message.includes('"status":"failed"')) {
|
|
286
|
+
logger.warning(`Authentication failed: ${message}`);
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
function readMessage(userId, ws, model, config) {
|
|
292
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
293
|
+
const maxRetries = config.max_websocket_connection_retries || 3;
|
|
294
|
+
const timeInterval = config.time_interval || 5;
|
|
295
|
+
let messageCount = 0;
|
|
296
|
+
let isAuthenticated = true;
|
|
297
|
+
logger.info(`Starting message reader for user ${userId}`);
|
|
298
|
+
logger.info(`Callbacks configured - Feed: ${model.subscribe_feed_data !== undefined}, ` +
|
|
299
|
+
`Order: ${model.order_data !== undefined}, ` +
|
|
300
|
+
`Position: ${model.position_data !== undefined}, ` +
|
|
301
|
+
`Option Greeks: ${model.subscribe_option_greeks_data !== undefined}`);
|
|
302
|
+
const messageHandler = (data) => __awaiter(this, void 0, void 0, function* () {
|
|
303
|
+
if (!(yield exports.connections.checkIfConnectionExists(ws))) {
|
|
304
|
+
logger.info("Connection no longer exists, stopping reader");
|
|
305
|
+
_clearShutdownFlag(ws);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
try {
|
|
309
|
+
const message = data.toString();
|
|
310
|
+
messageCount++;
|
|
311
|
+
logger.debug(`Message #${messageCount}: ${message.substring(0, 200)}...`);
|
|
312
|
+
if (!message) {
|
|
313
|
+
logger.debug("Empty message received, skipping");
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
let parsedData;
|
|
317
|
+
try {
|
|
318
|
+
parsedData = JSON.parse(message);
|
|
319
|
+
}
|
|
320
|
+
catch (e) {
|
|
321
|
+
logger.error(`JSON parse error: ${e.message}. Message: ${message.substring(0, 200)}`);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
// Handle authentication responses
|
|
325
|
+
if (parsedData.status && parsedData.message) {
|
|
326
|
+
if (_handleAuthenticationResponse(message)) {
|
|
327
|
+
isAuthenticated = true;
|
|
328
|
+
logger.info("Re-authentication successful");
|
|
329
|
+
}
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
// Route messages to appropriate callbacks
|
|
333
|
+
const updateType = _identifyUpdateType(parsedData);
|
|
334
|
+
if (updateType === UpdateType.ORDER) {
|
|
335
|
+
if (model.order_data) {
|
|
336
|
+
try {
|
|
337
|
+
model.order_data(parsedData);
|
|
338
|
+
logger.debug("Order callback invoked");
|
|
339
|
+
}
|
|
340
|
+
catch (e) {
|
|
341
|
+
logger.error(`Error in order callback: ${e.message}`, e);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
else if (updateType === UpdateType.POSITION) {
|
|
346
|
+
if (model.position_data) {
|
|
347
|
+
try {
|
|
348
|
+
model.position_data(parsedData);
|
|
349
|
+
logger.debug("Position callback invoked");
|
|
350
|
+
}
|
|
351
|
+
catch (e) {
|
|
352
|
+
logger.error(`Error in position callback: ${e.message}`, e);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
else if (updateType === UpdateType.OPTION_GREEKS) {
|
|
357
|
+
if (model.subscribe_option_greeks_data) {
|
|
358
|
+
try {
|
|
359
|
+
if (typeof parsedData === 'object') {
|
|
360
|
+
for (const [key, value] of Object.entries(parsedData)) {
|
|
361
|
+
if (typeof value === 'object' && value.gamma) {
|
|
362
|
+
model.subscribe_option_greeks_data(value);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
logger.debug("Option Greeks callback invoked");
|
|
367
|
+
}
|
|
368
|
+
catch (e) {
|
|
369
|
+
logger.error(`Error in option Greeks callback: ${e.message}`, e);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
if (model.subscribe_feed_data) {
|
|
375
|
+
try {
|
|
376
|
+
model.subscribe_feed_data(parsedData);
|
|
377
|
+
logger.debug("Feed callback invoked");
|
|
378
|
+
}
|
|
379
|
+
catch (e) {
|
|
380
|
+
logger.error(`Error in feed callback: ${e.message}`, e);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
catch (e) {
|
|
386
|
+
logger.error(`Error processing message: ${e.message}`, e);
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
const closeHandler = () => __awaiter(this, void 0, void 0, function* () {
|
|
390
|
+
// Check if shutdown was requested
|
|
391
|
+
if (_isShutdownRequested(ws)) {
|
|
392
|
+
logger.info("Connection closed intentionally");
|
|
393
|
+
_clearShutdownFlag(ws);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
// Unexpected disconnection - attempt reconnection
|
|
397
|
+
logger.warning("Unexpected disconnection");
|
|
398
|
+
console.log("\n Connection lost");
|
|
399
|
+
console.log("Attempting to reconnect...");
|
|
400
|
+
if (!(yield exports.connections.checkIfConnectionExists(ws))) {
|
|
401
|
+
logger.info("Connection no longer in manager, stopping reader");
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
// Attempt reconnection
|
|
405
|
+
const newWs = yield _attemptReconnection(userId, ws, config, maxRetries, timeInterval, model);
|
|
406
|
+
if (newWs === null) {
|
|
407
|
+
logger.error("Failed to reconnect after all attempts");
|
|
408
|
+
console.log("✗ Failed to reconnect. Please restart the application.");
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
logger.info("Reconnection successful, resuming message reading");
|
|
412
|
+
// Call user's reconnection callback if provided
|
|
413
|
+
if (model.on_reconnect) {
|
|
414
|
+
try {
|
|
415
|
+
model.on_reconnect(newWs);
|
|
416
|
+
logger.info("User's on_reconnect callback executed");
|
|
417
|
+
}
|
|
418
|
+
catch (callbackError) {
|
|
419
|
+
logger.error(`Error in on_reconnect callback: ${callbackError.message}`, callbackError);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
const errorHandler = (error) => __awaiter(this, void 0, void 0, function* () {
|
|
424
|
+
logger.error(`WebSocket error: ${error.message}`, error);
|
|
425
|
+
// Trigger close handler for reconnection logic
|
|
426
|
+
yield closeHandler();
|
|
427
|
+
});
|
|
428
|
+
ws.on('message', messageHandler);
|
|
429
|
+
ws.on('close', closeHandler);
|
|
430
|
+
ws.on('error', errorHandler);
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
function _attemptReconnection(userId, oldWs, config, maxRetries, timeInterval, model) {
|
|
434
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
435
|
+
// Get the actual tracked subscriptions
|
|
436
|
+
const tracked = _getTrackedSubscriptions(oldWs);
|
|
437
|
+
const allTokens = tracked.tokens;
|
|
438
|
+
const allOptionGreeksTokens = tracked.option_greeks_tokens;
|
|
439
|
+
logger.info(`Attempting reconnection. Will restore: ${allTokens.length} market tokens, ${allOptionGreeksTokens.length} option greeks tokens`);
|
|
440
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
441
|
+
if (!(yield exports.connections.checkIfConnectionExists(oldWs))) {
|
|
442
|
+
logger.info("Connection no longer exists in manager, stopping reconnection");
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
logger.info(`Reconnection attempt ${attempt}/${maxRetries}...`);
|
|
446
|
+
console.log(`Reconnection attempt ${attempt}/${maxRetries}...`);
|
|
447
|
+
yield new Promise(resolve => setTimeout(resolve, timeInterval * 1000));
|
|
448
|
+
try {
|
|
449
|
+
// Create new connection
|
|
450
|
+
const [baseUrl, headers, err] = getUrlAndHeaderData(userId, config);
|
|
451
|
+
if (err) {
|
|
452
|
+
logger.error(`Failed to get URL/headers: ${err.message}`);
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
const newWs = new ws_1.default(baseUrl, { headers });
|
|
456
|
+
// Wait for connection to open
|
|
457
|
+
yield new Promise((resolve, reject) => {
|
|
458
|
+
const timeout = setTimeout(() => {
|
|
459
|
+
reject(new Error('Connection timeout'));
|
|
460
|
+
}, 10000);
|
|
461
|
+
newWs.once('open', () => {
|
|
462
|
+
clearTimeout(timeout);
|
|
463
|
+
resolve();
|
|
464
|
+
});
|
|
465
|
+
newWs.once('error', (error) => {
|
|
466
|
+
clearTimeout(timeout);
|
|
467
|
+
reject(error);
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
logger.info(`New WebSocket connection created (attempt ${attempt})`);
|
|
471
|
+
// Wait for authentication message
|
|
472
|
+
const authMessage = yield new Promise((resolve, reject) => {
|
|
473
|
+
const timeout = setTimeout(() => {
|
|
474
|
+
reject(new Error('Authentication timeout'));
|
|
475
|
+
}, 5000);
|
|
476
|
+
newWs.once('message', (data) => {
|
|
477
|
+
clearTimeout(timeout);
|
|
478
|
+
resolve(data.toString());
|
|
479
|
+
});
|
|
480
|
+
newWs.once('error', (error) => {
|
|
481
|
+
clearTimeout(timeout);
|
|
482
|
+
reject(error);
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
logger.info(`Reconnection auth message: ${authMessage}`);
|
|
486
|
+
if (!authMessage.includes("Authentication successful")) {
|
|
487
|
+
logger.warning(`Authentication failed on reconnect: ${authMessage}`);
|
|
488
|
+
newWs.close();
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
// Transfer subscription tracking from old connection to new connection
|
|
492
|
+
const oldWsId = _getWsId(oldWs);
|
|
493
|
+
const newWsId = _getWsId(newWs);
|
|
494
|
+
if (subscriptionTracker.has(oldWsId)) {
|
|
495
|
+
const oldTracker = subscriptionTracker.get(oldWsId);
|
|
496
|
+
subscriptionTracker.set(newWsId, {
|
|
497
|
+
tokens: [...oldTracker.tokens],
|
|
498
|
+
option_greeks_tokens: [...oldTracker.option_greeks_tokens]
|
|
499
|
+
});
|
|
500
|
+
logger.debug(`Transferred subscription tracking from ${oldWsId} to ${newWsId}`);
|
|
501
|
+
}
|
|
502
|
+
// Update connection manager
|
|
503
|
+
yield exports.connections.deleteConnection(oldWs);
|
|
504
|
+
yield exports.connections.addConnection(newWs);
|
|
505
|
+
logger.info("Connection manager updated with new connection");
|
|
506
|
+
// Resubscribe to all tracked tokens
|
|
507
|
+
logger.info("Resubscribing to previous subscriptions...");
|
|
508
|
+
console.log("Resubscribing to previous feeds...");
|
|
509
|
+
// Resubscribe to market feed tokens
|
|
510
|
+
if (allTokens.length > 0) {
|
|
511
|
+
yield new Promise(resolve => setTimeout(resolve, 500));
|
|
512
|
+
const tokensStr = allTokens.join("|");
|
|
513
|
+
const msg = JSON.stringify({ action: "subscribe", tokens: tokensStr });
|
|
514
|
+
const error = yield exports.connections.writeMessage(newWs, msg);
|
|
515
|
+
if (error) {
|
|
516
|
+
logger.error(`Failed to resubscribe to market tokens: ${error}`);
|
|
517
|
+
}
|
|
518
|
+
else {
|
|
519
|
+
logger.info(`Resubscribed to ${allTokens.length} market feed token(s): ${allTokens.join(', ')}`);
|
|
520
|
+
console.log(`✓ Resubscribed to ${allTokens.length} market feed token(s)`);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
if (allOptionGreeksTokens.length > 0) {
|
|
524
|
+
yield new Promise(resolve => setTimeout(resolve, 500));
|
|
525
|
+
const tokensStr = allOptionGreeksTokens.join("|");
|
|
526
|
+
const msg = JSON.stringify({ action: "subscribe-option-greeks", tokens: tokensStr });
|
|
527
|
+
const error = yield exports.connections.writeMessage(newWs, msg);
|
|
528
|
+
if (error) {
|
|
529
|
+
logger.error(`Failed to resubscribe to option Greeks: ${error}`);
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
logger.info(`Resubscribed to ${allOptionGreeksTokens.length} option Greeks token(s): ${allOptionGreeksTokens.join(', ')}`);
|
|
533
|
+
console.log(`✓ Resubscribed to ${allOptionGreeksTokens.length} option Greeks token(s)`);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
yield new Promise(resolve => setTimeout(resolve, 1000));
|
|
537
|
+
logger.info("Reconnection and resubscription successful!");
|
|
538
|
+
console.log("✓ Reconnection complete - all subscriptions restored");
|
|
539
|
+
// IMPORTANT: Restart the message reader for the new WebSocket
|
|
540
|
+
readMessage(userId, newWs, model, config);
|
|
541
|
+
return newWs;
|
|
542
|
+
}
|
|
543
|
+
catch (e) {
|
|
544
|
+
logger.error(`Reconnection attempt ${attempt} failed: ${e.message}`, e);
|
|
545
|
+
console.log(`✗ Attempt ${attempt} failed: ${e.message.substring(0, 100)}...`);
|
|
546
|
+
if (attempt === maxRetries) {
|
|
547
|
+
logger.error("Max reconnection attempts reached");
|
|
548
|
+
console.log(`✗ All ${maxRetries} reconnection attempts failed`);
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
return null;
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
function subscribe(ws, tokens) {
|
|
557
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
558
|
+
if (!(yield exports.connections.checkIfConnectionExists(ws))) {
|
|
559
|
+
return {
|
|
560
|
+
error: {
|
|
561
|
+
message: "Connection does not exist"
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
_trackSubscription(ws, tokens, 'tokens');
|
|
566
|
+
let tokensStr;
|
|
567
|
+
if (tokens.length === 1 && tokens[0].includes("|")) {
|
|
568
|
+
tokensStr = tokens[0];
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
tokensStr = tokens.join("|");
|
|
572
|
+
}
|
|
573
|
+
const msg = JSON.stringify({
|
|
574
|
+
action: "subscribe",
|
|
575
|
+
tokens: tokensStr
|
|
576
|
+
});
|
|
577
|
+
logger.info(`Sending subscribe message: ${msg}`);
|
|
578
|
+
const error = yield exports.connections.writeMessage(ws, msg);
|
|
579
|
+
if (error) {
|
|
580
|
+
return { error: { message: error } };
|
|
581
|
+
}
|
|
582
|
+
logger.info("Subscribe message sent successfully");
|
|
583
|
+
return null;
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
function unsubscribe(ws, tokens) {
|
|
587
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
588
|
+
if (!(yield exports.connections.checkIfConnectionExists(ws))) {
|
|
589
|
+
return {
|
|
590
|
+
error: {
|
|
591
|
+
message: "Connection does not exist"
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
_untrackSubscription(ws, tokens, 'tokens');
|
|
596
|
+
const tokensStr = tokens.join("|");
|
|
597
|
+
const msg = JSON.stringify({
|
|
598
|
+
action: "unsubscribe",
|
|
599
|
+
tokens: tokensStr
|
|
600
|
+
});
|
|
601
|
+
logger.info(`Sending unsubscribe message: ${msg}`);
|
|
602
|
+
const error = yield exports.connections.writeMessage(ws, msg);
|
|
603
|
+
if (error) {
|
|
604
|
+
return { error: { message: error } };
|
|
605
|
+
}
|
|
606
|
+
return null;
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
function subscribeOptionGreeks(ws, tokens) {
|
|
610
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
611
|
+
if (!(yield exports.connections.checkIfConnectionExists(ws))) {
|
|
612
|
+
return {
|
|
613
|
+
error: {
|
|
614
|
+
message: "Connection does not exist"
|
|
615
|
+
}
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
_trackSubscription(ws, tokens, 'option_greeks_tokens');
|
|
619
|
+
let tokensStr;
|
|
620
|
+
if (tokens.length === 1 && tokens[0].includes("|")) {
|
|
621
|
+
tokensStr = tokens[0];
|
|
622
|
+
}
|
|
623
|
+
else {
|
|
624
|
+
tokensStr = tokens.join("|");
|
|
625
|
+
}
|
|
626
|
+
const msg = JSON.stringify({
|
|
627
|
+
action: "subscribe-option-greeks",
|
|
628
|
+
tokens: tokensStr
|
|
629
|
+
});
|
|
630
|
+
logger.info(`Sending subscribe option greeks message: ${msg}`);
|
|
631
|
+
const error = yield exports.connections.writeMessage(ws, msg);
|
|
632
|
+
if (error) {
|
|
633
|
+
return { error: { message: error } };
|
|
634
|
+
}
|
|
635
|
+
logger.info("Subscribe option greeks message sent successfully");
|
|
636
|
+
return null;
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
function unsubscribeOptionGreeks(ws, tokens) {
|
|
640
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
641
|
+
if (!(yield exports.connections.checkIfConnectionExists(ws))) {
|
|
642
|
+
return {
|
|
643
|
+
error: {
|
|
644
|
+
message: "Connection does not exist"
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
_untrackSubscription(ws, tokens, 'option_greeks_tokens');
|
|
649
|
+
let tokensStr;
|
|
650
|
+
if (tokens.length === 1 && tokens[0].includes("|")) {
|
|
651
|
+
tokensStr = tokens[0];
|
|
652
|
+
}
|
|
653
|
+
else {
|
|
654
|
+
tokensStr = tokens.join("|");
|
|
655
|
+
}
|
|
656
|
+
const msg = JSON.stringify({
|
|
657
|
+
action: "unsubscribe-option-greeks",
|
|
658
|
+
tokens: tokensStr
|
|
659
|
+
});
|
|
660
|
+
logger.info(`Sending unsubscribe option greeks message: ${msg}`);
|
|
661
|
+
const error = yield exports.connections.writeMessage(ws, msg);
|
|
662
|
+
if (error) {
|
|
663
|
+
return { error: { message: error } };
|
|
664
|
+
}
|
|
665
|
+
logger.info("Unsubscribe option greeks message sent successfully");
|
|
666
|
+
return null;
|
|
667
|
+
});
|
|
668
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.Firstock = exports.FirstockWebSocket = void 0;
|
|
16
|
+
const ws_1 = __importDefault(require("ws"));
|
|
17
|
+
const websocket_functions_1 = require("./websocket_functions");
|
|
18
|
+
const logger = {
|
|
19
|
+
info: (msg) => console.info(`[INFO] ${new Date().toISOString()} - ${msg}`),
|
|
20
|
+
error: (msg, err) => {
|
|
21
|
+
console.error(`[ERROR] ${new Date().toISOString()} - ${msg}`);
|
|
22
|
+
if (err)
|
|
23
|
+
console.error(err);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
class FirstockWebSocket {
|
|
27
|
+
constructor(options = {}) {
|
|
28
|
+
this.tokens = options.tokens || [];
|
|
29
|
+
this.option_greeks_tokens = options.option_greeks_tokens || [];
|
|
30
|
+
this.order_data = options.order_data;
|
|
31
|
+
this.position_data = options.position_data;
|
|
32
|
+
this.subscribe_feed_data = options.subscribe_feed_data;
|
|
33
|
+
this.subscribe_option_greeks_data = options.subscribe_option_greeks_data;
|
|
34
|
+
this.on_reconnect = options.on_reconnect;
|
|
35
|
+
}
|
|
36
|
+
toDict() {
|
|
37
|
+
return {
|
|
38
|
+
tokens: this.tokens,
|
|
39
|
+
option_greeks_tokens: this.option_greeks_tokens,
|
|
40
|
+
order_data: this.order_data,
|
|
41
|
+
position_data: this.position_data,
|
|
42
|
+
subscribe_feed_data: this.subscribe_feed_data,
|
|
43
|
+
subscribe_option_greeks_data: this.subscribe_option_greeks_data,
|
|
44
|
+
on_reconnect: this.on_reconnect
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
exports.FirstockWebSocket = FirstockWebSocket;
|
|
49
|
+
class Firstock {
|
|
50
|
+
static initializeWebsockets(userId, model, config) {
|
|
51
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
52
|
+
const finalConfig = Object.assign({ scheme: 'wss', host: 'socket.firstock.in', path: '/ws', source: 'developer-api', accept_encoding: 'gzip, deflate, br', accept_language: 'en-US,en;q=0.9', origin: 'https://firstock.in', max_websocket_connection_retries: 3, time_interval: 5 }, config);
|
|
53
|
+
const [baseUrl, headers, err] = (0, websocket_functions_1.getUrlAndHeaderData)(userId, finalConfig);
|
|
54
|
+
if (err) {
|
|
55
|
+
return [null, { error: { message: err.message } }];
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const ws = new ws_1.default(baseUrl, { headers });
|
|
59
|
+
yield new Promise((resolve, reject) => {
|
|
60
|
+
const timeout = setTimeout(() => {
|
|
61
|
+
reject(new Error('Connection timeout'));
|
|
62
|
+
}, 10000);
|
|
63
|
+
ws.once('open', () => {
|
|
64
|
+
clearTimeout(timeout);
|
|
65
|
+
resolve();
|
|
66
|
+
});
|
|
67
|
+
ws.once('error', (error) => {
|
|
68
|
+
clearTimeout(timeout);
|
|
69
|
+
reject(error);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
logger.info("WebSocket connection created");
|
|
73
|
+
yield websocket_functions_1.connections.addConnection(ws);
|
|
74
|
+
const msg = yield new Promise((resolve, reject) => {
|
|
75
|
+
const timeout = setTimeout(() => {
|
|
76
|
+
reject(new Error('Authentication timeout'));
|
|
77
|
+
}, 5000);
|
|
78
|
+
ws.once('message', (data) => {
|
|
79
|
+
clearTimeout(timeout);
|
|
80
|
+
resolve(data.toString());
|
|
81
|
+
});
|
|
82
|
+
ws.once('error', (error) => {
|
|
83
|
+
clearTimeout(timeout);
|
|
84
|
+
reject(error);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
logger.info(`Initial message received: ${msg}`);
|
|
88
|
+
if (msg.includes("Authentication successful")) {
|
|
89
|
+
logger.info("Authentication successful, starting message reader");
|
|
90
|
+
yield new Promise(resolve => setTimeout(resolve, 500));
|
|
91
|
+
const modelDict = model.toDict();
|
|
92
|
+
(0, websocket_functions_1.readMessage)(userId, ws, modelDict, finalConfig);
|
|
93
|
+
yield new Promise(resolve => setTimeout(resolve, 500));
|
|
94
|
+
if (model.tokens && model.tokens.length > 0) {
|
|
95
|
+
logger.info(`Subscribing to initial tokens: ${model.tokens.join(', ')}`);
|
|
96
|
+
const subscribeErr = yield (0, websocket_functions_1.subscribe)(ws, model.tokens);
|
|
97
|
+
if (subscribeErr) {
|
|
98
|
+
logger.error(`Initial subscription error: ${JSON.stringify(subscribeErr)}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (model.option_greeks_tokens && model.option_greeks_tokens.length > 0) {
|
|
102
|
+
logger.info(`Subscribing to option Greeks tokens: ${model.option_greeks_tokens.join(', ')}`);
|
|
103
|
+
const subscribeErr = yield (0, websocket_functions_1.subscribeOptionGreeks)(ws, model.option_greeks_tokens);
|
|
104
|
+
if (subscribeErr) {
|
|
105
|
+
logger.error(`Initial option Greeks subscription error: ${JSON.stringify(subscribeErr)}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return [ws, null];
|
|
109
|
+
}
|
|
110
|
+
else if (msg.includes("Maximum sessions limit")) {
|
|
111
|
+
yield websocket_functions_1.connections.deleteConnection(ws);
|
|
112
|
+
return [null, { error: { message: msg } }];
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
yield websocket_functions_1.connections.deleteConnection(ws);
|
|
116
|
+
return [null, { error: { message: `Unexpected authentication response: ${msg}` } }];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
logger.error(`WebSocket initialization error: ${e.message}`, e);
|
|
121
|
+
return [null, { error: { message: e.message } }];
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
static closeWebsocket(ws) {
|
|
126
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
127
|
+
if (ws === null) {
|
|
128
|
+
return {
|
|
129
|
+
error: {
|
|
130
|
+
message: "Connection does not exist"
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (yield websocket_functions_1.connections.checkIfConnectionExists(ws)) {
|
|
135
|
+
try {
|
|
136
|
+
ws.close();
|
|
137
|
+
yield websocket_functions_1.connections.deleteConnection(ws);
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
catch (e) {
|
|
141
|
+
const errorMsg = e.message.toLowerCase();
|
|
142
|
+
if (!errorMsg.includes("closed")) {
|
|
143
|
+
return {
|
|
144
|
+
error: {
|
|
145
|
+
message: e.message
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
yield websocket_functions_1.connections.deleteConnection(ws);
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
return {
|
|
157
|
+
error: {
|
|
158
|
+
message: "Connection does not exist"
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
static subscribe(ws, tokens) {
|
|
165
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
166
|
+
if (ws === null) {
|
|
167
|
+
return {
|
|
168
|
+
error: {
|
|
169
|
+
message: "Connection does not exist"
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
return (0, websocket_functions_1.subscribe)(ws, tokens);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
static unsubscribe(ws, tokens) {
|
|
177
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
178
|
+
if (ws === null) {
|
|
179
|
+
return {
|
|
180
|
+
error: {
|
|
181
|
+
message: "Connection does not exist"
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
return (0, websocket_functions_1.unsubscribe)(ws, tokens);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
static subscribeOptionGreeks(ws, tokens) {
|
|
189
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
190
|
+
if (ws === null) {
|
|
191
|
+
return {
|
|
192
|
+
error: {
|
|
193
|
+
message: "Connection does not exist"
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
return (0, websocket_functions_1.subscribeOptionGreeks)(ws, tokens);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
static unsubscribeOptionGreeks(ws, tokens) {
|
|
201
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
202
|
+
if (ws === null) {
|
|
203
|
+
return {
|
|
204
|
+
error: {
|
|
205
|
+
message: "Connection does not exist"
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
return (0, websocket_functions_1.unsubscribeOptionGreeks)(ws, tokens);
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
exports.Firstock = Firstock;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "firstock",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Node js package for using firstock developer apis",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -23,10 +23,13 @@
|
|
|
23
23
|
"homepage": "https://github.com/the-firstock/firstock-developer-sdk-nodejs#readme",
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"axios": "^1.10.0",
|
|
26
|
-
"sha256": "^0.2.0"
|
|
26
|
+
"sha256": "^0.2.0",
|
|
27
|
+
"util": "^0.12.5",
|
|
28
|
+
"ws": "^8.18.3"
|
|
27
29
|
},
|
|
28
30
|
"devDependencies": {
|
|
29
31
|
"@types/sha256": "^0.2.2",
|
|
32
|
+
"@types/ws": "^8.18.1",
|
|
30
33
|
"typescript": "^5.8.3"
|
|
31
34
|
}
|
|
32
35
|
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
const { Firstock, FirstockWebSocket } = require('./websockets');
|
|
2
|
+
const { appendFileSync } = require('fs');
|
|
3
|
+
|
|
4
|
+
function subscribeFeedData(data) {
|
|
5
|
+
try {
|
|
6
|
+
appendFileSync('websocket.log', `${JSON.stringify(data)}\n`);
|
|
7
|
+
console.log(data);
|
|
8
|
+
} catch (e) {
|
|
9
|
+
console.error(`Error writing to log: ${e}`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function subscribeOptionGreeksData(data) {
|
|
14
|
+
try {
|
|
15
|
+
const timestamp = new Date().toISOString();
|
|
16
|
+
appendFileSync('option_greeks.log', `${timestamp} - ${JSON.stringify(data)}\n`);
|
|
17
|
+
} catch (e) {
|
|
18
|
+
console.error(`Error writing option Greeks: ${e}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function orderBookData(data) {
|
|
23
|
+
try {
|
|
24
|
+
const timestamp = new Date().toISOString();
|
|
25
|
+
appendFileSync('order_detail.log', `${timestamp} - ${JSON.stringify(data)}\n`);
|
|
26
|
+
} catch (e) {
|
|
27
|
+
console.error(`Error opening log file: ${e}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function positionBookData(data) {
|
|
32
|
+
try {
|
|
33
|
+
const timestamp = new Date().toISOString();
|
|
34
|
+
appendFileSync('position_detail.log', `${timestamp} - ${JSON.stringify(data)}\n`);
|
|
35
|
+
} catch (e) {
|
|
36
|
+
console.error(`Error opening log file: ${e}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function subscribeFeedData2(data) {
|
|
41
|
+
try {
|
|
42
|
+
const timestamp = new Date().toISOString();
|
|
43
|
+
appendFileSync('websocket2.log', `${timestamp} - ${JSON.stringify(data)}\n`);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
console.error(`Error opening log file: ${e}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function subscribeFeedData3(data) {
|
|
50
|
+
try {
|
|
51
|
+
const timestamp = new Date().toISOString();
|
|
52
|
+
appendFileSync('websocket3.log', `${timestamp} - ${JSON.stringify(data)}\n`);
|
|
53
|
+
} catch (e) {
|
|
54
|
+
console.error(`Error opening log file: ${e}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function subscribeFeedData4(data) {
|
|
59
|
+
try {
|
|
60
|
+
const timestamp = new Date().toISOString();
|
|
61
|
+
appendFileSync('websocket4.log', `${timestamp} - ${JSON.stringify(data)}\n`);
|
|
62
|
+
} catch (e) {
|
|
63
|
+
console.error(`Error opening log file: ${e}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function main() {
|
|
68
|
+
const userId = 'NP2997';
|
|
69
|
+
|
|
70
|
+
// Connection reference holder
|
|
71
|
+
const connectionRef = { conn: null };
|
|
72
|
+
|
|
73
|
+
// Reconnection callback
|
|
74
|
+
function onReconnectCallback(newWs) {
|
|
75
|
+
console.log("🔄 Connection reference updated");
|
|
76
|
+
connectionRef.conn = newWs;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Create WebSocket model
|
|
80
|
+
const model = new FirstockWebSocket({
|
|
81
|
+
tokens: [],
|
|
82
|
+
option_greeks_tokens: [],
|
|
83
|
+
order_data: orderBookData,
|
|
84
|
+
position_data: positionBookData,
|
|
85
|
+
subscribe_feed_data: subscribeFeedData,
|
|
86
|
+
subscribe_option_greeks_data: subscribeOptionGreeksData,
|
|
87
|
+
on_reconnect: onReconnectCallback
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Initialize WebSocket
|
|
91
|
+
const [conn, err] = await Firstock.initializeWebsockets(userId, model);
|
|
92
|
+
connectionRef.conn = conn;
|
|
93
|
+
|
|
94
|
+
console.log("Error:", err);
|
|
95
|
+
|
|
96
|
+
if (err) {
|
|
97
|
+
console.log(`Connection failed: ${JSON.stringify(err)}`);
|
|
98
|
+
return;
|
|
99
|
+
} else {
|
|
100
|
+
console.log("WebSocket connected successfully!");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Subscribe to tokens
|
|
104
|
+
const subscribeErr = await Firstock.subscribe(connectionRef.conn, ["BSE:500470|NSE:26000"]);
|
|
105
|
+
console.log("Subscribe Error:", subscribeErr);
|
|
106
|
+
|
|
107
|
+
// Subscribe to option Greeks (uncomment to use)
|
|
108
|
+
// const optionErr = await Firstock.subscribeOptionGreeks(connectionRef.conn, ["NFO:44297"]);
|
|
109
|
+
// console.log("Option Greeks Subscribe Error:", optionErr);
|
|
110
|
+
|
|
111
|
+
// // Later, unsubscribe (uncomment to use)
|
|
112
|
+
// await new Promise(resolve => setTimeout(resolve, 30000));
|
|
113
|
+
// const unsubErr = await Firstock.unsubscribeOptionGreeks(connectionRef.conn, ["NFO:44283"]);
|
|
114
|
+
// console.log("Option Greeks Unsubscribe Error:", unsubErr);
|
|
115
|
+
|
|
116
|
+
// Multiple connections example (uncomment to use)
|
|
117
|
+
|
|
118
|
+
// const model2 = new FirstockWebSocket({
|
|
119
|
+
// tokens: [],
|
|
120
|
+
// subscribe_feed_data: subscribeFeedData2
|
|
121
|
+
// });
|
|
122
|
+
// const [conn2, err2] = await Firstock.initializeWebsockets(userId, model2);
|
|
123
|
+
// console.log("Error:", err2);
|
|
124
|
+
|
|
125
|
+
// const subscribeErr2 = await Firstock.subscribe(conn2, ["BSE:1"]);
|
|
126
|
+
// console.log("Error:", subscribeErr2);
|
|
127
|
+
|
|
128
|
+
// const model3 = new FirstockWebSocket({
|
|
129
|
+
// tokens: [],
|
|
130
|
+
// subscribe_feed_data: subscribeFeedData3
|
|
131
|
+
// });
|
|
132
|
+
// const [conn3, err3] = await Firstock.initializeWebsockets(userId, model3);
|
|
133
|
+
// console.log("Error:", err3);
|
|
134
|
+
|
|
135
|
+
// const subscribeErr3 = await Firstock.subscribe(conn3, ["NSE:26000|BSE:1"]);
|
|
136
|
+
// console.log("Error:", subscribeErr3);
|
|
137
|
+
|
|
138
|
+
// const model4 = new FirstockWebSocket({
|
|
139
|
+
// tokens: [],
|
|
140
|
+
// subscribe_feed_data: subscribeFeedData4
|
|
141
|
+
// });
|
|
142
|
+
// const [conn4, err4] = await Firstock.initializeWebsockets(userId, model4);
|
|
143
|
+
// console.log("Error:", err4);
|
|
144
|
+
|
|
145
|
+
// const subscribeErr4 = await Firstock.subscribe(conn4, ["NSE:26000"]);
|
|
146
|
+
// console.log("Error:", subscribeErr4);
|
|
147
|
+
|
|
148
|
+
// Unsubscribe example
|
|
149
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
150
|
+
const unsubErr = await Firstock.unsubscribe(connectionRef.conn, ["BSE:500470|NSE:26000"]);
|
|
151
|
+
console.log("Unsubscribe Error:", unsubErr);
|
|
152
|
+
|
|
153
|
+
// Wait for 25 seconds
|
|
154
|
+
await new Promise(resolve => setTimeout(resolve, 200000));
|
|
155
|
+
|
|
156
|
+
// Close WebSocket connection
|
|
157
|
+
const closeErr = await Firstock.closeWebsocket(connectionRef.conn);
|
|
158
|
+
console.log("Close Error:", closeErr);
|
|
159
|
+
|
|
160
|
+
// Close additional connections (uncomment if using multiple connections)
|
|
161
|
+
// const closeErr2 = await Firstock.closeWebsocket(conn2);
|
|
162
|
+
// console.log("Close Error:", closeErr2);
|
|
163
|
+
|
|
164
|
+
// const closeErr3 = await Firstock.closeWebsocket(conn3);
|
|
165
|
+
// console.log("Close Error:", closeErr3);
|
|
166
|
+
|
|
167
|
+
// Keep program running
|
|
168
|
+
console.log("\nWebSocket test running. Press Ctrl+C to exit.");
|
|
169
|
+
|
|
170
|
+
// Handle graceful shutdown
|
|
171
|
+
process.on('SIGINT', async () => {
|
|
172
|
+
console.log("\nExiting...");
|
|
173
|
+
if (connectionRef.conn) {
|
|
174
|
+
await Firstock.closeWebsocket(connectionRef.conn);
|
|
175
|
+
}
|
|
176
|
+
process.exit(0);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Keep alive - never resolves, keeps running until SIGINT
|
|
180
|
+
await new Promise(() => {});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Run main function
|
|
184
|
+
main().catch(error => {
|
|
185
|
+
console.error("Fatal error:", error);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
});
|