@tigeropenapi/tigeropen 0.5.4 → 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.
Files changed (73) hide show
  1. package/README.md +80 -0
  2. package/dist/cjs/auth/authentication.js +74 -0
  3. package/dist/cjs/auth/index.js +24 -0
  4. package/dist/cjs/auth/oauth2/client-registration.js +44 -0
  5. package/dist/cjs/auth/oauth2/device-authorization.js +40 -0
  6. package/dist/cjs/auth/oauth2/errors.js +64 -0
  7. package/dist/cjs/auth/oauth2/http-utils.js +112 -0
  8. package/dist/cjs/auth/oauth2/index.js +42 -0
  9. package/dist/cjs/auth/oauth2/loopback-receiver.js +215 -0
  10. package/dist/cjs/auth/oauth2/metadata.js +34 -0
  11. package/dist/cjs/auth/oauth2/oauth2-authentication.js +74 -0
  12. package/dist/cjs/auth/oauth2/session-manager.js +762 -0
  13. package/dist/cjs/auth/oauth2/token-store.js +252 -0
  14. package/dist/cjs/auth/oauth2/token.js +76 -0
  15. package/dist/cjs/push/index.js +1 -0
  16. package/dist/cjs/push/pb/Request.js +12 -1
  17. package/dist/cjs/push/proto-message.js +5 -1
  18. package/dist/cjs/push/push-client.js +34 -16
  19. package/dist/cjs/push/tick-util.js +172 -0
  20. package/dist/cjs/quote/quote-client.js +59 -12
  21. package/dist/cjs/trade/trade-client.js +55 -6
  22. package/dist/cjs/version.js +1 -1
  23. package/dist/esm/auth/authentication.js +68 -0
  24. package/dist/esm/auth/index.js +3 -0
  25. package/dist/esm/auth/oauth2/client-registration.js +40 -0
  26. package/dist/esm/auth/oauth2/device-authorization.js +36 -0
  27. package/dist/esm/auth/oauth2/errors.js +59 -0
  28. package/dist/esm/auth/oauth2/http-utils.js +105 -0
  29. package/dist/esm/auth/oauth2/index.js +11 -0
  30. package/dist/esm/auth/oauth2/loopback-receiver.js +178 -0
  31. package/dist/esm/auth/oauth2/metadata.js +30 -0
  32. package/dist/esm/auth/oauth2/oauth2-authentication.js +70 -0
  33. package/dist/esm/auth/oauth2/session-manager.js +725 -0
  34. package/dist/esm/auth/oauth2/token-store.js +214 -0
  35. package/dist/esm/auth/oauth2/token.js +71 -0
  36. package/dist/esm/package.json +3 -0
  37. package/dist/esm/push/index.js +1 -0
  38. package/dist/esm/push/pb/Request.js +12 -1
  39. package/dist/esm/push/proto-message.js +5 -1
  40. package/dist/esm/push/push-client.js +34 -16
  41. package/dist/esm/push/tick-util.js +165 -0
  42. package/dist/esm/quote/quote-client.js +59 -12
  43. package/dist/esm/trade/trade-client.js +55 -6
  44. package/dist/esm/version.js +1 -1
  45. package/dist/types/auth/authentication.d.ts +98 -0
  46. package/dist/types/auth/index.d.ts +4 -0
  47. package/dist/types/auth/oauth2/client-registration.d.ts +32 -0
  48. package/dist/types/auth/oauth2/device-authorization.d.ts +40 -0
  49. package/dist/types/auth/oauth2/errors.d.ts +46 -0
  50. package/dist/types/auth/oauth2/http-utils.d.ts +21 -0
  51. package/dist/types/auth/oauth2/index.d.ts +16 -0
  52. package/dist/types/auth/oauth2/loopback-receiver.d.ts +29 -0
  53. package/dist/types/auth/oauth2/metadata.d.ts +32 -0
  54. package/dist/types/auth/oauth2/oauth2-authentication.d.ts +31 -0
  55. package/dist/types/auth/oauth2/session-manager.d.ts +241 -0
  56. package/dist/types/auth/oauth2/token-store.d.ts +49 -0
  57. package/dist/types/auth/oauth2/token.d.ts +56 -0
  58. package/dist/types/index.d.ts +2 -2
  59. package/dist/types/model/order.d.ts +62 -11
  60. package/dist/types/model/quote-requests.d.ts +21 -2
  61. package/dist/types/model/quote.d.ts +72 -14
  62. package/dist/types/model/trade-requests.d.ts +17 -4
  63. package/dist/types/model/trade.d.ts +11 -1
  64. package/dist/types/push/callbacks.d.ts +3 -7
  65. package/dist/types/push/index.d.ts +2 -1
  66. package/dist/types/push/pb/Request.d.ts +1 -0
  67. package/dist/types/push/proto-message.d.ts +4 -1
  68. package/dist/types/push/push-client.d.ts +33 -0
  69. package/dist/types/push/tick-util.d.ts +30 -0
  70. package/dist/types/quote/quote-client.d.ts +27 -6
  71. package/dist/types/trade/trade-client.d.ts +27 -3
  72. package/dist/types/version.d.ts +1 -1
  73. package/package.json +8 -4
@@ -0,0 +1,215 @@
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.LoopbackReceiver = exports.CALLBACK_PATH = void 0;
37
+ /**
38
+ * Single-use OAuth 2.0 loopback redirect receiver (RFC 8252).
39
+ *
40
+ * The receiver binds only to `127.0.0.1` to prevent authorization code exposure on the
41
+ * local network. Dynamic registration uses a system-assigned port as permitted by
42
+ * RFC 8252 §7.3; manual registration requires a port matching its redirect URI. The IP
43
+ * literal avoids the name-resolution risks described by RFC 8252 §8.3.
44
+ */
45
+ const http = __importStar(require("http"));
46
+ const errors_1 = require("./errors");
47
+ exports.CALLBACK_PATH = '/callback';
48
+ /** Inline SVG avoids external resources and font-dependent glyph rendering. */
49
+ function icon(color, path) {
50
+ return `<svg width='64' height='64' viewBox='0 0 64 64' aria-hidden='true'>`
51
+ + `<circle cx='32' cy='32' r='29' fill='none' stroke='${color}' stroke-width='4'/>`
52
+ + `<path d='${path}' fill='none' stroke='${color}' stroke-width='5'`
53
+ + ` stroke-linecap='round' stroke-linejoin='round'/></svg>`;
54
+ }
55
+ /** Green tick. */
56
+ const ICON_SUCCESS = icon('#22c55e', 'M19 33l9.5 9.5L45 22');
57
+ /** Grey dash for the normal `access_denied` outcome. */
58
+ const ICON_CANCELLED = icon('#9ca3af', 'M20 32h24');
59
+ /** Red cross, for outcomes that really are failures. */
60
+ const ICON_FAILED = icon('#ef4444', 'M22 22l20 20M42 22L22 42');
61
+ /** English callback text, kept consistent with the Java and Python SDKs. */
62
+ const TEXT = {
63
+ title: 'Tiger OpenAPI',
64
+ success: ['Authorization successful', 'You can close this page and return to the application.'],
65
+ cancelled: ['Authorization cancelled', 'You declined this authorization. You can close this page.'],
66
+ failed: ['Authorization failed', 'Please return to the application and try again.'],
67
+ };
68
+ /** Classify `access_denied` as cancellation and other errors as failures. */
69
+ function outcomeOf(params) {
70
+ if ('code' in params)
71
+ return 'success';
72
+ if (params['error'] === 'access_denied')
73
+ return 'cancelled';
74
+ return 'failed';
75
+ }
76
+ /**
77
+ * Render a redacted callback outcome page.
78
+ *
79
+ * Callback parameters, including authorization codes and errors, are omitted because
80
+ * browser history, screenshots, and extensions may expose page content. Full parameters
81
+ * remain available to internal callback processing.
82
+ */
83
+ function renderPage(params) {
84
+ const outcome = outcomeOf(params);
85
+ const svg = { success: ICON_SUCCESS, cancelled: ICON_CANCELLED, failed: ICON_FAILED }[outcome];
86
+ const [heading, detail] = TEXT[outcome];
87
+ return `<!DOCTYPE html><html><head><meta charset='utf-8'>`
88
+ + `<meta name='viewport' content='width=device-width,initial-scale=1'>`
89
+ + `<title>${TEXT['title']}</title></head>`
90
+ + `<body style="margin:0;min-height:100vh;display:flex;align-items:center;`
91
+ + `justify-content:center;`
92
+ + `font-family:-apple-system,BlinkMacSystemFont,'PingFang SC','Microsoft YaHei',`
93
+ + `sans-serif;background:#fafafa;color:#1f2328">`
94
+ + `<main style='text-align:center;padding:48px 24px;max-width:420px'>`
95
+ + `${svg}`
96
+ + `<h2 style='margin:24px 0 8px;font-size:20px;font-weight:600'>${heading}</h2>`
97
+ + `<p style='margin:0;font-size:14px;line-height:1.6;color:#6b7280'>${detail}</p>`
98
+ + `</main></body></html>`;
99
+ }
100
+ /** Single-shot local callback server. */
101
+ class LoopbackReceiver {
102
+ constructor(server) {
103
+ this.port = 0;
104
+ this.params = {};
105
+ this.fired = false;
106
+ this.waiters = [];
107
+ this.server = server;
108
+ }
109
+ /**
110
+ * Bind the receiver. It must be listening before the authorization request is issued.
111
+ *
112
+ * @param port port to bind; 0 lets the system assign a free one
113
+ */
114
+ static async start(port = 0) {
115
+ const server = http.createServer();
116
+ const receiver = new LoopbackReceiver(server);
117
+ server.on('request', (req, res) => {
118
+ const url = new URL(req.url ?? '/', 'http://127.0.0.1');
119
+ if (url.pathname !== exports.CALLBACK_PATH) {
120
+ res.writeHead(404, { 'Content-Length': '0' });
121
+ res.end();
122
+ return;
123
+ }
124
+ const snapshot = receiver.record(url.searchParams);
125
+ const body = Buffer.from(renderPage(snapshot), 'utf-8');
126
+ res.writeHead(200, {
127
+ 'Content-Type': 'text/html; charset=utf-8',
128
+ 'Content-Length': String(body.length),
129
+ });
130
+ res.end(body);
131
+ receiver.release(snapshot);
132
+ });
133
+ await new Promise((resolve, reject) => {
134
+ const onError = (e) => {
135
+ // A fixed port can conflict with another listener; system-assigned ports do not.
136
+ const hint = port === 0 ? '' : ` (port ${port} is in use; free it or set another callbackPort`
137
+ + ' matching the redirect_uri registered for your client)';
138
+ reject(new errors_1.OAuth2Error(errors_1.OAuth2Category.CallbackFailed, `cannot bind loopback callback server on 127.0.0.1:${port}${hint}`, e));
139
+ };
140
+ server.once('error', onError);
141
+ server.listen(port, '127.0.0.1', () => {
142
+ server.removeListener('error', onError);
143
+ const address = server.address();
144
+ receiver.port = typeof address === 'object' && address ? address.port : port;
145
+ resolve();
146
+ });
147
+ });
148
+ return receiver;
149
+ }
150
+ get redirectUri() {
151
+ return `http://127.0.0.1:${this.port}${exports.CALLBACK_PATH}`;
152
+ }
153
+ get boundPort() {
154
+ return this.port;
155
+ }
156
+ /**
157
+ * Wait for the browser callback.
158
+ *
159
+ * @returns the query parameters carried by the callback
160
+ * @throws OAuth2Error on timeout
161
+ */
162
+ waitForCallback(timeoutSeconds) {
163
+ if (this.fired) {
164
+ return Promise.resolve({ ...this.params });
165
+ }
166
+ return new Promise((resolve, reject) => {
167
+ const timer = setTimeout(() => {
168
+ this.waiters = this.waiters.filter((w) => w !== onParams);
169
+ reject(new errors_1.OAuth2Error(errors_1.OAuth2Category.CallbackFailed, 'waiting for authorization callback timed out'));
170
+ }, timeoutSeconds * 1000);
171
+ const onParams = (params) => {
172
+ clearTimeout(timer);
173
+ resolve(params);
174
+ };
175
+ this.waiters.push(onParams);
176
+ });
177
+ }
178
+ close() {
179
+ try {
180
+ // The browser keeps its connection alive after the redirect, and close() alone
181
+ // waits for it. That would hold the port -- which matters for the fixed port a
182
+ // manually registered client has to reuse on the next authorization.
183
+ this.server.closeAllConnections?.();
184
+ }
185
+ catch {
186
+ // Older runtimes have no such method; the idle timeout releases the port instead.
187
+ }
188
+ try {
189
+ this.server.close();
190
+ }
191
+ catch {
192
+ // Already closed; nothing to release.
193
+ }
194
+ }
195
+ /** Record the callback params (first one wins) and return a snapshot of them. */
196
+ record(search) {
197
+ if (!this.fired) {
198
+ for (const [key, value] of search.entries()) {
199
+ this.params[key] = value;
200
+ }
201
+ }
202
+ return { ...this.params };
203
+ }
204
+ release(snapshot) {
205
+ if (this.fired)
206
+ return;
207
+ this.fired = true;
208
+ const waiters = this.waiters;
209
+ this.waiters = [];
210
+ for (const waiter of waiters) {
211
+ waiter(snapshot);
212
+ }
213
+ }
214
+ }
215
+ exports.LoopbackReceiver = LoopbackReceiver;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ /**
3
+ * Authorization server metadata (RFC 8414).
4
+ *
5
+ * Endpoints always come from metadata, never hardcoded, so switching environments only
6
+ * means changing the issuer.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.OAuth2Metadata = void 0;
10
+ class OAuth2Metadata {
11
+ constructor(init = {}) {
12
+ this.issuer = init.issuer;
13
+ this.authorizationEndpoint = init.authorizationEndpoint;
14
+ this.tokenEndpoint = init.tokenEndpoint;
15
+ this.registrationEndpoint = init.registrationEndpoint;
16
+ this.deviceAuthorizationEndpoint = init.deviceAuthorizationEndpoint;
17
+ this.revocationEndpoint = init.revocationEndpoint;
18
+ this.scopesSupported = init.scopesSupported ?? [];
19
+ }
20
+ static fromJSON(data) {
21
+ if (!data)
22
+ return null;
23
+ return new OAuth2Metadata({
24
+ issuer: data.issuer,
25
+ authorizationEndpoint: data.authorization_endpoint,
26
+ tokenEndpoint: data.token_endpoint,
27
+ registrationEndpoint: data.registration_endpoint,
28
+ deviceAuthorizationEndpoint: data.device_authorization_endpoint,
29
+ revocationEndpoint: data.revocation_endpoint,
30
+ scopesSupported: data.scopes_supported ?? [],
31
+ });
32
+ }
33
+ }
34
+ exports.OAuth2Metadata = OAuth2Metadata;
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OAuth2Authentication = exports.BEARER_PREFIX = void 0;
4
+ /** Request authentication backed by an OAuth 2.0 session manager. */
5
+ const authentication_1 = require("../authentication");
6
+ const errors_1 = require("./errors");
7
+ exports.BEARER_PREFIX = 'Bearer ';
8
+ const UNAUTHORIZED_STATUS = 401;
9
+ /** Authenticate with an OAuth2 access token. */
10
+ class OAuth2Authentication {
11
+ constructor(sessionManager) {
12
+ if (!sessionManager) {
13
+ throw new Error('OAuth2SessionManager is required');
14
+ }
15
+ this.sessions = sessionManager;
16
+ }
17
+ get authType() {
18
+ return authentication_1.AuthenticationType.OAuth2;
19
+ }
20
+ get sessionManager() {
21
+ return this.sessions;
22
+ }
23
+ /**
24
+ * Attach a Bearer token to the request context.
25
+ *
26
+ * Near-expiry refreshes use the session manager's single-flight synchronization.
27
+ * Missing authorization rejects with `ReauthorizationRequired` without starting an
28
+ * implicit browser flow.
29
+ */
30
+ async apply(context) {
31
+ const accessToken = await this.sessions.getAccessToken();
32
+ context.authorizationHeader = exports.BEARER_PREFIX + accessToken;
33
+ return new authentication_1.AuthenticationAttempt(authentication_1.AuthenticationType.OAuth2, accessToken);
34
+ }
35
+ /**
36
+ * Process HTTP 401 responses.
37
+ *
38
+ * HTTP 403 indicates insufficient permission and cannot be resolved by refreshing a
39
+ * token with the same scope. Server errors and timeouts are not credential failures.
40
+ */
41
+ async onUnauthorized(attempt, statusCode) {
42
+ if (statusCode !== UNAUTHORIZED_STATUS) {
43
+ return authentication_1.RetryDecision.noRetry();
44
+ }
45
+ const credential = attempt?.credential;
46
+ try {
47
+ // Reuse a token already rotated elsewhere without another token request.
48
+ const refreshed = await this.sessions.refreshAfterUnauthorized(credential);
49
+ if (!refreshed || !refreshed.accessToken) {
50
+ return authentication_1.RetryDecision.noRetry();
51
+ }
52
+ if (credential !== undefined && refreshed.accessToken === credential) {
53
+ // Credential unchanged, so a retry would get the same 401.
54
+ return authentication_1.RetryDecision.noRetry();
55
+ }
56
+ return authentication_1.RetryDecision.retry(exports.BEARER_PREFIX + refreshed.accessToken);
57
+ }
58
+ catch (e) {
59
+ if (!(e instanceof errors_1.OAuth2Error)) {
60
+ // Not an OAuth2Error, so most likely a defect in the refresh path.
61
+ console.warn('[TigerOpen] oauth2 refresh after 401 error', e);
62
+ }
63
+ return authentication_1.RetryDecision.noRetry();
64
+ }
65
+ }
66
+ /**
67
+ * The session is owned by the application and may still be in use, so it is not
68
+ * closed here.
69
+ */
70
+ close() {
71
+ // Nothing to release.
72
+ }
73
+ }
74
+ exports.OAuth2Authentication = OAuth2Authentication;