@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.
Files changed (141) hide show
  1. package/README.md +475 -104
  2. package/dist/cjs/client/api-request.js +16 -10
  3. package/dist/cjs/client/api-response.js +23 -3
  4. package/dist/cjs/client/case-convert.js +35 -0
  5. package/dist/cjs/client/http-client.js +47 -21
  6. package/dist/cjs/client/index.js +6 -2
  7. package/dist/cjs/config/client-config.js +130 -49
  8. package/dist/cjs/config/domain.js +21 -2
  9. package/dist/cjs/config/index.js +3 -1
  10. package/dist/cjs/index.js +1 -1
  11. package/dist/cjs/model/contract.js +1 -2
  12. package/dist/cjs/model/enums.js +89 -7
  13. package/dist/cjs/model/order-helpers.js +9 -9
  14. package/dist/cjs/model/order-status.js +46 -0
  15. package/dist/cjs/model/order.js +5 -2
  16. package/dist/cjs/model/position.js +1 -2
  17. package/dist/cjs/model/quote-requests.js +11 -0
  18. package/dist/cjs/model/quote.js +7 -0
  19. package/dist/cjs/model/trade-requests.js +13 -0
  20. package/dist/cjs/model/trade.js +2 -0
  21. package/dist/cjs/push/index.js +6 -3
  22. package/dist/cjs/push/pb/AssetData.js +208 -0
  23. package/dist/cjs/push/pb/KlineData.js +184 -0
  24. package/dist/cjs/push/pb/OptionTopData.js +427 -0
  25. package/dist/cjs/push/pb/OrderStatusData.js +592 -0
  26. package/dist/cjs/push/pb/OrderTransactionData.js +256 -0
  27. package/dist/cjs/push/pb/PositionData.js +316 -0
  28. package/dist/cjs/push/pb/PushData.js +219 -0
  29. package/dist/cjs/push/pb/QuoteBBOData.js +160 -0
  30. package/dist/cjs/push/pb/QuoteBasicData.js +329 -0
  31. package/dist/cjs/push/pb/QuoteData.js +507 -0
  32. package/dist/cjs/push/pb/QuoteDepthData.js +220 -0
  33. package/dist/cjs/push/pb/Request.js +278 -0
  34. package/dist/cjs/push/pb/Response.js +97 -0
  35. package/dist/cjs/push/pb/SocketCommon.js +86 -0
  36. package/dist/cjs/push/pb/StockTopData.js +199 -0
  37. package/dist/cjs/push/pb/TickData.js +202 -0
  38. package/dist/cjs/push/pb/TradeTickData.js +308 -0
  39. package/dist/cjs/push/pb/index.js +49 -0
  40. package/dist/cjs/push/proto-message.js +135 -0
  41. package/dist/cjs/push/push-client.js +361 -163
  42. package/dist/cjs/push/push-message.js +8 -60
  43. package/dist/cjs/push/varint.js +76 -0
  44. package/dist/cjs/quote/index.js +3 -2
  45. package/dist/cjs/quote/quote-client.js +440 -94
  46. package/dist/cjs/signer/signer.js +10 -10
  47. package/dist/cjs/trade/trade-client.js +197 -62
  48. package/dist/esm/client/api-request.js +14 -8
  49. package/dist/esm/client/api-response.js +22 -3
  50. package/dist/esm/client/case-convert.js +31 -0
  51. package/dist/esm/client/http-client.js +48 -22
  52. package/dist/esm/client/index.js +3 -2
  53. package/dist/esm/config/client-config.js +131 -51
  54. package/dist/esm/config/domain.js +20 -2
  55. package/dist/esm/config/index.js +2 -2
  56. package/dist/esm/index.js +1 -1
  57. package/dist/esm/model/contract.js +1 -2
  58. package/dist/esm/model/enums.js +87 -6
  59. package/dist/esm/model/order-helpers.js +9 -9
  60. package/dist/esm/model/order-status.js +43 -0
  61. package/dist/esm/model/order.js +5 -2
  62. package/dist/esm/model/position.js +1 -2
  63. package/dist/esm/model/quote-requests.js +10 -0
  64. package/dist/esm/model/quote.js +6 -0
  65. package/dist/esm/model/trade-requests.js +12 -0
  66. package/dist/esm/model/trade.js +1 -0
  67. package/dist/esm/push/index.js +4 -2
  68. package/dist/esm/push/pb/AssetData.js +205 -0
  69. package/dist/esm/push/pb/KlineData.js +181 -0
  70. package/dist/esm/push/pb/OptionTopData.js +424 -0
  71. package/dist/esm/push/pb/OrderStatusData.js +589 -0
  72. package/dist/esm/push/pb/OrderTransactionData.js +253 -0
  73. package/dist/esm/push/pb/PositionData.js +313 -0
  74. package/dist/esm/push/pb/PushData.js +216 -0
  75. package/dist/esm/push/pb/QuoteBBOData.js +157 -0
  76. package/dist/esm/push/pb/QuoteBasicData.js +326 -0
  77. package/dist/esm/push/pb/QuoteData.js +504 -0
  78. package/dist/esm/push/pb/QuoteDepthData.js +217 -0
  79. package/dist/esm/push/pb/Request.js +275 -0
  80. package/dist/esm/push/pb/Response.js +94 -0
  81. package/dist/esm/push/pb/SocketCommon.js +83 -0
  82. package/dist/esm/push/pb/StockTopData.js +196 -0
  83. package/dist/esm/push/pb/TickData.js +199 -0
  84. package/dist/esm/push/pb/TradeTickData.js +305 -0
  85. package/dist/esm/push/pb/index.js +25 -0
  86. package/dist/esm/push/proto-message.js +127 -0
  87. package/dist/esm/push/push-client.js +329 -164
  88. package/dist/esm/push/push-message.js +7 -56
  89. package/dist/esm/push/varint.js +72 -0
  90. package/dist/esm/quote/index.js +2 -2
  91. package/dist/esm/quote/quote-client.js +439 -94
  92. package/dist/esm/signer/signer.js +10 -10
  93. package/dist/esm/trade/trade-client.js +197 -62
  94. package/dist/types/client/api-request.d.ts +12 -11
  95. package/dist/types/client/api-response.d.ts +15 -7
  96. package/dist/types/client/case-convert.d.ts +16 -0
  97. package/dist/types/client/http-client.d.ts +26 -14
  98. package/dist/types/client/index.d.ts +3 -2
  99. package/dist/types/config/client-config.d.ts +29 -13
  100. package/dist/types/config/domain.d.ts +9 -0
  101. package/dist/types/config/index.d.ts +2 -2
  102. package/dist/types/index.d.ts +1 -1
  103. package/dist/types/model/contract.d.ts +13 -29
  104. package/dist/types/model/enums.d.ts +60 -6
  105. package/dist/types/model/order-helpers.d.ts +21 -20
  106. package/dist/types/model/order-status.d.ts +14 -0
  107. package/dist/types/model/order.d.ts +101 -55
  108. package/dist/types/model/position.d.ts +26 -24
  109. package/dist/types/model/quote-requests.d.ts +344 -0
  110. package/dist/types/model/quote.d.ts +572 -0
  111. package/dist/types/model/trade-requests.d.ts +195 -0
  112. package/dist/types/model/trade.d.ts +235 -0
  113. package/dist/types/push/callbacks.d.ts +32 -51
  114. package/dist/types/push/index.d.ts +5 -3
  115. package/dist/types/push/pb/AssetData.d.ts +47 -0
  116. package/dist/types/push/pb/KlineData.d.ts +43 -0
  117. package/dist/types/push/pb/OptionTopData.d.ts +76 -0
  118. package/dist/types/push/pb/OrderStatusData.d.ts +100 -0
  119. package/dist/types/push/pb/OrderTransactionData.d.ts +51 -0
  120. package/dist/types/push/pb/PositionData.d.ts +63 -0
  121. package/dist/types/push/pb/PushData.d.ts +46 -0
  122. package/dist/types/push/pb/QuoteBBOData.d.ts +35 -0
  123. package/dist/types/push/pb/QuoteBasicData.d.ts +66 -0
  124. package/dist/types/push/pb/QuoteData.d.ts +94 -0
  125. package/dist/types/push/pb/QuoteDepthData.d.ts +37 -0
  126. package/dist/types/push/pb/Request.d.ts +44 -0
  127. package/dist/types/push/pb/Response.d.ts +30 -0
  128. package/dist/types/push/pb/SocketCommon.d.ts +64 -0
  129. package/dist/types/push/pb/StockTopData.d.ts +37 -0
  130. package/dist/types/push/pb/TickData.d.ts +44 -0
  131. package/dist/types/push/pb/TradeTickData.d.ts +53 -0
  132. package/dist/types/push/pb/index.d.ts +39 -0
  133. package/dist/types/push/proto-message.d.ts +33 -0
  134. package/dist/types/push/push-client.d.ts +100 -68
  135. package/dist/types/push/push-message.d.ts +7 -147
  136. package/dist/types/push/varint.d.ts +23 -0
  137. package/dist/types/quote/index.d.ts +2 -2
  138. package/dist/types/quote/quote-client.d.ts +154 -47
  139. package/dist/types/signer/signer.d.ts +6 -6
  140. package/dist/types/trade/trade-client.d.ts +106 -38
  141. 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 { MessageType, SubjectType, createPushMessage, serializeMessage, deserializeMessage, } from './push-message';
3
- /** 默认推送服务器地址 */
4
- const DEFAULT_PUSH_URL = 'wss://openapi-push.tigerfintech.com';
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 WebSocket 推送客户端
47
+ * PushClient - TCP + TLS push client (Protobuf protocol)
22
48
  */
23
49
  export class PushClient {
24
50
  constructor(config, options) {
25
- this.ws = null;
51
+ this.socket = null;
26
52
  this._state = ConnectionState.Disconnected;
27
53
  this.callbacks = {};
28
- /** 订阅状态管理:subject -> Set<symbol> */
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
- /** 可注入的 WebSocket 工厂(用于测试) */
36
- this.wsFactory = null;
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.pushUrl = options?.pushUrl ?? DEFAULT_PUSH_URL;
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
- const ws = this.createWebSocket(this.pushUrl);
62
- this.ws = ws;
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
- ws.close();
65
- this._state = ConnectionState.Disconnected;
66
- this.ws = null;
67
- reject(new Error('连接超时'));
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
- ws.onopen = () => {
70
- clearTimeout(timeout);
71
- // 发送认证消息
72
- try {
73
- this.authenticate();
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
- ws.close();
81
- this._state = ConnectionState.Disconnected;
82
- this.ws = null;
83
- reject(err);
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
- ws.onmessage = (ev) => {
87
- this.handleMessage(String(ev.data));
88
- };
89
- ws.onclose = () => {
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.ws = null;
140
+ this.socket = null;
96
141
  if (wasConnected && this.autoReconnect && !this.stopped) {
97
142
  this.scheduleReconnect();
98
143
  }
99
- };
100
- ws.onerror = (ev) => {
101
- if (this._state === ConnectionState.Connecting) {
144
+ });
145
+ socket.on('error', (err) => {
146
+ if (!settled) {
147
+ settled = true;
102
148
  clearTimeout(timeout);
103
149
  this._state = ConnectionState.Disconnected;
104
- this.ws = null;
105
- reject(new Error('WebSocket 连接失败'));
150
+ this.socket = null;
151
+ reject(new Error(`TLS connection failed: ${err.message}`));
106
152
  }
107
153
  else {
108
- this.callbacks.onError?.(new Error('WebSocket 错误'));
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.ws) {
119
- this.ws.onclose = null;
120
- this.ws.onerror = null;
121
- this.ws.onmessage = null;
122
- this.ws.close();
123
- this.ws = null;
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
- const ts = new Date().toISOString().replace('T', ' ').substring(0, 19);
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
- tigerId: this.config.tigerId,
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.ws)
144
- throw new Error('WebSocket 连接未建立');
145
- this.ws.send(serializeMessage(msg));
146
- }
147
- /** 创建 WebSocket 实例 */
148
- createWebSocket(url) {
149
- if (this.wsFactory)
150
- return this.wsFactory(url);
151
- // 动态导入 ws 库(Node.js 环境)
152
- // eslint-disable-next-line @typescript-eslint/no-require-imports
153
- const WS = require('ws');
154
- return new WS(url);
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 msg = createPushMessage(MessageType.Heartbeat);
162
- this.sendMessage(msg);
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
- handleMessage(raw) {
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 (msg.type) {
219
- case MessageType.Kickout:
220
- cb.onKickout?.(msg.data);
221
- break;
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 MessageType.Tick:
229
- cb.onTick?.(msg.data);
310
+ case SocketCommon_DataType.Option:
311
+ if (pushData.quoteData)
312
+ cb.onOption?.(pushData.quoteData);
230
313
  break;
231
- case MessageType.Depth:
232
- cb.onDepth?.(msg.data);
314
+ case SocketCommon_DataType.Future:
315
+ if (pushData.quoteData)
316
+ cb.onFuture?.(pushData.quoteData);
233
317
  break;
234
- case MessageType.Option:
235
- cb.onOption?.(msg.data);
318
+ case SocketCommon_DataType.QuoteDepth:
319
+ if (pushData.quoteDepthData)
320
+ cb.onDepth?.(pushData.quoteDepthData);
236
321
  break;
237
- case MessageType.Future:
238
- cb.onFuture?.(msg.data);
322
+ case SocketCommon_DataType.TradeTick:
323
+ if (pushData.tradeTickData)
324
+ cb.onTick?.(pushData.tradeTickData);
239
325
  break;
240
- case MessageType.Kline:
241
- cb.onKline?.(msg.data);
326
+ case SocketCommon_DataType.Asset:
327
+ if (pushData.assetData)
328
+ cb.onAsset?.(pushData.assetData);
242
329
  break;
243
- case MessageType.Asset:
244
- cb.onAsset?.(msg.data);
330
+ case SocketCommon_DataType.Position:
331
+ if (pushData.positionData)
332
+ cb.onPosition?.(pushData.positionData);
245
333
  break;
246
- case MessageType.Position:
247
- cb.onPosition?.(msg.data);
334
+ case SocketCommon_DataType.OrderStatus:
335
+ if (pushData.orderStatusData)
336
+ cb.onOrder?.(pushData.orderStatusData);
248
337
  break;
249
- case MessageType.Order:
250
- cb.onOrder?.(msg.data);
338
+ case SocketCommon_DataType.OrderTransaction:
339
+ if (pushData.orderTransactionData)
340
+ cb.onTransaction?.(pushData.orderTransactionData);
251
341
  break;
252
- case MessageType.Transaction:
253
- cb.onTransaction?.(msg.data);
342
+ case SocketCommon_DataType.StockTop:
343
+ if (pushData.stockTopData)
344
+ cb.onStockTop?.(pushData.stockTopData);
254
345
  break;
255
- case MessageType.StockTop:
256
- cb.onStockTop?.(msg.data);
346
+ case SocketCommon_DataType.OptionTop:
347
+ if (pushData.optionTopData)
348
+ cb.onOptionTop?.(pushData.optionTopData);
257
349
  break;
258
- case MessageType.OptionTop:
259
- cb.onOptionTop?.(msg.data);
350
+ case SocketCommon_DataType.Kline:
351
+ if (pushData.klineData)
352
+ cb.onKline?.(pushData.klineData);
260
353
  break;
261
- case MessageType.FullTick:
262
- cb.onFullTick?.(msg.data);
354
+ case SocketCommon_DataType.Cc:
355
+ // 加密货币(Cc)推送复用 QuoteData body,路由到 onQuote 回调
356
+ if (pushData.quoteData)
357
+ cb.onQuote?.(pushData.quoteData);
263
358
  break;
264
- case MessageType.QuoteBBO:
265
- cb.onQuoteBBO?.(msg.data);
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 req = { subject, symbols, account, market };
272
- const msg = createPushMessage(MessageType.Subscribe, subject, req);
273
- this.sendMessage(msg);
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 req = { subject, symbols };
278
- const msg = createPushMessage(MessageType.Unsubscribe, subject, req);
279
- this.sendMessage(msg);
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
- /** 订阅 K 线 */
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
- /** 退订 K 线 */
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
  }