@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.
- package/README.md +80 -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/index.js +1 -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 +34 -16
- package/dist/cjs/push/tick-util.js +172 -0
- package/dist/cjs/quote/quote-client.js +59 -12
- package/dist/cjs/trade/trade-client.js +55 -6
- 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/index.js +1 -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 +34 -16
- package/dist/esm/push/tick-util.js +165 -0
- package/dist/esm/quote/quote-client.js +59 -12
- package/dist/esm/trade/trade-client.js +55 -6
- 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 +2 -2
- package/dist/types/model/order.d.ts +62 -11
- package/dist/types/model/quote-requests.d.ts +21 -2
- package/dist/types/model/quote.d.ts +72 -14
- package/dist/types/model/trade-requests.d.ts +17 -4
- package/dist/types/model/trade.d.ts +11 -1
- package/dist/types/push/callbacks.d.ts +3 -7
- package/dist/types/push/index.d.ts +2 -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/push/tick-util.d.ts +30 -0
- package/dist/types/quote/quote-client.d.ts +27 -6
- package/dist/types/trade/trade-client.d.ts +27 -3
- package/dist/types/version.d.ts +1 -1
- package/package.json +8 -4
|
@@ -9,6 +9,7 @@ import { HttpClient } from '../client/http-client.js';
|
|
|
9
9
|
import { createApiRequest } from '../client/api-request.js';
|
|
10
10
|
import { unmarshalData } from '../client/api-response.js';
|
|
11
11
|
import * as tokenMethods from '../client/token-methods.js';
|
|
12
|
+
import { getTradeCondByCode, isUsStockSymbol } from '../push/tick-util.js';
|
|
12
13
|
/**
|
|
13
14
|
* resolveOptionTimezone returns the timezone string to use for expiry conversion.
|
|
14
15
|
* Explicit tz takes precedence; otherwise infer from symbol (.HK suffix → Asia/Hong_Kong, else America/New_York).
|
|
@@ -148,12 +149,22 @@ export class QuoteClient {
|
|
|
148
149
|
async getKline(req) {
|
|
149
150
|
return this.callInto('kline', req);
|
|
150
151
|
}
|
|
151
|
-
async getTimeline(
|
|
152
|
-
|
|
152
|
+
async getTimeline(symbolsOrRequest) {
|
|
153
|
+
const req = Array.isArray(symbolsOrRequest) ? { symbols: symbolsOrRequest } : symbolsOrRequest;
|
|
154
|
+
return this.callInto('timeline', req, req.secType === 'CC' ? '3.0' : undefined);
|
|
153
155
|
}
|
|
154
156
|
/** Tick-by-tick trades. wire: trade_tick */
|
|
155
157
|
async getTradeTick(req) {
|
|
156
|
-
|
|
158
|
+
const out = await this.callInto('trade_tick', req);
|
|
159
|
+
for (const tick of out) {
|
|
160
|
+
const isUs = isUsStockSymbol(tick.symbol);
|
|
161
|
+
for (const item of tick.items) {
|
|
162
|
+
if (item.cond && item.cond.length > 0) {
|
|
163
|
+
item.cond = getTradeCondByCode(isUs, item.cond[0]);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return out;
|
|
157
168
|
}
|
|
158
169
|
/** Depth snapshot. wire: quote_depth */
|
|
159
170
|
async getQuoteDepth(req) {
|
|
@@ -319,6 +330,7 @@ export class QuoteClient {
|
|
|
319
330
|
while (acc.length < totalSize) {
|
|
320
331
|
const sub = {
|
|
321
332
|
symbols: req.symbol ? [req.symbol] : undefined,
|
|
333
|
+
secType: req.secType,
|
|
322
334
|
period: req.period,
|
|
323
335
|
right: req.right,
|
|
324
336
|
beginTime,
|
|
@@ -369,7 +381,7 @@ export class QuoteClient {
|
|
|
369
381
|
async getStockIndustry(req) {
|
|
370
382
|
return this.callInto('stock_industry', req);
|
|
371
383
|
}
|
|
372
|
-
/**
|
|
384
|
+
/** Market data permission entries. wire: get_quote_permission */
|
|
373
385
|
async getQuotePermission(req) {
|
|
374
386
|
return this.callInto('get_quote_permission', req);
|
|
375
387
|
}
|
|
@@ -380,9 +392,16 @@ export class QuoteClient {
|
|
|
380
392
|
// ==========================================================================
|
|
381
393
|
// Batch 4: option / future extensions
|
|
382
394
|
// ==========================================================================
|
|
383
|
-
/** Option tick trades. wire: option_trade_tick
|
|
395
|
+
/** Option tick trades. wire: option_trade_tick
|
|
396
|
+
*
|
|
397
|
+
* Server expects `biz_content` as a top-level JSON array (matches Java's
|
|
398
|
+
* `BatchApiModel` and Python's `MultipleContractParams.to_openapi_dict`).
|
|
399
|
+
* We unwrap the request here so the array is emitted directly instead of
|
|
400
|
+
* `{"contracts": [...]}`. Sending it as an object triggers
|
|
401
|
+
* `biz param error(failed to parse parameters in 'biz_content')`.
|
|
402
|
+
*/
|
|
384
403
|
async getOptionTradeTicks(req) {
|
|
385
|
-
return this.callInto('option_trade_tick', req);
|
|
404
|
+
return this.callInto('option_trade_tick', req.contracts ?? []);
|
|
386
405
|
}
|
|
387
406
|
/** Option intraday timeline. wire: option_timeline */
|
|
388
407
|
async getOptionTimeline(req) {
|
|
@@ -421,10 +440,16 @@ export class QuoteClient {
|
|
|
421
440
|
return this.callInto('future_main_contract', req);
|
|
422
441
|
}
|
|
423
442
|
/** Futures K-line. wire: future_kline
|
|
424
|
-
* beginTime / endTime default to -1 when unset (server requires them present).
|
|
443
|
+
* beginTime / endTime default to -1 when unset (server requires them present).
|
|
444
|
+
* Server expects `contract_codes` (plural) — normalize singular `contractCode`
|
|
445
|
+
* to the plural form so callers can pass either shape. */
|
|
425
446
|
async getFutureKline(req) {
|
|
447
|
+
const contractCodes = req.contractCodes
|
|
448
|
+
?? (req.contractCode ? [req.contractCode] : undefined);
|
|
449
|
+
const { contractCode: _drop, ...rest } = req;
|
|
426
450
|
const body = {
|
|
427
|
-
...
|
|
451
|
+
...rest,
|
|
452
|
+
...(contractCodes ? { contractCodes } : {}),
|
|
428
453
|
beginTime: req.beginTime ?? -1,
|
|
429
454
|
endTime: req.endTime ?? -1,
|
|
430
455
|
};
|
|
@@ -442,8 +467,9 @@ export class QuoteClient {
|
|
|
442
467
|
endTime = -1;
|
|
443
468
|
const acc = [];
|
|
444
469
|
while (acc.length < totalSize) {
|
|
470
|
+
// Server rejects singular `contract_code` — send the plural form.
|
|
445
471
|
const sub = {
|
|
446
|
-
|
|
472
|
+
contractCodes: req.contractCode ? [req.contractCode] : undefined,
|
|
447
473
|
period: req.period,
|
|
448
474
|
beginTime,
|
|
449
475
|
endTime,
|
|
@@ -522,9 +548,30 @@ export class QuoteClient {
|
|
|
522
548
|
async getWarrantFilter(req) {
|
|
523
549
|
return this.callInto('warrant_filter', req);
|
|
524
550
|
}
|
|
525
|
-
/** Industry list. wire: industry_list
|
|
526
|
-
|
|
527
|
-
|
|
551
|
+
/** Industry list. wire: industry_list
|
|
552
|
+
*
|
|
553
|
+
* `industryLevel` is required by the server — omitting it returns
|
|
554
|
+
* `industry level error, all supported is: [GSECTOR, GGROUP, GIND, GSUBIND]`.
|
|
555
|
+
* Defaults to `GGROUP` when unset, matching Python SDK's
|
|
556
|
+
* `IndustryLevel.GGROUP` default.
|
|
557
|
+
*
|
|
558
|
+
* Wire fields differ from other endpoints (see `IndustryItem`): server sends
|
|
559
|
+
* `nameCN` / `nameEN` / `industryLevel`. This method hydrates the
|
|
560
|
+
* backwards-compat `name` (from `nameEN || nameCN`) and mirrors
|
|
561
|
+
* `industryLevel` to `level` so existing callers keep working.
|
|
562
|
+
*/
|
|
563
|
+
async getIndustryList(req = {}) {
|
|
564
|
+
const payload = { industryLevel: 'GGROUP', ...req };
|
|
565
|
+
const items = await this.callInto('industry_list', payload);
|
|
566
|
+
if (!Array.isArray(items))
|
|
567
|
+
return [];
|
|
568
|
+
for (const it of items) {
|
|
569
|
+
if (it.name === undefined)
|
|
570
|
+
it.name = it.nameEN || it.nameCN;
|
|
571
|
+
if (it.level === undefined)
|
|
572
|
+
it.level = it.industryLevel;
|
|
573
|
+
}
|
|
574
|
+
return items;
|
|
528
575
|
}
|
|
529
576
|
/** Stocks inside an industry. wire: industry_stock_list */
|
|
530
577
|
async getIndustryStocks(req) {
|
|
@@ -109,14 +109,54 @@ export class TradeClient {
|
|
|
109
109
|
});
|
|
110
110
|
}
|
|
111
111
|
// === Order operations ===
|
|
112
|
+
/**
|
|
113
|
+
* Prepare an order request for the wire.
|
|
114
|
+
*
|
|
115
|
+
* The gateway expects `algo_params` as a `[{tag, value}, ...]` array
|
|
116
|
+
* (matching the Python SDK's AlgoParams.to_dict serialization), not the
|
|
117
|
+
* plain object shape that reads naturally in TypeScript. This helper
|
|
118
|
+
* normalizes the object form into the array form on outbound payloads
|
|
119
|
+
* so callers can pass the natural object and TWAP/VWAP orders work end
|
|
120
|
+
* to end without wire-format workarounds.
|
|
121
|
+
*
|
|
122
|
+
* Fields with undefined/null values are dropped; empty algoParams
|
|
123
|
+
* becomes an empty array rather than being stripped, matching the
|
|
124
|
+
* Python SDK behavior when the user passes an empty AlgoParams().
|
|
125
|
+
*/
|
|
126
|
+
marshalOrderForWire(order) {
|
|
127
|
+
const { algoParams, ...rest } = order;
|
|
128
|
+
const withAccount = { ...rest, account: this.account };
|
|
129
|
+
if (algoParams === undefined)
|
|
130
|
+
return withAccount;
|
|
131
|
+
// Already a tag/value array (advanced callers who want raw control)
|
|
132
|
+
if (Array.isArray(algoParams)) {
|
|
133
|
+
withAccount.algoParams = algoParams;
|
|
134
|
+
return withAccount;
|
|
135
|
+
}
|
|
136
|
+
// Object shape → tag/value array.
|
|
137
|
+
// Keys are converted to snake_case explicitly here (e.g. `startTime` →
|
|
138
|
+
// `start_time`) so they land correctly in the `tag` string that the
|
|
139
|
+
// gateway reads. `keysToSnakeCase` only rewrites object *keys*, not
|
|
140
|
+
// string *values*, so `tag` would be left camelCase if we didn't do
|
|
141
|
+
// this conversion ourselves.
|
|
142
|
+
const tagValues = [];
|
|
143
|
+
for (const [key, value] of Object.entries(algoParams)) {
|
|
144
|
+
if (value === undefined || value === null)
|
|
145
|
+
continue;
|
|
146
|
+
const snakeKey = key.replace(/[A-Z]/g, (c) => '_' + c.toLowerCase());
|
|
147
|
+
tagValues.push({ tag: snakeKey, value });
|
|
148
|
+
}
|
|
149
|
+
withAccount.algoParams = tagValues;
|
|
150
|
+
return withAccount;
|
|
151
|
+
}
|
|
112
152
|
async placeOrder(order) {
|
|
113
|
-
return this.callInto('place_order',
|
|
153
|
+
return this.callInto('place_order', this.marshalOrderForWire(order));
|
|
114
154
|
}
|
|
115
155
|
async previewOrder(order) {
|
|
116
|
-
return this.callInto('preview_order',
|
|
156
|
+
return this.callInto('preview_order', this.marshalOrderForWire(order));
|
|
117
157
|
}
|
|
118
158
|
async modifyOrder(id, order) {
|
|
119
|
-
return this.callInto('modify_order', { ...
|
|
159
|
+
return this.callInto('modify_order', { ...this.marshalOrderForWire(order), id });
|
|
120
160
|
}
|
|
121
161
|
async cancelOrder(id, secretKey) {
|
|
122
162
|
const params = { account: this.account, id };
|
|
@@ -181,11 +221,17 @@ export class TradeClient {
|
|
|
181
221
|
async getPositions(req) {
|
|
182
222
|
return this.callIntoItems('positions', withAccount(req, this.account));
|
|
183
223
|
}
|
|
184
|
-
/**
|
|
224
|
+
/**
|
|
225
|
+
* Query assets. v0.4.0: supports sub-account list and aggregation options.
|
|
226
|
+
* `assetQuoteType`(ETH / RTH / OVERNIGHT) 选择资产行情口径。
|
|
227
|
+
*/
|
|
185
228
|
async getAssets(req) {
|
|
186
229
|
return this.callIntoItems('assets', withAccount(req, this.account));
|
|
187
230
|
}
|
|
188
|
-
/**
|
|
231
|
+
/**
|
|
232
|
+
* Query prime assets. v0.4.0: takes AssetsRequest for filters.
|
|
233
|
+
* `assetQuoteType`(ETH / RTH / OVERNIGHT) 选择资产行情口径。
|
|
234
|
+
*/
|
|
189
235
|
async getPrimeAssets(req) {
|
|
190
236
|
return this.callInto('prime_assets', withAccount(req, this.account));
|
|
191
237
|
}
|
|
@@ -206,7 +252,10 @@ export class TradeClient {
|
|
|
206
252
|
async getAnalyticsAsset(req) {
|
|
207
253
|
return this.callIntoItems('analytics_asset', withAccount(req, this.account));
|
|
208
254
|
}
|
|
209
|
-
/**
|
|
255
|
+
/**
|
|
256
|
+
* Aggregate assets (prime) by base currency.
|
|
257
|
+
* `assetQuoteType`(ETH / RTH / OVERNIGHT) 选择资产行情口径。
|
|
258
|
+
*/
|
|
210
259
|
async getAggregateAssets(req) {
|
|
211
260
|
return this.callInto('aggregate_assets', withAccount(req, this.account));
|
|
212
261
|
}
|
package/dist/esm/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const SDK_VERSION = '0.
|
|
1
|
+
export const SDK_VERSION = '0.5.7';
|
|
@@ -0,0 +1,98 @@
|
|
|
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 declare enum AuthenticationType {
|
|
10
|
+
/** tigerId + RSA private key signature */
|
|
11
|
+
Signature = "SIGNATURE",
|
|
12
|
+
/** `Authorization: Bearer <access_token>` */
|
|
13
|
+
OAuth2 = "OAUTH2"
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Per-request authentication context.
|
|
17
|
+
*
|
|
18
|
+
* Business parameters (`method`, `version`, `biz_content`, `timestamp`) are filled in by
|
|
19
|
+
* the client and must not be modified by an authentication implementation.
|
|
20
|
+
*/
|
|
21
|
+
export declare class RequestAuthContext {
|
|
22
|
+
/** Request body parameters. Read-only as far as authentication is concerned. */
|
|
23
|
+
readonly params: Record<string, string>;
|
|
24
|
+
/**
|
|
25
|
+
* Whether a request may be retried with a new credential after a 401.
|
|
26
|
+
*
|
|
27
|
+
* Order placement, modification, and cancellation are not retryable because a 401
|
|
28
|
+
* does not prove that the operation was not executed.
|
|
29
|
+
*/
|
|
30
|
+
readonly retryable: boolean;
|
|
31
|
+
/** Full `Authorization` header value; undefined means do not send the header. */
|
|
32
|
+
authorizationHeader?: string;
|
|
33
|
+
constructor(params: Record<string, string>, retryable?: boolean);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Credential snapshot used to coordinate authentication retries.
|
|
37
|
+
*
|
|
38
|
+
* Comparing the failed credential with current state detects rotation completed
|
|
39
|
+
* elsewhere and avoids duplicate refresh requests.
|
|
40
|
+
*/
|
|
41
|
+
export declare class AuthenticationAttempt {
|
|
42
|
+
readonly authType: AuthenticationType;
|
|
43
|
+
/**
|
|
44
|
+
* Credential used by the request, including a raw OAuth 2.0 access token.
|
|
45
|
+
*
|
|
46
|
+
* This value is restricted to equality comparisons and must not be logged or exposed.
|
|
47
|
+
*/
|
|
48
|
+
readonly credential: string | undefined;
|
|
49
|
+
constructor(authType: AuthenticationType, credential: string | undefined);
|
|
50
|
+
/** Never prints the credential value. */
|
|
51
|
+
toString(): string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Authentication retry decision.
|
|
55
|
+
*
|
|
56
|
+
* A retry always supplies a new full `Authorization` header value, including the
|
|
57
|
+
* `Bearer ` prefix. There is deliberately no "retry with the same credential" option:
|
|
58
|
+
* that only collects the same 401 twice.
|
|
59
|
+
*/
|
|
60
|
+
export declare class RetryDecision {
|
|
61
|
+
private static readonly NO_RETRY;
|
|
62
|
+
readonly shouldRetry: boolean;
|
|
63
|
+
readonly authorizationHeader: string | undefined;
|
|
64
|
+
private constructor();
|
|
65
|
+
static noRetry(): RetryDecision;
|
|
66
|
+
/** @param authorizationHeader full new header value, including `Bearer ` */
|
|
67
|
+
static retry(authorizationHeader: string): RetryDecision;
|
|
68
|
+
/** Never prints the header value. */
|
|
69
|
+
toString(): string;
|
|
70
|
+
}
|
|
71
|
+
/** Authentication interface. */
|
|
72
|
+
export interface Authentication {
|
|
73
|
+
/**
|
|
74
|
+
* Which kind of authentication. For logging and assertions; business logic should not
|
|
75
|
+
* branch on it.
|
|
76
|
+
*/
|
|
77
|
+
readonly authType: AuthenticationType;
|
|
78
|
+
/**
|
|
79
|
+
* Write the credential into the request and return a snapshot of which credential was
|
|
80
|
+
* used.
|
|
81
|
+
*
|
|
82
|
+
* The OAuth2 implementation may refresh here, so this may reject with an
|
|
83
|
+
* `OAuth2Error`.
|
|
84
|
+
*
|
|
85
|
+
* @returns snapshot, passed back to {@link onUnauthorized}
|
|
86
|
+
*/
|
|
87
|
+
apply(context: RequestAuthContext): Promise<AuthenticationAttempt>;
|
|
88
|
+
/**
|
|
89
|
+
* Return the retry decision for an unauthorized response.
|
|
90
|
+
*
|
|
91
|
+
* Refresh applies only to HTTP 401 credential failures, never to HTTP 403, server
|
|
92
|
+
* errors, or timeouts. Authentication retries occur at most once per request and
|
|
93
|
+
* require a different credential.
|
|
94
|
+
*/
|
|
95
|
+
onUnauthorized(attempt: AuthenticationAttempt | undefined, statusCode: number): Promise<RetryDecision>;
|
|
96
|
+
/** Release resources. Must be called when switching authentication method. */
|
|
97
|
+
close(): void;
|
|
98
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Authentication layer: the request-level data plane plus the OAuth 2.0 control plane. */
|
|
2
|
+
export { AuthenticationAttempt, AuthenticationType, RequestAuthContext, RetryDecision, } from './authentication';
|
|
3
|
+
export type { Authentication } from './authentication';
|
|
4
|
+
export * from './oauth2';
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
+
/** Raw on-disk shape, shared with the Java and Python SDKs. */
|
|
9
|
+
export interface OAuth2ClientRegistrationJson {
|
|
10
|
+
issuer?: string;
|
|
11
|
+
client_id?: string;
|
|
12
|
+
client_name?: string;
|
|
13
|
+
registered_scopes?: string;
|
|
14
|
+
client_id_issued_at?: number;
|
|
15
|
+
}
|
|
16
|
+
export declare class OAuth2ClientRegistration {
|
|
17
|
+
issuer?: string;
|
|
18
|
+
clientId?: string;
|
|
19
|
+
clientName?: string;
|
|
20
|
+
registeredScopes?: string;
|
|
21
|
+
clientIdIssuedAt: number;
|
|
22
|
+
constructor(init?: {
|
|
23
|
+
issuer?: string;
|
|
24
|
+
clientId?: string;
|
|
25
|
+
clientName?: string;
|
|
26
|
+
registeredScopes?: string;
|
|
27
|
+
clientIdIssuedAt?: number;
|
|
28
|
+
});
|
|
29
|
+
toJSON(): OAuth2ClientRegistrationJson;
|
|
30
|
+
static fromJSON(data: OAuth2ClientRegistrationJson | null | undefined): OAuth2ClientRegistration | null;
|
|
31
|
+
toString(): string;
|
|
32
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
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 declare const DEFAULT_DEVICE_INTERVAL_SECONDS = 5;
|
|
7
|
+
/** Raw shape of the device authorization response. */
|
|
8
|
+
export interface OAuth2DeviceAuthorizationJson {
|
|
9
|
+
device_code?: string;
|
|
10
|
+
user_code?: string;
|
|
11
|
+
verification_uri?: string;
|
|
12
|
+
verification_uri_complete?: string;
|
|
13
|
+
expires_in?: number;
|
|
14
|
+
interval?: number;
|
|
15
|
+
}
|
|
16
|
+
export declare class OAuth2DeviceAuthorization {
|
|
17
|
+
/** Credential used to redeem the token; it must not be displayed or logged. */
|
|
18
|
+
deviceCode?: string;
|
|
19
|
+
/** Short code presented to the resource owner. */
|
|
20
|
+
userCode?: string;
|
|
21
|
+
/** Verification URI presented to the resource owner. */
|
|
22
|
+
verificationUri?: string;
|
|
23
|
+
/** Verification URI with the user code included. */
|
|
24
|
+
verificationUriComplete?: string;
|
|
25
|
+
expiresIn: number;
|
|
26
|
+
/** Polling interval in seconds; 0 means the server sent none */
|
|
27
|
+
interval: number;
|
|
28
|
+
constructor(init?: {
|
|
29
|
+
deviceCode?: string;
|
|
30
|
+
userCode?: string;
|
|
31
|
+
verificationUri?: string;
|
|
32
|
+
verificationUriComplete?: string;
|
|
33
|
+
expiresIn?: number;
|
|
34
|
+
interval?: number;
|
|
35
|
+
});
|
|
36
|
+
effectiveIntervalSeconds(): number;
|
|
37
|
+
static fromJSON(data: OAuth2DeviceAuthorizationJson | null | undefined): OAuth2DeviceAuthorization | null;
|
|
38
|
+
/** Prints no deviceCode. */
|
|
39
|
+
toString(): string;
|
|
40
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Categorized errors from OAuth 2.0 authorization flows.
|
|
3
|
+
*
|
|
4
|
+
* Authorization server error details are retained in the error message.
|
|
5
|
+
*/
|
|
6
|
+
/** Error category. Each one implies a different action. */
|
|
7
|
+
export declare enum OAuth2Category {
|
|
8
|
+
/** Cannot fetch authorization server metadata: wrong address, service down, no network */
|
|
9
|
+
MetadataUnavailable = "METADATA_UNAVAILABLE",
|
|
10
|
+
/** No usable client_id, and automatic registration is not possible */
|
|
11
|
+
ClientRegistrationRequired = "CLIENT_REGISTRATION_REQUIRED",
|
|
12
|
+
/** Callback stage failed: timeout, user denial, or state mismatch */
|
|
13
|
+
CallbackFailed = "CALLBACK_FAILED",
|
|
14
|
+
/**
|
|
15
|
+
* Device flow not approved in time, or explicitly denied. Kept separate from
|
|
16
|
+
* ReauthorizationRequired because only a new device authorization is needed -- the
|
|
17
|
+
* stored token was not touched.
|
|
18
|
+
*/
|
|
19
|
+
DeviceAuthorizationFailed = "DEVICE_AUTHORIZATION_FAILED",
|
|
20
|
+
/** refresh_token expired, revoked, or never obtained. Retrying is pointless */
|
|
21
|
+
ReauthorizationRequired = "REAUTHORIZATION_REQUIRED",
|
|
22
|
+
/** Token endpoint call failed, and not in the terminal way above */
|
|
23
|
+
TokenRequestFailed = "TOKEN_REQUEST_FAILED",
|
|
24
|
+
/** Reading or writing the local token file failed */
|
|
25
|
+
StorageFailed = "STORAGE_FAILED"
|
|
26
|
+
}
|
|
27
|
+
/** Thrown by any stage of the OAuth2 flow. */
|
|
28
|
+
export declare class OAuth2Error extends Error {
|
|
29
|
+
readonly category: OAuth2Category;
|
|
30
|
+
readonly cause?: unknown;
|
|
31
|
+
constructor(category: OAuth2Category, message: string, cause?: unknown);
|
|
32
|
+
get reauthorizationRequired(): boolean;
|
|
33
|
+
toString(): string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A grant the authorization server refused.
|
|
37
|
+
*
|
|
38
|
+
* Carries the error code so a caller can tell a rejected credential from a rejected
|
|
39
|
+
* client registration. Its category is `ReauthorizationRequired`, so callers that only
|
|
40
|
+
* know the public type behave as they would for any terminal authorization failure.
|
|
41
|
+
*/
|
|
42
|
+
export declare class RejectedGrantError extends OAuth2Error {
|
|
43
|
+
readonly error: string;
|
|
44
|
+
constructor(error: string, description: string);
|
|
45
|
+
get isClientRejected(): boolean;
|
|
46
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every call here is a small JSON exchange with the authorization server, so a request
|
|
3
|
+
* that has not answered in this long is not going to. Same values as the Java and
|
|
4
|
+
* Python SDKs.
|
|
5
|
+
*/
|
|
6
|
+
export declare const CONNECT_TIMEOUT_SECONDS = 5;
|
|
7
|
+
export declare const READ_TIMEOUT_SECONDS = 10;
|
|
8
|
+
/** Form fields whose value is null or undefined are skipped. */
|
|
9
|
+
export type FormFields = Record<string, string | undefined | null>;
|
|
10
|
+
export declare function getJson(url: string): Promise<Record<string, unknown>>;
|
|
11
|
+
export declare function postJson(url: string, payload: unknown): Promise<Record<string, unknown>>;
|
|
12
|
+
export declare function postForm(url: string, form: FormFields): Promise<Record<string, unknown>>;
|
|
13
|
+
/**
|
|
14
|
+
* Post a form and return the HTTP status without requiring a response body.
|
|
15
|
+
*
|
|
16
|
+
* RFC 7009 revocation returns an empty success body, so no parsing is performed.
|
|
17
|
+
*
|
|
18
|
+
* @returns the HTTP status code
|
|
19
|
+
* @throws OAuth2Error on connection failure
|
|
20
|
+
*/
|
|
21
|
+
export declare function postFormForStatus(url: string, form: FormFields): Promise<number>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** OAuth 2.0 authentication, authorization flows, and token persistence. */
|
|
2
|
+
export { OAuth2ClientRegistration } from './client-registration';
|
|
3
|
+
export type { OAuth2ClientRegistrationJson } from './client-registration';
|
|
4
|
+
export { OAuth2DeviceAuthorization, DEFAULT_DEVICE_INTERVAL_SECONDS } from './device-authorization';
|
|
5
|
+
export type { OAuth2DeviceAuthorizationJson } from './device-authorization';
|
|
6
|
+
export { OAuth2Category, OAuth2Error, RejectedGrantError } from './errors';
|
|
7
|
+
export { LoopbackReceiver, CALLBACK_PATH } from './loopback-receiver';
|
|
8
|
+
export { OAuth2Metadata } from './metadata';
|
|
9
|
+
export { OAuth2Authentication, BEARER_PREFIX } from './oauth2-authentication';
|
|
10
|
+
export { OAuth2SessionManager, BUILTIN_ISSUER, CLIENT_NAME, DEFAULT_AUTHORIZE_TIMEOUT_SECONDS, DEFAULT_MANUAL_CALLBACK_PORT, DEFAULT_REFRESH_AHEAD_MILLIS, ENV_ISSUER, MIN_USABLE_REMAINING_MILLIS, } from './session-manager';
|
|
11
|
+
export type { AuthorizationUrlHandler, DeviceAuthorizationHandler, OAuth2SessionManagerOptions, } from './session-manager';
|
|
12
|
+
export { OAuth2Token, TOKEN_SCHEMA_VERSION, currentMillis } from './token';
|
|
13
|
+
export type { OAuth2TokenJson } from './token';
|
|
14
|
+
export { OAuth2TokenStore, DEFAULT_SURFACE, ENV_HOME, hashIssuer } from './token-store';
|
|
15
|
+
export type { TokenStore } from './token-store';
|
|
16
|
+
export { CONNECT_TIMEOUT_SECONDS, READ_TIMEOUT_SECONDS } from './http-utils';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export declare const CALLBACK_PATH = "/callback";
|
|
2
|
+
/** Single-shot local callback server. */
|
|
3
|
+
export declare class LoopbackReceiver {
|
|
4
|
+
private readonly server;
|
|
5
|
+
private port;
|
|
6
|
+
private params;
|
|
7
|
+
private fired;
|
|
8
|
+
private waiters;
|
|
9
|
+
private constructor();
|
|
10
|
+
/**
|
|
11
|
+
* Bind the receiver. It must be listening before the authorization request is issued.
|
|
12
|
+
*
|
|
13
|
+
* @param port port to bind; 0 lets the system assign a free one
|
|
14
|
+
*/
|
|
15
|
+
static start(port?: number): Promise<LoopbackReceiver>;
|
|
16
|
+
get redirectUri(): string;
|
|
17
|
+
get boundPort(): number;
|
|
18
|
+
/**
|
|
19
|
+
* Wait for the browser callback.
|
|
20
|
+
*
|
|
21
|
+
* @returns the query parameters carried by the callback
|
|
22
|
+
* @throws OAuth2Error on timeout
|
|
23
|
+
*/
|
|
24
|
+
waitForCallback(timeoutSeconds: number): Promise<Record<string, string>>;
|
|
25
|
+
close(): void;
|
|
26
|
+
/** Record the callback params (first one wins) and return a snapshot of them. */
|
|
27
|
+
private record;
|
|
28
|
+
private release;
|
|
29
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authorization server metadata (RFC 8414).
|
|
3
|
+
*
|
|
4
|
+
* Endpoints always come from metadata, never hardcoded, so switching environments only
|
|
5
|
+
* means changing the issuer.
|
|
6
|
+
*/
|
|
7
|
+
/** Raw shape of `{issuer}/.well-known/oauth-authorization-server`. */
|
|
8
|
+
export interface OAuth2MetadataJson {
|
|
9
|
+
issuer?: string;
|
|
10
|
+
authorization_endpoint?: string;
|
|
11
|
+
token_endpoint?: string;
|
|
12
|
+
registration_endpoint?: string;
|
|
13
|
+
device_authorization_endpoint?: string;
|
|
14
|
+
revocation_endpoint?: string;
|
|
15
|
+
scopes_supported?: string[];
|
|
16
|
+
}
|
|
17
|
+
export declare class OAuth2Metadata {
|
|
18
|
+
issuer?: string;
|
|
19
|
+
authorizationEndpoint?: string;
|
|
20
|
+
tokenEndpoint?: string;
|
|
21
|
+
/** Absent means only a manually configured clientId can be used */
|
|
22
|
+
registrationEndpoint?: string;
|
|
23
|
+
/** Absent means this environment does not support the device flow */
|
|
24
|
+
deviceAuthorizationEndpoint?: string;
|
|
25
|
+
/** RFC 7009 revocation. Absent means logout can only clear the local token */
|
|
26
|
+
revocationEndpoint?: string;
|
|
27
|
+
scopesSupported: string[];
|
|
28
|
+
constructor(init?: Partial<Omit<OAuth2Metadata, 'scopesSupported'>> & {
|
|
29
|
+
scopesSupported?: string[];
|
|
30
|
+
});
|
|
31
|
+
static fromJSON(data: OAuth2MetadataJson | null | undefined): OAuth2Metadata | null;
|
|
32
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Request authentication backed by an OAuth 2.0 session manager. */
|
|
2
|
+
import { AuthenticationAttempt, AuthenticationType, RequestAuthContext, RetryDecision, type Authentication } from '../authentication';
|
|
3
|
+
import type { OAuth2SessionManager } from './session-manager';
|
|
4
|
+
export declare const BEARER_PREFIX = "Bearer ";
|
|
5
|
+
/** Authenticate with an OAuth2 access token. */
|
|
6
|
+
export declare class OAuth2Authentication implements Authentication {
|
|
7
|
+
private readonly sessions;
|
|
8
|
+
constructor(sessionManager: OAuth2SessionManager);
|
|
9
|
+
get authType(): AuthenticationType;
|
|
10
|
+
get sessionManager(): OAuth2SessionManager;
|
|
11
|
+
/**
|
|
12
|
+
* Attach a Bearer token to the request context.
|
|
13
|
+
*
|
|
14
|
+
* Near-expiry refreshes use the session manager's single-flight synchronization.
|
|
15
|
+
* Missing authorization rejects with `ReauthorizationRequired` without starting an
|
|
16
|
+
* implicit browser flow.
|
|
17
|
+
*/
|
|
18
|
+
apply(context: RequestAuthContext): Promise<AuthenticationAttempt>;
|
|
19
|
+
/**
|
|
20
|
+
* Process HTTP 401 responses.
|
|
21
|
+
*
|
|
22
|
+
* HTTP 403 indicates insufficient permission and cannot be resolved by refreshing a
|
|
23
|
+
* token with the same scope. Server errors and timeouts are not credential failures.
|
|
24
|
+
*/
|
|
25
|
+
onUnauthorized(attempt: AuthenticationAttempt | undefined, statusCode: number): Promise<RetryDecision>;
|
|
26
|
+
/**
|
|
27
|
+
* The session is owned by the application and may still be in use, so it is not
|
|
28
|
+
* closed here.
|
|
29
|
+
*/
|
|
30
|
+
close(): void;
|
|
31
|
+
}
|