@tigeropenapi/tigeropen 0.1.0 → 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.
- package/README.md +475 -104
- package/dist/cjs/client/api-request.js +16 -10
- package/dist/cjs/client/api-response.js +23 -3
- package/dist/cjs/client/case-convert.js +35 -0
- package/dist/cjs/client/http-client.js +47 -21
- package/dist/cjs/client/index.js +6 -2
- package/dist/cjs/config/client-config.js +130 -49
- package/dist/cjs/config/domain.js +21 -2
- package/dist/cjs/config/index.js +3 -1
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/model/contract.js +1 -2
- package/dist/cjs/model/enums.js +89 -7
- package/dist/cjs/model/order-helpers.js +9 -9
- package/dist/cjs/model/order-status.js +46 -0
- package/dist/cjs/model/order.js +5 -2
- package/dist/cjs/model/position.js +1 -2
- package/dist/cjs/model/quote-requests.js +11 -0
- package/dist/cjs/model/quote.js +7 -0
- package/dist/cjs/model/trade-requests.js +13 -0
- package/dist/cjs/model/trade.js +2 -0
- package/dist/cjs/push/index.js +6 -3
- package/dist/cjs/push/pb/AssetData.js +208 -0
- package/dist/cjs/push/pb/KlineData.js +184 -0
- package/dist/cjs/push/pb/OptionTopData.js +427 -0
- package/dist/cjs/push/pb/OrderStatusData.js +592 -0
- package/dist/cjs/push/pb/OrderTransactionData.js +256 -0
- package/dist/cjs/push/pb/PositionData.js +316 -0
- package/dist/cjs/push/pb/PushData.js +219 -0
- package/dist/cjs/push/pb/QuoteBBOData.js +160 -0
- package/dist/cjs/push/pb/QuoteBasicData.js +329 -0
- package/dist/cjs/push/pb/QuoteData.js +507 -0
- package/dist/cjs/push/pb/QuoteDepthData.js +220 -0
- package/dist/cjs/push/pb/Request.js +278 -0
- package/dist/cjs/push/pb/Response.js +97 -0
- package/dist/cjs/push/pb/SocketCommon.js +86 -0
- package/dist/cjs/push/pb/StockTopData.js +199 -0
- package/dist/cjs/push/pb/TickData.js +202 -0
- package/dist/cjs/push/pb/TradeTickData.js +308 -0
- package/dist/cjs/push/pb/index.js +49 -0
- package/dist/cjs/push/proto-message.js +135 -0
- package/dist/cjs/push/push-client.js +361 -163
- package/dist/cjs/push/push-message.js +8 -60
- package/dist/cjs/push/varint.js +76 -0
- package/dist/cjs/quote/index.js +3 -2
- package/dist/cjs/quote/quote-client.js +440 -94
- package/dist/cjs/signer/signer.js +10 -10
- package/dist/cjs/trade/trade-client.js +197 -62
- package/dist/esm/client/api-request.js +14 -8
- package/dist/esm/client/api-response.js +22 -3
- package/dist/esm/client/case-convert.js +31 -0
- package/dist/esm/client/http-client.js +48 -22
- package/dist/esm/client/index.js +3 -2
- package/dist/esm/config/client-config.js +131 -51
- package/dist/esm/config/domain.js +20 -2
- package/dist/esm/config/index.js +2 -2
- package/dist/esm/index.js +1 -1
- package/dist/esm/model/contract.js +1 -2
- package/dist/esm/model/enums.js +87 -6
- package/dist/esm/model/order-helpers.js +9 -9
- package/dist/esm/model/order-status.js +43 -0
- package/dist/esm/model/order.js +5 -2
- package/dist/esm/model/position.js +1 -2
- package/dist/esm/model/quote-requests.js +10 -0
- package/dist/esm/model/quote.js +6 -0
- package/dist/esm/model/trade-requests.js +12 -0
- package/dist/esm/model/trade.js +1 -0
- package/dist/esm/push/index.js +4 -2
- package/dist/esm/push/pb/AssetData.js +205 -0
- package/dist/esm/push/pb/KlineData.js +181 -0
- package/dist/esm/push/pb/OptionTopData.js +424 -0
- package/dist/esm/push/pb/OrderStatusData.js +589 -0
- package/dist/esm/push/pb/OrderTransactionData.js +253 -0
- package/dist/esm/push/pb/PositionData.js +313 -0
- package/dist/esm/push/pb/PushData.js +216 -0
- package/dist/esm/push/pb/QuoteBBOData.js +157 -0
- package/dist/esm/push/pb/QuoteBasicData.js +326 -0
- package/dist/esm/push/pb/QuoteData.js +504 -0
- package/dist/esm/push/pb/QuoteDepthData.js +217 -0
- package/dist/esm/push/pb/Request.js +275 -0
- package/dist/esm/push/pb/Response.js +94 -0
- package/dist/esm/push/pb/SocketCommon.js +83 -0
- package/dist/esm/push/pb/StockTopData.js +196 -0
- package/dist/esm/push/pb/TickData.js +199 -0
- package/dist/esm/push/pb/TradeTickData.js +305 -0
- package/dist/esm/push/pb/index.js +25 -0
- package/dist/esm/push/proto-message.js +127 -0
- package/dist/esm/push/push-client.js +329 -164
- package/dist/esm/push/push-message.js +7 -56
- package/dist/esm/push/varint.js +72 -0
- package/dist/esm/quote/index.js +2 -2
- package/dist/esm/quote/quote-client.js +439 -94
- package/dist/esm/signer/signer.js +10 -10
- package/dist/esm/trade/trade-client.js +197 -62
- package/dist/types/client/api-request.d.ts +12 -11
- package/dist/types/client/api-response.d.ts +15 -7
- package/dist/types/client/case-convert.d.ts +16 -0
- package/dist/types/client/http-client.d.ts +26 -14
- package/dist/types/client/index.d.ts +3 -2
- package/dist/types/config/client-config.d.ts +29 -13
- package/dist/types/config/domain.d.ts +9 -0
- package/dist/types/config/index.d.ts +2 -2
- package/dist/types/index.d.ts +1 -1
- package/dist/types/model/contract.d.ts +13 -29
- package/dist/types/model/enums.d.ts +60 -6
- package/dist/types/model/order-helpers.d.ts +21 -20
- package/dist/types/model/order-status.d.ts +14 -0
- package/dist/types/model/order.d.ts +101 -55
- package/dist/types/model/position.d.ts +26 -24
- package/dist/types/model/quote-requests.d.ts +344 -0
- package/dist/types/model/quote.d.ts +572 -0
- package/dist/types/model/trade-requests.d.ts +195 -0
- package/dist/types/model/trade.d.ts +235 -0
- package/dist/types/push/callbacks.d.ts +32 -51
- package/dist/types/push/index.d.ts +5 -3
- package/dist/types/push/pb/AssetData.d.ts +47 -0
- package/dist/types/push/pb/KlineData.d.ts +43 -0
- package/dist/types/push/pb/OptionTopData.d.ts +76 -0
- package/dist/types/push/pb/OrderStatusData.d.ts +100 -0
- package/dist/types/push/pb/OrderTransactionData.d.ts +51 -0
- package/dist/types/push/pb/PositionData.d.ts +63 -0
- package/dist/types/push/pb/PushData.d.ts +46 -0
- package/dist/types/push/pb/QuoteBBOData.d.ts +35 -0
- package/dist/types/push/pb/QuoteBasicData.d.ts +66 -0
- package/dist/types/push/pb/QuoteData.d.ts +94 -0
- package/dist/types/push/pb/QuoteDepthData.d.ts +37 -0
- package/dist/types/push/pb/Request.d.ts +44 -0
- package/dist/types/push/pb/Response.d.ts +30 -0
- package/dist/types/push/pb/SocketCommon.d.ts +64 -0
- package/dist/types/push/pb/StockTopData.d.ts +37 -0
- package/dist/types/push/pb/TickData.d.ts +44 -0
- package/dist/types/push/pb/TradeTickData.d.ts +53 -0
- package/dist/types/push/pb/index.d.ts +39 -0
- package/dist/types/push/proto-message.d.ts +33 -0
- package/dist/types/push/push-client.d.ts +100 -68
- package/dist/types/push/push-message.d.ts +7 -147
- package/dist/types/push/varint.d.ts +23 -0
- package/dist/types/quote/index.d.ts +2 -2
- package/dist/types/quote/quote-client.d.ts +154 -47
- package/dist/types/signer/signer.d.ts +6 -6
- package/dist/types/trade/trade-client.d.ts +106 -38
- package/package.json +11 -6
|
@@ -1,16 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PushClient - TCP + TLS push client (Protobuf protocol)
|
|
3
|
+
*
|
|
4
|
+
* Connects to the push server via raw TCP + TLS to receive real-time
|
|
5
|
+
* market data and account push notifications.
|
|
6
|
+
* Uses varint32 length-prefixed + Protobuf binary frame format,
|
|
7
|
+
* aligned with the Java SDK's Netty + Protobuf implementation.
|
|
8
|
+
* Supports connection authentication, subscribe/unsubscribe, callbacks,
|
|
9
|
+
* heartbeat keep-alive, and automatic reconnection.
|
|
10
|
+
*/
|
|
11
|
+
import * as tls from 'tls';
|
|
1
12
|
import { signWithRSA } from '../signer/signer';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
13
|
+
import { SubjectType } from './push-message';
|
|
14
|
+
import { Request } from './pb/Request';
|
|
15
|
+
import { Response } from './pb/Response';
|
|
16
|
+
import { SocketCommon_Command, SocketCommon_DataType } from './pb/SocketCommon';
|
|
17
|
+
import { encodeVarint32, decodeVarint32 } from './varint';
|
|
18
|
+
import { buildConnectMessage, buildHeartBeatMessage, buildSubscribeMessage, buildUnSubscribeMessage, buildDisconnectMessage, subjectToDataType, } from './proto-message';
|
|
19
|
+
/** Default push server host */
|
|
20
|
+
const DEFAULT_PUSH_HOST = 'openapi.tigerfintech.com';
|
|
21
|
+
/** Default push server port */
|
|
22
|
+
const DEFAULT_PUSH_PORT = 9883;
|
|
23
|
+
/** Default heartbeat interval (ms) */
|
|
6
24
|
const DEFAULT_HEARTBEAT_INTERVAL = 10000;
|
|
7
|
-
/**
|
|
25
|
+
/** Default reconnect interval (ms) */
|
|
8
26
|
const DEFAULT_RECONNECT_INTERVAL = 5000;
|
|
9
|
-
/**
|
|
27
|
+
/** Max reconnect interval (ms) */
|
|
10
28
|
const MAX_RECONNECT_INTERVAL = 60000;
|
|
11
|
-
/**
|
|
29
|
+
/** Default connect timeout (ms) */
|
|
12
30
|
const DEFAULT_CONNECT_TIMEOUT = 30000;
|
|
13
|
-
/**
|
|
31
|
+
/** SDK version identifier */
|
|
32
|
+
const SDK_VERSION = 'typescript/0.4.0';
|
|
33
|
+
/** Protocol version */
|
|
34
|
+
const ACCEPT_VERSION = '1.0';
|
|
35
|
+
/** Default send heartbeat interval (ms) */
|
|
36
|
+
const DEFAULT_SEND_INTERVAL = 10000;
|
|
37
|
+
/** Default receive heartbeat interval (ms) */
|
|
38
|
+
const DEFAULT_RECEIVE_INTERVAL = 10000;
|
|
39
|
+
/** Connection state */
|
|
14
40
|
export var ConnectionState;
|
|
15
41
|
(function (ConnectionState) {
|
|
16
42
|
ConnectionState[ConnectionState["Disconnected"] = 0] = "Disconnected";
|
|
@@ -18,151 +44,220 @@ export var ConnectionState;
|
|
|
18
44
|
ConnectionState[ConnectionState["Connected"] = 2] = "Connected";
|
|
19
45
|
})(ConnectionState || (ConnectionState = {}));
|
|
20
46
|
/**
|
|
21
|
-
* PushClient
|
|
47
|
+
* PushClient - TCP + TLS push client (Protobuf protocol)
|
|
22
48
|
*/
|
|
23
49
|
export class PushClient {
|
|
24
50
|
constructor(config, options) {
|
|
25
|
-
this.
|
|
51
|
+
this.socket = null;
|
|
26
52
|
this._state = ConnectionState.Disconnected;
|
|
27
53
|
this.callbacks = {};
|
|
28
|
-
/**
|
|
54
|
+
/** Subscription state: subject -> Set<symbol> */
|
|
29
55
|
this.subscriptions = new Map();
|
|
30
|
-
/**
|
|
56
|
+
/** Account-level subscriptions */
|
|
31
57
|
this.accountSubs = new Set();
|
|
32
58
|
this.heartbeatTimer = null;
|
|
33
59
|
this.reconnectTimer = null;
|
|
34
60
|
this.stopped = false;
|
|
35
|
-
/**
|
|
36
|
-
this.
|
|
61
|
+
/** TCP stream receive buffer */
|
|
62
|
+
this.recvBuffer = Buffer.alloc(0);
|
|
63
|
+
/** Injectable TLS socket factory (for testing) */
|
|
64
|
+
this.socketFactory = null;
|
|
37
65
|
this.config = config;
|
|
38
|
-
this.
|
|
66
|
+
this.pushHost = options?.pushHost ?? DEFAULT_PUSH_HOST;
|
|
67
|
+
this.pushPort = options?.pushPort ?? DEFAULT_PUSH_PORT;
|
|
39
68
|
this.heartbeatInterval = options?.heartbeatInterval ?? DEFAULT_HEARTBEAT_INTERVAL;
|
|
40
69
|
this.reconnectInterval = options?.reconnectInterval ?? DEFAULT_RECONNECT_INTERVAL;
|
|
41
70
|
this.autoReconnect = options?.autoReconnect ?? true;
|
|
42
71
|
this.connectTimeout = options?.connectTimeout ?? DEFAULT_CONNECT_TIMEOUT;
|
|
72
|
+
this.useFullTick = options?.useFullTick ?? false;
|
|
43
73
|
}
|
|
44
|
-
/**
|
|
74
|
+
/** Get current connection state */
|
|
45
75
|
get state() {
|
|
46
76
|
return this._state;
|
|
47
77
|
}
|
|
48
|
-
/**
|
|
78
|
+
/** Set callback functions */
|
|
49
79
|
setCallbacks(cb) {
|
|
50
80
|
this.callbacks = cb;
|
|
51
81
|
}
|
|
52
|
-
/**
|
|
82
|
+
/** Connect to the push server and authenticate */
|
|
53
83
|
connect() {
|
|
54
84
|
return new Promise((resolve, reject) => {
|
|
55
85
|
if (this._state !== ConnectionState.Disconnected) {
|
|
56
|
-
reject(new Error('
|
|
86
|
+
reject(new Error('Client is already connected or connecting'));
|
|
57
87
|
return;
|
|
58
88
|
}
|
|
59
89
|
this._state = ConnectionState.Connecting;
|
|
60
90
|
this.stopped = false;
|
|
61
|
-
|
|
62
|
-
this.
|
|
91
|
+
this.recvBuffer = Buffer.alloc(0);
|
|
92
|
+
const socket = this.createSocket(this.pushHost, this.pushPort);
|
|
93
|
+
this.socket = socket;
|
|
94
|
+
let settled = false;
|
|
63
95
|
const timeout = setTimeout(() => {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
96
|
+
if (!settled) {
|
|
97
|
+
settled = true;
|
|
98
|
+
socket.destroy();
|
|
99
|
+
this._state = ConnectionState.Disconnected;
|
|
100
|
+
this.socket = null;
|
|
101
|
+
reject(new Error('Connect timeout'));
|
|
102
|
+
}
|
|
68
103
|
}, this.connectTimeout);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
104
|
+
// Resolve once we receive CONNECTED response from server
|
|
105
|
+
const onConnected = () => {
|
|
106
|
+
if (!settled) {
|
|
107
|
+
settled = true;
|
|
108
|
+
clearTimeout(timeout);
|
|
74
109
|
this._state = ConnectionState.Connected;
|
|
75
110
|
this.startHeartbeat();
|
|
76
111
|
this.callbacks.onConnect?.();
|
|
77
112
|
resolve();
|
|
78
113
|
}
|
|
114
|
+
};
|
|
115
|
+
socket.on('connect', () => {
|
|
116
|
+
// TLS handshake complete, send authentication message
|
|
117
|
+
try {
|
|
118
|
+
this.authenticate();
|
|
119
|
+
}
|
|
79
120
|
catch (err) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
121
|
+
if (!settled) {
|
|
122
|
+
settled = true;
|
|
123
|
+
clearTimeout(timeout);
|
|
124
|
+
socket.destroy();
|
|
125
|
+
this._state = ConnectionState.Disconnected;
|
|
126
|
+
this.socket = null;
|
|
127
|
+
reject(err);
|
|
128
|
+
}
|
|
84
129
|
}
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
this.
|
|
88
|
-
};
|
|
89
|
-
|
|
130
|
+
});
|
|
131
|
+
socket.on('data', (chunk) => {
|
|
132
|
+
this.handleData(chunk, onConnected);
|
|
133
|
+
});
|
|
134
|
+
socket.on('close', () => {
|
|
90
135
|
if (this.stopped)
|
|
91
136
|
return;
|
|
92
137
|
this.stopHeartbeat();
|
|
93
138
|
const wasConnected = this._state === ConnectionState.Connected;
|
|
94
139
|
this._state = ConnectionState.Disconnected;
|
|
95
|
-
this.
|
|
140
|
+
this.socket = null;
|
|
96
141
|
if (wasConnected && this.autoReconnect && !this.stopped) {
|
|
97
142
|
this.scheduleReconnect();
|
|
98
143
|
}
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
if (
|
|
144
|
+
});
|
|
145
|
+
socket.on('error', (err) => {
|
|
146
|
+
if (!settled) {
|
|
147
|
+
settled = true;
|
|
102
148
|
clearTimeout(timeout);
|
|
103
149
|
this._state = ConnectionState.Disconnected;
|
|
104
|
-
this.
|
|
105
|
-
reject(new Error(
|
|
150
|
+
this.socket = null;
|
|
151
|
+
reject(new Error(`TLS connection failed: ${err.message}`));
|
|
106
152
|
}
|
|
107
153
|
else {
|
|
108
|
-
this.callbacks.onError?.(new Error(
|
|
154
|
+
this.callbacks.onError?.(new Error(`TLS error: ${err.message}`));
|
|
109
155
|
}
|
|
110
|
-
};
|
|
156
|
+
});
|
|
111
157
|
});
|
|
112
158
|
}
|
|
113
|
-
/**
|
|
159
|
+
/** Disconnect from the push server */
|
|
114
160
|
disconnect() {
|
|
115
161
|
this.stopped = true;
|
|
116
162
|
this.stopHeartbeat();
|
|
117
163
|
this.clearReconnectTimer();
|
|
118
|
-
if (this.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
164
|
+
if (this.socket) {
|
|
165
|
+
// Send DISCONNECT message before closing
|
|
166
|
+
try {
|
|
167
|
+
const req = buildDisconnectMessage();
|
|
168
|
+
this.sendMessage(req);
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// Ignore send failure, continue closing
|
|
172
|
+
}
|
|
173
|
+
this.socket.destroy();
|
|
174
|
+
this.socket = null;
|
|
124
175
|
}
|
|
125
176
|
this._state = ConnectionState.Disconnected;
|
|
177
|
+
this.recvBuffer = Buffer.alloc(0);
|
|
126
178
|
this.callbacks.onDisconnect?.();
|
|
127
179
|
}
|
|
128
|
-
/**
|
|
180
|
+
/** Send authentication message */
|
|
129
181
|
authenticate() {
|
|
130
|
-
|
|
182
|
+
// Sign content is the tigerId string itself (aligned with Java SDK)
|
|
131
183
|
const sign = signWithRSA(this.config.privateKey, this.config.tigerId);
|
|
132
|
-
const req =
|
|
133
|
-
|
|
134
|
-
sign,
|
|
135
|
-
timestamp: ts,
|
|
136
|
-
version: '2.0',
|
|
137
|
-
};
|
|
138
|
-
const msg = createPushMessage(MessageType.Connect, undefined, req);
|
|
139
|
-
this.sendMessage(msg);
|
|
184
|
+
const req = buildConnectMessage(this.config.tigerId, sign, SDK_VERSION, ACCEPT_VERSION, DEFAULT_SEND_INTERVAL, DEFAULT_RECEIVE_INTERVAL, this.useFullTick);
|
|
185
|
+
this.sendMessage(req);
|
|
140
186
|
}
|
|
141
|
-
/**
|
|
187
|
+
/** Send a Protobuf Request message (varint32 length prefix + binary) */
|
|
142
188
|
sendMessage(msg) {
|
|
143
|
-
if (!this.
|
|
144
|
-
throw new Error('
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
189
|
+
if (!this.socket)
|
|
190
|
+
throw new Error('TLS connection not established');
|
|
191
|
+
const encoded = Request.encode(msg).finish();
|
|
192
|
+
const framed = encodeVarint32(encoded);
|
|
193
|
+
this.socket.write(framed);
|
|
194
|
+
}
|
|
195
|
+
/** Create a TLS socket instance */
|
|
196
|
+
createSocket(host, port) {
|
|
197
|
+
if (this.socketFactory)
|
|
198
|
+
return this.socketFactory(host, port);
|
|
199
|
+
return tls.connect({ host, port, rejectUnauthorized: false });
|
|
200
|
+
}
|
|
201
|
+
/** Handle incoming TCP data: buffer + decode varint32 frames */
|
|
202
|
+
handleData(chunk, onConnected) {
|
|
203
|
+
this.recvBuffer = Buffer.concat([this.recvBuffer, chunk]);
|
|
204
|
+
while (this.recvBuffer.length > 0) {
|
|
205
|
+
const frame = decodeVarint32(new Uint8Array(this.recvBuffer));
|
|
206
|
+
if (!frame)
|
|
207
|
+
break; // Incomplete frame, wait for more data
|
|
208
|
+
this.recvBuffer = Buffer.from(frame.remaining);
|
|
209
|
+
// Deserialize Protobuf Response
|
|
210
|
+
let response;
|
|
211
|
+
try {
|
|
212
|
+
response = Response.decode(frame.message);
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
this.callbacks.onError?.(new Error('Protobuf deserialization failed'));
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
this.handleResponse(response, onConnected);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/** Handle a decoded Response message */
|
|
222
|
+
handleResponse(response, onConnected) {
|
|
223
|
+
const cb = this.callbacks;
|
|
224
|
+
switch (response.command) {
|
|
225
|
+
case SocketCommon_Command.CONNECTED:
|
|
226
|
+
// Server confirmed authentication
|
|
227
|
+
onConnected?.();
|
|
228
|
+
break;
|
|
229
|
+
case SocketCommon_Command.HEARTBEAT:
|
|
230
|
+
// Heartbeat response, ignore
|
|
231
|
+
break;
|
|
232
|
+
case SocketCommon_Command.MESSAGE:
|
|
233
|
+
if (response.body) {
|
|
234
|
+
this.dispatchPushData(response.body);
|
|
235
|
+
}
|
|
236
|
+
break;
|
|
237
|
+
case SocketCommon_Command.ERROR:
|
|
238
|
+
cb.onError?.(new Error(`Server error: ${response.msg ?? 'unknown error'}`));
|
|
239
|
+
// Check for kickout
|
|
240
|
+
if (response.msg && response.msg.toLowerCase().includes('kickout')) {
|
|
241
|
+
cb.onKickout?.(response.msg);
|
|
242
|
+
}
|
|
243
|
+
break;
|
|
244
|
+
case SocketCommon_Command.DISCONNECT:
|
|
245
|
+
cb.onDisconnect?.();
|
|
246
|
+
break;
|
|
247
|
+
default:
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/** Heartbeat keep-alive */
|
|
157
252
|
startHeartbeat() {
|
|
158
253
|
this.stopHeartbeat();
|
|
159
254
|
this.heartbeatTimer = setInterval(() => {
|
|
160
255
|
try {
|
|
161
|
-
const
|
|
162
|
-
this.sendMessage(
|
|
256
|
+
const req = buildHeartBeatMessage();
|
|
257
|
+
this.sendMessage(req);
|
|
163
258
|
}
|
|
164
259
|
catch {
|
|
165
|
-
//
|
|
260
|
+
// Heartbeat send failed, connection may be broken
|
|
166
261
|
}
|
|
167
262
|
}, this.heartbeatInterval);
|
|
168
263
|
}
|
|
@@ -172,7 +267,7 @@ export class PushClient {
|
|
|
172
267
|
this.heartbeatTimer = null;
|
|
173
268
|
}
|
|
174
269
|
}
|
|
175
|
-
/**
|
|
270
|
+
/** Auto reconnect with exponential backoff */
|
|
176
271
|
scheduleReconnect() {
|
|
177
272
|
let interval = this.reconnectInterval;
|
|
178
273
|
const attempt = () => {
|
|
@@ -195,7 +290,7 @@ export class PushClient {
|
|
|
195
290
|
this.reconnectTimer = null;
|
|
196
291
|
}
|
|
197
292
|
}
|
|
198
|
-
/**
|
|
293
|
+
/** Resubscribe after reconnection */
|
|
199
294
|
resubscribe() {
|
|
200
295
|
for (const [subject, symbols] of this.subscriptions) {
|
|
201
296
|
this.doSubscribe(subject, Array.from(symbols));
|
|
@@ -204,81 +299,86 @@ export class PushClient {
|
|
|
204
299
|
this.doSubscribe(subject, undefined, this.config.account);
|
|
205
300
|
}
|
|
206
301
|
}
|
|
207
|
-
/**
|
|
208
|
-
|
|
209
|
-
let msg;
|
|
210
|
-
try {
|
|
211
|
-
msg = deserializeMessage(raw);
|
|
212
|
-
}
|
|
213
|
-
catch {
|
|
214
|
-
this.callbacks.onError?.(new Error('反序列化消息失败'));
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
302
|
+
/** Dispatch push data to the appropriate callback based on dataType */
|
|
303
|
+
dispatchPushData(pushData) {
|
|
217
304
|
const cb = this.callbacks;
|
|
218
|
-
switch (
|
|
219
|
-
case
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
case MessageType.Error:
|
|
223
|
-
cb.onError?.(new Error(`服务端错误: ${msg.data}`));
|
|
224
|
-
break;
|
|
225
|
-
case MessageType.Quote:
|
|
226
|
-
cb.onQuote?.(msg.data);
|
|
305
|
+
switch (pushData.dataType) {
|
|
306
|
+
case SocketCommon_DataType.Quote:
|
|
307
|
+
if (pushData.quoteData)
|
|
308
|
+
cb.onQuote?.(pushData.quoteData);
|
|
227
309
|
break;
|
|
228
|
-
case
|
|
229
|
-
|
|
310
|
+
case SocketCommon_DataType.Option:
|
|
311
|
+
if (pushData.quoteData)
|
|
312
|
+
cb.onOption?.(pushData.quoteData);
|
|
230
313
|
break;
|
|
231
|
-
case
|
|
232
|
-
|
|
314
|
+
case SocketCommon_DataType.Future:
|
|
315
|
+
if (pushData.quoteData)
|
|
316
|
+
cb.onFuture?.(pushData.quoteData);
|
|
233
317
|
break;
|
|
234
|
-
case
|
|
235
|
-
|
|
318
|
+
case SocketCommon_DataType.QuoteDepth:
|
|
319
|
+
if (pushData.quoteDepthData)
|
|
320
|
+
cb.onDepth?.(pushData.quoteDepthData);
|
|
236
321
|
break;
|
|
237
|
-
case
|
|
238
|
-
|
|
322
|
+
case SocketCommon_DataType.TradeTick:
|
|
323
|
+
if (pushData.tradeTickData)
|
|
324
|
+
cb.onTick?.(pushData.tradeTickData);
|
|
239
325
|
break;
|
|
240
|
-
case
|
|
241
|
-
|
|
326
|
+
case SocketCommon_DataType.Asset:
|
|
327
|
+
if (pushData.assetData)
|
|
328
|
+
cb.onAsset?.(pushData.assetData);
|
|
242
329
|
break;
|
|
243
|
-
case
|
|
244
|
-
|
|
330
|
+
case SocketCommon_DataType.Position:
|
|
331
|
+
if (pushData.positionData)
|
|
332
|
+
cb.onPosition?.(pushData.positionData);
|
|
245
333
|
break;
|
|
246
|
-
case
|
|
247
|
-
|
|
334
|
+
case SocketCommon_DataType.OrderStatus:
|
|
335
|
+
if (pushData.orderStatusData)
|
|
336
|
+
cb.onOrder?.(pushData.orderStatusData);
|
|
248
337
|
break;
|
|
249
|
-
case
|
|
250
|
-
|
|
338
|
+
case SocketCommon_DataType.OrderTransaction:
|
|
339
|
+
if (pushData.orderTransactionData)
|
|
340
|
+
cb.onTransaction?.(pushData.orderTransactionData);
|
|
251
341
|
break;
|
|
252
|
-
case
|
|
253
|
-
|
|
342
|
+
case SocketCommon_DataType.StockTop:
|
|
343
|
+
if (pushData.stockTopData)
|
|
344
|
+
cb.onStockTop?.(pushData.stockTopData);
|
|
254
345
|
break;
|
|
255
|
-
case
|
|
256
|
-
|
|
346
|
+
case SocketCommon_DataType.OptionTop:
|
|
347
|
+
if (pushData.optionTopData)
|
|
348
|
+
cb.onOptionTop?.(pushData.optionTopData);
|
|
257
349
|
break;
|
|
258
|
-
case
|
|
259
|
-
|
|
350
|
+
case SocketCommon_DataType.Kline:
|
|
351
|
+
if (pushData.klineData)
|
|
352
|
+
cb.onKline?.(pushData.klineData);
|
|
260
353
|
break;
|
|
261
|
-
case
|
|
262
|
-
|
|
354
|
+
case SocketCommon_DataType.Cc:
|
|
355
|
+
// 加密货币(Cc)推送复用 QuoteData body,路由到 onQuote 回调
|
|
356
|
+
if (pushData.quoteData)
|
|
357
|
+
cb.onQuote?.(pushData.quoteData);
|
|
263
358
|
break;
|
|
264
|
-
|
|
265
|
-
|
|
359
|
+
default:
|
|
360
|
+
// FullTick and QuoteBBO use TradeTick/Quote dataType
|
|
361
|
+
// TickData (fullTick) via tickData field
|
|
362
|
+
if (pushData.tickData)
|
|
363
|
+
cb.onFullTick?.(pushData.tickData);
|
|
266
364
|
break;
|
|
267
365
|
}
|
|
268
366
|
}
|
|
269
|
-
/**
|
|
367
|
+
/** Internal subscribe method */
|
|
270
368
|
doSubscribe(subject, symbols, account, market) {
|
|
271
|
-
const
|
|
272
|
-
const
|
|
273
|
-
|
|
369
|
+
const dataType = subjectToDataType(subject);
|
|
370
|
+
const symbolsStr = symbols ? symbols.join(',') : undefined;
|
|
371
|
+
const req = buildSubscribeMessage(dataType, symbolsStr, account, market);
|
|
372
|
+
this.sendMessage(req);
|
|
274
373
|
}
|
|
275
|
-
/**
|
|
374
|
+
/** Internal unsubscribe method */
|
|
276
375
|
doUnsubscribe(subject, symbols) {
|
|
277
|
-
const
|
|
278
|
-
const
|
|
279
|
-
|
|
376
|
+
const dataType = subjectToDataType(subject);
|
|
377
|
+
const symbolsStr = symbols ? symbols.join(',') : undefined;
|
|
378
|
+
const req = buildUnSubscribeMessage(dataType, symbolsStr);
|
|
379
|
+
this.sendMessage(req);
|
|
280
380
|
}
|
|
281
|
-
/**
|
|
381
|
+
/** Add subscription record */
|
|
282
382
|
addSubscription(subject, symbols) {
|
|
283
383
|
if (!this.subscriptions.has(subject)) {
|
|
284
384
|
this.subscriptions.set(subject, new Set());
|
|
@@ -287,7 +387,7 @@ export class PushClient {
|
|
|
287
387
|
for (const s of symbols)
|
|
288
388
|
set.add(s);
|
|
289
389
|
}
|
|
290
|
-
/**
|
|
390
|
+
/** Remove subscription record */
|
|
291
391
|
removeSubscription(subject, symbols) {
|
|
292
392
|
if (!symbols) {
|
|
293
393
|
this.subscriptions.delete(subject);
|
|
@@ -301,110 +401,175 @@ export class PushClient {
|
|
|
301
401
|
this.subscriptions.delete(subject);
|
|
302
402
|
}
|
|
303
403
|
}
|
|
304
|
-
// =====
|
|
305
|
-
/**
|
|
404
|
+
// ===== Market data subscribe/unsubscribe =====
|
|
405
|
+
/** Subscribe to quotes */
|
|
306
406
|
subscribeQuote(symbols) {
|
|
307
407
|
this.doSubscribe(SubjectType.Quote, symbols);
|
|
308
408
|
this.addSubscription(SubjectType.Quote, symbols);
|
|
309
409
|
}
|
|
310
|
-
/**
|
|
410
|
+
/** Unsubscribe from quotes */
|
|
311
411
|
unsubscribeQuote(symbols) {
|
|
312
412
|
this.doUnsubscribe(SubjectType.Quote, symbols);
|
|
313
413
|
this.removeSubscription(SubjectType.Quote, symbols);
|
|
314
414
|
}
|
|
315
|
-
/**
|
|
415
|
+
/** Subscribe to trade ticks */
|
|
316
416
|
subscribeTick(symbols) {
|
|
317
417
|
this.doSubscribe(SubjectType.Tick, symbols);
|
|
318
418
|
this.addSubscription(SubjectType.Tick, symbols);
|
|
319
419
|
}
|
|
320
|
-
/**
|
|
420
|
+
/** Unsubscribe from trade ticks */
|
|
321
421
|
unsubscribeTick(symbols) {
|
|
322
422
|
this.doUnsubscribe(SubjectType.Tick, symbols);
|
|
323
423
|
this.removeSubscription(SubjectType.Tick, symbols);
|
|
324
424
|
}
|
|
325
|
-
/**
|
|
425
|
+
/** Subscribe to depth data */
|
|
326
426
|
subscribeDepth(symbols) {
|
|
327
427
|
this.doSubscribe(SubjectType.Depth, symbols);
|
|
328
428
|
this.addSubscription(SubjectType.Depth, symbols);
|
|
329
429
|
}
|
|
330
|
-
/**
|
|
430
|
+
/** Unsubscribe from depth data */
|
|
331
431
|
unsubscribeDepth(symbols) {
|
|
332
432
|
this.doUnsubscribe(SubjectType.Depth, symbols);
|
|
333
433
|
this.removeSubscription(SubjectType.Depth, symbols);
|
|
334
434
|
}
|
|
335
|
-
/**
|
|
435
|
+
/** Subscribe to option quotes */
|
|
336
436
|
subscribeOption(symbols) {
|
|
337
437
|
this.doSubscribe(SubjectType.Option, symbols);
|
|
338
438
|
this.addSubscription(SubjectType.Option, symbols);
|
|
339
439
|
}
|
|
340
|
-
/**
|
|
440
|
+
/** Unsubscribe from option quotes */
|
|
341
441
|
unsubscribeOption(symbols) {
|
|
342
442
|
this.doUnsubscribe(SubjectType.Option, symbols);
|
|
343
443
|
this.removeSubscription(SubjectType.Option, symbols);
|
|
344
444
|
}
|
|
345
|
-
/**
|
|
445
|
+
/** Subscribe to futures quotes */
|
|
346
446
|
subscribeFuture(symbols) {
|
|
347
447
|
this.doSubscribe(SubjectType.Future, symbols);
|
|
348
448
|
this.addSubscription(SubjectType.Future, symbols);
|
|
349
449
|
}
|
|
350
|
-
/**
|
|
450
|
+
/** Unsubscribe from futures quotes */
|
|
351
451
|
unsubscribeFuture(symbols) {
|
|
352
452
|
this.doUnsubscribe(SubjectType.Future, symbols);
|
|
353
453
|
this.removeSubscription(SubjectType.Future, symbols);
|
|
354
454
|
}
|
|
355
|
-
/**
|
|
455
|
+
/** Subscribe to kline data */
|
|
356
456
|
subscribeKline(symbols) {
|
|
357
457
|
this.doSubscribe(SubjectType.Kline, symbols);
|
|
358
458
|
this.addSubscription(SubjectType.Kline, symbols);
|
|
359
459
|
}
|
|
360
|
-
/**
|
|
460
|
+
/** Unsubscribe from kline data */
|
|
361
461
|
unsubscribeKline(symbols) {
|
|
362
462
|
this.doUnsubscribe(SubjectType.Kline, symbols);
|
|
363
463
|
this.removeSubscription(SubjectType.Kline, symbols);
|
|
364
464
|
}
|
|
365
|
-
// =====
|
|
366
|
-
/**
|
|
465
|
+
// ===== v0.4.0: stock_top / option_top / cc / market =====
|
|
466
|
+
/**
|
|
467
|
+
* Subscribe to stock top rankings (v0.4.0).
|
|
468
|
+
* @param market US / HK / CN
|
|
469
|
+
* @param indicators LATEST_PRICE / HIGHER / LOWER / VOLUME / AMOUNT / AMPLITUDE / TURNOVER_RATE
|
|
470
|
+
*/
|
|
471
|
+
subscribeStockTop(market, indicators) {
|
|
472
|
+
this.doSubscribe(SubjectType.StockTop, indicators, undefined, market);
|
|
473
|
+
this.addSubscription(SubjectType.StockTop, indicators);
|
|
474
|
+
}
|
|
475
|
+
/** Unsubscribe from stock top rankings (v0.4.0). */
|
|
476
|
+
unsubscribeStockTop(market, indicators) {
|
|
477
|
+
const dataType = subjectToDataType(SubjectType.StockTop);
|
|
478
|
+
const symbolsStr = indicators.join(',');
|
|
479
|
+
const req = buildUnSubscribeMessage(dataType, symbolsStr, undefined, market);
|
|
480
|
+
this.sendMessage(req);
|
|
481
|
+
this.removeSubscription(SubjectType.StockTop, indicators);
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Subscribe to option top rankings (v0.4.0).
|
|
485
|
+
* @param market US / HK / CN
|
|
486
|
+
*/
|
|
487
|
+
subscribeOptionTop(market, indicators) {
|
|
488
|
+
this.doSubscribe(SubjectType.OptionTop, indicators, undefined, market);
|
|
489
|
+
this.addSubscription(SubjectType.OptionTop, indicators);
|
|
490
|
+
}
|
|
491
|
+
/** Unsubscribe from option top rankings (v0.4.0). */
|
|
492
|
+
unsubscribeOptionTop(market, indicators) {
|
|
493
|
+
const dataType = subjectToDataType(SubjectType.OptionTop);
|
|
494
|
+
const symbolsStr = indicators.join(',');
|
|
495
|
+
const req = buildUnSubscribeMessage(dataType, symbolsStr, undefined, market);
|
|
496
|
+
this.sendMessage(req);
|
|
497
|
+
this.removeSubscription(SubjectType.OptionTop, indicators);
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Subscribe to crypto realtime quotes (v0.4.0).
|
|
501
|
+
* Data is delivered via the `onQuote` callback (same QuoteData body).
|
|
502
|
+
* @param symbols e.g. ["BTCUSD", "ETHUSD"]
|
|
503
|
+
*/
|
|
504
|
+
subscribeCc(symbols) {
|
|
505
|
+
this.doSubscribe(SubjectType.Cc, symbols);
|
|
506
|
+
this.addSubscription(SubjectType.Cc, symbols);
|
|
507
|
+
}
|
|
508
|
+
/** Unsubscribe from crypto realtime quotes (v0.4.0). */
|
|
509
|
+
unsubscribeCc(symbols) {
|
|
510
|
+
this.doUnsubscribe(SubjectType.Cc, symbols);
|
|
511
|
+
this.removeSubscription(SubjectType.Cc, symbols);
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Subscribe to market status updates (v0.4.0).
|
|
515
|
+
* Protocol uses dataType=Quote with only the `market` field set.
|
|
516
|
+
* Data is delivered via the `onQuote` callback.
|
|
517
|
+
*/
|
|
518
|
+
subscribeMarket(market) {
|
|
519
|
+
// symbols 为空,只带 market 字段
|
|
520
|
+
this.doSubscribe(SubjectType.Market, undefined, undefined, market);
|
|
521
|
+
this.addSubscription(SubjectType.Market, [market]);
|
|
522
|
+
}
|
|
523
|
+
/** Unsubscribe from market status (v0.4.0). */
|
|
524
|
+
unsubscribeMarket(market) {
|
|
525
|
+
const dataType = subjectToDataType(SubjectType.Market);
|
|
526
|
+
const req = buildUnSubscribeMessage(dataType, undefined, undefined, market);
|
|
527
|
+
this.sendMessage(req);
|
|
528
|
+
this.removeSubscription(SubjectType.Market, [market]);
|
|
529
|
+
}
|
|
530
|
+
// ===== Account push subscribe/unsubscribe =====
|
|
531
|
+
/** Subscribe to asset changes */
|
|
367
532
|
subscribeAsset(account) {
|
|
368
533
|
this.doSubscribe(SubjectType.Asset, undefined, account || this.config.account);
|
|
369
534
|
this.accountSubs.add(SubjectType.Asset);
|
|
370
535
|
}
|
|
371
|
-
/**
|
|
536
|
+
/** Unsubscribe from asset changes */
|
|
372
537
|
unsubscribeAsset() {
|
|
373
538
|
this.doUnsubscribe(SubjectType.Asset);
|
|
374
539
|
this.accountSubs.delete(SubjectType.Asset);
|
|
375
540
|
}
|
|
376
|
-
/**
|
|
541
|
+
/** Subscribe to position changes */
|
|
377
542
|
subscribePosition(account) {
|
|
378
543
|
this.doSubscribe(SubjectType.Position, undefined, account || this.config.account);
|
|
379
544
|
this.accountSubs.add(SubjectType.Position);
|
|
380
545
|
}
|
|
381
|
-
/**
|
|
546
|
+
/** Unsubscribe from position changes */
|
|
382
547
|
unsubscribePosition() {
|
|
383
548
|
this.doUnsubscribe(SubjectType.Position);
|
|
384
549
|
this.accountSubs.delete(SubjectType.Position);
|
|
385
550
|
}
|
|
386
|
-
/**
|
|
551
|
+
/** Subscribe to order status */
|
|
387
552
|
subscribeOrder(account) {
|
|
388
553
|
this.doSubscribe(SubjectType.Order, undefined, account || this.config.account);
|
|
389
554
|
this.accountSubs.add(SubjectType.Order);
|
|
390
555
|
}
|
|
391
|
-
/**
|
|
556
|
+
/** Unsubscribe from order status */
|
|
392
557
|
unsubscribeOrder() {
|
|
393
558
|
this.doUnsubscribe(SubjectType.Order);
|
|
394
559
|
this.accountSubs.delete(SubjectType.Order);
|
|
395
560
|
}
|
|
396
|
-
/**
|
|
561
|
+
/** Subscribe to transaction details */
|
|
397
562
|
subscribeTransaction(account) {
|
|
398
563
|
this.doSubscribe(SubjectType.Transaction, undefined, account || this.config.account);
|
|
399
564
|
this.accountSubs.add(SubjectType.Transaction);
|
|
400
565
|
}
|
|
401
|
-
/**
|
|
566
|
+
/** Unsubscribe from transaction details */
|
|
402
567
|
unsubscribeTransaction() {
|
|
403
568
|
this.doUnsubscribe(SubjectType.Transaction);
|
|
404
569
|
this.accountSubs.delete(SubjectType.Transaction);
|
|
405
570
|
}
|
|
406
|
-
// =====
|
|
407
|
-
/**
|
|
571
|
+
// ===== Query subscription state =====
|
|
572
|
+
/** Get current market data subscriptions */
|
|
408
573
|
getSubscriptions() {
|
|
409
574
|
const result = new Map();
|
|
410
575
|
for (const [subject, symbols] of this.subscriptions) {
|
|
@@ -412,7 +577,7 @@ export class PushClient {
|
|
|
412
577
|
}
|
|
413
578
|
return result;
|
|
414
579
|
}
|
|
415
|
-
/**
|
|
580
|
+
/** Get account-level subscriptions */
|
|
416
581
|
getAccountSubscriptions() {
|
|
417
582
|
return Array.from(this.accountSubs);
|
|
418
583
|
}
|