@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
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
|
```
|
|
@@ -486,8 +500,61 @@ const chain = await qc.optionChain('AAPL', '2024-01-19');
|
|
|
486
500
|
|
|
487
501
|
// 期货交易所
|
|
488
502
|
const exchanges = await qc.futureExchange();
|
|
503
|
+
|
|
504
|
+
// 逐笔成交
|
|
505
|
+
const ticks = await qc.getTradeTick({ symbols: ['AAPL'] });
|
|
506
|
+
// ticks[0].items[i].partCode — 交易所短名,如 "NYSE"、"NSDQ"
|
|
507
|
+
// ticks[0].items[i].partName — 交易所全名,如 "New York Stock Exchange, LLC (NYSE)"
|
|
508
|
+
// ticks[0].items[i].cond — 已转换的成交条件,如 "US_REGULAR_SALE",参见下方说明
|
|
489
509
|
```
|
|
490
510
|
|
|
511
|
+
### 逐笔成交 `cond` 字段说明
|
|
512
|
+
|
|
513
|
+
`trade_tick` 接口和 push 层返回的 `cond` 字段已由 SDK 转换为可读字符串,含义如下:
|
|
514
|
+
|
|
515
|
+
**美股(US)**
|
|
516
|
+
|
|
517
|
+
| 字符 | 含义 |
|
|
518
|
+
|------|------|
|
|
519
|
+
| ` `(空格)| 常规交易(Regular Sale) |
|
|
520
|
+
| `B` | 批量交易(Bunched Trade) |
|
|
521
|
+
| `C` | 现金交易(Cash Trade) |
|
|
522
|
+
| `F` | 跨市场交易(Intermarket Sweep) |
|
|
523
|
+
| `G` | 批量卖出(Bunched Sold Trade) |
|
|
524
|
+
| `H` | 离价交易(Price Variation Trade) |
|
|
525
|
+
| `I` | 碎股交易(Odd Lot Trade) |
|
|
526
|
+
| `K` | 纽交所第 127/155 条交易 |
|
|
527
|
+
| `L` | 延迟交易(Sold Last) |
|
|
528
|
+
| `M` | 中央收市价(Market Center Close Price) |
|
|
529
|
+
| `N` | 隔日交易(Next Day Trade) |
|
|
530
|
+
| `O` | 中央开盘价交易(Market Center Opening Trade) |
|
|
531
|
+
| `P` | 前参考价(Prior Reference Price) |
|
|
532
|
+
| `Q` | 中央开盘价(Market Center Open Price) |
|
|
533
|
+
| `R` | 卖方(Seller) |
|
|
534
|
+
| `T` | 盘前盘后交易(Form T) |
|
|
535
|
+
| `U` | 延长交易时段(Extended Trading Hours) |
|
|
536
|
+
| `V` | 合单交易(Contingent Trade) |
|
|
537
|
+
| `W` | 均价交易(Average Price Trade) |
|
|
538
|
+
| `X` | 跨市场交易(Cross Trade) |
|
|
539
|
+
| `Z` | 场外售出(Sold Out of Sequence) |
|
|
540
|
+
| `4` | 衍生工具定价(Derivatively Priced) |
|
|
541
|
+
| `7` | 合单交易(Qualified Contingent Trade) |
|
|
542
|
+
|
|
543
|
+
**港股(HK)**
|
|
544
|
+
|
|
545
|
+
| 字符 | 含义 |
|
|
546
|
+
|------|------|
|
|
547
|
+
| ` `(空格)| 自动对盘(Automatch Normal) |
|
|
548
|
+
| `D` | 碎股交易(Odd Lot Trade) |
|
|
549
|
+
| `U` | 竞价交易(Auction Trade) |
|
|
550
|
+
| `*` | 场外交易(Overseas Trade) |
|
|
551
|
+
| `P` | 开市前成交(Late Trade Off Exchange) |
|
|
552
|
+
| `M` | 非自动对盘(Non-Direct Off Exchange Trade) |
|
|
553
|
+
| `X` | 同券商自动对盘(Direct Off Exchange Trade) |
|
|
554
|
+
| `Y` | 同券商非自动对盘(Automatic Internalized) |
|
|
555
|
+
|
|
556
|
+
如上表所示,`cond` 值因市场(美股/港股)不同而有所区别。
|
|
557
|
+
|
|
491
558
|
## 交易
|
|
492
559
|
|
|
493
560
|
```typescript
|
|
@@ -599,10 +666,23 @@ pc.disconnect();
|
|
|
599
666
|
| `subscribeFuture(symbols)` | 期货行情 | `onFuture(QuoteData)` |
|
|
600
667
|
| `subscribeKline(symbols)` | K 线数据 | `onKline(KlineData)` |
|
|
601
668
|
| `subscribeAsset(account?)` | 资产变动 | `onAsset(AssetData)` |
|
|
669
|
+
| `subscribeAsset(options)` | 资产变动,`{ account?, assetQuoteType? }` | `onAsset(AssetData)` |
|
|
602
670
|
| `subscribePosition(account?)` | 持仓变动 | `onPosition(PositionData)` |
|
|
671
|
+
| `subscribePosition(options)` | 持仓变动,`{ account?, assetQuoteType? }` | `onPosition(PositionData)` |
|
|
603
672
|
| `subscribeOrder(account?)` | 订单状态 | `onOrder(OrderStatusData)` |
|
|
604
673
|
| `subscribeTransaction(account?)` | 成交明细 | `onTransaction(OrderTransactionData)` |
|
|
605
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
|
+
|
|
606
686
|
## 项目结构
|
|
607
687
|
|
|
608
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; } });
|