@tigeropenapi/tigeropen 0.1.0 → 0.3.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 (129) 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 +46 -20
  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/order-helpers.js +9 -9
  13. package/dist/cjs/model/order.js +5 -2
  14. package/dist/cjs/model/position.js +1 -2
  15. package/dist/cjs/model/quote.js +7 -0
  16. package/dist/cjs/model/trade.js +2 -0
  17. package/dist/cjs/push/index.js +6 -3
  18. package/dist/cjs/push/pb/AssetData.js +208 -0
  19. package/dist/cjs/push/pb/KlineData.js +184 -0
  20. package/dist/cjs/push/pb/OptionTopData.js +427 -0
  21. package/dist/cjs/push/pb/OrderStatusData.js +568 -0
  22. package/dist/cjs/push/pb/OrderTransactionData.js +256 -0
  23. package/dist/cjs/push/pb/PositionData.js +316 -0
  24. package/dist/cjs/push/pb/PushData.js +219 -0
  25. package/dist/cjs/push/pb/QuoteBBOData.js +160 -0
  26. package/dist/cjs/push/pb/QuoteBasicData.js +329 -0
  27. package/dist/cjs/push/pb/QuoteData.js +507 -0
  28. package/dist/cjs/push/pb/QuoteDepthData.js +220 -0
  29. package/dist/cjs/push/pb/Request.js +278 -0
  30. package/dist/cjs/push/pb/Response.js +97 -0
  31. package/dist/cjs/push/pb/SocketCommon.js +86 -0
  32. package/dist/cjs/push/pb/StockTopData.js +199 -0
  33. package/dist/cjs/push/pb/TickData.js +202 -0
  34. package/dist/cjs/push/pb/TradeTickData.js +308 -0
  35. package/dist/cjs/push/pb/index.js +49 -0
  36. package/dist/cjs/push/proto-message.js +129 -0
  37. package/dist/cjs/push/push-client.js +292 -164
  38. package/dist/cjs/push/push-message.js +4 -60
  39. package/dist/cjs/push/varint.js +76 -0
  40. package/dist/cjs/quote/index.js +3 -2
  41. package/dist/cjs/quote/quote-client.js +128 -94
  42. package/dist/cjs/signer/signer.js +10 -10
  43. package/dist/cjs/trade/trade-client.js +56 -62
  44. package/dist/esm/client/api-request.js +14 -8
  45. package/dist/esm/client/api-response.js +22 -3
  46. package/dist/esm/client/case-convert.js +31 -0
  47. package/dist/esm/client/http-client.js +47 -21
  48. package/dist/esm/client/index.js +3 -2
  49. package/dist/esm/config/client-config.js +131 -51
  50. package/dist/esm/config/domain.js +20 -2
  51. package/dist/esm/config/index.js +2 -2
  52. package/dist/esm/index.js +1 -1
  53. package/dist/esm/model/contract.js +1 -2
  54. package/dist/esm/model/order-helpers.js +9 -9
  55. package/dist/esm/model/order.js +5 -2
  56. package/dist/esm/model/position.js +1 -2
  57. package/dist/esm/model/quote.js +6 -0
  58. package/dist/esm/model/trade.js +1 -0
  59. package/dist/esm/push/index.js +4 -2
  60. package/dist/esm/push/pb/AssetData.js +205 -0
  61. package/dist/esm/push/pb/KlineData.js +181 -0
  62. package/dist/esm/push/pb/OptionTopData.js +424 -0
  63. package/dist/esm/push/pb/OrderStatusData.js +565 -0
  64. package/dist/esm/push/pb/OrderTransactionData.js +253 -0
  65. package/dist/esm/push/pb/PositionData.js +313 -0
  66. package/dist/esm/push/pb/PushData.js +216 -0
  67. package/dist/esm/push/pb/QuoteBBOData.js +157 -0
  68. package/dist/esm/push/pb/QuoteBasicData.js +326 -0
  69. package/dist/esm/push/pb/QuoteData.js +504 -0
  70. package/dist/esm/push/pb/QuoteDepthData.js +217 -0
  71. package/dist/esm/push/pb/Request.js +275 -0
  72. package/dist/esm/push/pb/Response.js +94 -0
  73. package/dist/esm/push/pb/SocketCommon.js +83 -0
  74. package/dist/esm/push/pb/StockTopData.js +196 -0
  75. package/dist/esm/push/pb/TickData.js +199 -0
  76. package/dist/esm/push/pb/TradeTickData.js +305 -0
  77. package/dist/esm/push/pb/index.js +25 -0
  78. package/dist/esm/push/proto-message.js +121 -0
  79. package/dist/esm/push/push-client.js +260 -165
  80. package/dist/esm/push/push-message.js +3 -56
  81. package/dist/esm/push/varint.js +72 -0
  82. package/dist/esm/quote/index.js +2 -2
  83. package/dist/esm/quote/quote-client.js +127 -94
  84. package/dist/esm/signer/signer.js +10 -10
  85. package/dist/esm/trade/trade-client.js +56 -62
  86. package/dist/types/client/api-request.d.ts +12 -11
  87. package/dist/types/client/api-response.d.ts +15 -7
  88. package/dist/types/client/case-convert.d.ts +16 -0
  89. package/dist/types/client/http-client.d.ts +26 -14
  90. package/dist/types/client/index.d.ts +3 -2
  91. package/dist/types/config/client-config.d.ts +29 -13
  92. package/dist/types/config/domain.d.ts +9 -0
  93. package/dist/types/config/index.d.ts +2 -2
  94. package/dist/types/index.d.ts +1 -1
  95. package/dist/types/model/contract.d.ts +13 -29
  96. package/dist/types/model/order-helpers.d.ts +21 -20
  97. package/dist/types/model/order.d.ts +101 -55
  98. package/dist/types/model/position.d.ts +26 -24
  99. package/dist/types/model/quote.d.ts +311 -0
  100. package/dist/types/model/trade.d.ts +113 -0
  101. package/dist/types/push/callbacks.d.ts +32 -51
  102. package/dist/types/push/index.d.ts +5 -3
  103. package/dist/types/push/pb/AssetData.d.ts +47 -0
  104. package/dist/types/push/pb/KlineData.d.ts +43 -0
  105. package/dist/types/push/pb/OptionTopData.d.ts +76 -0
  106. package/dist/types/push/pb/OrderStatusData.d.ts +96 -0
  107. package/dist/types/push/pb/OrderTransactionData.d.ts +51 -0
  108. package/dist/types/push/pb/PositionData.d.ts +63 -0
  109. package/dist/types/push/pb/PushData.d.ts +46 -0
  110. package/dist/types/push/pb/QuoteBBOData.d.ts +35 -0
  111. package/dist/types/push/pb/QuoteBasicData.d.ts +66 -0
  112. package/dist/types/push/pb/QuoteData.d.ts +94 -0
  113. package/dist/types/push/pb/QuoteDepthData.d.ts +37 -0
  114. package/dist/types/push/pb/Request.d.ts +44 -0
  115. package/dist/types/push/pb/Response.d.ts +30 -0
  116. package/dist/types/push/pb/SocketCommon.d.ts +64 -0
  117. package/dist/types/push/pb/StockTopData.d.ts +37 -0
  118. package/dist/types/push/pb/TickData.d.ts +44 -0
  119. package/dist/types/push/pb/TradeTickData.d.ts +53 -0
  120. package/dist/types/push/pb/index.d.ts +39 -0
  121. package/dist/types/push/proto-message.d.ts +33 -0
  122. package/dist/types/push/push-client.d.ts +69 -68
  123. package/dist/types/push/push-message.d.ts +3 -147
  124. package/dist/types/push/varint.d.ts +23 -0
  125. package/dist/types/quote/index.d.ts +2 -2
  126. package/dist/types/quote/quote-client.d.ts +41 -47
  127. package/dist/types/signer/signer.d.ts +6 -6
  128. package/dist/types/trade/trade-client.d.ts +34 -38
  129. package/package.json +11 -6
@@ -1,17 +1,23 @@
1
1
  "use strict";
2
- /**
3
- * API 请求构造模块
4
- */
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.createApiRequest = createApiRequest;
7
4
  /**
8
- * 创建 API 请求,将业务参数序列化为 JSON 字符串作为 bizContent。
5
+ * API request builder.
6
+ *
7
+ * Requests use camelCase parameters in TypeScript (idiomatic), but the
8
+ * server wire format is snake_case, so we convert keys before serializing.
9
+ */
10
+ const case_convert_1 = require("./case-convert");
11
+ /**
12
+ * Create an API request, serializing business parameters to JSON as bizContent.
13
+ * camelCase keys are automatically converted to snake_case to match the server.
9
14
  *
10
- * @param method - API 方法名
11
- * @param bizParams - 业务参数(对象、字符串或 null)
12
- * @returns ApiRequest 对象
15
+ * @param method - API method name
16
+ * @param bizParams - Business parameters (object, string, or null); objects are converted recursively
17
+ * @param version - Optional API version override
18
+ * @returns ApiRequest object
13
19
  */
14
- function createApiRequest(method, bizParams) {
20
+ function createApiRequest(method, bizParams, version) {
15
21
  let bizContent;
16
22
  if (typeof bizParams === 'string') {
17
23
  bizContent = bizParams;
@@ -20,7 +26,7 @@ function createApiRequest(method, bizParams) {
20
26
  bizContent = '{}';
21
27
  }
22
28
  else {
23
- bizContent = JSON.stringify(bizParams);
29
+ bizContent = JSON.stringify((0, case_convert_1.keysToSnakeCase)(bizParams));
24
30
  }
25
- return { method, bizContent };
31
+ return { method, bizContent, version };
26
32
  }
@@ -1,13 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseApiResponse = parseApiResponse;
4
+ exports.unmarshalData = unmarshalData;
4
5
  /**
5
- * API 响应解析模块
6
+ * API response parsing.
6
7
  */
7
8
  const errors_1 = require("./errors");
8
9
  /**
9
- * 解析 API 响应 JSON 字符串。
10
- * code 为 0 时返回 ApiResponse;code 不为 0 时抛出 TigerError。
10
+ * Parse the raw HTTP response body.
11
+ * Throws TigerError when code != 0.
11
12
  */
12
13
  function parseApiResponse(body) {
13
14
  const resp = JSON.parse(body);
@@ -16,3 +17,22 @@ function parseApiResponse(body) {
16
17
  }
17
18
  return resp;
18
19
  }
20
+ /**
21
+ * Decode an ApiResponse.data payload into a typed value.
22
+ * Handles the server's occasional double-encoded JSON (where `data` is a
23
+ * JSON string that itself wraps JSON), seen on some trade endpoints.
24
+ */
25
+ function unmarshalData(data) {
26
+ if (data == null)
27
+ return undefined;
28
+ if (typeof data === 'string') {
29
+ // Attempt to parse JSON string (double-encoded case)
30
+ try {
31
+ return JSON.parse(data);
32
+ }
33
+ catch {
34
+ return data;
35
+ }
36
+ }
37
+ return data;
38
+ }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ /**
3
+ * Case conversion utilities.
4
+ *
5
+ * TypeScript SDK uses camelCase idiomatically. The server expects
6
+ * snake_case for request field names and returns camelCase for responses.
7
+ * These helpers convert camelCase objects to snake_case before
8
+ * serialization; response side needs no conversion.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.camelToSnake = camelToSnake;
12
+ exports.keysToSnakeCase = keysToSnakeCase;
13
+ /** Convert a single camelCase key to snake_case. */
14
+ function camelToSnake(key) {
15
+ return key.replace(/[A-Z]/g, (c) => '_' + c.toLowerCase());
16
+ }
17
+ /**
18
+ * Recursively convert all object keys from camelCase to snake_case.
19
+ * Preserves arrays, primitives, and Date/null/undefined.
20
+ * Keys already containing an underscore are kept as-is (already snake_case).
21
+ */
22
+ function keysToSnakeCase(value) {
23
+ if (Array.isArray(value)) {
24
+ return value.map((v) => keysToSnakeCase(v));
25
+ }
26
+ if (value && typeof value === 'object' && value.constructor === Object) {
27
+ const out = {};
28
+ for (const [k, v] of Object.entries(value)) {
29
+ const newKey = k.includes('_') ? k : camelToSnake(k);
30
+ out[newKey] = keysToSnakeCase(v);
31
+ }
32
+ return out;
33
+ }
34
+ return value;
35
+ }
@@ -17,29 +17,33 @@ const DEFAULT_SIGN_TYPE = 'RSA';
17
17
  /** 默认 API 版本 */
18
18
  const DEFAULT_VERSION = '2.0';
19
19
  /**
20
- * HttpClient 封装 HTTP 请求、签名、重试、超时
20
+ * HttpClient handles HTTP requests, signing, retry, and timeout
21
21
  */
22
22
  class HttpClient {
23
- constructor(config, retryPolicy) {
23
+ constructor(config, retryPolicy, options) {
24
24
  this.config = config;
25
25
  this.retryPolicy = retryPolicy ?? (0, retry_1.defaultRetryPolicy)();
26
+ this.targetUrl = options?.useQuoteServerUrl ? config.quoteServerUrl : config.serverUrl;
26
27
  }
27
28
  /**
28
- * 构造公共请求参数
29
+ * Build common request parameters
29
30
  */
30
- buildCommonParams(apiMethod, bizContent) {
31
+ buildCommonParams(apiMethod, bizContent, version) {
31
32
  const params = {
32
33
  tiger_id: this.config.tigerId,
33
34
  method: apiMethod,
34
35
  charset: DEFAULT_CHARSET,
35
36
  sign_type: DEFAULT_SIGN_TYPE,
36
37
  timestamp: new Date().toISOString().replace('T', ' ').substring(0, 19),
37
- version: DEFAULT_VERSION,
38
+ version: version || DEFAULT_VERSION,
38
39
  biz_content: bizContent,
39
40
  };
40
41
  if (this.config.language) {
41
42
  params['language'] = this.config.language;
42
43
  }
44
+ if (this.config.deviceId) {
45
+ params['device_id'] = this.config.deviceId;
46
+ }
43
47
  return params;
44
48
  }
45
49
  /**
@@ -50,7 +54,7 @@ class HttpClient {
50
54
  return (0, signer_1.signWithRSA)(this.config.privateKey, content);
51
55
  }
52
56
  /**
53
- * 发送 HTTP POST 请求
57
+ * Send HTTP POST request
54
58
  */
55
59
  async doHttpPost(params) {
56
60
  const body = JSON.stringify(params);
@@ -61,7 +65,7 @@ class HttpClient {
61
65
  if (this.config.token) {
62
66
  headers['Authorization'] = this.config.token;
63
67
  }
64
- const response = await fetch(this.config.serverUrl, {
68
+ const response = await fetch(this.targetUrl, {
65
69
  method: 'POST',
66
70
  headers,
67
71
  body,
@@ -70,11 +74,11 @@ class HttpClient {
70
74
  return response.text();
71
75
  }
72
76
  /**
73
- * 执行结构化 API 请求,返回解析后的 ApiResponse。
74
- * 供 QuoteClient/TradeClient 内部使用。
77
+ * Execute a structured API request, returning a parsed ApiResponse.
78
+ * Used internally by QuoteClient/TradeClient.
75
79
  */
76
80
  async executeRequest(request) {
77
- const params = this.buildCommonParams(request.method, request.bizContent);
81
+ const params = this.buildCommonParams(request.method, request.bizContent, request.version);
78
82
  const sign = this.signParams(params);
79
83
  params['sign'] = sign;
80
84
  let lastErr;
@@ -88,7 +92,9 @@ class HttpClient {
88
92
  }
89
93
  try {
90
94
  const body = await this.doHttpPost(params);
91
- return (0, api_response_1.parseApiResponse)(body);
95
+ const response = (0, api_response_1.parseApiResponse)(body);
96
+ this.verifyResponseSign(params['timestamp'], response.sign);
97
+ return response;
92
98
  }
93
99
  catch (err) {
94
100
  lastErr = err;
@@ -100,22 +106,22 @@ class HttpClient {
100
106
  throw lastErr;
101
107
  }
102
108
  /**
103
- * 通用 API 调用方法。
109
+ * General-purpose API call method.
104
110
  *
105
- * @param apiMethod - API 方法名(如 "market_state"、"place_order")
106
- * @param requestJson - 原始 biz_content JSON 字符串
107
- * @returns 原始 response JSON 字符串,不做任何解析
111
+ * @param apiMethod - API method name (e.g. "market_state", "place_order")
112
+ * @param requestJson - Raw biz_content JSON string
113
+ * @returns Raw response JSON string, unparsed
108
114
  */
109
115
  async execute(apiMethod, requestJson) {
110
- // 参数校验
116
+ // Parameter validation
111
117
  if (!apiMethod) {
112
- throw new errors_1.TigerError(-1, 'api_method 不能为空');
118
+ throw new errors_1.TigerError(-1, 'api_method must not be empty');
113
119
  }
114
120
  try {
115
121
  JSON.parse(requestJson);
116
122
  }
117
123
  catch {
118
- throw new errors_1.TigerError(-1, 'request_json 不是有效的 JSON');
124
+ throw new errors_1.TigerError(-1, 'request_json is not valid JSON');
119
125
  }
120
126
  const params = this.buildCommonParams(apiMethod, requestJson);
121
127
  const sign = this.signParams(params);
@@ -130,7 +136,11 @@ class HttpClient {
130
136
  await this.sleep(backoff);
131
137
  }
132
138
  try {
133
- return await this.doHttpPost(params);
139
+ const body = await this.doHttpPost(params);
140
+ // Verify response signature even for raw responses
141
+ const parsed = JSON.parse(body);
142
+ this.verifyResponseSign(params['timestamp'], parsed.sign);
143
+ return body;
134
144
  }
135
145
  catch (err) {
136
146
  lastErr = err;
@@ -141,9 +151,25 @@ class HttpClient {
141
151
  }
142
152
  throw lastErr;
143
153
  }
144
- /** 延迟指定毫秒 */
154
+ /** Delay for the specified milliseconds */
145
155
  sleep(ms) {
146
156
  return new Promise((resolve) => setTimeout(resolve, ms));
147
157
  }
158
+ /**
159
+ * Verify the response signature using the tiger public key.
160
+ *
161
+ * @param timestamp - The timestamp sent in the request params
162
+ * @param signBase64 - The `sign` field from the response body
163
+ * @throws TigerError if the signature is missing or invalid
164
+ */
165
+ verifyResponseSign(timestamp, signBase64) {
166
+ if (!signBase64) {
167
+ throw new errors_1.TigerError(-1, 'Response signature is missing');
168
+ }
169
+ const valid = (0, signer_1.verifyWithRSA)(this.config.tigerPublicKey, timestamp, signBase64);
170
+ if (!valid) {
171
+ throw new errors_1.TigerError(-1, 'Response signature verification failed');
172
+ }
173
+ }
148
174
  }
149
175
  exports.HttpClient = HttpClient;
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.parseApiResponse = exports.createApiRequest = exports.isTradeOperation = exports.defaultRetryPolicy = exports.RetryPolicy = exports.classifyErrorCode = exports.TigerError = exports.HttpClient = void 0;
3
+ exports.keysToSnakeCase = exports.camelToSnake = exports.unmarshalData = exports.parseApiResponse = exports.createApiRequest = exports.isTradeOperation = exports.defaultRetryPolicy = exports.RetryPolicy = exports.classifyErrorCode = exports.TigerError = exports.HttpClient = void 0;
4
4
  /**
5
- * 传输层模块导出
5
+ * Transport layer exports.
6
6
  */
7
7
  var http_client_1 = require("./http-client");
8
8
  Object.defineProperty(exports, "HttpClient", { enumerable: true, get: function () { return http_client_1.HttpClient; } });
@@ -17,3 +17,7 @@ var api_request_1 = require("./api-request");
17
17
  Object.defineProperty(exports, "createApiRequest", { enumerable: true, get: function () { return api_request_1.createApiRequest; } });
18
18
  var api_response_1 = require("./api-response");
19
19
  Object.defineProperty(exports, "parseApiResponse", { enumerable: true, get: function () { return api_response_1.parseApiResponse; } });
20
+ Object.defineProperty(exports, "unmarshalData", { enumerable: true, get: function () { return api_response_1.unmarshalData; } });
21
+ var case_convert_1 = require("./case-convert");
22
+ Object.defineProperty(exports, "camelToSnake", { enumerable: true, get: function () { return case_convert_1.camelToSnake; } });
23
+ Object.defineProperty(exports, "keysToSnakeCase", { enumerable: true, get: function () { return case_convert_1.keysToSnakeCase; } });
@@ -1,61 +1,129 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadPropertiesFile = loadPropertiesFile;
3
4
  exports.createClientConfig = createClientConfig;
4
5
  /**
5
- * ClientConfig - 客户端配置管理
6
+ * ClientConfig - Client configuration management
6
7
  *
7
- * 聚合所有配置参数,支持:
8
- * - 代码直接设置
9
- * - 从 properties 配置文件加载
10
- * - 环境变量覆盖
8
+ * Aggregates all configuration parameters with support for:
9
+ * - Direct code configuration
10
+ * - Properties file loading (explicit path or auto-discovery)
11
+ * - Environment variable overrides
11
12
  *
12
- * 优先级:环境变量 > 代码设置(含配置文件) > 默认值
13
+ * Priority: environment variables > code options (incl. config file) > defaults
13
14
  */
14
15
  const fs_1 = require("fs");
16
+ const path_1 = require("path");
17
+ const os_1 = require("os");
15
18
  const config_parser_1 = require("./config-parser");
16
19
  const domain_1 = require("./domain");
17
- /** 默认值 */
20
+ /** Default config file name */
21
+ const CONFIG_FILE_NAME = 'tiger_openapi_config.properties';
22
+ /** Defaults */
18
23
  const DEFAULT_LANGUAGE = 'zh_CN';
19
24
  const DEFAULT_TIMEOUT = 15;
20
25
  const DEFAULT_SERVER_URL = 'https://openapi.tigerfintech.com/gateway';
21
- const SANDBOX_SERVER_URL = 'https://openapi-sandbox.tigerfintech.com/gateway';
22
- /** 环境变量名 */
26
+ /** Tiger public key for response signature verification (Base64-encoded) */
27
+ const TIGER_PUBLIC_KEY = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNF3G8SoEcCZh2rshUbayDgLLrj6rKgzNMxDL2HSnKcB0+GPOsndqSv+a4IBu9+I3fyBp5hkyMMG2+AXugd9pMpy6VxJxlNjhX1MYbNTZJUT4nudki4uh+LMOkIBHOceGNXjgB+cXqmlUnjlqha/HgboeHSnSgpM3dKSJQlIOsDwIDAQAB';
28
+ /** Environment variable names */
23
29
  const ENV_TIGER_ID = 'TIGEROPEN_TIGER_ID';
24
30
  const ENV_PRIVATE_KEY = 'TIGEROPEN_PRIVATE_KEY';
25
31
  const ENV_ACCOUNT = 'TIGEROPEN_ACCOUNT';
32
+ const ENV_TOKEN = 'TIGEROPEN_TOKEN';
26
33
  /**
27
- * 创建客户端配置。
34
+ * Load and parse a Java-style properties file.
28
35
  *
29
- * 优先级:环境变量 > 代码设置(含配置文件) > 默认值。
30
- * 必填字段 tigerId 和 privateKey 为空时抛出错误。
36
+ * @param filePath - Absolute or relative path to the properties file
37
+ * @returns Parsed key-value pairs, or empty object if the file cannot be read
38
+ */
39
+ function loadPropertiesFile(filePath) {
40
+ try {
41
+ const content = (0, fs_1.readFileSync)(filePath, 'utf-8');
42
+ return (0, config_parser_1.parsePropertiesString)(content);
43
+ }
44
+ catch {
45
+ return {};
46
+ }
47
+ }
48
+ /**
49
+ * Discover the config file by searching well-known locations.
50
+ *
51
+ * Search order:
52
+ * 1. `./tiger_openapi_config.properties` (current working directory)
53
+ * 2. `~/.tigeropen/tiger_openapi_config.properties` (home directory)
54
+ *
55
+ * @returns The resolved file path, or `undefined` if not found
56
+ */
57
+ function discoverConfigFile() {
58
+ const candidates = [
59
+ (0, path_1.join)(process.cwd(), CONFIG_FILE_NAME),
60
+ (0, path_1.join)((0, os_1.homedir)(), '.tigeropen', CONFIG_FILE_NAME),
61
+ ];
62
+ for (const candidate of candidates) {
63
+ if ((0, fs_1.existsSync)(candidate)) {
64
+ return candidate;
65
+ }
66
+ }
67
+ return undefined;
68
+ }
69
+ /**
70
+ * Auto-detect device identifier from the first non-internal MAC address.
31
71
  *
32
- * @param options - 配置选项
33
- * @returns 完整的客户端配置对象
34
- * @throws tigerId 或 privateKey 为空时抛出错误
72
+ * @returns MAC address string, or empty string if none found
73
+ */
74
+ function detectDeviceId() {
75
+ const interfaces = (0, os_1.networkInterfaces)();
76
+ for (const [, addrs] of Object.entries(interfaces)) {
77
+ if (!addrs)
78
+ continue;
79
+ for (const addr of addrs) {
80
+ if (!addr.internal && addr.mac && addr.mac !== '00:00:00:00:00:00') {
81
+ return addr.mac;
82
+ }
83
+ }
84
+ }
85
+ return '';
86
+ }
87
+ /**
88
+ * Create a client configuration.
89
+ *
90
+ * When called with no arguments (or without `propertiesFilePath`), the function
91
+ * automatically searches for `tiger_openapi_config.properties` in the current
92
+ * directory and `~/.tigeropen/`. Discovered values serve as defaults; explicit
93
+ * options and environment variables take precedence.
94
+ *
95
+ * Priority: environment variables > code options > config file > defaults
96
+ *
97
+ * @param options - Configuration options (optional)
98
+ * @returns Complete client configuration object
99
+ * @throws When tigerId or privateKey is empty after all resolution
35
100
  */
36
101
  function createClientConfig(options) {
37
- // 从配置文件加载(如果指定了路径)
102
+ const opts = options ?? {};
103
+ // Load properties: explicit path > auto-discovery
38
104
  let fileProps = {};
39
- if (options.propertiesFilePath) {
40
- try {
41
- const content = (0, fs_1.readFileSync)(options.propertiesFilePath, 'utf-8');
42
- fileProps = (0, config_parser_1.parsePropertiesString)(content);
43
- }
44
- catch {
45
- // 文件加载失败时静默跳过,后续校验会捕获必填字段缺失
105
+ if (opts.propertiesFilePath) {
106
+ fileProps = loadPropertiesFile(opts.propertiesFilePath);
107
+ }
108
+ else {
109
+ const discovered = discoverConfigFile();
110
+ if (discovered) {
111
+ fileProps = loadPropertiesFile(discovered);
46
112
  }
47
113
  }
48
- // 从配置文件提取值(代码设置优先于配置文件)
49
- let tigerId = options.tigerId || fileProps['tiger_id'] || '';
50
- let privateKey = options.privateKey || resolvePrivateKey(fileProps) || '';
51
- let account = options.account || fileProps['account'] || '';
52
- const license = options.license || fileProps['license'];
53
- const language = options.language || fileProps['language'] || DEFAULT_LANGUAGE;
54
- const timezone = options.timezone || fileProps['timezone'];
55
- // 环境变量覆盖(最高优先级)
114
+ // Merge values (code options override config file)
115
+ let tigerId = opts.tigerId || fileProps['tiger_id'] || '';
116
+ let privateKey = opts.privateKey || resolvePrivateKey(fileProps) || '';
117
+ let account = opts.account || fileProps['account'] || '';
118
+ let token = opts.token || fileProps['token'];
119
+ const license = opts.license || fileProps['license'];
120
+ const language = opts.language || fileProps['language'] || DEFAULT_LANGUAGE;
121
+ const timezone = opts.timezone || fileProps['timezone'];
122
+ // Environment variables override everything
56
123
  const envTigerId = process.env[ENV_TIGER_ID];
57
124
  const envPrivateKey = process.env[ENV_PRIVATE_KEY];
58
125
  const envAccount = process.env[ENV_ACCOUNT];
126
+ const envToken = process.env[ENV_TOKEN];
59
127
  if (envTigerId) {
60
128
  tigerId = envTigerId;
61
129
  }
@@ -65,25 +133,24 @@ function createClientConfig(options) {
65
133
  if (envAccount) {
66
134
  account = envAccount;
67
135
  }
68
- // 校验必填字段
136
+ if (envToken) {
137
+ token = envToken;
138
+ }
139
+ // Validate required fields
69
140
  if (!tigerId) {
70
- throw new Error(`tigerId 不能为空,请通过 options.tigerId 或环境变量 ${ENV_TIGER_ID} 设置`);
141
+ throw new Error(`tigerId is required. Set it via options.tigerId, a config file, or the ${ENV_TIGER_ID} environment variable.`);
71
142
  }
72
143
  if (!privateKey) {
73
- throw new Error(`privateKey 不能为空,请通过 options.privateKey 或环境变量 ${ENV_PRIVATE_KEY} 设置`);
144
+ throw new Error(`privateKey is required. Set it via options.privateKey, a config file, or the ${ENV_PRIVATE_KEY} environment variable.`);
74
145
  }
75
- // 确定服务器 URL:sandbox > 动态域名 > 默认
76
- const sandboxDebug = options.sandboxDebug ?? false;
77
- const enableDynamicDomain = options.enableDynamicDomain ?? true;
146
+ // Resolve server URL: explicit > dynamic domain > default
147
+ const enableDynamicDomain = opts.enableDynamicDomain ?? true;
78
148
  let serverUrl;
79
- if (sandboxDebug) {
80
- serverUrl = SANDBOX_SERVER_URL;
81
- }
82
- else if (options.serverUrl) {
83
- serverUrl = options.serverUrl;
149
+ let quoteServerUrl;
150
+ if (opts.serverUrl) {
151
+ serverUrl = opts.serverUrl;
84
152
  }
85
153
  else {
86
- // 尝试动态域名获取
87
154
  let dynamicUrl = '';
88
155
  if (enableDynamicDomain) {
89
156
  const domainConf = (0, domain_1.queryDomains)(license);
@@ -91,6 +158,18 @@ function createClientConfig(options) {
91
158
  }
92
159
  serverUrl = dynamicUrl || DEFAULT_SERVER_URL;
93
160
  }
161
+ // Resolve quote server URL: explicit > dynamic domain > serverUrl fallback
162
+ if (opts.quoteServerUrl) {
163
+ quoteServerUrl = opts.quoteServerUrl;
164
+ }
165
+ else {
166
+ let dynamicQuoteUrl = '';
167
+ if (enableDynamicDomain) {
168
+ const domainConf = (0, domain_1.queryDomains)(license);
169
+ dynamicQuoteUrl = (0, domain_1.resolveDynamicQuoteServerUrl)(domainConf, license);
170
+ }
171
+ quoteServerUrl = dynamicQuoteUrl || serverUrl;
172
+ }
94
173
  return {
95
174
  tigerId,
96
175
  privateKey,
@@ -98,16 +177,18 @@ function createClientConfig(options) {
98
177
  license,
99
178
  language,
100
179
  timezone,
101
- timeout: options.timeout ?? DEFAULT_TIMEOUT,
102
- sandboxDebug,
103
- token: options.token,
104
- tokenRefreshDuration: options.tokenRefreshDuration,
180
+ timeout: opts.timeout ?? DEFAULT_TIMEOUT,
181
+ token,
182
+ tokenRefreshDuration: opts.tokenRefreshDuration,
105
183
  serverUrl,
184
+ quoteServerUrl,
185
+ deviceId: detectDeviceId(),
186
+ tigerPublicKey: TIGER_PUBLIC_KEY,
106
187
  };
107
188
  }
108
189
  /**
109
- * 从 properties 键值对中解析私钥。
110
- * 优先级:private_key > private_key_pk8 > private_key_pk1
190
+ * Resolve private key from properties.
191
+ * Priority: private_key > private_key_pk8 > private_key_pk1
111
192
  */
112
193
  function resolvePrivateKey(props) {
113
194
  return props['private_key'] || props['private_key_pk8'] || props['private_key_pk1'] || '';
@@ -7,6 +7,7 @@
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
8
  exports.queryDomains = queryDomains;
9
9
  exports.resolveDynamicServerUrl = resolveDynamicServerUrl;
10
+ exports.resolveDynamicQuoteServerUrl = resolveDynamicQuoteServerUrl;
10
11
  /** 域名花园地址 */
11
12
  const DOMAIN_GARDEN_ADDRESS = 'https://cg.play-analytics.com';
12
13
  /** 动态域名查询超时(毫秒) */
@@ -67,8 +68,26 @@ function resolveDynamicServerUrl(domainConf, license) {
67
68
  return '';
68
69
  }
69
70
  /**
70
- * 同步 HTTP GET 请求(Node.js 环境)。
71
- * 使用 child_process.execSync 实现同步请求,超时快速失败。
71
+ * Resolve the quote server URL from dynamic domain configuration.
72
+ * Uses the `{LICENSE}-QUOTE` key, falling back to empty string.
73
+ *
74
+ * @param domainConf - Domain configuration map from queryDomains
75
+ * @param license - License type (e.g. "TBNZ")
76
+ * @returns Quote server URL with gateway suffix, or empty string
77
+ */
78
+ function resolveDynamicQuoteServerUrl(domainConf, license) {
79
+ if (!domainConf || Object.keys(domainConf).length === 0) {
80
+ return '';
81
+ }
82
+ const key = license ? `${license}-QUOTE` : `${DOMAIN_KEY_COMMON}-QUOTE`;
83
+ if (typeof domainConf[key] === 'string') {
84
+ return domainConf[key] + GATEWAY_SUFFIX;
85
+ }
86
+ return '';
87
+ }
88
+ /**
89
+ * Synchronous HTTP GET request (Node.js environment).
90
+ * Uses child_process.execSync for synchronous requests with fast timeout.
72
91
  */
73
92
  function syncHttpGet(url, timeoutMs) {
74
93
  try {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.resolveDynamicServerUrl = exports.queryDomains = exports.createClientConfig = exports.parsePropertiesString = exports.parsePropertiesFile = void 0;
3
+ exports.resolveDynamicQuoteServerUrl = exports.resolveDynamicServerUrl = exports.queryDomains = exports.loadPropertiesFile = exports.createClientConfig = exports.parsePropertiesString = exports.parsePropertiesFile = void 0;
4
4
  /**
5
5
  * 配置层导出
6
6
  */
@@ -9,6 +9,8 @@ Object.defineProperty(exports, "parsePropertiesFile", { enumerable: true, get: f
9
9
  Object.defineProperty(exports, "parsePropertiesString", { enumerable: true, get: function () { return config_parser_1.parsePropertiesString; } });
10
10
  var client_config_1 = require("./client-config");
11
11
  Object.defineProperty(exports, "createClientConfig", { enumerable: true, get: function () { return client_config_1.createClientConfig; } });
12
+ Object.defineProperty(exports, "loadPropertiesFile", { enumerable: true, get: function () { return client_config_1.loadPropertiesFile; } });
12
13
  var domain_1 = require("./domain");
13
14
  Object.defineProperty(exports, "queryDomains", { enumerable: true, get: function () { return domain_1.queryDomains; } });
14
15
  Object.defineProperty(exports, "resolveDynamicServerUrl", { enumerable: true, get: function () { return domain_1.resolveDynamicServerUrl; } });
16
+ Object.defineProperty(exports, "resolveDynamicQuoteServerUrl", { enumerable: true, get: function () { return domain_1.resolveDynamicQuoteServerUrl; } });
package/dist/cjs/index.js CHANGED
@@ -18,4 +18,4 @@
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
19
  exports.VERSION = void 0;
20
20
  /** SDK 版本号 */
21
- exports.VERSION = '0.1.0';
21
+ exports.VERSION = '0.3.0';
@@ -1,6 +1,5 @@
1
1
  "use strict";
2
2
  /**
3
- * Contract 合约接口
4
- * 字段名 camelCase 与 API JSON 天然一致,不改名、不加封装层
3
+ * Contract / TickSize models.
5
4
  */
6
5
  Object.defineProperty(exports, "__esModule", { value: true });