@itick/browser-sdk
Version:
Official iTick API SDK for browser. Real-time & historical data for global Stocks, Forex, Crypto, Indices, Futures, Funds, Precious Metals. REST (OHLCV/K-line) + low-latency WebSocket. Promise-based, TypeScript-ready. For quant trading & fintech
212 lines • 7.35 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const index_1 = require("./index");
class SocketClient {
constructor(token, options) {
// WebSocket connection related properties
this.socket = null;
this.isRunning = false;
this.reconnectAttempts = 0;
this.pingInterval = null;
this.reconnectTimeout = null;
// Callback functions
this.messageHandlers = [];
this.errorHandlers = [];
this.openHandlers = [];
this.closeHandlers = [];
// Subscription information storage
this.lastSubscription = null;
this.wssURL = options.wssURL ?? 'wss://api.itick.org';
this.wsPath = options.wsPath;
this.token = token;
this.RECONNECT_INTERVAL = options.reconnectInterval ?? 5000;
this.MAX_RECONNECT_ATTEMPTS = options.maxReconnectTimes ?? 0;
this.PING_INTERVAL = options.pingInterval ?? 30000;
this.lastSubscription = options.subscribeData ?? null;
this._connectWebSocket();
}
/**
* WebSocket Message Handler
*/
onSocketMessage(handler) {
this.messageHandlers.push(handler);
}
/**
* WebSocket Error Handler
*/
onSocketError(handler) {
this.errorHandlers.push(handler);
}
/**
* WebSocket Connection Open Handler
*/
onSocketOpen(handler) {
this.openHandlers.push(handler);
}
/**
* WebSocket Connection Close Handler
*/
onSocketClose(handler) {
this.closeHandlers.push(handler);
}
/**
* Internal WebSocket Connection Method
*/
async _connectWebSocket() {
const url = (0, index_1.buildWebSocketUrl)(this.wssURL, this.wsPath || '');
try {
this.isRunning = true;
this.socket = await (0, index_1.createWebSocket)(url, this.token);
this.socket.onopen = () => {
this.reconnectAttempts = 0;
this._startPing();
this.openHandlers.forEach(handler => handler());
// Subscribe to data after successful connection
this._resubscribeLast();
};
this.socket.onmessage = (event) => {
const messageStr = typeof event.data === 'string' ? event.data : event.data.toString();
this.messageHandlers.forEach(handler => handler(JSON.parse(messageStr)));
};
this.socket.onclose = (event) => {
this._stopPing();
this.closeHandlers.forEach(handler => handler(event));
if (this.isRunning) {
this._scheduleReconnect();
}
};
this.socket.onerror = (event) => {
this.errorHandlers.forEach(handler => handler(event));
};
}
catch (error) {
this.errorHandlers.forEach(handler => handler(error));
if (this.isRunning) {
this._scheduleReconnect();
}
}
}
/**
* Start heartbeat ping
*/
_startPing() {
this._stopPing();
this.pingInterval = setInterval(() => {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify({ ac: "ping", params: Date.now() }));
}
}, this.PING_INTERVAL);
}
/**
* Stop heartbeat ping
*/
_stopPing() {
if (this.pingInterval) {
clearInterval(this.pingInterval);
this.pingInterval = null;
}
}
/**
* Schedule reconnection
*/
_scheduleReconnect() {
// When MAX_RECONNECT_ATTEMPTS is 0, it means unlimited reconnection
if (this.MAX_RECONNECT_ATTEMPTS > 0 && this.reconnectAttempts >= this.MAX_RECONNECT_ATTEMPTS) {
this.errorHandlers.forEach(handler => handler(new Error('Maximum reconnect attempts reached')));
return;
}
this.reconnectAttempts++;
this.reconnectTimeout = setTimeout(() => {
if (this.isRunning) {
this._connectWebSocket().catch((error) => { });
}
}, this.RECONNECT_INTERVAL);
}
/**
* WebSocket Subscription Method
* @description
* message object must contain ac, types and params fields
* - ac: operation type, must be "subscribe" or "unsubscribe"
* - types: subscription types, can be comma-separated string or string array, supports "quote", "depth", "tick", "kline@1m" etc. kline@1m can also be written as kline@1, SDK will automatically convert to correct format
* - params: subscription parameters, can be comma-separated string or string array, format is "code$region" (e.g., "AAPL$US") SDK will automatically handle formatting
* - String array format is recommended for clearer code and fewer errors
* @example
* Example 1: Subscribe to AAPL and TSA real-time quotes/order book/trades/1-minute K-line data
* ```json
* {
* "ac": "subscribe",
* "types": "quote,depth,tick,kline@1",
* "params": "AAPL$US,TSA$US"
* }
* ```
* Example 2: Subscribe to AAPL and TSA real-time quotes/order book/trades/1-minute K-line data
* ```json
* {
* "ac": "subscribe",
* "types": ["quote","depth","tick","kline@1m"],
* "params": ["AAPL$US","TSA$US"]
* }
* ```
*
* Both methods can correctly subscribe, SDK will automatically handle type conversion and parameter formatting
*
*/
subscribeSocket(data) {
if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
throw new Error('WebSocket not connected');
}
try {
let { ac, types, codes } = data;
const type = (0, index_1.parseeSubscribeType)(types);
const symbol = (0, index_1.parseSymbols)(codes);
this.socket.send(JSON.stringify({ ac, types: type, params: symbol }));
}
catch (err) {
console.error(err);
}
}
/**
* Restore subscription after reconnection
*/
_resubscribeLast() {
if (!this.lastSubscription)
return;
try {
let { types, codes } = this.lastSubscription;
const type = (0, index_1.parseeSubscribeType)(types);
const symbol = (0, index_1.parseSymbols)(codes);
this.socket?.send(JSON.stringify({ ac: 'subscribe', types: type, params: symbol }));
}
catch (err) {
console.error(err);
}
}
/**
* Close WebSocket
*/
closeWebSocket() {
this.isRunning = false;
this._stopPing();
if (this.reconnectTimeout) {
clearTimeout(this.reconnectTimeout);
}
if (this.socket) {
this.socket.close();
}
// Clear last subscription information when closing
this.lastSubscription = null;
}
/**
* Check if WebSocket is connected
*/
checkSocketConnected() {
if (!this.socket)
return false;
return this.socket.readyState === WebSocket.OPEN;
}
disconnectSocket() {
this.closeWebSocket();
}
}
exports.default = SocketClient;
//# sourceMappingURL=SocketClient.js.map