@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.
Files changed (61) hide show
  1. package/README.md +27 -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/pb/Request.js +12 -1
  16. package/dist/cjs/push/proto-message.js +5 -1
  17. package/dist/cjs/push/push-client.js +29 -13
  18. package/dist/cjs/trade/trade-client.js +12 -3
  19. package/dist/cjs/version.js +1 -1
  20. package/dist/esm/auth/authentication.js +68 -0
  21. package/dist/esm/auth/index.js +3 -0
  22. package/dist/esm/auth/oauth2/client-registration.js +40 -0
  23. package/dist/esm/auth/oauth2/device-authorization.js +36 -0
  24. package/dist/esm/auth/oauth2/errors.js +59 -0
  25. package/dist/esm/auth/oauth2/http-utils.js +105 -0
  26. package/dist/esm/auth/oauth2/index.js +11 -0
  27. package/dist/esm/auth/oauth2/loopback-receiver.js +178 -0
  28. package/dist/esm/auth/oauth2/metadata.js +30 -0
  29. package/dist/esm/auth/oauth2/oauth2-authentication.js +70 -0
  30. package/dist/esm/auth/oauth2/session-manager.js +725 -0
  31. package/dist/esm/auth/oauth2/token-store.js +214 -0
  32. package/dist/esm/auth/oauth2/token.js +71 -0
  33. package/dist/esm/package.json +3 -0
  34. package/dist/esm/push/pb/Request.js +12 -1
  35. package/dist/esm/push/proto-message.js +5 -1
  36. package/dist/esm/push/push-client.js +29 -13
  37. package/dist/esm/trade/trade-client.js +12 -3
  38. package/dist/esm/version.js +1 -1
  39. package/dist/types/auth/authentication.d.ts +98 -0
  40. package/dist/types/auth/index.d.ts +4 -0
  41. package/dist/types/auth/oauth2/client-registration.d.ts +32 -0
  42. package/dist/types/auth/oauth2/device-authorization.d.ts +40 -0
  43. package/dist/types/auth/oauth2/errors.d.ts +46 -0
  44. package/dist/types/auth/oauth2/http-utils.d.ts +21 -0
  45. package/dist/types/auth/oauth2/index.d.ts +16 -0
  46. package/dist/types/auth/oauth2/loopback-receiver.d.ts +29 -0
  47. package/dist/types/auth/oauth2/metadata.d.ts +32 -0
  48. package/dist/types/auth/oauth2/oauth2-authentication.d.ts +31 -0
  49. package/dist/types/auth/oauth2/session-manager.d.ts +241 -0
  50. package/dist/types/auth/oauth2/token-store.d.ts +49 -0
  51. package/dist/types/auth/oauth2/token.d.ts +56 -0
  52. package/dist/types/index.d.ts +1 -1
  53. package/dist/types/model/quote.d.ts +1 -0
  54. package/dist/types/model/trade-requests.d.ts +5 -0
  55. package/dist/types/push/index.d.ts +1 -1
  56. package/dist/types/push/pb/Request.d.ts +1 -0
  57. package/dist/types/push/proto-message.d.ts +4 -1
  58. package/dist/types/push/push-client.d.ts +33 -0
  59. package/dist/types/trade/trade-client.d.ts +12 -3
  60. package/dist/types/version.d.ts +1 -1
  61. package/package.json +6 -4
package/README.md CHANGED
@@ -276,10 +276,24 @@ pc.disconnect();
276
276
  | `subscribeFuture(symbols)` | Futures quotes | `onFuture(QuoteData)` |
277
277
  | `subscribeKline(symbols)` | K-line data | `onKline(KlineData)` |
278
278
  | `subscribeAsset(account?)` | Asset changes | `onAsset(AssetData)` |
279
+ | `subscribeAsset(options)` | Asset changes, `{ account?, assetQuoteType? }` | `onAsset(AssetData)` |
279
280
  | `subscribePosition(account?)` | Position changes | `onPosition(PositionData)` |
281
+ | `subscribePosition(options)` | Position changes, `{ account?, assetQuoteType? }` | `onPosition(PositionData)` |
280
282
  | `subscribeOrder(account?)` | Order status | `onOrder(OrderStatusData)` |
281
283
  | `subscribeTransaction(account?)` | Transactions | `onTransaction(OrderTransactionData)` |
282
284
 
285
+ `assetQuoteType` selects the quote basis of the pushed asset / position data —
286
+ `'ETH'`, `'RTH'` or `'OVERNIGHT'`. Omit it to keep the server default.
287
+
288
+ ```typescript
289
+ pc.subscribeAsset({ assetQuoteType: 'OVERNIGHT' });
290
+ pc.subscribePosition({ account: 'U1234567', assetQuoteType: 'RTH' });
291
+ ```
292
+
293
+ The same enum name is accepted as `assetQuoteType` on the HTTP side by
294
+ `getPositions` / `getAssets` / `getPrimeAssets` / `getAggregateAssets`
295
+ (wire name `asset_quote_type`).
296
+
283
297
  ## Project Structure
284
298
 
285
299
  ```
@@ -652,10 +666,23 @@ pc.disconnect();
652
666
  | `subscribeFuture(symbols)` | 期货行情 | `onFuture(QuoteData)` |
653
667
  | `subscribeKline(symbols)` | K 线数据 | `onKline(KlineData)` |
654
668
  | `subscribeAsset(account?)` | 资产变动 | `onAsset(AssetData)` |
669
+ | `subscribeAsset(options)` | 资产变动,`{ account?, assetQuoteType? }` | `onAsset(AssetData)` |
655
670
  | `subscribePosition(account?)` | 持仓变动 | `onPosition(PositionData)` |
671
+ | `subscribePosition(options)` | 持仓变动,`{ account?, assetQuoteType? }` | `onPosition(PositionData)` |
656
672
  | `subscribeOrder(account?)` | 订单状态 | `onOrder(OrderStatusData)` |
657
673
  | `subscribeTransaction(account?)` | 成交明细 | `onTransaction(OrderTransactionData)` |
658
674
 
675
+ `assetQuoteType` 用于选择推送的资产/持仓行情口径,取值为枚举名
676
+ `'ETH'` / `'RTH'` / `'OVERNIGHT'`,不传则用服务端默认值。
677
+
678
+ ```typescript
679
+ pc.subscribeAsset({ assetQuoteType: 'OVERNIGHT' });
680
+ pc.subscribePosition({ account: 'U1234567', assetQuoteType: 'RTH' });
681
+ ```
682
+
683
+ 拉取侧 `getPositions` / `getAssets` / `getPrimeAssets` / `getAggregateAssets`
684
+ 同样接受 `assetQuoteType` 字段(wire 名 `asset_quote_type`)。
685
+
659
686
  ## 项目结构
660
687
 
661
688
  ```
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ /**
3
+ * Request authentication interfaces.
4
+ *
5
+ * Implementations attach credentials, handle authentication retries, and release
6
+ * resources. OAuth 2.0 authorization flows live in `OAuth2SessionManager`; this module
7
+ * is the data plane and knows nothing about login.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.RetryDecision = exports.AuthenticationAttempt = exports.RequestAuthContext = exports.AuthenticationType = void 0;
11
+ /** Which kind of authentication. */
12
+ var AuthenticationType;
13
+ (function (AuthenticationType) {
14
+ /** tigerId + RSA private key signature */
15
+ AuthenticationType["Signature"] = "SIGNATURE";
16
+ /** `Authorization: Bearer <access_token>` */
17
+ AuthenticationType["OAuth2"] = "OAUTH2";
18
+ })(AuthenticationType || (exports.AuthenticationType = AuthenticationType = {}));
19
+ /**
20
+ * Per-request authentication context.
21
+ *
22
+ * Business parameters (`method`, `version`, `biz_content`, `timestamp`) are filled in by
23
+ * the client and must not be modified by an authentication implementation.
24
+ */
25
+ class RequestAuthContext {
26
+ constructor(params, retryable = true) {
27
+ this.params = params;
28
+ this.retryable = retryable;
29
+ }
30
+ }
31
+ exports.RequestAuthContext = RequestAuthContext;
32
+ /**
33
+ * Credential snapshot used to coordinate authentication retries.
34
+ *
35
+ * Comparing the failed credential with current state detects rotation completed
36
+ * elsewhere and avoids duplicate refresh requests.
37
+ */
38
+ class AuthenticationAttempt {
39
+ constructor(authType, credential) {
40
+ this.authType = authType;
41
+ this.credential = credential;
42
+ }
43
+ /** Never prints the credential value. */
44
+ toString() {
45
+ return `AuthenticationAttempt{type=${this.authType}, credential=${this.credential === undefined ? '<none>' : '***'}}`;
46
+ }
47
+ }
48
+ exports.AuthenticationAttempt = AuthenticationAttempt;
49
+ /**
50
+ * Authentication retry decision.
51
+ *
52
+ * A retry always supplies a new full `Authorization` header value, including the
53
+ * `Bearer ` prefix. There is deliberately no "retry with the same credential" option:
54
+ * that only collects the same 401 twice.
55
+ */
56
+ class RetryDecision {
57
+ constructor(shouldRetry, authorizationHeader) {
58
+ this.shouldRetry = shouldRetry;
59
+ this.authorizationHeader = authorizationHeader;
60
+ }
61
+ static noRetry() {
62
+ return RetryDecision.NO_RETRY;
63
+ }
64
+ /** @param authorizationHeader full new header value, including `Bearer ` */
65
+ static retry(authorizationHeader) {
66
+ return new RetryDecision(true, authorizationHeader);
67
+ }
68
+ /** Never prints the header value. */
69
+ toString() {
70
+ return `RetryDecision{shouldRetry=${this.shouldRetry}}`;
71
+ }
72
+ }
73
+ exports.RetryDecision = RetryDecision;
74
+ RetryDecision.NO_RETRY = new RetryDecision(false, undefined);
@@ -0,0 +1,24 @@
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.RetryDecision = exports.RequestAuthContext = exports.AuthenticationType = exports.AuthenticationAttempt = void 0;
18
+ /** Authentication layer: the request-level data plane plus the OAuth 2.0 control plane. */
19
+ var authentication_1 = require("./authentication");
20
+ Object.defineProperty(exports, "AuthenticationAttempt", { enumerable: true, get: function () { return authentication_1.AuthenticationAttempt; } });
21
+ Object.defineProperty(exports, "AuthenticationType", { enumerable: true, get: function () { return authentication_1.AuthenticationType; } });
22
+ Object.defineProperty(exports, "RequestAuthContext", { enumerable: true, get: function () { return authentication_1.RequestAuthContext; } });
23
+ Object.defineProperty(exports, "RetryDecision", { enumerable: true, get: function () { return authentication_1.RetryDecision; } });
24
+ __exportStar(require("./oauth2"), exports);
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ /**
3
+ * OAuth 2.0 dynamic client registration state (RFC 7591).
4
+ *
5
+ * Registration state is stored separately from tokens and retained across logout.
6
+ * `registeredScopes` records the granted scope set; scope expansion requires a new
7
+ * registration to avoid `invalid_scope`.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.OAuth2ClientRegistration = void 0;
11
+ class OAuth2ClientRegistration {
12
+ constructor(init = {}) {
13
+ this.issuer = init.issuer;
14
+ this.clientId = init.clientId;
15
+ this.clientName = init.clientName;
16
+ this.registeredScopes = init.registeredScopes;
17
+ this.clientIdIssuedAt = init.clientIdIssuedAt ?? 0;
18
+ }
19
+ toJSON() {
20
+ return {
21
+ issuer: this.issuer,
22
+ client_id: this.clientId,
23
+ client_name: this.clientName,
24
+ registered_scopes: this.registeredScopes,
25
+ client_id_issued_at: this.clientIdIssuedAt,
26
+ };
27
+ }
28
+ static fromJSON(data) {
29
+ if (!data)
30
+ return null;
31
+ return new OAuth2ClientRegistration({
32
+ issuer: data.issuer,
33
+ clientId: data.client_id,
34
+ clientName: data.client_name,
35
+ registeredScopes: data.registered_scopes,
36
+ clientIdIssuedAt: data.client_id_issued_at ?? 0,
37
+ });
38
+ }
39
+ toString() {
40
+ return `OAuth2ClientRegistration{issuer=${this.issuer}, clientId=${this.clientId}, `
41
+ + `clientName=${this.clientName}, registeredScopes=${this.registeredScopes}}`;
42
+ }
43
+ }
44
+ exports.OAuth2ClientRegistration = OAuth2ClientRegistration;
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ /** Device authorization response (RFC 8628). */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.OAuth2DeviceAuthorization = exports.DEFAULT_DEVICE_INTERVAL_SECONDS = void 0;
5
+ /**
6
+ * Default polling interval per RFC 8628 §3.5. Tiger's authorization server does not
7
+ * return `interval`, so this default applies in practice.
8
+ */
9
+ exports.DEFAULT_DEVICE_INTERVAL_SECONDS = 5;
10
+ class OAuth2DeviceAuthorization {
11
+ constructor(init = {}) {
12
+ this.deviceCode = init.deviceCode;
13
+ this.userCode = init.userCode;
14
+ this.verificationUri = init.verificationUri;
15
+ this.verificationUriComplete = init.verificationUriComplete;
16
+ this.expiresIn = init.expiresIn ?? 0;
17
+ this.interval = init.interval ?? 0;
18
+ }
19
+ effectiveIntervalSeconds() {
20
+ return this.interval > 0 ? this.interval : exports.DEFAULT_DEVICE_INTERVAL_SECONDS;
21
+ }
22
+ static fromJSON(data) {
23
+ if (!data)
24
+ return null;
25
+ return new OAuth2DeviceAuthorization({
26
+ deviceCode: data.device_code,
27
+ userCode: data.user_code,
28
+ verificationUri: data.verification_uri,
29
+ verificationUriComplete: data.verification_uri_complete,
30
+ expiresIn: data.expires_in ?? 0,
31
+ interval: data.interval ?? 0,
32
+ });
33
+ }
34
+ /** Prints no deviceCode. */
35
+ toString() {
36
+ return `OAuth2DeviceAuthorization{userCode=${this.userCode}, verificationUri=${this.verificationUri}, `
37
+ + `expiresIn=${this.expiresIn}, interval=${this.interval}}`;
38
+ }
39
+ }
40
+ exports.OAuth2DeviceAuthorization = OAuth2DeviceAuthorization;
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ /**
3
+ * Categorized errors from OAuth 2.0 authorization flows.
4
+ *
5
+ * Authorization server error details are retained in the error message.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.RejectedGrantError = exports.OAuth2Error = exports.OAuth2Category = void 0;
9
+ /** Error category. Each one implies a different action. */
10
+ var OAuth2Category;
11
+ (function (OAuth2Category) {
12
+ /** Cannot fetch authorization server metadata: wrong address, service down, no network */
13
+ OAuth2Category["MetadataUnavailable"] = "METADATA_UNAVAILABLE";
14
+ /** No usable client_id, and automatic registration is not possible */
15
+ OAuth2Category["ClientRegistrationRequired"] = "CLIENT_REGISTRATION_REQUIRED";
16
+ /** Callback stage failed: timeout, user denial, or state mismatch */
17
+ OAuth2Category["CallbackFailed"] = "CALLBACK_FAILED";
18
+ /**
19
+ * Device flow not approved in time, or explicitly denied. Kept separate from
20
+ * ReauthorizationRequired because only a new device authorization is needed -- the
21
+ * stored token was not touched.
22
+ */
23
+ OAuth2Category["DeviceAuthorizationFailed"] = "DEVICE_AUTHORIZATION_FAILED";
24
+ /** refresh_token expired, revoked, or never obtained. Retrying is pointless */
25
+ OAuth2Category["ReauthorizationRequired"] = "REAUTHORIZATION_REQUIRED";
26
+ /** Token endpoint call failed, and not in the terminal way above */
27
+ OAuth2Category["TokenRequestFailed"] = "TOKEN_REQUEST_FAILED";
28
+ /** Reading or writing the local token file failed */
29
+ OAuth2Category["StorageFailed"] = "STORAGE_FAILED";
30
+ })(OAuth2Category || (exports.OAuth2Category = OAuth2Category = {}));
31
+ /** Thrown by any stage of the OAuth2 flow. */
32
+ class OAuth2Error extends Error {
33
+ constructor(category, message, cause) {
34
+ super(message);
35
+ this.name = 'OAuth2Error';
36
+ this.category = category;
37
+ this.cause = cause;
38
+ }
39
+ get reauthorizationRequired() {
40
+ return this.category === OAuth2Category.ReauthorizationRequired;
41
+ }
42
+ toString() {
43
+ return `OAuth2Error{${this.category}: ${this.message}}`;
44
+ }
45
+ }
46
+ exports.OAuth2Error = OAuth2Error;
47
+ /**
48
+ * A grant the authorization server refused.
49
+ *
50
+ * Carries the error code so a caller can tell a rejected credential from a rejected
51
+ * client registration. Its category is `ReauthorizationRequired`, so callers that only
52
+ * know the public type behave as they would for any terminal authorization failure.
53
+ */
54
+ class RejectedGrantError extends OAuth2Error {
55
+ constructor(error, description) {
56
+ super(OAuth2Category.ReauthorizationRequired, `token request rejected: ${error} ${description}`);
57
+ this.name = 'RejectedGrantError';
58
+ this.error = error;
59
+ }
60
+ get isClientRejected() {
61
+ return this.error === 'invalid_client';
62
+ }
63
+ }
64
+ exports.RejectedGrantError = RejectedGrantError;
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.READ_TIMEOUT_SECONDS = exports.CONNECT_TIMEOUT_SECONDS = void 0;
4
+ exports.getJson = getJson;
5
+ exports.postJson = postJson;
6
+ exports.postForm = postForm;
7
+ exports.postFormForStatus = postFormForStatus;
8
+ /**
9
+ * HTTP helpers for OAuth 2.0 JSON and form-encoded requests.
10
+ *
11
+ * OAuth error responses retain 4xx response bodies for protocol-level error handling.
12
+ */
13
+ const errors_1 = require("./errors");
14
+ /**
15
+ * Every call here is a small JSON exchange with the authorization server, so a request
16
+ * that has not answered in this long is not going to. Same values as the Java and
17
+ * Python SDKs.
18
+ */
19
+ exports.CONNECT_TIMEOUT_SECONDS = 5;
20
+ exports.READ_TIMEOUT_SECONDS = 10;
21
+ /** Combined budget handed to `AbortSignal.timeout`; fetch does not split the two. */
22
+ const REQUEST_TIMEOUT_MS = (exports.CONNECT_TIMEOUT_SECONDS + exports.READ_TIMEOUT_SECONDS) * 1000;
23
+ const JSON_CONTENT_TYPE = 'application/json; charset=utf-8';
24
+ const FORM_CONTENT_TYPE = 'application/x-www-form-urlencoded; charset=utf-8';
25
+ function encodeForm(form) {
26
+ const params = new URLSearchParams();
27
+ for (const [key, value] of Object.entries(form)) {
28
+ if (value !== undefined && value !== null) {
29
+ params.append(key, value);
30
+ }
31
+ }
32
+ return params.toString();
33
+ }
34
+ async function getJson(url) {
35
+ return execute('GET', url, undefined, undefined);
36
+ }
37
+ async function postJson(url, payload) {
38
+ return execute('POST', url, JSON.stringify(payload), JSON_CONTENT_TYPE);
39
+ }
40
+ async function postForm(url, form) {
41
+ return execute('POST', url, encodeForm(form), FORM_CONTENT_TYPE);
42
+ }
43
+ /**
44
+ * Post a form and return the HTTP status without requiring a response body.
45
+ *
46
+ * RFC 7009 revocation returns an empty success body, so no parsing is performed.
47
+ *
48
+ * @returns the HTTP status code
49
+ * @throws OAuth2Error on connection failure
50
+ */
51
+ async function postFormForStatus(url, form) {
52
+ let response;
53
+ try {
54
+ response = await fetch(url, {
55
+ method: 'POST',
56
+ headers: { Accept: 'application/json', 'Content-Type': FORM_CONTENT_TYPE },
57
+ body: encodeForm(form),
58
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
59
+ });
60
+ }
61
+ catch (e) {
62
+ throw new errors_1.OAuth2Error(errors_1.OAuth2Category.TokenRequestFailed, `request fail: ${url}`, e);
63
+ }
64
+ return response.status;
65
+ }
66
+ /**
67
+ * Send the request and parse the response body as an object.
68
+ *
69
+ * A 4xx returns its body instead of throwing: OAuth2 errors (`invalid_grant`,
70
+ * `authorization_pending` and friends) live in the 4xx body, and the caller needs the
71
+ * `error` field to tell "wait and retry" from "re-authorize". Only a connection failure
72
+ * or a non-JSON response throws.
73
+ */
74
+ async function execute(method, url, body, contentType) {
75
+ const headers = { Accept: 'application/json' };
76
+ if (contentType) {
77
+ headers['Content-Type'] = contentType;
78
+ }
79
+ let response;
80
+ try {
81
+ response = await fetch(url, {
82
+ method,
83
+ headers,
84
+ body,
85
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
86
+ });
87
+ }
88
+ catch (e) {
89
+ throw new errors_1.OAuth2Error(errors_1.OAuth2Category.MetadataUnavailable, `request fail: ${url}`, e);
90
+ }
91
+ let text;
92
+ try {
93
+ text = await response.text();
94
+ }
95
+ catch (e) {
96
+ throw new errors_1.OAuth2Error(errors_1.OAuth2Category.TokenRequestFailed, `cannot decode response from ${url}, http status ${response.status}`, e);
97
+ }
98
+ if (!text) {
99
+ throw new errors_1.OAuth2Error(errors_1.OAuth2Category.TokenRequestFailed, `empty response from ${url}, http status ${response.status}`);
100
+ }
101
+ let parsed;
102
+ try {
103
+ parsed = JSON.parse(text);
104
+ }
105
+ catch (e) {
106
+ throw new errors_1.OAuth2Error(errors_1.OAuth2Category.TokenRequestFailed, `non-JSON response from ${url}, http status ${response.status}`, e);
107
+ }
108
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
109
+ throw new errors_1.OAuth2Error(errors_1.OAuth2Category.TokenRequestFailed, `unexpected response from ${url}, http status ${response.status}`);
110
+ }
111
+ return parsed;
112
+ }
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.READ_TIMEOUT_SECONDS = exports.CONNECT_TIMEOUT_SECONDS = exports.hashIssuer = exports.ENV_HOME = exports.DEFAULT_SURFACE = exports.OAuth2TokenStore = exports.currentMillis = exports.TOKEN_SCHEMA_VERSION = exports.OAuth2Token = exports.MIN_USABLE_REMAINING_MILLIS = exports.ENV_ISSUER = exports.DEFAULT_REFRESH_AHEAD_MILLIS = exports.DEFAULT_MANUAL_CALLBACK_PORT = exports.DEFAULT_AUTHORIZE_TIMEOUT_SECONDS = exports.CLIENT_NAME = exports.BUILTIN_ISSUER = exports.OAuth2SessionManager = exports.BEARER_PREFIX = exports.OAuth2Authentication = exports.OAuth2Metadata = exports.CALLBACK_PATH = exports.LoopbackReceiver = exports.RejectedGrantError = exports.OAuth2Error = exports.OAuth2Category = exports.DEFAULT_DEVICE_INTERVAL_SECONDS = exports.OAuth2DeviceAuthorization = exports.OAuth2ClientRegistration = void 0;
4
+ /** OAuth 2.0 authentication, authorization flows, and token persistence. */
5
+ var client_registration_1 = require("./client-registration");
6
+ Object.defineProperty(exports, "OAuth2ClientRegistration", { enumerable: true, get: function () { return client_registration_1.OAuth2ClientRegistration; } });
7
+ var device_authorization_1 = require("./device-authorization");
8
+ Object.defineProperty(exports, "OAuth2DeviceAuthorization", { enumerable: true, get: function () { return device_authorization_1.OAuth2DeviceAuthorization; } });
9
+ Object.defineProperty(exports, "DEFAULT_DEVICE_INTERVAL_SECONDS", { enumerable: true, get: function () { return device_authorization_1.DEFAULT_DEVICE_INTERVAL_SECONDS; } });
10
+ var errors_1 = require("./errors");
11
+ Object.defineProperty(exports, "OAuth2Category", { enumerable: true, get: function () { return errors_1.OAuth2Category; } });
12
+ Object.defineProperty(exports, "OAuth2Error", { enumerable: true, get: function () { return errors_1.OAuth2Error; } });
13
+ Object.defineProperty(exports, "RejectedGrantError", { enumerable: true, get: function () { return errors_1.RejectedGrantError; } });
14
+ var loopback_receiver_1 = require("./loopback-receiver");
15
+ Object.defineProperty(exports, "LoopbackReceiver", { enumerable: true, get: function () { return loopback_receiver_1.LoopbackReceiver; } });
16
+ Object.defineProperty(exports, "CALLBACK_PATH", { enumerable: true, get: function () { return loopback_receiver_1.CALLBACK_PATH; } });
17
+ var metadata_1 = require("./metadata");
18
+ Object.defineProperty(exports, "OAuth2Metadata", { enumerable: true, get: function () { return metadata_1.OAuth2Metadata; } });
19
+ var oauth2_authentication_1 = require("./oauth2-authentication");
20
+ Object.defineProperty(exports, "OAuth2Authentication", { enumerable: true, get: function () { return oauth2_authentication_1.OAuth2Authentication; } });
21
+ Object.defineProperty(exports, "BEARER_PREFIX", { enumerable: true, get: function () { return oauth2_authentication_1.BEARER_PREFIX; } });
22
+ var session_manager_1 = require("./session-manager");
23
+ Object.defineProperty(exports, "OAuth2SessionManager", { enumerable: true, get: function () { return session_manager_1.OAuth2SessionManager; } });
24
+ Object.defineProperty(exports, "BUILTIN_ISSUER", { enumerable: true, get: function () { return session_manager_1.BUILTIN_ISSUER; } });
25
+ Object.defineProperty(exports, "CLIENT_NAME", { enumerable: true, get: function () { return session_manager_1.CLIENT_NAME; } });
26
+ Object.defineProperty(exports, "DEFAULT_AUTHORIZE_TIMEOUT_SECONDS", { enumerable: true, get: function () { return session_manager_1.DEFAULT_AUTHORIZE_TIMEOUT_SECONDS; } });
27
+ Object.defineProperty(exports, "DEFAULT_MANUAL_CALLBACK_PORT", { enumerable: true, get: function () { return session_manager_1.DEFAULT_MANUAL_CALLBACK_PORT; } });
28
+ Object.defineProperty(exports, "DEFAULT_REFRESH_AHEAD_MILLIS", { enumerable: true, get: function () { return session_manager_1.DEFAULT_REFRESH_AHEAD_MILLIS; } });
29
+ Object.defineProperty(exports, "ENV_ISSUER", { enumerable: true, get: function () { return session_manager_1.ENV_ISSUER; } });
30
+ Object.defineProperty(exports, "MIN_USABLE_REMAINING_MILLIS", { enumerable: true, get: function () { return session_manager_1.MIN_USABLE_REMAINING_MILLIS; } });
31
+ var token_1 = require("./token");
32
+ Object.defineProperty(exports, "OAuth2Token", { enumerable: true, get: function () { return token_1.OAuth2Token; } });
33
+ Object.defineProperty(exports, "TOKEN_SCHEMA_VERSION", { enumerable: true, get: function () { return token_1.TOKEN_SCHEMA_VERSION; } });
34
+ Object.defineProperty(exports, "currentMillis", { enumerable: true, get: function () { return token_1.currentMillis; } });
35
+ var token_store_1 = require("./token-store");
36
+ Object.defineProperty(exports, "OAuth2TokenStore", { enumerable: true, get: function () { return token_store_1.OAuth2TokenStore; } });
37
+ Object.defineProperty(exports, "DEFAULT_SURFACE", { enumerable: true, get: function () { return token_store_1.DEFAULT_SURFACE; } });
38
+ Object.defineProperty(exports, "ENV_HOME", { enumerable: true, get: function () { return token_store_1.ENV_HOME; } });
39
+ Object.defineProperty(exports, "hashIssuer", { enumerable: true, get: function () { return token_store_1.hashIssuer; } });
40
+ var http_utils_1 = require("./http-utils");
41
+ Object.defineProperty(exports, "CONNECT_TIMEOUT_SECONDS", { enumerable: true, get: function () { return http_utils_1.CONNECT_TIMEOUT_SECONDS; } });
42
+ Object.defineProperty(exports, "READ_TIMEOUT_SECONDS", { enumerable: true, get: function () { return http_utils_1.READ_TIMEOUT_SECONDS; } });
@@ -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;