@tigeropenapi/tigeropen 0.5.5 → 0.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -0
- package/dist/cjs/auth/authentication.js +74 -0
- package/dist/cjs/auth/index.js +24 -0
- package/dist/cjs/auth/oauth2/client-registration.js +44 -0
- package/dist/cjs/auth/oauth2/device-authorization.js +40 -0
- package/dist/cjs/auth/oauth2/errors.js +64 -0
- package/dist/cjs/auth/oauth2/http-utils.js +112 -0
- package/dist/cjs/auth/oauth2/index.js +42 -0
- package/dist/cjs/auth/oauth2/loopback-receiver.js +215 -0
- package/dist/cjs/auth/oauth2/metadata.js +34 -0
- package/dist/cjs/auth/oauth2/oauth2-authentication.js +74 -0
- package/dist/cjs/auth/oauth2/session-manager.js +762 -0
- package/dist/cjs/auth/oauth2/token-store.js +252 -0
- package/dist/cjs/auth/oauth2/token.js +76 -0
- package/dist/cjs/push/pb/Request.js +12 -1
- package/dist/cjs/push/proto-message.js +5 -1
- package/dist/cjs/push/push-client.js +29 -13
- package/dist/cjs/trade/trade-client.js +12 -3
- package/dist/cjs/version.js +1 -1
- package/dist/esm/auth/authentication.js +68 -0
- package/dist/esm/auth/index.js +3 -0
- package/dist/esm/auth/oauth2/client-registration.js +40 -0
- package/dist/esm/auth/oauth2/device-authorization.js +36 -0
- package/dist/esm/auth/oauth2/errors.js +59 -0
- package/dist/esm/auth/oauth2/http-utils.js +105 -0
- package/dist/esm/auth/oauth2/index.js +11 -0
- package/dist/esm/auth/oauth2/loopback-receiver.js +178 -0
- package/dist/esm/auth/oauth2/metadata.js +30 -0
- package/dist/esm/auth/oauth2/oauth2-authentication.js +70 -0
- package/dist/esm/auth/oauth2/session-manager.js +725 -0
- package/dist/esm/auth/oauth2/token-store.js +214 -0
- package/dist/esm/auth/oauth2/token.js +71 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/push/pb/Request.js +12 -1
- package/dist/esm/push/proto-message.js +5 -1
- package/dist/esm/push/push-client.js +29 -13
- package/dist/esm/trade/trade-client.js +12 -3
- package/dist/esm/version.js +1 -1
- package/dist/types/auth/authentication.d.ts +98 -0
- package/dist/types/auth/index.d.ts +4 -0
- package/dist/types/auth/oauth2/client-registration.d.ts +32 -0
- package/dist/types/auth/oauth2/device-authorization.d.ts +40 -0
- package/dist/types/auth/oauth2/errors.d.ts +46 -0
- package/dist/types/auth/oauth2/http-utils.d.ts +21 -0
- package/dist/types/auth/oauth2/index.d.ts +16 -0
- package/dist/types/auth/oauth2/loopback-receiver.d.ts +29 -0
- package/dist/types/auth/oauth2/metadata.d.ts +32 -0
- package/dist/types/auth/oauth2/oauth2-authentication.d.ts +31 -0
- package/dist/types/auth/oauth2/session-manager.d.ts +241 -0
- package/dist/types/auth/oauth2/token-store.d.ts +49 -0
- package/dist/types/auth/oauth2/token.d.ts +56 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/model/quote.d.ts +1 -0
- package/dist/types/model/trade-requests.d.ts +5 -0
- package/dist/types/push/index.d.ts +1 -1
- package/dist/types/push/pb/Request.d.ts +1 -0
- package/dist/types/push/proto-message.d.ts +4 -1
- package/dist/types/push/push-client.d.ts +33 -0
- package/dist/types/trade/trade-client.d.ts +12 -3
- package/dist/types/version.d.ts +1 -1
- package/package.json +6 -4
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.OAuth2TokenStore = exports.DEFAULT_SURFACE = exports.ENV_HOME = void 0;
|
|
37
|
+
exports.hashIssuer = hashIssuer;
|
|
38
|
+
/**
|
|
39
|
+
* File-backed OAuth 2.0 authorization state.
|
|
40
|
+
*
|
|
41
|
+
* Storage layout:
|
|
42
|
+
*
|
|
43
|
+
* ~/.tiger/openapi/
|
|
44
|
+
* tokens/{clientId}.json authorization state, mode 0600, removed on logout
|
|
45
|
+
* clients/{issuerHash}-{surface}.json dynamic registration, retained across logout
|
|
46
|
+
*
|
|
47
|
+
* Registration files are keyed by issuer because the client ID is not yet available
|
|
48
|
+
* during lookup, and by surface so that each entry point registers its own client.
|
|
49
|
+
* Without the surface, whichever entry point ran first would own the registration and
|
|
50
|
+
* every other one would silently inherit its identity: one name on the user's
|
|
51
|
+
* authorization page, one revocation switch for all of them, and one shared token file
|
|
52
|
+
* whose refresh-token rotation they would race over.
|
|
53
|
+
*
|
|
54
|
+
* Token files need no surface: each surface registers a distinct client ID, so their
|
|
55
|
+
* token files are already distinct. A manually configured client ID is shared on
|
|
56
|
+
* purpose -- one client ID means one application.
|
|
57
|
+
*
|
|
58
|
+
* Storage root precedence is: explicit argument, `TIGEROPEN_HOME`, then
|
|
59
|
+
* `~/.tiger/openapi`. The format is shared with the Java and Python SDKs.
|
|
60
|
+
*/
|
|
61
|
+
const crypto_1 = require("crypto");
|
|
62
|
+
const fs = __importStar(require("fs"));
|
|
63
|
+
const os_1 = require("os");
|
|
64
|
+
const path = __importStar(require("path"));
|
|
65
|
+
const client_registration_1 = require("./client-registration");
|
|
66
|
+
const errors_1 = require("./errors");
|
|
67
|
+
const token_1 = require("./token");
|
|
68
|
+
exports.ENV_HOME = 'TIGEROPEN_HOME';
|
|
69
|
+
/**
|
|
70
|
+
* Entry point owning a registration. Tools built on this SDK must pass their own value,
|
|
71
|
+
* otherwise they would be filed as the TypeScript SDK and share its client. Keep in
|
|
72
|
+
* step with the Java SDK's `sdk-java` and the Python SDK's `sdk-python`.
|
|
73
|
+
*/
|
|
74
|
+
exports.DEFAULT_SURFACE = 'sdk-typescript';
|
|
75
|
+
const SAFE_CHARS = /[^a-zA-Z0-9\-_.]/g;
|
|
76
|
+
/**
|
|
77
|
+
* Surfaces become part of a file name, so they are restricted rather than sanitised: a
|
|
78
|
+
* silently rewritten surface would file two entry points under one name.
|
|
79
|
+
*/
|
|
80
|
+
const SURFACE_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
81
|
+
class OAuth2TokenStore {
|
|
82
|
+
/**
|
|
83
|
+
* @param home storage root; defaults to `TIGEROPEN_HOME` then `~/.tiger/openapi`
|
|
84
|
+
* @param surface entry point owning the registration, e.g. `sdk-typescript`, `cli`.
|
|
85
|
+
* Lowercase alphanumerics and hyphens only
|
|
86
|
+
*/
|
|
87
|
+
constructor(home, surface = exports.DEFAULT_SURFACE) {
|
|
88
|
+
const base = home || process.env[exports.ENV_HOME] || path.join((0, os_1.homedir)(), '.tiger', 'openapi');
|
|
89
|
+
this.home = path.resolve(expandHome(base));
|
|
90
|
+
this.surface = validateSurface(surface);
|
|
91
|
+
}
|
|
92
|
+
/** Load a token, treating a missing file or issuer mismatch as a cache miss. */
|
|
93
|
+
loadToken(clientId, expectedIssuer) {
|
|
94
|
+
const file = this.tokenFile(clientId);
|
|
95
|
+
if (!fs.existsSync(file))
|
|
96
|
+
return null;
|
|
97
|
+
let token;
|
|
98
|
+
try {
|
|
99
|
+
token = token_1.OAuth2Token.fromJSON(JSON.parse(fs.readFileSync(file, 'utf-8')));
|
|
100
|
+
}
|
|
101
|
+
catch (e) {
|
|
102
|
+
// A corrupt file tells us nothing; treat it as no authorization rather than crash.
|
|
103
|
+
console.error(`[TigerOpen] read token file fail: ${file}`, e);
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
if (!token)
|
|
107
|
+
return null;
|
|
108
|
+
if (token.schemaVersion !== token_1.TOKEN_SCHEMA_VERSION) {
|
|
109
|
+
// Unknown schema versions are not interpreted.
|
|
110
|
+
throw new errors_1.OAuth2Error(errors_1.OAuth2Category.StorageFailed, `unsupported token schema_version: ${token.schemaVersion}`);
|
|
111
|
+
}
|
|
112
|
+
if (expectedIssuer !== undefined && expectedIssuer !== token.issuer) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
return token;
|
|
116
|
+
}
|
|
117
|
+
saveToken(token) {
|
|
118
|
+
if (!token || !token.clientId) {
|
|
119
|
+
throw new errors_1.OAuth2Error(errors_1.OAuth2Category.StorageFailed, 'token or clientId is empty, cannot save');
|
|
120
|
+
}
|
|
121
|
+
writeAtomic(this.tokenFile(token.clientId), JSON.stringify(token.toJSON(), null, 2));
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Delete authorization state while retaining client registration.
|
|
125
|
+
*
|
|
126
|
+
* @returns the path removed, or null when there was nothing to remove. A clientId is
|
|
127
|
+
* not its own file name, so a caller reporting what it deleted has to be told rather
|
|
128
|
+
* than build the path itself
|
|
129
|
+
*/
|
|
130
|
+
deleteToken(clientId) {
|
|
131
|
+
if (!clientId)
|
|
132
|
+
return null;
|
|
133
|
+
const file = this.tokenFile(clientId);
|
|
134
|
+
return deleteFile(file) ? file : null;
|
|
135
|
+
}
|
|
136
|
+
loadClientId(issuer) {
|
|
137
|
+
return this.loadClientRegistration(issuer)?.clientId ?? null;
|
|
138
|
+
}
|
|
139
|
+
loadClientRegistration(issuer) {
|
|
140
|
+
const file = this.clientFile(issuer);
|
|
141
|
+
if (!fs.existsSync(file))
|
|
142
|
+
return null;
|
|
143
|
+
try {
|
|
144
|
+
return client_registration_1.OAuth2ClientRegistration.fromJSON(JSON.parse(fs.readFileSync(file, 'utf-8')));
|
|
145
|
+
}
|
|
146
|
+
catch (e) {
|
|
147
|
+
console.error(`[TigerOpen] read client registration file fail: ${file}`, e);
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
saveClientRegistration(registration) {
|
|
152
|
+
if (!registration || !registration.issuer) {
|
|
153
|
+
throw new errors_1.OAuth2Error(errors_1.OAuth2Category.StorageFailed, 'registration or issuer is empty, cannot save');
|
|
154
|
+
}
|
|
155
|
+
writeAtomic(this.clientFile(registration.issuer), JSON.stringify(registration.toJSON(), null, 2));
|
|
156
|
+
}
|
|
157
|
+
/** Delete registration state after an `invalid_client` response. */
|
|
158
|
+
deleteClientRegistration(issuer) {
|
|
159
|
+
if (!issuer)
|
|
160
|
+
return;
|
|
161
|
+
deleteFile(this.clientFile(issuer));
|
|
162
|
+
}
|
|
163
|
+
tokenFile(clientId) {
|
|
164
|
+
return path.join(this.home, 'tokens', `${safeFileName(clientId)}.json`);
|
|
165
|
+
}
|
|
166
|
+
clientFile(issuer) {
|
|
167
|
+
return path.join(this.home, 'clients', `${hashIssuer(issuer)}-${this.surface}.json`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
exports.OAuth2TokenStore = OAuth2TokenStore;
|
|
171
|
+
/** Return the Java-compatible 32-character SHA-256 issuer file key. */
|
|
172
|
+
function hashIssuer(issuer) {
|
|
173
|
+
return (0, crypto_1.createHash)('sha256').update(issuer || '', 'utf-8').digest('hex').slice(0, 32);
|
|
174
|
+
}
|
|
175
|
+
/** Sanitize an untrusted client ID for use as a file name. */
|
|
176
|
+
function safeFileName(raw) {
|
|
177
|
+
let name = (raw || '').replace(SAFE_CHARS, '_');
|
|
178
|
+
while (name.includes('..')) {
|
|
179
|
+
name = name.replace(/\.\./g, '__');
|
|
180
|
+
}
|
|
181
|
+
return name || '_';
|
|
182
|
+
}
|
|
183
|
+
/** Reject a surface that cannot be used verbatim as part of a file name. */
|
|
184
|
+
function validateSurface(surface) {
|
|
185
|
+
if (typeof surface !== 'string' || !SURFACE_PATTERN.test(surface)) {
|
|
186
|
+
throw new errors_1.OAuth2Error(errors_1.OAuth2Category.StorageFailed, `invalid surface '${surface}': expected lowercase alphanumerics and hyphens, e.g. 'cli'`);
|
|
187
|
+
}
|
|
188
|
+
return surface;
|
|
189
|
+
}
|
|
190
|
+
function expandHome(value) {
|
|
191
|
+
if (value === '~')
|
|
192
|
+
return (0, os_1.homedir)();
|
|
193
|
+
if (value.startsWith('~/') || value.startsWith(`~${path.sep}`)) {
|
|
194
|
+
return path.join((0, os_1.homedir)(), value.slice(2));
|
|
195
|
+
}
|
|
196
|
+
return value;
|
|
197
|
+
}
|
|
198
|
+
/** @returns true when the file was there and is now gone */
|
|
199
|
+
function deleteFile(file) {
|
|
200
|
+
try {
|
|
201
|
+
if (fs.existsSync(file)) {
|
|
202
|
+
fs.unlinkSync(file);
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
catch (e) {
|
|
207
|
+
console.error(`[TigerOpen] delete file fail: ${file}`, e);
|
|
208
|
+
}
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Atomically replace a file through a same-directory temporary file.
|
|
213
|
+
*
|
|
214
|
+
* Same-filesystem replacement prevents partial JSON writes. Directories and files are
|
|
215
|
+
* restricted to modes 0700 and 0600 where supported.
|
|
216
|
+
*/
|
|
217
|
+
function writeAtomic(file, content) {
|
|
218
|
+
const dir = path.dirname(file);
|
|
219
|
+
try {
|
|
220
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
221
|
+
restrict(dir, 0o700);
|
|
222
|
+
// Same directory, so the rename below stays on one filesystem and is atomic.
|
|
223
|
+
const tmp = path.join(dir, `.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}.json`);
|
|
224
|
+
try {
|
|
225
|
+
fs.writeFileSync(tmp, content, { encoding: 'utf-8', mode: 0o600 });
|
|
226
|
+
restrict(tmp, 0o600);
|
|
227
|
+
fs.renameSync(tmp, file);
|
|
228
|
+
}
|
|
229
|
+
catch (e) {
|
|
230
|
+
try {
|
|
231
|
+
fs.unlinkSync(tmp);
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
// The temporary file was never created; nothing to clean up.
|
|
235
|
+
}
|
|
236
|
+
throw e;
|
|
237
|
+
}
|
|
238
|
+
restrict(file, 0o600);
|
|
239
|
+
}
|
|
240
|
+
catch (e) {
|
|
241
|
+
throw new errors_1.OAuth2Error(errors_1.OAuth2Category.StorageFailed, `write file fail: ${file}`, e);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/** Restrict a credential file or directory where the platform supports it. */
|
|
245
|
+
function restrict(target, mode) {
|
|
246
|
+
try {
|
|
247
|
+
fs.chmodSync(target, mode);
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
// Windows and some network filesystems have no POSIX modes; the write still stands.
|
|
251
|
+
}
|
|
252
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Persisted OAuth 2.0 authorization state.
|
|
4
|
+
*
|
|
5
|
+
* The `issuer` and `clientId` bind credentials to an authorization server and client,
|
|
6
|
+
* preventing credential reuse across environments. The on-disk field names are
|
|
7
|
+
* snake_case and shared with the Java and Python SDKs.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.OAuth2Token = exports.TOKEN_SCHEMA_VERSION = void 0;
|
|
11
|
+
exports.currentMillis = currentMillis;
|
|
12
|
+
/** On-disk format version. An unrecognised version is an error, not a guess. */
|
|
13
|
+
exports.TOKEN_SCHEMA_VERSION = 1;
|
|
14
|
+
function currentMillis() {
|
|
15
|
+
return Date.now();
|
|
16
|
+
}
|
|
17
|
+
/** An access token and its metadata. */
|
|
18
|
+
class OAuth2Token {
|
|
19
|
+
constructor(init = {}) {
|
|
20
|
+
this.schemaVersion = init.schemaVersion ?? exports.TOKEN_SCHEMA_VERSION;
|
|
21
|
+
this.issuer = init.issuer;
|
|
22
|
+
this.clientId = init.clientId;
|
|
23
|
+
this.accessToken = init.accessToken;
|
|
24
|
+
this.refreshToken = init.refreshToken;
|
|
25
|
+
this.tokenType = init.tokenType || 'Bearer';
|
|
26
|
+
this.scope = init.scope;
|
|
27
|
+
this.expiresAt = init.expiresAt ?? 0;
|
|
28
|
+
}
|
|
29
|
+
/** Time until expiry; negative once already expired. */
|
|
30
|
+
remainingMillis() {
|
|
31
|
+
return this.expiresAt - currentMillis();
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* @param aheadMillis how early to refresh. The window exists because time passes
|
|
35
|
+
* between sending a request and the server validating it, so using a token right up
|
|
36
|
+
* to its expiry produces intermittent 401s
|
|
37
|
+
*/
|
|
38
|
+
needsRefresh(aheadMillis) {
|
|
39
|
+
return this.remainingMillis() <= aheadMillis;
|
|
40
|
+
}
|
|
41
|
+
hasRefreshToken() {
|
|
42
|
+
return !!this.refreshToken;
|
|
43
|
+
}
|
|
44
|
+
toJSON() {
|
|
45
|
+
return {
|
|
46
|
+
schema_version: this.schemaVersion,
|
|
47
|
+
issuer: this.issuer,
|
|
48
|
+
client_id: this.clientId,
|
|
49
|
+
access_token: this.accessToken,
|
|
50
|
+
refresh_token: this.refreshToken,
|
|
51
|
+
token_type: this.tokenType,
|
|
52
|
+
scope: this.scope,
|
|
53
|
+
expires_at: this.expiresAt,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
static fromJSON(data) {
|
|
57
|
+
if (!data)
|
|
58
|
+
return null;
|
|
59
|
+
return new OAuth2Token({
|
|
60
|
+
schemaVersion: data.schema_version ?? exports.TOKEN_SCHEMA_VERSION,
|
|
61
|
+
issuer: data.issuer,
|
|
62
|
+
clientId: data.client_id,
|
|
63
|
+
accessToken: data.access_token,
|
|
64
|
+
refreshToken: data.refresh_token,
|
|
65
|
+
tokenType: data.token_type || 'Bearer',
|
|
66
|
+
scope: data.scope,
|
|
67
|
+
expiresAt: data.expires_at ?? 0,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/** Prints no token values. */
|
|
71
|
+
toString() {
|
|
72
|
+
return `OAuth2Token{clientId=${this.clientId}, issuer=${this.issuer}, scope=${this.scope}, `
|
|
73
|
+
+ `expiresAt=${this.expiresAt}, hasRefreshToken=${this.hasRefreshToken()}}`;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
exports.OAuth2Token = OAuth2Token;
|
|
@@ -203,7 +203,7 @@ exports.Request_Connect = {
|
|
|
203
203
|
},
|
|
204
204
|
};
|
|
205
205
|
function createBaseRequest_Subscribe() {
|
|
206
|
-
return { dataType: 0, symbols: undefined, account: undefined, market: undefined };
|
|
206
|
+
return { dataType: 0, symbols: undefined, account: undefined, market: undefined, assetQuoteType: undefined };
|
|
207
207
|
}
|
|
208
208
|
exports.Request_Subscribe = {
|
|
209
209
|
encode(message, writer = new wire_1.BinaryWriter()) {
|
|
@@ -219,6 +219,9 @@ exports.Request_Subscribe = {
|
|
|
219
219
|
if (message.market !== undefined) {
|
|
220
220
|
writer.uint32(34).string(message.market);
|
|
221
221
|
}
|
|
222
|
+
if (message.assetQuoteType !== undefined) {
|
|
223
|
+
writer.uint32(42).string(message.assetQuoteType);
|
|
224
|
+
}
|
|
222
225
|
return writer;
|
|
223
226
|
},
|
|
224
227
|
decode(input, length) {
|
|
@@ -256,6 +259,13 @@ exports.Request_Subscribe = {
|
|
|
256
259
|
message.market = reader.string();
|
|
257
260
|
continue;
|
|
258
261
|
}
|
|
262
|
+
case 5: {
|
|
263
|
+
if (tag !== 42) {
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
message.assetQuoteType = reader.string();
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
259
269
|
}
|
|
260
270
|
if ((tag & 7) === 4 || tag === 0) {
|
|
261
271
|
break;
|
|
@@ -273,6 +283,7 @@ exports.Request_Subscribe = {
|
|
|
273
283
|
message.symbols = object.symbols ?? undefined;
|
|
274
284
|
message.account = object.account ?? undefined;
|
|
275
285
|
message.market = object.market ?? undefined;
|
|
286
|
+
message.assetQuoteType = object.assetQuoteType ?? undefined;
|
|
276
287
|
return message;
|
|
277
288
|
},
|
|
278
289
|
};
|
|
@@ -53,8 +53,11 @@ function buildHeartBeatMessage() {
|
|
|
53
53
|
}
|
|
54
54
|
/**
|
|
55
55
|
* 构建 SUBSCRIBE 请求消息
|
|
56
|
+
*
|
|
57
|
+
* @param assetQuoteType 资产行情类型枚举名(ETH / RTH / OVERNIGHT),
|
|
58
|
+
* 仅对 Asset / Position 两种订阅有意义,其余场景传 undefined
|
|
56
59
|
*/
|
|
57
|
-
function buildSubscribeMessage(dataType, symbols, account, market) {
|
|
60
|
+
function buildSubscribeMessage(dataType, symbols, account, market, assetQuoteType) {
|
|
58
61
|
return Request_1.Request.fromPartial({
|
|
59
62
|
command: SocketCommon_1.SocketCommon_Command.SUBSCRIBE,
|
|
60
63
|
id: nextRequestId(),
|
|
@@ -63,6 +66,7 @@ function buildSubscribeMessage(dataType, symbols, account, market) {
|
|
|
63
66
|
symbols,
|
|
64
67
|
account,
|
|
65
68
|
market,
|
|
69
|
+
assetQuoteType,
|
|
66
70
|
}),
|
|
67
71
|
});
|
|
68
72
|
}
|
|
@@ -95,6 +95,8 @@ class PushClient {
|
|
|
95
95
|
this.subscriptions = new Map();
|
|
96
96
|
/** Account-level subscriptions: subject -> account string used at subscribe time */
|
|
97
97
|
this.accountSubs = new Map();
|
|
98
|
+
/** Account-level subscriptions: subject -> assetQuoteType used at subscribe time (reconnect replay) */
|
|
99
|
+
this.accountSubQuoteTypes = new Map();
|
|
98
100
|
this.heartbeatTimer = null;
|
|
99
101
|
this.reconnectTimer = null;
|
|
100
102
|
this.stopped = false;
|
|
@@ -355,7 +357,7 @@ class PushClient {
|
|
|
355
357
|
this.doSubscribe(subject, Array.from(symbols));
|
|
356
358
|
}
|
|
357
359
|
for (const [subject, account] of this.accountSubs) {
|
|
358
|
-
this.doSubscribe(subject, undefined, account);
|
|
360
|
+
this.doSubscribe(subject, undefined, account, undefined, this.accountSubQuoteTypes.get(subject));
|
|
359
361
|
}
|
|
360
362
|
}
|
|
361
363
|
/** Dispatch push data to the appropriate callback based on dataType */
|
|
@@ -425,10 +427,10 @@ class PushClient {
|
|
|
425
427
|
}
|
|
426
428
|
}
|
|
427
429
|
/** Internal subscribe method */
|
|
428
|
-
doSubscribe(subject, symbols, account, market) {
|
|
430
|
+
doSubscribe(subject, symbols, account, market, assetQuoteType) {
|
|
429
431
|
const dataType = (0, proto_message_1.subjectToDataType)(subject);
|
|
430
432
|
const symbolsStr = symbols ? symbols.join(',') : undefined;
|
|
431
|
-
const req = (0, proto_message_1.buildSubscribeMessage)(dataType, symbolsStr, account, market);
|
|
433
|
+
const req = (0, proto_message_1.buildSubscribeMessage)(dataType, symbolsStr, account, market, assetQuoteType);
|
|
432
434
|
this.sendMessage(req);
|
|
433
435
|
}
|
|
434
436
|
/** Internal unsubscribe method */
|
|
@@ -588,27 +590,41 @@ class PushClient {
|
|
|
588
590
|
this.removeSubscription(push_message_1.SubjectType.Market, [market]);
|
|
589
591
|
}
|
|
590
592
|
// ===== Account push subscribe/unsubscribe =====
|
|
591
|
-
/**
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
593
|
+
/**
|
|
594
|
+
* Shared implementation for the account/options overloads of
|
|
595
|
+
* subscribeAsset / subscribePosition.
|
|
596
|
+
*/
|
|
597
|
+
subscribeAccount(subject, accountOrOptions) {
|
|
598
|
+
const opts = typeof accountOrOptions === 'string'
|
|
599
|
+
? { account: accountOrOptions }
|
|
600
|
+
: (accountOrOptions ?? {});
|
|
601
|
+
const acct = opts.account || this.config.account;
|
|
602
|
+
this.doSubscribe(subject, undefined, acct, undefined, opts.assetQuoteType);
|
|
603
|
+
this.accountSubs.set(subject, acct);
|
|
604
|
+
if (opts.assetQuoteType) {
|
|
605
|
+
this.accountSubQuoteTypes.set(subject, opts.assetQuoteType);
|
|
606
|
+
}
|
|
607
|
+
else {
|
|
608
|
+
this.accountSubQuoteTypes.delete(subject);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
subscribeAsset(accountOrOptions) {
|
|
612
|
+
this.subscribeAccount(push_message_1.SubjectType.Asset, accountOrOptions);
|
|
596
613
|
}
|
|
597
614
|
/** Unsubscribe from asset changes */
|
|
598
615
|
unsubscribeAsset() {
|
|
599
616
|
this.doUnsubscribe(push_message_1.SubjectType.Asset);
|
|
600
617
|
this.accountSubs.delete(push_message_1.SubjectType.Asset);
|
|
618
|
+
this.accountSubQuoteTypes.delete(push_message_1.SubjectType.Asset);
|
|
601
619
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
const acct = account || this.config.account;
|
|
605
|
-
this.doSubscribe(push_message_1.SubjectType.Position, undefined, acct);
|
|
606
|
-
this.accountSubs.set(push_message_1.SubjectType.Position, acct);
|
|
620
|
+
subscribePosition(accountOrOptions) {
|
|
621
|
+
this.subscribeAccount(push_message_1.SubjectType.Position, accountOrOptions);
|
|
607
622
|
}
|
|
608
623
|
/** Unsubscribe from position changes */
|
|
609
624
|
unsubscribePosition() {
|
|
610
625
|
this.doUnsubscribe(push_message_1.SubjectType.Position);
|
|
611
626
|
this.accountSubs.delete(push_message_1.SubjectType.Position);
|
|
627
|
+
this.accountSubQuoteTypes.delete(push_message_1.SubjectType.Position);
|
|
612
628
|
}
|
|
613
629
|
/** Subscribe to order status */
|
|
614
630
|
subscribeOrder(account) {
|
|
@@ -257,11 +257,17 @@ class TradeClient {
|
|
|
257
257
|
async getPositions(req) {
|
|
258
258
|
return this.callIntoItems('positions', withAccount(req, this.account));
|
|
259
259
|
}
|
|
260
|
-
/**
|
|
260
|
+
/**
|
|
261
|
+
* Query assets. v0.4.0: supports sub-account list and aggregation options.
|
|
262
|
+
* `assetQuoteType`(ETH / RTH / OVERNIGHT) 选择资产行情口径。
|
|
263
|
+
*/
|
|
261
264
|
async getAssets(req) {
|
|
262
265
|
return this.callIntoItems('assets', withAccount(req, this.account));
|
|
263
266
|
}
|
|
264
|
-
/**
|
|
267
|
+
/**
|
|
268
|
+
* Query prime assets. v0.4.0: takes AssetsRequest for filters.
|
|
269
|
+
* `assetQuoteType`(ETH / RTH / OVERNIGHT) 选择资产行情口径。
|
|
270
|
+
*/
|
|
265
271
|
async getPrimeAssets(req) {
|
|
266
272
|
return this.callInto('prime_assets', withAccount(req, this.account));
|
|
267
273
|
}
|
|
@@ -282,7 +288,10 @@ class TradeClient {
|
|
|
282
288
|
async getAnalyticsAsset(req) {
|
|
283
289
|
return this.callIntoItems('analytics_asset', withAccount(req, this.account));
|
|
284
290
|
}
|
|
285
|
-
/**
|
|
291
|
+
/**
|
|
292
|
+
* Aggregate assets (prime) by base currency.
|
|
293
|
+
* `assetQuoteType`(ETH / RTH / OVERNIGHT) 选择资产行情口径。
|
|
294
|
+
*/
|
|
286
295
|
async getAggregateAssets(req) {
|
|
287
296
|
return this.callInto('aggregate_assets', withAccount(req, this.account));
|
|
288
297
|
}
|
package/dist/cjs/version.js
CHANGED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request authentication interfaces.
|
|
3
|
+
*
|
|
4
|
+
* Implementations attach credentials, handle authentication retries, and release
|
|
5
|
+
* resources. OAuth 2.0 authorization flows live in `OAuth2SessionManager`; this module
|
|
6
|
+
* is the data plane and knows nothing about login.
|
|
7
|
+
*/
|
|
8
|
+
/** Which kind of authentication. */
|
|
9
|
+
export var AuthenticationType;
|
|
10
|
+
(function (AuthenticationType) {
|
|
11
|
+
/** tigerId + RSA private key signature */
|
|
12
|
+
AuthenticationType["Signature"] = "SIGNATURE";
|
|
13
|
+
/** `Authorization: Bearer <access_token>` */
|
|
14
|
+
AuthenticationType["OAuth2"] = "OAUTH2";
|
|
15
|
+
})(AuthenticationType || (AuthenticationType = {}));
|
|
16
|
+
/**
|
|
17
|
+
* Per-request authentication context.
|
|
18
|
+
*
|
|
19
|
+
* Business parameters (`method`, `version`, `biz_content`, `timestamp`) are filled in by
|
|
20
|
+
* the client and must not be modified by an authentication implementation.
|
|
21
|
+
*/
|
|
22
|
+
export class RequestAuthContext {
|
|
23
|
+
constructor(params, retryable = true) {
|
|
24
|
+
this.params = params;
|
|
25
|
+
this.retryable = retryable;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Credential snapshot used to coordinate authentication retries.
|
|
30
|
+
*
|
|
31
|
+
* Comparing the failed credential with current state detects rotation completed
|
|
32
|
+
* elsewhere and avoids duplicate refresh requests.
|
|
33
|
+
*/
|
|
34
|
+
export class AuthenticationAttempt {
|
|
35
|
+
constructor(authType, credential) {
|
|
36
|
+
this.authType = authType;
|
|
37
|
+
this.credential = credential;
|
|
38
|
+
}
|
|
39
|
+
/** Never prints the credential value. */
|
|
40
|
+
toString() {
|
|
41
|
+
return `AuthenticationAttempt{type=${this.authType}, credential=${this.credential === undefined ? '<none>' : '***'}}`;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Authentication retry decision.
|
|
46
|
+
*
|
|
47
|
+
* A retry always supplies a new full `Authorization` header value, including the
|
|
48
|
+
* `Bearer ` prefix. There is deliberately no "retry with the same credential" option:
|
|
49
|
+
* that only collects the same 401 twice.
|
|
50
|
+
*/
|
|
51
|
+
export class RetryDecision {
|
|
52
|
+
constructor(shouldRetry, authorizationHeader) {
|
|
53
|
+
this.shouldRetry = shouldRetry;
|
|
54
|
+
this.authorizationHeader = authorizationHeader;
|
|
55
|
+
}
|
|
56
|
+
static noRetry() {
|
|
57
|
+
return RetryDecision.NO_RETRY;
|
|
58
|
+
}
|
|
59
|
+
/** @param authorizationHeader full new header value, including `Bearer ` */
|
|
60
|
+
static retry(authorizationHeader) {
|
|
61
|
+
return new RetryDecision(true, authorizationHeader);
|
|
62
|
+
}
|
|
63
|
+
/** Never prints the header value. */
|
|
64
|
+
toString() {
|
|
65
|
+
return `RetryDecision{shouldRetry=${this.shouldRetry}}`;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
RetryDecision.NO_RETRY = new RetryDecision(false, undefined);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.0 dynamic client registration state (RFC 7591).
|
|
3
|
+
*
|
|
4
|
+
* Registration state is stored separately from tokens and retained across logout.
|
|
5
|
+
* `registeredScopes` records the granted scope set; scope expansion requires a new
|
|
6
|
+
* registration to avoid `invalid_scope`.
|
|
7
|
+
*/
|
|
8
|
+
export class OAuth2ClientRegistration {
|
|
9
|
+
constructor(init = {}) {
|
|
10
|
+
this.issuer = init.issuer;
|
|
11
|
+
this.clientId = init.clientId;
|
|
12
|
+
this.clientName = init.clientName;
|
|
13
|
+
this.registeredScopes = init.registeredScopes;
|
|
14
|
+
this.clientIdIssuedAt = init.clientIdIssuedAt ?? 0;
|
|
15
|
+
}
|
|
16
|
+
toJSON() {
|
|
17
|
+
return {
|
|
18
|
+
issuer: this.issuer,
|
|
19
|
+
client_id: this.clientId,
|
|
20
|
+
client_name: this.clientName,
|
|
21
|
+
registered_scopes: this.registeredScopes,
|
|
22
|
+
client_id_issued_at: this.clientIdIssuedAt,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
static fromJSON(data) {
|
|
26
|
+
if (!data)
|
|
27
|
+
return null;
|
|
28
|
+
return new OAuth2ClientRegistration({
|
|
29
|
+
issuer: data.issuer,
|
|
30
|
+
clientId: data.client_id,
|
|
31
|
+
clientName: data.client_name,
|
|
32
|
+
registeredScopes: data.registered_scopes,
|
|
33
|
+
clientIdIssuedAt: data.client_id_issued_at ?? 0,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
toString() {
|
|
37
|
+
return `OAuth2ClientRegistration{issuer=${this.issuer}, clientId=${this.clientId}, `
|
|
38
|
+
+ `clientName=${this.clientName}, registeredScopes=${this.registeredScopes}}`;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Device authorization response (RFC 8628). */
|
|
2
|
+
/**
|
|
3
|
+
* Default polling interval per RFC 8628 §3.5. Tiger's authorization server does not
|
|
4
|
+
* return `interval`, so this default applies in practice.
|
|
5
|
+
*/
|
|
6
|
+
export const DEFAULT_DEVICE_INTERVAL_SECONDS = 5;
|
|
7
|
+
export class OAuth2DeviceAuthorization {
|
|
8
|
+
constructor(init = {}) {
|
|
9
|
+
this.deviceCode = init.deviceCode;
|
|
10
|
+
this.userCode = init.userCode;
|
|
11
|
+
this.verificationUri = init.verificationUri;
|
|
12
|
+
this.verificationUriComplete = init.verificationUriComplete;
|
|
13
|
+
this.expiresIn = init.expiresIn ?? 0;
|
|
14
|
+
this.interval = init.interval ?? 0;
|
|
15
|
+
}
|
|
16
|
+
effectiveIntervalSeconds() {
|
|
17
|
+
return this.interval > 0 ? this.interval : DEFAULT_DEVICE_INTERVAL_SECONDS;
|
|
18
|
+
}
|
|
19
|
+
static fromJSON(data) {
|
|
20
|
+
if (!data)
|
|
21
|
+
return null;
|
|
22
|
+
return new OAuth2DeviceAuthorization({
|
|
23
|
+
deviceCode: data.device_code,
|
|
24
|
+
userCode: data.user_code,
|
|
25
|
+
verificationUri: data.verification_uri,
|
|
26
|
+
verificationUriComplete: data.verification_uri_complete,
|
|
27
|
+
expiresIn: data.expires_in ?? 0,
|
|
28
|
+
interval: data.interval ?? 0,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/** Prints no deviceCode. */
|
|
32
|
+
toString() {
|
|
33
|
+
return `OAuth2DeviceAuthorization{userCode=${this.userCode}, verificationUri=${this.verificationUri}, `
|
|
34
|
+
+ `expiresIn=${this.expiresIn}, interval=${this.interval}}`;
|
|
35
|
+
}
|
|
36
|
+
}
|