@tigeropenapi/tigeropen 0.3.0 → 0.4.5

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 (64) hide show
  1. package/dist/cjs/client/case-convert.js +4 -3
  2. package/dist/cjs/client/http-client.js +123 -6
  3. package/dist/cjs/config/client-config.js +56 -12
  4. package/dist/cjs/config/index.js +3 -1
  5. package/dist/cjs/config/token-manager.js +54 -4
  6. package/dist/cjs/index.js +51 -2
  7. package/dist/cjs/model/enums.js +89 -7
  8. package/dist/cjs/model/order-status.js +46 -0
  9. package/dist/cjs/model/quote-requests.js +11 -0
  10. package/dist/cjs/model/trade-requests.js +13 -0
  11. package/dist/cjs/push/pb/OrderStatusData.js +24 -0
  12. package/dist/cjs/push/proto-message.js +6 -0
  13. package/dist/cjs/push/push-client.js +111 -16
  14. package/dist/cjs/push/push-message.js +4 -0
  15. package/dist/cjs/quote/quote-client.js +333 -9
  16. package/dist/cjs/trade/trade-client.js +204 -25
  17. package/dist/esm/client/api-request.js +1 -1
  18. package/dist/esm/client/api-response.js +1 -1
  19. package/dist/esm/client/case-convert.js +4 -3
  20. package/dist/esm/client/http-client.js +128 -11
  21. package/dist/esm/client/index.js +6 -6
  22. package/dist/esm/config/client-config.js +59 -15
  23. package/dist/esm/config/index.js +4 -3
  24. package/dist/esm/config/token-manager.js +55 -5
  25. package/dist/esm/index.js +15 -1
  26. package/dist/esm/logger/index.js +1 -1
  27. package/dist/esm/model/contract-helpers.js +1 -1
  28. package/dist/esm/model/enums.js +87 -6
  29. package/dist/esm/model/order-helpers.js +1 -1
  30. package/dist/esm/model/order-status.js +43 -0
  31. package/dist/esm/model/quote-requests.js +10 -0
  32. package/dist/esm/model/trade-requests.js +12 -0
  33. package/dist/esm/push/index.js +5 -5
  34. package/dist/esm/push/pb/OrderStatusData.js +24 -0
  35. package/dist/esm/push/pb/PushData.js +11 -11
  36. package/dist/esm/push/pb/QuoteBasicData.js +1 -1
  37. package/dist/esm/push/pb/Response.js +1 -1
  38. package/dist/esm/push/pb/index.js +17 -17
  39. package/dist/esm/push/proto-message.js +9 -3
  40. package/dist/esm/push/push-client.js +118 -23
  41. package/dist/esm/push/push-message.js +4 -0
  42. package/dist/esm/quote/index.js +1 -1
  43. package/dist/esm/quote/quote-client.js +335 -11
  44. package/dist/esm/signer/index.js +2 -2
  45. package/dist/esm/trade/index.js +1 -1
  46. package/dist/esm/trade/trade-client.js +206 -27
  47. package/dist/types/client/case-convert.d.ts +3 -1
  48. package/dist/types/client/http-client.d.ts +32 -1
  49. package/dist/types/config/client-config.d.ts +16 -1
  50. package/dist/types/config/index.d.ts +2 -0
  51. package/dist/types/config/token-manager.d.ts +27 -3
  52. package/dist/types/index.d.ts +22 -1
  53. package/dist/types/model/enums.d.ts +60 -6
  54. package/dist/types/model/order-status.d.ts +14 -0
  55. package/dist/types/model/quote-requests.d.ts +344 -0
  56. package/dist/types/model/quote.d.ts +261 -0
  57. package/dist/types/model/trade-requests.d.ts +260 -0
  58. package/dist/types/model/trade.d.ts +214 -1
  59. package/dist/types/push/pb/OrderStatusData.d.ts +4 -0
  60. package/dist/types/push/push-client.d.ts +36 -3
  61. package/dist/types/push/push-message.d.ts +4 -0
  62. package/dist/types/quote/quote-client.d.ts +121 -6
  63. package/dist/types/trade/trade-client.d.ts +95 -12
  64. package/package.json +2 -2
@@ -1,10 +1,11 @@
1
- import { getSignContent } from '../signer/sign-content';
2
- import { signWithRSA, verifyWithRSA } from '../signer/signer';
3
- import { TigerError } from './errors';
4
- import { defaultRetryPolicy } from './retry';
5
- import { parseApiResponse } from './api-response';
1
+ import { TokenManager } from '../config/token-manager.js';
2
+ import { getSignContent } from '../signer/sign-content.js';
3
+ import { signWithRSA, verifyWithRSA } from '../signer/signer.js';
4
+ import { TigerError } from './errors.js';
5
+ import { defaultRetryPolicy } from './retry.js';
6
+ import { parseApiResponse, unmarshalData } from './api-response.js';
6
7
  /** SDK 版本号 */
7
- const SDK_VERSION = '0.1.0';
8
+ const SDK_VERSION = '0.4.0';
8
9
  /** User-Agent 字符串 */
9
10
  const USER_AGENT = `openapi-typescript-sdk-${SDK_VERSION}`;
10
11
  /** 默认字符集 */
@@ -13,14 +14,45 @@ const DEFAULT_CHARSET = 'UTF-8';
13
14
  const DEFAULT_SIGN_TYPE = 'RSA';
14
15
  /** 默认 API 版本 */
15
16
  const DEFAULT_VERSION = '2.0';
17
+ /** Token 刷新 API 方法名 */
18
+ const METHOD_TOKEN_REFRESH = 'user_token_refresh';
19
+ /** 默认后台 token 检查间隔(毫秒):5 分钟 */
20
+ const DEFAULT_TOKEN_CHECK_INTERVAL = 5 * 60 * 1000;
16
21
  /**
17
22
  * HttpClient handles HTTP requests, signing, retry, and timeout
18
23
  */
19
24
  export class HttpClient {
20
25
  constructor(config, retryPolicy, options) {
26
+ /** non-null when auto-refresh is active */
27
+ this.tokenManager = null;
21
28
  this.config = config;
22
29
  this.retryPolicy = retryPolicy ?? defaultRetryPolicy();
23
30
  this.targetUrl = options?.useQuoteServerUrl ? config.quoteServerUrl : config.serverUrl;
31
+ // Auto-start token refresh when tokenRefreshDuration is configured (mirrors Go SDK behaviour)
32
+ if (config.tokenRefreshDuration && config.tokenRefreshDuration > 0) {
33
+ const interval = config.tokenCheckInterval ?? DEFAULT_TOKEN_CHECK_INTERVAL;
34
+ const opts = {
35
+ refreshDuration: config.tokenRefreshDuration,
36
+ refreshInterval: interval,
37
+ };
38
+ if (config.tokenWriter) {
39
+ opts.tokenWriter = config.tokenWriter;
40
+ }
41
+ if (config.tokenLoader) {
42
+ opts.tokenLoader = config.tokenLoader;
43
+ }
44
+ this.tokenManager = this.startTokenAutoRefresh(null, opts);
45
+ }
46
+ }
47
+ /**
48
+ * Stop the background token auto-refresh timer.
49
+ * Call this when the HttpClient is no longer needed to avoid timer leaks.
50
+ */
51
+ close() {
52
+ if (this.tokenManager) {
53
+ this.tokenManager.stopAutoRefresh();
54
+ this.tokenManager = null;
55
+ }
24
56
  }
25
57
  /**
26
58
  * Build common request parameters
@@ -31,7 +63,11 @@ export class HttpClient {
31
63
  method: apiMethod,
32
64
  charset: DEFAULT_CHARSET,
33
65
  sign_type: DEFAULT_SIGN_TYPE,
34
- timestamp: new Date().toISOString().replace('T', ' ').substring(0, 19),
66
+ timestamp: (() => {
67
+ const d = new Date();
68
+ const pad = (n) => String(n).padStart(2, '0');
69
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
70
+ })(),
35
71
  version: version || DEFAULT_VERSION,
36
72
  biz_content: bizContent,
37
73
  };
@@ -58,6 +94,7 @@ export class HttpClient {
58
94
  const headers = {
59
95
  'Content-Type': 'application/json;charset=UTF-8',
60
96
  'User-Agent': USER_AGENT,
97
+ 'Connection': 'close',
61
98
  };
62
99
  if (this.config.token) {
63
100
  headers['Authorization'] = this.config.token;
@@ -68,7 +105,8 @@ export class HttpClient {
68
105
  body,
69
106
  signal: AbortSignal.timeout(this.config.timeout * 1000),
70
107
  });
71
- return response.text();
108
+ const text = await response.text();
109
+ return text;
72
110
  }
73
111
  /**
74
112
  * Execute a structured API request, returning a parsed ApiResponse.
@@ -95,6 +133,10 @@ export class HttpClient {
95
133
  }
96
134
  catch (err) {
97
135
  lastErr = err;
136
+ // Only retry on network errors, not API business errors (TigerError with code)
137
+ if (err.code !== undefined && err.code !== -1) {
138
+ throw err; // API returned a business error code, don't retry
139
+ }
98
140
  if (!this.retryPolicy.shouldRetry(request.method)) {
99
141
  throw err;
100
142
  }
@@ -107,9 +149,10 @@ export class HttpClient {
107
149
  *
108
150
  * @param apiMethod - API method name (e.g. "market_state", "place_order")
109
151
  * @param requestJson - Raw biz_content JSON string
152
+ * @param version - Optional API version override (default: "2.0")
110
153
  * @returns Raw response JSON string, unparsed
111
154
  */
112
- async execute(apiMethod, requestJson) {
155
+ async execute(apiMethod, requestJson, version) {
113
156
  // Parameter validation
114
157
  if (!apiMethod) {
115
158
  throw new TigerError(-1, 'api_method must not be empty');
@@ -120,7 +163,7 @@ export class HttpClient {
120
163
  catch {
121
164
  throw new TigerError(-1, 'request_json is not valid JSON');
122
165
  }
123
- const params = this.buildCommonParams(apiMethod, requestJson);
166
+ const params = this.buildCommonParams(apiMethod, requestJson, version);
124
167
  const sign = this.signParams(params);
125
168
  params['sign'] = sign;
126
169
  let lastErr;
@@ -141,6 +184,10 @@ export class HttpClient {
141
184
  }
142
185
  catch (err) {
143
186
  lastErr = err;
187
+ // Don't retry API business errors
188
+ if (err.code !== undefined && err.code !== -1) {
189
+ throw err;
190
+ }
144
191
  if (!this.retryPolicy.shouldRetry(apiMethod)) {
145
192
  throw err;
146
193
  }
@@ -148,6 +195,75 @@ export class HttpClient {
148
195
  }
149
196
  throw lastErr;
150
197
  }
198
+ /**
199
+ * 调用 user_token_refresh 接口获取新 token,返回新 token 字符串。
200
+ * 此方法仅查询,不修改 config 中的 token。
201
+ */
202
+ async queryToken() {
203
+ const params = this.buildCommonParams(METHOD_TOKEN_REFRESH, '{}');
204
+ const sign = this.signParams(params);
205
+ params['sign'] = sign;
206
+ const body = await this.doHttpPost(params);
207
+ const response = parseApiResponse(body);
208
+ this.verifyResponseSign(params['timestamp'], response.sign);
209
+ const data = unmarshalData(response.data);
210
+ const newToken = data?.token;
211
+ if (!newToken) {
212
+ throw new TigerError(-1, '服务端返回空 token');
213
+ }
214
+ return newToken;
215
+ }
216
+ /**
217
+ * 刷新 token:调用接口获取新 token 后更新 config.token。
218
+ *
219
+ * @param tokenManager - 可选外部 TokenManager;若提供则调用 setToken() 写文件并触发回调。
220
+ * 若 NewHttpClient 已自动启动内部 TokenManager,其内存 token 也会同步更新。
221
+ */
222
+ async refreshToken(tokenManager) {
223
+ const newToken = await this.queryToken();
224
+ this.config.token = newToken;
225
+ // Keep internal tokenManager in sync so shouldTokenRefresh stays accurate.
226
+ if (this.tokenManager && tokenManager !== this.tokenManager) {
227
+ this.tokenManager.syncToken(newToken);
228
+ }
229
+ if (tokenManager) {
230
+ tokenManager.setToken(newToken);
231
+ }
232
+ }
233
+ /**
234
+ * 启动后台 token 自动刷新。
235
+ *
236
+ * @param tokenManager - 可为 null:SDK 内部自动创建一个仅内存的 TokenManager,
237
+ * 适合直接通过 token 在代码里设置、不使用文件的场景。
238
+ * @param options - 仅在 tokenManager 为 null 时生效,用于配置内部 TokenManager
239
+ * (refreshDuration / refreshInterval / tokenWriter / tokenLoader)。
240
+ * @returns 使用或创建的 TokenManager 实例
241
+ */
242
+ startTokenAutoRefresh(tokenManager, options) {
243
+ if (!tokenManager) {
244
+ tokenManager = new TokenManager(options);
245
+ // 从 config.token 同步当前 token,使 shouldTokenRefresh 能正确判断。
246
+ // 若 config.token 暂时为空(异步 tokenLoader 尚未 resolve),则先异步加载。
247
+ if (this.config.token) {
248
+ tokenManager.syncToken(this.config.token);
249
+ }
250
+ else if (options?.tokenLoader) {
251
+ // tokenLoader 可能是异步的;在后台 resolve 后同步到 config.token 和 TokenManager。
252
+ Promise.resolve(options.tokenLoader()).then((t) => {
253
+ if (t) {
254
+ this.config.token = t;
255
+ tokenManager.syncToken(t);
256
+ }
257
+ }).catch(() => { });
258
+ }
259
+ }
260
+ tokenManager.startAutoRefresh(async () => {
261
+ const newToken = await this.queryToken();
262
+ this.config.token = newToken;
263
+ return newToken;
264
+ });
265
+ return tokenManager;
266
+ }
151
267
  /** Delay for the specified milliseconds */
152
268
  sleep(ms) {
153
269
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -161,7 +277,8 @@ export class HttpClient {
161
277
  */
162
278
  verifyResponseSign(timestamp, signBase64) {
163
279
  if (!signBase64) {
164
- throw new TigerError(-1, 'Response signature is missing');
280
+ // Some endpoints don't return a signature; skip verification silently.
281
+ return;
165
282
  }
166
283
  const valid = verifyWithRSA(this.config.tigerPublicKey, timestamp, signBase64);
167
284
  if (!valid) {
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Transport layer exports.
3
3
  */
4
- export { HttpClient } from './http-client';
5
- export { TigerError, classifyErrorCode } from './errors';
6
- export { RetryPolicy, defaultRetryPolicy, isTradeOperation } from './retry';
7
- export { createApiRequest } from './api-request';
8
- export { parseApiResponse, unmarshalData } from './api-response';
9
- export { camelToSnake, keysToSnakeCase } from './case-convert';
4
+ export { HttpClient } from './http-client.js';
5
+ export { TigerError, classifyErrorCode } from './errors.js';
6
+ export { RetryPolicy, defaultRetryPolicy, isTradeOperation } from './retry.js';
7
+ export { createApiRequest } from './api-request.js';
8
+ export { parseApiResponse, unmarshalData } from './api-response.js';
9
+ export { camelToSnake, keysToSnakeCase } from './case-convert.js';
@@ -8,11 +8,12 @@
8
8
  *
9
9
  * Priority: environment variables > code options (incl. config file) > defaults
10
10
  */
11
- import { readFileSync, existsSync } from 'fs';
11
+ import { readFileSync, existsSync, statSync } from 'fs';
12
12
  import { join } from 'path';
13
13
  import { homedir, networkInterfaces } from 'os';
14
- import { parsePropertiesString } from './config-parser';
15
- import { queryDomains, resolveDynamicServerUrl, resolveDynamicQuoteServerUrl } from './domain';
14
+ import { parsePropertiesString } from './config-parser.js';
15
+ import { queryDomains, resolveDynamicServerUrl, resolveDynamicQuoteServerUrl } from './domain.js';
16
+ import { TokenManager } from './token-manager.js';
16
17
  /** Default config file name */
17
18
  const CONFIG_FILE_NAME = 'tiger_openapi_config.properties';
18
19
  /** Defaults */
@@ -26,6 +27,7 @@ const ENV_TIGER_ID = 'TIGEROPEN_TIGER_ID';
26
27
  const ENV_PRIVATE_KEY = 'TIGEROPEN_PRIVATE_KEY';
27
28
  const ENV_ACCOUNT = 'TIGEROPEN_ACCOUNT';
28
29
  const ENV_TOKEN = 'TIGEROPEN_TOKEN';
30
+ const ENV_TOKEN_FILE = 'TIGEROPEN_TOKEN_FILE';
29
31
  /**
30
32
  * Load and parse a Java-style properties file.
31
33
  *
@@ -99,7 +101,18 @@ export function createClientConfig(options) {
99
101
  // Load properties: explicit path > auto-discovery
100
102
  let fileProps = {};
101
103
  if (opts.propertiesFilePath) {
102
- fileProps = loadPropertiesFile(opts.propertiesFilePath);
104
+ // Support both directory path and full file path
105
+ let filePath = opts.propertiesFilePath;
106
+ if (existsSync(filePath) && statSync(filePath).isDirectory()) {
107
+ filePath = join(filePath, CONFIG_FILE_NAME);
108
+ }
109
+ else if (!existsSync(filePath) && !filePath.endsWith('.properties')) {
110
+ // Path doesn't exist as-is; try treating it as a directory
111
+ const asDir = join(filePath, CONFIG_FILE_NAME);
112
+ if (existsSync(asDir))
113
+ filePath = asDir;
114
+ }
115
+ fileProps = loadPropertiesFile(filePath);
103
116
  }
104
117
  else {
105
118
  const discovered = discoverConfigFile();
@@ -132,6 +145,37 @@ export function createClientConfig(options) {
132
145
  if (envToken) {
133
146
  token = envToken;
134
147
  }
148
+ // Token loading priority: env TIGEROPEN_TOKEN > tokenLoader > token file (TIGEROPEN_TOKEN_FILE or default)
149
+ if (!token) {
150
+ if (opts.tokenLoader) {
151
+ // tokenLoader is async-capable; we do a best-effort synchronous call here.
152
+ // If the loader returns a Promise, the token will be set later via HttpClient.
153
+ try {
154
+ const result = opts.tokenLoader();
155
+ if (result instanceof Promise) {
156
+ // Cannot await in sync constructor; HttpClient.startTokenAutoRefresh() will
157
+ // resolve this Promise and sync the token after construction.
158
+ }
159
+ else if (result) {
160
+ token = result;
161
+ }
162
+ }
163
+ catch {
164
+ // loader failed; leave token undefined
165
+ }
166
+ }
167
+ else {
168
+ // Load from token file: TIGEROPEN_TOKEN_FILE env var > default file
169
+ const tokenFilePath = process.env[ENV_TOKEN_FILE] || 'tiger_openapi_token.properties';
170
+ const tm = new TokenManager({ filePath: tokenFilePath });
171
+ try {
172
+ token = tm.loadTokenSync();
173
+ }
174
+ catch {
175
+ // file not found or no token field; leave token undefined
176
+ }
177
+ }
178
+ }
135
179
  // Validate required fields
136
180
  if (!tigerId) {
137
181
  throw new Error(`tigerId is required. Set it via options.tigerId, a config file, or the ${ENV_TIGER_ID} environment variable.`);
@@ -143,15 +187,16 @@ export function createClientConfig(options) {
143
187
  const enableDynamicDomain = opts.enableDynamicDomain ?? true;
144
188
  let serverUrl;
145
189
  let quoteServerUrl;
190
+ // Query dynamic domains once (may spawn a child process); reuse for both URLs.
191
+ let domainConf = {};
192
+ if (enableDynamicDomain && (!opts.serverUrl || !opts.quoteServerUrl)) {
193
+ domainConf = queryDomains(license);
194
+ }
146
195
  if (opts.serverUrl) {
147
196
  serverUrl = opts.serverUrl;
148
197
  }
149
198
  else {
150
- let dynamicUrl = '';
151
- if (enableDynamicDomain) {
152
- const domainConf = queryDomains(license);
153
- dynamicUrl = resolveDynamicServerUrl(domainConf, license);
154
- }
199
+ const dynamicUrl = resolveDynamicServerUrl(domainConf, license);
155
200
  serverUrl = dynamicUrl || DEFAULT_SERVER_URL;
156
201
  }
157
202
  // Resolve quote server URL: explicit > dynamic domain > serverUrl fallback
@@ -159,11 +204,7 @@ export function createClientConfig(options) {
159
204
  quoteServerUrl = opts.quoteServerUrl;
160
205
  }
161
206
  else {
162
- let dynamicQuoteUrl = '';
163
- if (enableDynamicDomain) {
164
- const domainConf = queryDomains(license);
165
- dynamicQuoteUrl = resolveDynamicQuoteServerUrl(domainConf, license);
166
- }
207
+ const dynamicQuoteUrl = resolveDynamicQuoteServerUrl(domainConf, license);
167
208
  quoteServerUrl = dynamicQuoteUrl || serverUrl;
168
209
  }
169
210
  return {
@@ -176,10 +217,13 @@ export function createClientConfig(options) {
176
217
  timeout: opts.timeout ?? DEFAULT_TIMEOUT,
177
218
  token,
178
219
  tokenRefreshDuration: opts.tokenRefreshDuration,
220
+ tokenCheckInterval: opts.tokenCheckInterval,
221
+ tokenLoader: opts.tokenLoader,
222
+ tokenWriter: opts.tokenWriter,
179
223
  serverUrl,
180
224
  quoteServerUrl,
181
225
  deviceId: detectDeviceId(),
182
- tigerPublicKey: TIGER_PUBLIC_KEY,
226
+ tigerPublicKey: opts.tigerPublicKey ?? TIGER_PUBLIC_KEY,
183
227
  };
184
228
  }
185
229
  /**
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * 配置层导出
3
3
  */
4
- export { parsePropertiesFile, parsePropertiesString } from './config-parser';
5
- export { createClientConfig, loadPropertiesFile } from './client-config';
6
- export { queryDomains, resolveDynamicServerUrl, resolveDynamicQuoteServerUrl } from './domain';
4
+ export { parsePropertiesFile, parsePropertiesString } from './config-parser.js';
5
+ export { createClientConfig, loadPropertiesFile } from './client-config.js';
6
+ export { TokenManager } from './token-manager.js';
7
+ export { queryDomains, resolveDynamicServerUrl, resolveDynamicQuoteServerUrl } from './domain.js';
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import { readFileSync, writeFileSync, mkdirSync } from 'fs';
8
8
  import { dirname } from 'path';
9
- import { parsePropertiesString } from './config-parser';
9
+ import { parsePropertiesString } from './config-parser.js';
10
10
  /** 默认 Token 文件名 */
11
11
  const DEFAULT_TOKEN_FILE = 'tiger_openapi_token.properties';
12
12
  /** 默认刷新间隔(毫秒):24 小时 */
@@ -18,6 +18,7 @@ export class TokenManager {
18
18
  constructor(options) {
19
19
  this.token = '';
20
20
  this.timer = null;
21
+ this.fileEnabled = options?.filePath !== undefined;
21
22
  this.filePath = options?.filePath ?? DEFAULT_TOKEN_FILE;
22
23
  this.refreshInterval = options?.refreshInterval ?? DEFAULT_REFRESH_INTERVAL;
23
24
  let dur = options?.refreshDuration ?? 0;
@@ -25,9 +26,39 @@ export class TokenManager {
25
26
  dur = 30;
26
27
  }
27
28
  this.refreshDuration = dur;
29
+ this.tokenLoader = options?.tokenLoader;
30
+ this.tokenWriter = options?.tokenWriter;
28
31
  }
29
- /** 从 properties 文件加载 Token */
30
- loadToken() {
32
+ /**
33
+ * 从 properties 文件(或自定义 tokenLoader)加载 Token。
34
+ * 若设置了 tokenLoader,优先调用;否则从 properties 文件读取。
35
+ */
36
+ async loadToken() {
37
+ if (this.tokenLoader) {
38
+ const token = await this.tokenLoader();
39
+ if (!token) {
40
+ throw new Error('自定义 tokenLoader 返回空值');
41
+ }
42
+ this.token = token;
43
+ return token;
44
+ }
45
+ const content = readFileSync(this.filePath, 'utf-8');
46
+ const props = parsePropertiesString(content);
47
+ const token = props['token'];
48
+ if (!token) {
49
+ throw new Error('Token 文件中未找到 token 字段');
50
+ }
51
+ this.token = token;
52
+ return token;
53
+ }
54
+ /**
55
+ * 同步版文件加载(仅用于无 tokenLoader 时的兼容入口)。
56
+ * 建议优先使用 loadToken()(异步)。
57
+ */
58
+ loadTokenSync() {
59
+ if (this.tokenLoader) {
60
+ throw new Error('tokenLoader 是异步函数,请使用 loadToken()');
61
+ }
31
62
  const content = readFileSync(this.filePath, 'utf-8');
32
63
  const props = parsePropertiesString(content);
33
64
  const token = props['token'];
@@ -41,10 +72,25 @@ export class TokenManager {
41
72
  getToken() {
42
73
  return this.token;
43
74
  }
44
- /** 设置 Token 并更新文件 */
75
+ /**
76
+ * 设置 Token;若启用了文件持久化(显式传入 filePath),则同步写文件。
77
+ * 成功后触发 tokenWriter 回调(如有)。
78
+ */
45
79
  setToken(token) {
46
80
  this.token = token;
47
- this.saveTokenToFile(token);
81
+ if (this.fileEnabled) {
82
+ this.saveTokenToFile(token);
83
+ }
84
+ if (this.tokenWriter) {
85
+ this.tokenWriter(token);
86
+ }
87
+ }
88
+ /**
89
+ * 仅更新内存中的 token,不写文件,不触发 tokenWriter 回调。
90
+ * 用于多个组件共享 token 时的内部同步。
91
+ */
92
+ syncToken(token) {
93
+ this.token = token;
48
94
  }
49
95
  /** 将 Token 保存到 properties 文件 */
50
96
  saveTokenToFile(token) {
@@ -101,6 +147,10 @@ export class TokenManager {
101
147
  // 刷新失败时静默跳过
102
148
  }
103
149
  }, this.refreshInterval);
150
+ // 防止 timer 阻止 Node.js 进程退出
151
+ if (this.timer.unref) {
152
+ this.timer.unref();
153
+ }
104
154
  }
105
155
  /** 停止后台刷新 */
106
156
  stopAutoRefresh() {
package/dist/esm/index.js CHANGED
@@ -15,4 +15,18 @@
15
15
  * @packageDocumentation
16
16
  */
17
17
  /** SDK 版本号 */
18
- export const VERSION = '0.3.0';
18
+ export const VERSION = '0.4.4';
19
+ // Config
20
+ export { createClientConfig, loadPropertiesFile } from './config/index.js';
21
+ export { TokenManager } from './config/index.js';
22
+ // Transport
23
+ export { HttpClient } from './client/index.js';
24
+ export { TigerError, classifyErrorCode } from './client/index.js';
25
+ // Clients
26
+ export { QuoteClient, parseOptionIdentifier } from './quote/index.js';
27
+ export { TradeClient } from './trade/index.js';
28
+ export { PushClient, ConnectionState, SubjectType } from './push/index.js';
29
+ export { marketOrder, limitOrder, stopOrder, stopLimitOrder } from './model/order-helpers.js';
30
+ export { normalizeOrderStatus } from './model/order-status.js';
31
+ // Enums
32
+ export { OrderStatus, SecurityType, OrderType, TimeInForce, Market, Currency, QuoteRight, SegmentType, OrderSortBy, CorporateActionType, IndustryLevel, SortDirection, OptionAnalysisPeriod, FinancialReportPeriod, Language, License, BarPeriod, orderStatusCode, } from './model/enums.js';
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * 日志模块导出
3
3
  */
4
- export * from './logger';
4
+ export * from './logger.js';
@@ -1,4 +1,4 @@
1
- import { SecurityType } from './enums';
1
+ import { SecurityType } from './enums.js';
2
2
  /** 构造股票合约 */
3
3
  export function stockContract(symbol, currency) {
4
4
  return { symbol, secType: SecurityType.STK, currency };
@@ -48,19 +48,49 @@ export var OrderType;
48
48
  OrderType["VWAP"] = "VWAP";
49
49
  OrderType["OCA"] = "OCA";
50
50
  })(OrderType || (OrderType = {}));
51
- /** 订单状态枚举 */
51
+ /**
52
+ * 订单状态枚举。对齐 Java SDK OrderStatus 定义。
53
+ *
54
+ * 服务端 wire 返回的值同时可能是:
55
+ * - 数字: -2 / -1 / 3 / 4 / 5 / 6 / 7 / 8
56
+ * - 字符串: "Invalid" / "Initial" / "PendingCancel" / "Cancelled" /
57
+ * "Submitted" / "Filled" / "Inactive" / "PendingSubmit"
58
+ *
59
+ * Client 层通过 normalizeOrderStatus() 做数字→字符串归一,不做跨别名合并。
60
+ */
52
61
  export var OrderStatus;
53
62
  (function (OrderStatus) {
54
- OrderStatus["PendingNew"] = "PendingNew";
63
+ OrderStatus["Invalid"] = "Invalid";
55
64
  OrderStatus["Initial"] = "Initial";
65
+ OrderStatus["PendingCancel"] = "PendingCancel";
66
+ OrderStatus["Cancelled"] = "Cancelled";
56
67
  OrderStatus["Submitted"] = "Submitted";
57
- OrderStatus["PartiallyFilled"] = "PartiallyFilled";
58
68
  OrderStatus["Filled"] = "Filled";
59
- OrderStatus["Cancelled"] = "Cancelled";
60
- OrderStatus["PendingCancel"] = "PendingCancel";
61
69
  OrderStatus["Inactive"] = "Inactive";
62
- OrderStatus["Invalid"] = "Invalid";
70
+ OrderStatus["PendingSubmit"] = "PendingSubmit";
63
71
  })(OrderStatus || (OrderStatus = {}));
72
+ /** 返回订单状态对应的服务端数字码,未知返回 0。 */
73
+ export function orderStatusCode(status) {
74
+ switch (status) {
75
+ case OrderStatus.Invalid:
76
+ return -2;
77
+ case OrderStatus.Initial:
78
+ return -1;
79
+ case OrderStatus.PendingCancel:
80
+ return 3;
81
+ case OrderStatus.Cancelled:
82
+ return 4;
83
+ case OrderStatus.Submitted:
84
+ return 5;
85
+ case OrderStatus.Filled:
86
+ return 6;
87
+ case OrderStatus.Inactive:
88
+ return 7;
89
+ case OrderStatus.PendingSubmit:
90
+ return 8;
91
+ }
92
+ return 0;
93
+ }
64
94
  /** K 线周期枚举 */
65
95
  export var BarPeriod;
66
96
  (function (BarPeriod) {
@@ -105,3 +135,54 @@ export var TimeInForce;
105
135
  TimeInForce["GTC"] = "GTC";
106
136
  TimeInForce["OPG"] = "OPG";
107
137
  })(TimeInForce || (TimeInForce = {}));
138
+ /** 订单排序字段 */
139
+ export var OrderSortBy;
140
+ (function (OrderSortBy) {
141
+ OrderSortBy["LatestCreated"] = "LATEST_CREATED";
142
+ OrderSortBy["LatestStatusUpdated"] = "LATEST_STATUS_UPDATED";
143
+ })(OrderSortBy || (OrderSortBy = {}));
144
+ /** 账户分部类型 */
145
+ export var SegmentType;
146
+ (function (SegmentType) {
147
+ SegmentType["All"] = "ALL";
148
+ SegmentType["Sec"] = "SEC";
149
+ SegmentType["Fut"] = "FUT";
150
+ SegmentType["Fund"] = "FUND";
151
+ })(SegmentType || (SegmentType = {}));
152
+ /** 公司行动类型 */
153
+ export var CorporateActionType;
154
+ (function (CorporateActionType) {
155
+ CorporateActionType["Split"] = "split";
156
+ CorporateActionType["Dividend"] = "dividend";
157
+ CorporateActionType["Earning"] = "earning";
158
+ })(CorporateActionType || (CorporateActionType = {}));
159
+ /** 行业级别 (1~4 级) */
160
+ export var IndustryLevel;
161
+ (function (IndustryLevel) {
162
+ IndustryLevel["GSector"] = "GSECTOR";
163
+ IndustryLevel["GGroup"] = "GGROUP";
164
+ IndustryLevel["GInd"] = "GIND";
165
+ IndustryLevel["GSubInd"] = "GSUBIND";
166
+ })(IndustryLevel || (IndustryLevel = {}));
167
+ /** 排序方向 */
168
+ export var SortDirection;
169
+ (function (SortDirection) {
170
+ SortDirection["No"] = "SortDir_No";
171
+ SortDirection["Ascend"] = "SortDir_Ascend";
172
+ SortDirection["Descend"] = "SortDir_Descend";
173
+ })(SortDirection || (SortDirection = {}));
174
+ /** 期权分析周期 */
175
+ export var OptionAnalysisPeriod;
176
+ (function (OptionAnalysisPeriod) {
177
+ OptionAnalysisPeriod["ThreeYear"] = "3year";
178
+ OptionAnalysisPeriod["FiftyTwoWeek"] = "52week";
179
+ OptionAnalysisPeriod["TwentySixWeek"] = "26week";
180
+ OptionAnalysisPeriod["ThirteenWeek"] = "13week";
181
+ })(OptionAnalysisPeriod || (OptionAnalysisPeriod = {}));
182
+ /** 财报类型 */
183
+ export var FinancialReportPeriod;
184
+ (function (FinancialReportPeriod) {
185
+ FinancialReportPeriod["Annual"] = "Annual";
186
+ FinancialReportPeriod["Quarterly"] = "Quarterly";
187
+ FinancialReportPeriod["LTM"] = "LTM";
188
+ })(FinancialReportPeriod || (FinancialReportPeriod = {}));
@@ -1,4 +1,4 @@
1
- import { OrderType, TimeInForce } from './enums';
1
+ import { OrderType, TimeInForce } from './enums.js';
2
2
  /** Market order */
3
3
  export function marketOrder(account, symbol, secType, action, quantity) {
4
4
  return {
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Order status normalization.
3
+ *
4
+ * 服务端返回的订单状态有时是整数(-2/-1/3/4/5/6/7/8),有时是字符串
5
+ * ("Invalid"/"Submitted"/...)。本模块把二者统一为 Java SDK OrderStatus 的
6
+ * 字符串值。不做跨别名合并。
7
+ *
8
+ * 对齐 Java SDK OrderStatus.java。
9
+ */
10
+ import { OrderStatus } from './enums.js';
11
+ /** 把数字状态码映射到字符串,未识别返回空串。 */
12
+ function statusIntToString(n) {
13
+ switch (n) {
14
+ case -2:
15
+ return OrderStatus.Invalid;
16
+ case -1:
17
+ return OrderStatus.Initial;
18
+ case 3:
19
+ return OrderStatus.PendingCancel;
20
+ case 4:
21
+ return OrderStatus.Cancelled;
22
+ case 5:
23
+ return OrderStatus.Submitted;
24
+ case 6:
25
+ return OrderStatus.Filled;
26
+ case 7:
27
+ return OrderStatus.Inactive;
28
+ case 8:
29
+ return OrderStatus.PendingSubmit;
30
+ }
31
+ return '';
32
+ }
33
+ /**
34
+ * 把服务端可能返回的 string/number 订单状态归一为字符串。
35
+ * 字符串原样透传;数字按映射表转换;其他输入返回空串。
36
+ */
37
+ export function normalizeOrderStatus(raw) {
38
+ if (typeof raw === 'string')
39
+ return raw;
40
+ if (typeof raw === 'number')
41
+ return statusIntToString(raw);
42
+ return '';
43
+ }