smartapi-typescript
Version:
TypeScript library for Angel One SmartAPI broker API
277 lines • 11.7 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.OrderStatusWebSocket = void 0;
const ws_1 = __importDefault(require("ws"));
const events_1 = require("events");
const apiUrls_1 = require("../constants/apiUrls");
const types_1 = require("../types");
/**
* WebSocket connection states
*/
var ConnectionState;
(function (ConnectionState) {
ConnectionState[ConnectionState["CONNECTING"] = 0] = "CONNECTING";
ConnectionState[ConnectionState["CONNECTED"] = 1] = "CONNECTED";
ConnectionState[ConnectionState["DISCONNECTING"] = 2] = "DISCONNECTING";
ConnectionState[ConnectionState["DISCONNECTED"] = 3] = "DISCONNECTED";
ConnectionState[ConnectionState["ERROR"] = 4] = "ERROR";
})(ConnectionState || (ConnectionState = {}));
/**
* SmartAPI Order Status WebSocket client
* Implementation based on Order Status WebSocket API documentation
* Used for real-time order updates
*/
class OrderStatusWebSocket extends events_1.EventEmitter {
/**
* Initialize a new OrderStatusWebSocket client
* @param jwtToken JWT token from login API (auth token)
* @param debug Enable debug logging
*/
constructor(jwtToken, debug = false) {
super();
this.ws = null;
this.connectionState = ConnectionState.DISCONNECTED;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.reconnectInterval = 5000; // 5 seconds
this.heartbeatInterval = null;
this.pingInterval = null;
this.debug = false;
this.isBrowser = false;
this.clientCode = '';
this.jwtToken = jwtToken;
this.wsUrl = apiUrls_1.API_URLS.ORDER_STATUS_WEBSOCKET;
this.debug = debug;
// Detect if we're running in a browser environment
this.isBrowser = typeof window !== 'undefined';
}
/**
* Log debug messages if debug mode is enabled
* @param message Message to log
* @param data Optional data to log
*/
log(message, data) {
if (this.debug) {
console.log(`[OrderStatusWebSocket] ${message}`);
if (data !== undefined) {
console.log(data);
}
}
}
/**
* Connect to the Order Status WebSocket server
*/
connect() {
this.log('Connecting to Order Status WebSocket server', this.wsUrl);
return new Promise((resolve, reject) => {
if (this.connectionState === ConnectionState.CONNECTED) {
this.log('Already connected');
resolve(true);
return;
}
this.connectionState = ConnectionState.CONNECTING;
try {
// We need to set Authorization header for authentication
const options = {
headers: {
'Authorization': `Bearer ${this.jwtToken}`
}
};
this.ws = new ws_1.default(this.wsUrl, options);
this.ws.on('open', () => {
this.connectionState = ConnectionState.CONNECTED;
this.log('WebSocket connected');
this.reconnectAttempts = 0;
this.setupHeartbeat();
this.emit('connected');
resolve(true);
});
this.ws.on('message', (data) => {
try {
// Parse the message as JSON
if (typeof data === 'string') {
// Check if it's a heartbeat/pong response
if (data === 'pong') {
this.log('Received heartbeat response');
return;
}
try {
const message = JSON.parse(data);
// Save client code from initial response
if (message['order-status'] === types_1.OrderStatusCode.CONNECTED) {
this.clientCode = message['user-id'];
this.log('Connected successfully. Client code:', this.clientCode);
}
// Handle error responses
if (message['status-code'] !== '200') {
this.log('Received error', message);
this.emit('error', {
code: message['status-code'],
message: message['error-message'] || 'Unknown error'
});
// Handle specific error codes
if (message['status-code'] === '401') {
this.log('Authentication failed: Invalid authorization token');
this.disconnect();
}
else if (message['status-code'] === '403') {
this.log('Authentication failed: Authorization token expired');
this.disconnect();
}
else if (message['status-code'] === '429') {
this.log('Connection limit breached: Maximum 3 connections per client code');
this.disconnect();
}
}
else {
// Emit the appropriate event based on order status
this.log('Received order update', message);
this.emit('message', message);
// Emit specific event for the order status
if (message['order-status'] && message['order-status'] !== types_1.OrderStatusCode.CONNECTED) {
this.emit('order-update', message);
// Also emit specific event for the order status
this.emit(`order-${message['order-status']}`, message);
// If there's an orderid, emit event with that too
if (message.orderData && message.orderData.orderid) {
this.emit(`order-${message.orderData.orderid}`, message);
}
}
}
}
catch (e) {
this.log('Unknown text message', data);
}
}
}
catch (e) {
this.log('Failed to process WebSocket message', e);
}
});
this.ws.on('error', (error) => {
this.log('WebSocket error', error);
this.connectionState = ConnectionState.ERROR;
this.emit('error', error);
reject(error);
});
this.ws.on('close', (code, reason) => {
this.log('WebSocket closed', { code, reason });
this.clearHeartbeat();
this.connectionState = ConnectionState.DISCONNECTED;
this.emit('disconnect', { code, reason });
// Attempt reconnection
this.attemptReconnect();
});
}
catch (error) {
this.log('Failed to create WebSocket connection', error);
this.connectionState = ConnectionState.ERROR;
this.emit('error', error);
reject(error);
}
});
}
/**
* Attempt to reconnect to the WebSocket server
*/
attemptReconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
this.log('Maximum reconnect attempts reached');
this.emit('reconnect_failed');
return;
}
this.reconnectAttempts++;
this.log(`Attempting to reconnect (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
this.emit('reconnecting', this.reconnectAttempts);
setTimeout(() => {
this.connect().catch(() => {
// Error will be emitted by the connect method
});
}, this.reconnectInterval);
}
/**
* Set up ping/pong heartbeat to keep the connection alive
* According to the documentation, clients should send a ping message every 10 seconds
*/
setupHeartbeat() {
this.clearHeartbeat();
// Send ping every 10 seconds as required by the API docs
this.pingInterval = setInterval(() => {
var _a;
if (((_a = this.ws) === null || _a === void 0 ? void 0 : _a.readyState) === ws_1.default.OPEN) {
this.log('Sending ping');
this.ws.send('ping');
}
}, 10000); // 10 seconds
}
/**
* Clear the heartbeat interval
*/
clearHeartbeat() {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
this.heartbeatInterval = null;
}
if (this.pingInterval) {
clearInterval(this.pingInterval);
this.pingInterval = null;
}
}
/**
* Disconnect from the WebSocket server
*/
disconnect() {
if (this.connectionState === ConnectionState.DISCONNECTED) {
this.log('Already disconnected');
return;
}
this.log('Disconnecting');
this.connectionState = ConnectionState.DISCONNECTING;
this.clearHeartbeat();
if (this.ws) {
try {
this.ws.close();
}
catch (error) {
this.log('Error closing WebSocket', error);
}
this.ws = null;
this.connectionState = ConnectionState.DISCONNECTED;
this.emit('disconnect', { reason: 'User initiated disconnect' });
}
}
/**
* Check if the WebSocket is currently connected
* @returns True if connected, false otherwise
*/
isConnected() {
return this.connectionState === ConnectionState.CONNECTED;
}
/**
* Get the client code received in the connection response
* @returns Client code or empty string if not connected yet
*/
getClientCode() {
return this.clientCode;
}
/**
* Set the JWT token (useful if token gets refreshed)
* @param jwtToken New JWT token
*/
setJwtToken(jwtToken) {
this.jwtToken = jwtToken;
// If we're connected, disconnect and reconnect with the new token
if (this.isConnected()) {
this.log('JWT Token updated, reconnecting...');
this.disconnect();
this.connect().catch(error => {
this.log('Failed to reconnect with new token', error);
});
}
}
}
exports.OrderStatusWebSocket = OrderStatusWebSocket;
//# sourceMappingURL=OrderStatusWebSocket.js.map