@anthropic-ai/sdk 0.92.0 → 0.94.0
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/CHANGELOG.md +16 -0
- package/client.d.mts +73 -0
- package/client.d.mts.map +1 -1
- package/client.d.ts +73 -0
- package/client.d.ts.map +1 -1
- package/client.js +258 -9
- package/client.js.map +1 -1
- package/client.mjs +258 -9
- package/client.mjs.map +1 -1
- package/core/credentials.d.mts +131 -0
- package/core/credentials.d.mts.map +1 -0
- package/core/credentials.d.ts +131 -0
- package/core/credentials.d.ts.map +1 -0
- package/core/credentials.js +317 -0
- package/core/credentials.js.map +1 -0
- package/core/credentials.mjs +277 -0
- package/core/credentials.mjs.map +1 -0
- package/internal/utils/time.d.mts +3 -0
- package/internal/utils/time.d.mts.map +1 -0
- package/internal/utils/time.d.ts +3 -0
- package/internal/utils/time.d.ts.map +1 -0
- package/internal/utils/time.js +8 -0
- package/internal/utils/time.js.map +1 -0
- package/internal/utils/time.mjs +5 -0
- package/internal/utils/time.mjs.map +1 -0
- package/lib/credentials/credential-chain.d.mts +39 -0
- package/lib/credentials/credential-chain.d.mts.map +1 -0
- package/lib/credentials/credential-chain.d.ts +39 -0
- package/lib/credentials/credential-chain.d.ts.map +1 -0
- package/lib/credentials/credential-chain.js +245 -0
- package/lib/credentials/credential-chain.js.map +1 -0
- package/lib/credentials/credential-chain.mjs +208 -0
- package/lib/credentials/credential-chain.mjs.map +1 -0
- package/lib/credentials/identity-token.d.mts +11 -0
- package/lib/credentials/identity-token.d.mts.map +1 -0
- package/lib/credentials/identity-token.d.ts +11 -0
- package/lib/credentials/identity-token.d.ts.map +1 -0
- package/lib/credentials/identity-token.js +72 -0
- package/lib/credentials/identity-token.js.map +1 -0
- package/lib/credentials/identity-token.mjs +35 -0
- package/lib/credentials/identity-token.mjs.map +1 -0
- package/lib/credentials/oidc-federation.d.mts +40 -0
- package/lib/credentials/oidc-federation.d.mts.map +1 -0
- package/lib/credentials/oidc-federation.d.ts +40 -0
- package/lib/credentials/oidc-federation.d.ts.map +1 -0
- package/lib/credentials/oidc-federation.js +82 -0
- package/lib/credentials/oidc-federation.js.map +1 -0
- package/lib/credentials/oidc-federation.mjs +79 -0
- package/lib/credentials/oidc-federation.mjs.map +1 -0
- package/lib/credentials/token-cache.d.mts +54 -0
- package/lib/credentials/token-cache.d.mts.map +1 -0
- package/lib/credentials/token-cache.d.ts +54 -0
- package/lib/credentials/token-cache.d.ts.map +1 -0
- package/lib/credentials/token-cache.js +112 -0
- package/lib/credentials/token-cache.js.map +1 -0
- package/lib/credentials/token-cache.mjs +108 -0
- package/lib/credentials/token-cache.mjs.map +1 -0
- package/lib/credentials/types.d.mts +96 -0
- package/lib/credentials/types.d.mts.map +1 -0
- package/lib/credentials/types.d.ts +96 -0
- package/lib/credentials/types.d.ts.map +1 -0
- package/lib/credentials/types.js +266 -0
- package/lib/credentials/types.js.map +1 -0
- package/lib/credentials/types.mjs +224 -0
- package/lib/credentials/types.mjs.map +1 -0
- package/lib/credentials/user-oauth.d.mts +21 -0
- package/lib/credentials/user-oauth.d.mts.map +1 -0
- package/lib/credentials/user-oauth.d.ts +21 -0
- package/lib/credentials/user-oauth.d.ts.map +1 -0
- package/lib/credentials/user-oauth.js +130 -0
- package/lib/credentials/user-oauth.js.map +1 -0
- package/lib/credentials/user-oauth.mjs +94 -0
- package/lib/credentials/user-oauth.mjs.map +1 -0
- package/lib/credentials.d.mts +4 -0
- package/lib/credentials.d.mts.map +1 -0
- package/lib/credentials.d.ts +4 -0
- package/lib/credentials.d.ts.map +1 -0
- package/lib/credentials.js +8 -0
- package/lib/credentials.js.map +1 -0
- package/lib/credentials.mjs +3 -0
- package/lib/credentials.mjs.map +1 -0
- package/package.json +1 -1
- package/src/client.ts +342 -14
- package/src/core/credentials.ts +379 -0
- package/src/internal/utils/time.ts +4 -0
- package/src/lib/credentials/credential-chain.ts +292 -0
- package/src/lib/credentials/identity-token.ts +37 -0
- package/src/lib/credentials/oidc-federation.ts +140 -0
- package/src/lib/credentials/token-cache.ts +130 -0
- package/src/lib/credentials/types.ts +295 -0
- package/src/lib/credentials/user-oauth.ts +144 -0
- package/src/lib/credentials.ts +3 -0
- package/src/version.ts +1 -1
- package/version.d.mts +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.mjs +1 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Fetch } from "../../internal/builtin-types.mjs";
|
|
2
|
+
import type { AccessTokenProvider, IdentityTokenProvider } from "./types.mjs";
|
|
3
|
+
export type OIDCFederationConfig = {
|
|
4
|
+
identityTokenProvider: IdentityTokenProvider;
|
|
5
|
+
federationRuleId: string;
|
|
6
|
+
organizationId: string;
|
|
7
|
+
serviceAccountId?: string | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Optional `wrkspc_*` tagged ID, or the literal `"default"` to scope the
|
|
10
|
+
* token to the organization's default workspace. When omitted the server
|
|
11
|
+
* picks the rule's sole enabled workspace, else the org default if the rule
|
|
12
|
+
* covers it. Required when the rule enables more than one non-default
|
|
13
|
+
* workspace, or to target a specific workspace other than the one the
|
|
14
|
+
* server would pick. The minted token is workspace-scoped: per-request
|
|
15
|
+
* workspace selection (the `anthropic-workspace-id` header) is not supported
|
|
16
|
+
* for federation tokens — switching workspaces requires a new token exchange
|
|
17
|
+
* with a different `workspaceId`.
|
|
18
|
+
*/
|
|
19
|
+
workspaceId?: string | undefined;
|
|
20
|
+
baseURL: string;
|
|
21
|
+
fetch: Fetch;
|
|
22
|
+
/**
|
|
23
|
+
* Overrides the outgoing User-Agent header on the token exchange. When
|
|
24
|
+
* empty, sends an SDK-identified UA so the token endpoint's access logs
|
|
25
|
+
* identify the caller.
|
|
26
|
+
*/
|
|
27
|
+
userAgent?: string | undefined;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Exchanges an external OIDC JWT for an Anthropic access token via the
|
|
31
|
+
* RFC 7523 jwt-bearer grant.
|
|
32
|
+
*
|
|
33
|
+
* Each invocation performs a fresh token exchange. Wrap in a
|
|
34
|
+
* {@link TokenCache} to avoid exchanging on every request.
|
|
35
|
+
*
|
|
36
|
+
* Federation grants do not return a refresh token — callers re-exchange
|
|
37
|
+
* their assertion on expiry.
|
|
38
|
+
*/
|
|
39
|
+
export declare function oidcFederationProvider(config: OIDCFederationConfig): AccessTokenProvider;
|
|
40
|
+
//# sourceMappingURL=oidc-federation.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oidc-federation.d.mts","sourceRoot":"","sources":["../../src/lib/credentials/oidc-federation.ts"],"names":[],"mappings":"OAAO,KAAK,EAAE,KAAK,EAAE;OACd,KAAK,EAAE,mBAAmB,EAAE,qBAAqB,EAAE;AAc1D,MAAM,MAAM,oBAAoB,GAAG;IACjC,qBAAqB,EAAE,qBAAqB,CAAC;IAC7C,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC;;;;;;;;;;OAUG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,KAAK,CAAC;IACb;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,oBAAoB,GAAG,mBAAmB,CAuFxF"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Fetch } from "../../internal/builtin-types.js";
|
|
2
|
+
import type { AccessTokenProvider, IdentityTokenProvider } from "./types.js";
|
|
3
|
+
export type OIDCFederationConfig = {
|
|
4
|
+
identityTokenProvider: IdentityTokenProvider;
|
|
5
|
+
federationRuleId: string;
|
|
6
|
+
organizationId: string;
|
|
7
|
+
serviceAccountId?: string | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Optional `wrkspc_*` tagged ID, or the literal `"default"` to scope the
|
|
10
|
+
* token to the organization's default workspace. When omitted the server
|
|
11
|
+
* picks the rule's sole enabled workspace, else the org default if the rule
|
|
12
|
+
* covers it. Required when the rule enables more than one non-default
|
|
13
|
+
* workspace, or to target a specific workspace other than the one the
|
|
14
|
+
* server would pick. The minted token is workspace-scoped: per-request
|
|
15
|
+
* workspace selection (the `anthropic-workspace-id` header) is not supported
|
|
16
|
+
* for federation tokens — switching workspaces requires a new token exchange
|
|
17
|
+
* with a different `workspaceId`.
|
|
18
|
+
*/
|
|
19
|
+
workspaceId?: string | undefined;
|
|
20
|
+
baseURL: string;
|
|
21
|
+
fetch: Fetch;
|
|
22
|
+
/**
|
|
23
|
+
* Overrides the outgoing User-Agent header on the token exchange. When
|
|
24
|
+
* empty, sends an SDK-identified UA so the token endpoint's access logs
|
|
25
|
+
* identify the caller.
|
|
26
|
+
*/
|
|
27
|
+
userAgent?: string | undefined;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Exchanges an external OIDC JWT for an Anthropic access token via the
|
|
31
|
+
* RFC 7523 jwt-bearer grant.
|
|
32
|
+
*
|
|
33
|
+
* Each invocation performs a fresh token exchange. Wrap in a
|
|
34
|
+
* {@link TokenCache} to avoid exchanging on every request.
|
|
35
|
+
*
|
|
36
|
+
* Federation grants do not return a refresh token — callers re-exchange
|
|
37
|
+
* their assertion on expiry.
|
|
38
|
+
*/
|
|
39
|
+
export declare function oidcFederationProvider(config: OIDCFederationConfig): AccessTokenProvider;
|
|
40
|
+
//# sourceMappingURL=oidc-federation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oidc-federation.d.ts","sourceRoot":"","sources":["../../src/lib/credentials/oidc-federation.ts"],"names":[],"mappings":"OAAO,KAAK,EAAE,KAAK,EAAE;OACd,KAAK,EAAE,mBAAmB,EAAE,qBAAqB,EAAE;AAc1D,MAAM,MAAM,oBAAoB,GAAG;IACjC,qBAAqB,EAAE,qBAAqB,CAAC;IAC7C,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC;;;;;;;;;;OAUG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,KAAK,CAAC;IACb;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,oBAAoB,GAAG,mBAAmB,CAuFxF"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.oidcFederationProvider = oidcFederationProvider;
|
|
4
|
+
const types_1 = require("./types.js");
|
|
5
|
+
const time_1 = require("../../internal/utils/time.js");
|
|
6
|
+
const version_1 = require("../../version.js");
|
|
7
|
+
/**
|
|
8
|
+
* Exchanges an external OIDC JWT for an Anthropic access token via the
|
|
9
|
+
* RFC 7523 jwt-bearer grant.
|
|
10
|
+
*
|
|
11
|
+
* Each invocation performs a fresh token exchange. Wrap in a
|
|
12
|
+
* {@link TokenCache} to avoid exchanging on every request.
|
|
13
|
+
*
|
|
14
|
+
* Federation grants do not return a refresh token — callers re-exchange
|
|
15
|
+
* their assertion on expiry.
|
|
16
|
+
*/
|
|
17
|
+
function oidcFederationProvider(config) {
|
|
18
|
+
return async () => {
|
|
19
|
+
(0, types_1.requireSecureTokenEndpoint)(config.baseURL);
|
|
20
|
+
const jwt = await config.identityTokenProvider();
|
|
21
|
+
// The token endpoint enforces a 16 KiB assertion limit; surface a clear
|
|
22
|
+
// client-side error so misconfigured projected-token sources are
|
|
23
|
+
// diagnosable without a server round-trip.
|
|
24
|
+
if (jwt.length > 16 * 1024) {
|
|
25
|
+
throw new types_1.WorkloadIdentityError(`Identity token is ${Math.ceil(jwt.length / 1024)} KiB, exceeds the 16 KiB assertion limit`);
|
|
26
|
+
}
|
|
27
|
+
const body = {
|
|
28
|
+
grant_type: types_1.GRANT_TYPE_JWT_BEARER,
|
|
29
|
+
assertion: jwt,
|
|
30
|
+
federation_rule_id: config.federationRuleId,
|
|
31
|
+
organization_id: config.organizationId,
|
|
32
|
+
};
|
|
33
|
+
if (config.serviceAccountId) {
|
|
34
|
+
body['service_account_id'] = config.serviceAccountId;
|
|
35
|
+
}
|
|
36
|
+
if (config.workspaceId) {
|
|
37
|
+
body['workspace_id'] = config.workspaceId;
|
|
38
|
+
}
|
|
39
|
+
const url = `${config.baseURL}${types_1.TOKEN_ENDPOINT}`;
|
|
40
|
+
let resp;
|
|
41
|
+
try {
|
|
42
|
+
resp = await config.fetch(url, {
|
|
43
|
+
method: 'POST',
|
|
44
|
+
headers: {
|
|
45
|
+
'Content-Type': 'application/json',
|
|
46
|
+
'anthropic-beta': `${types_1.OAUTH_API_BETA_HEADER},${types_1.FEDERATION_BETA_HEADER}`,
|
|
47
|
+
'User-Agent': config.userAgent || `anthropic-sdk-typescript/${version_1.VERSION} oidcFederationProvider`,
|
|
48
|
+
},
|
|
49
|
+
body: JSON.stringify(body),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
throw new types_1.WorkloadIdentityError(`Failed to reach token endpoint ${url}: ${err}`);
|
|
54
|
+
}
|
|
55
|
+
const requestId = resp.headers.get('Request-Id');
|
|
56
|
+
if (!resp.ok) {
|
|
57
|
+
const text = await resp.text().catch(() => '');
|
|
58
|
+
const redacted = (0, types_1.redactSensitive)(text);
|
|
59
|
+
// A 401 is hard to debug from the status code alone, so surface
|
|
60
|
+
// guidance: check the federation rule, optionally set a workspace ID
|
|
61
|
+
// (the most common fix when no workspaceId is configured), and point at
|
|
62
|
+
// the Workload identity page in Claude Console for the server-side
|
|
63
|
+
// authentication event log. Other statuses (5xx, 400, ...) get no hint.
|
|
64
|
+
let hint = '';
|
|
65
|
+
if (resp.status === 401) {
|
|
66
|
+
const hintMiddle = config.workspaceId ? '' : ("If your federation rule is scoped to multiple workspaces, set the ANTHROPIC_WORKSPACE_ID environment variable, the 'workspace_id' config key, or the `workspaceId` option. ");
|
|
67
|
+
hint = ` Ensure your federation rule matches your identity token. ${hintMiddle}View your authentication events in the Workload identity page of Claude Console for more details.`;
|
|
68
|
+
}
|
|
69
|
+
throw new types_1.WorkloadIdentityError(`Token exchange failed with status ${resp.status}${requestId ? ` (request-id ${requestId})` : ''}: ${redacted}${hint}`, resp.status, redacted, requestId);
|
|
70
|
+
}
|
|
71
|
+
const data = await (0, types_1.parseTokenResponse)(resp, requestId);
|
|
72
|
+
const expiresIn = Number(data.expires_in);
|
|
73
|
+
if (!Number.isFinite(expiresIn)) {
|
|
74
|
+
throw new types_1.WorkloadIdentityError(`Token endpoint response missing required fields: ${JSON.stringify((0, types_1.redactSensitive)(data))}`, resp.status, (0, types_1.redactSensitive)(data), requestId);
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
token: data.access_token,
|
|
78
|
+
expiresAt: (0, time_1.nowAsSeconds)() + expiresIn,
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=oidc-federation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oidc-federation.js","sourceRoot":"","sources":["../../src/lib/credentials/oidc-federation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { FEDERATION_BETA_HEADER, GRANT_TYPE_JWT_BEARER, OAUTH_API_BETA_HEADER, TOKEN_ENDPOINT, WorkloadIdentityError, parseTokenResponse, redactSensitive, requireSecureTokenEndpoint, } from "./types.mjs";
|
|
2
|
+
import { nowAsSeconds } from "../../internal/utils/time.mjs";
|
|
3
|
+
import { VERSION } from "../../version.mjs";
|
|
4
|
+
/**
|
|
5
|
+
* Exchanges an external OIDC JWT for an Anthropic access token via the
|
|
6
|
+
* RFC 7523 jwt-bearer grant.
|
|
7
|
+
*
|
|
8
|
+
* Each invocation performs a fresh token exchange. Wrap in a
|
|
9
|
+
* {@link TokenCache} to avoid exchanging on every request.
|
|
10
|
+
*
|
|
11
|
+
* Federation grants do not return a refresh token — callers re-exchange
|
|
12
|
+
* their assertion on expiry.
|
|
13
|
+
*/
|
|
14
|
+
export function oidcFederationProvider(config) {
|
|
15
|
+
return async () => {
|
|
16
|
+
requireSecureTokenEndpoint(config.baseURL);
|
|
17
|
+
const jwt = await config.identityTokenProvider();
|
|
18
|
+
// The token endpoint enforces a 16 KiB assertion limit; surface a clear
|
|
19
|
+
// client-side error so misconfigured projected-token sources are
|
|
20
|
+
// diagnosable without a server round-trip.
|
|
21
|
+
if (jwt.length > 16 * 1024) {
|
|
22
|
+
throw new WorkloadIdentityError(`Identity token is ${Math.ceil(jwt.length / 1024)} KiB, exceeds the 16 KiB assertion limit`);
|
|
23
|
+
}
|
|
24
|
+
const body = {
|
|
25
|
+
grant_type: GRANT_TYPE_JWT_BEARER,
|
|
26
|
+
assertion: jwt,
|
|
27
|
+
federation_rule_id: config.federationRuleId,
|
|
28
|
+
organization_id: config.organizationId,
|
|
29
|
+
};
|
|
30
|
+
if (config.serviceAccountId) {
|
|
31
|
+
body['service_account_id'] = config.serviceAccountId;
|
|
32
|
+
}
|
|
33
|
+
if (config.workspaceId) {
|
|
34
|
+
body['workspace_id'] = config.workspaceId;
|
|
35
|
+
}
|
|
36
|
+
const url = `${config.baseURL}${TOKEN_ENDPOINT}`;
|
|
37
|
+
let resp;
|
|
38
|
+
try {
|
|
39
|
+
resp = await config.fetch(url, {
|
|
40
|
+
method: 'POST',
|
|
41
|
+
headers: {
|
|
42
|
+
'Content-Type': 'application/json',
|
|
43
|
+
'anthropic-beta': `${OAUTH_API_BETA_HEADER},${FEDERATION_BETA_HEADER}`,
|
|
44
|
+
'User-Agent': config.userAgent || `anthropic-sdk-typescript/${VERSION} oidcFederationProvider`,
|
|
45
|
+
},
|
|
46
|
+
body: JSON.stringify(body),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
throw new WorkloadIdentityError(`Failed to reach token endpoint ${url}: ${err}`);
|
|
51
|
+
}
|
|
52
|
+
const requestId = resp.headers.get('Request-Id');
|
|
53
|
+
if (!resp.ok) {
|
|
54
|
+
const text = await resp.text().catch(() => '');
|
|
55
|
+
const redacted = redactSensitive(text);
|
|
56
|
+
// A 401 is hard to debug from the status code alone, so surface
|
|
57
|
+
// guidance: check the federation rule, optionally set a workspace ID
|
|
58
|
+
// (the most common fix when no workspaceId is configured), and point at
|
|
59
|
+
// the Workload identity page in Claude Console for the server-side
|
|
60
|
+
// authentication event log. Other statuses (5xx, 400, ...) get no hint.
|
|
61
|
+
let hint = '';
|
|
62
|
+
if (resp.status === 401) {
|
|
63
|
+
const hintMiddle = config.workspaceId ? '' : ("If your federation rule is scoped to multiple workspaces, set the ANTHROPIC_WORKSPACE_ID environment variable, the 'workspace_id' config key, or the `workspaceId` option. ");
|
|
64
|
+
hint = ` Ensure your federation rule matches your identity token. ${hintMiddle}View your authentication events in the Workload identity page of Claude Console for more details.`;
|
|
65
|
+
}
|
|
66
|
+
throw new WorkloadIdentityError(`Token exchange failed with status ${resp.status}${requestId ? ` (request-id ${requestId})` : ''}: ${redacted}${hint}`, resp.status, redacted, requestId);
|
|
67
|
+
}
|
|
68
|
+
const data = await parseTokenResponse(resp, requestId);
|
|
69
|
+
const expiresIn = Number(data.expires_in);
|
|
70
|
+
if (!Number.isFinite(expiresIn)) {
|
|
71
|
+
throw new WorkloadIdentityError(`Token endpoint response missing required fields: ${JSON.stringify(redactSensitive(data))}`, resp.status, redactSensitive(data), requestId);
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
token: data.access_token,
|
|
75
|
+
expiresAt: nowAsSeconds() + expiresIn,
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=oidc-federation.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oidc-federation.mjs","sourceRoot":"","sources":["../../src/lib/credentials/oidc-federation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { AccessTokenProvider } from "./types.mjs";
|
|
2
|
+
/**
|
|
3
|
+
* Wraps an {@link AccessTokenProvider} with two-tier proactive refresh
|
|
4
|
+
* and concurrent deduplication.
|
|
5
|
+
*
|
|
6
|
+
* Refresh policy on each {@link getToken} call:
|
|
7
|
+
*
|
|
8
|
+
* - No cached token → call provider (blocking), cache, return.
|
|
9
|
+
* - Cached with `expiresAt == null` → return cached forever.
|
|
10
|
+
* - More than 120s remaining → return cached.
|
|
11
|
+
* - 30–120s remaining (advisory window) → return stale token immediately,
|
|
12
|
+
* kick off background refresh. On failure, log and keep stale.
|
|
13
|
+
* - Less than 30s remaining or expired (mandatory) → block and refresh.
|
|
14
|
+
* On failure, throw.
|
|
15
|
+
*
|
|
16
|
+
* Concurrent mandatory callers coalesce into a single provider call.
|
|
17
|
+
*/
|
|
18
|
+
export declare class TokenCache {
|
|
19
|
+
private provider;
|
|
20
|
+
private cached;
|
|
21
|
+
private pendingRefresh;
|
|
22
|
+
private nextForce;
|
|
23
|
+
private lastAdvisoryError;
|
|
24
|
+
private onAdvisoryRefreshError;
|
|
25
|
+
constructor(provider: AccessTokenProvider, onAdvisoryRefreshError?: (err: unknown) => void);
|
|
26
|
+
getToken(): Promise<string>;
|
|
27
|
+
/**
|
|
28
|
+
* Clears the cached token and marks the next {@link getToken} as a forced
|
|
29
|
+
* refresh, so the underlying provider bypasses any on-disk freshness check.
|
|
30
|
+
* Called after a 401 — the server has just told us the token is bad even
|
|
31
|
+
* if its `expires_at` still looks fresh.
|
|
32
|
+
*/
|
|
33
|
+
invalidate(): void;
|
|
34
|
+
/**
|
|
35
|
+
* Mandatory refresh. Joins any in-flight refresh unless forced — a forced
|
|
36
|
+
* refresh must not coalesce into a non-forced one that may re-serve the
|
|
37
|
+
* same stale disk token.
|
|
38
|
+
*/
|
|
39
|
+
private refresh;
|
|
40
|
+
/**
|
|
41
|
+
* Advisory background refresh. Shares the same in-flight promise as
|
|
42
|
+
* mandatory refreshes for deduplication, but swallows errors so the
|
|
43
|
+
* stale cached token keeps being served. Backs off for
|
|
44
|
+
* {@link ADVISORY_REFRESH_BACKOFF_IN_SECONDS} after a failure so an
|
|
45
|
+
* outage during the advisory window doesn't hammer the token endpoint.
|
|
46
|
+
*/
|
|
47
|
+
private backgroundRefresh;
|
|
48
|
+
/**
|
|
49
|
+
* Core refresh. Sets {@link pendingRefresh} so concurrent callers
|
|
50
|
+
* (both advisory and mandatory) coalesce into a single provider call.
|
|
51
|
+
*/
|
|
52
|
+
private doRefresh;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=token-cache.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-cache.d.mts","sourceRoot":"","sources":["../../src/lib/credentials/token-cache.ts"],"names":[],"mappings":"OAAO,KAAK,EAAe,mBAAmB,EAAE;AAQhD;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,cAAc,CAAqC;IAC3D,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,sBAAsB,CAAuC;gBAEzD,QAAQ,EAAE,mBAAmB,EAAE,sBAAsB,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI;IAKpF,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;IA6BjC;;;;;OAKG;IACH,UAAU,IAAI,IAAI;IAKlB;;;;OAIG;IACH,OAAO,CAAC,OAAO;IAOf;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IAezB;;;OAGG;IACH,OAAO,CAAC,SAAS;CAclB"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { AccessTokenProvider } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Wraps an {@link AccessTokenProvider} with two-tier proactive refresh
|
|
4
|
+
* and concurrent deduplication.
|
|
5
|
+
*
|
|
6
|
+
* Refresh policy on each {@link getToken} call:
|
|
7
|
+
*
|
|
8
|
+
* - No cached token → call provider (blocking), cache, return.
|
|
9
|
+
* - Cached with `expiresAt == null` → return cached forever.
|
|
10
|
+
* - More than 120s remaining → return cached.
|
|
11
|
+
* - 30–120s remaining (advisory window) → return stale token immediately,
|
|
12
|
+
* kick off background refresh. On failure, log and keep stale.
|
|
13
|
+
* - Less than 30s remaining or expired (mandatory) → block and refresh.
|
|
14
|
+
* On failure, throw.
|
|
15
|
+
*
|
|
16
|
+
* Concurrent mandatory callers coalesce into a single provider call.
|
|
17
|
+
*/
|
|
18
|
+
export declare class TokenCache {
|
|
19
|
+
private provider;
|
|
20
|
+
private cached;
|
|
21
|
+
private pendingRefresh;
|
|
22
|
+
private nextForce;
|
|
23
|
+
private lastAdvisoryError;
|
|
24
|
+
private onAdvisoryRefreshError;
|
|
25
|
+
constructor(provider: AccessTokenProvider, onAdvisoryRefreshError?: (err: unknown) => void);
|
|
26
|
+
getToken(): Promise<string>;
|
|
27
|
+
/**
|
|
28
|
+
* Clears the cached token and marks the next {@link getToken} as a forced
|
|
29
|
+
* refresh, so the underlying provider bypasses any on-disk freshness check.
|
|
30
|
+
* Called after a 401 — the server has just told us the token is bad even
|
|
31
|
+
* if its `expires_at` still looks fresh.
|
|
32
|
+
*/
|
|
33
|
+
invalidate(): void;
|
|
34
|
+
/**
|
|
35
|
+
* Mandatory refresh. Joins any in-flight refresh unless forced — a forced
|
|
36
|
+
* refresh must not coalesce into a non-forced one that may re-serve the
|
|
37
|
+
* same stale disk token.
|
|
38
|
+
*/
|
|
39
|
+
private refresh;
|
|
40
|
+
/**
|
|
41
|
+
* Advisory background refresh. Shares the same in-flight promise as
|
|
42
|
+
* mandatory refreshes for deduplication, but swallows errors so the
|
|
43
|
+
* stale cached token keeps being served. Backs off for
|
|
44
|
+
* {@link ADVISORY_REFRESH_BACKOFF_IN_SECONDS} after a failure so an
|
|
45
|
+
* outage during the advisory window doesn't hammer the token endpoint.
|
|
46
|
+
*/
|
|
47
|
+
private backgroundRefresh;
|
|
48
|
+
/**
|
|
49
|
+
* Core refresh. Sets {@link pendingRefresh} so concurrent callers
|
|
50
|
+
* (both advisory and mandatory) coalesce into a single provider call.
|
|
51
|
+
*/
|
|
52
|
+
private doRefresh;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=token-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-cache.d.ts","sourceRoot":"","sources":["../../src/lib/credentials/token-cache.ts"],"names":[],"mappings":"OAAO,KAAK,EAAe,mBAAmB,EAAE;AAQhD;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,cAAc,CAAqC;IAC3D,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,sBAAsB,CAAuC;gBAEzD,QAAQ,EAAE,mBAAmB,EAAE,sBAAsB,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI;IAKpF,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;IA6BjC;;;;;OAKG;IACH,UAAU,IAAI,IAAI;IAKlB;;;;OAIG;IACH,OAAO,CAAC,OAAO;IAOf;;;;;;OAMG;IACH,OAAO,CAAC,iBAAiB;IAezB;;;OAGG;IACH,OAAO,CAAC,SAAS;CAclB"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TokenCache = void 0;
|
|
4
|
+
const types_1 = require("./types.js");
|
|
5
|
+
const time_1 = require("../../internal/utils/time.js");
|
|
6
|
+
/**
|
|
7
|
+
* Wraps an {@link AccessTokenProvider} with two-tier proactive refresh
|
|
8
|
+
* and concurrent deduplication.
|
|
9
|
+
*
|
|
10
|
+
* Refresh policy on each {@link getToken} call:
|
|
11
|
+
*
|
|
12
|
+
* - No cached token → call provider (blocking), cache, return.
|
|
13
|
+
* - Cached with `expiresAt == null` → return cached forever.
|
|
14
|
+
* - More than 120s remaining → return cached.
|
|
15
|
+
* - 30–120s remaining (advisory window) → return stale token immediately,
|
|
16
|
+
* kick off background refresh. On failure, log and keep stale.
|
|
17
|
+
* - Less than 30s remaining or expired (mandatory) → block and refresh.
|
|
18
|
+
* On failure, throw.
|
|
19
|
+
*
|
|
20
|
+
* Concurrent mandatory callers coalesce into a single provider call.
|
|
21
|
+
*/
|
|
22
|
+
class TokenCache {
|
|
23
|
+
constructor(provider, onAdvisoryRefreshError) {
|
|
24
|
+
this.cached = null;
|
|
25
|
+
this.pendingRefresh = null;
|
|
26
|
+
this.nextForce = false;
|
|
27
|
+
this.lastAdvisoryError = 0;
|
|
28
|
+
this.provider = provider;
|
|
29
|
+
this.onAdvisoryRefreshError = onAdvisoryRefreshError;
|
|
30
|
+
}
|
|
31
|
+
async getToken() {
|
|
32
|
+
const force = this.nextForce;
|
|
33
|
+
this.nextForce = false;
|
|
34
|
+
const cached = this.cached;
|
|
35
|
+
if (force || cached == null) {
|
|
36
|
+
const token = await this.refresh(force);
|
|
37
|
+
return token.token;
|
|
38
|
+
}
|
|
39
|
+
if (cached.expiresAt == null) {
|
|
40
|
+
return cached.token;
|
|
41
|
+
}
|
|
42
|
+
const remaining = cached.expiresAt - (0, time_1.nowAsSeconds)();
|
|
43
|
+
if (remaining > types_1.ADVISORY_REFRESH_THRESHOLD_IN_SECONDS) {
|
|
44
|
+
return cached.token;
|
|
45
|
+
}
|
|
46
|
+
if (remaining > types_1.MANDATORY_REFRESH_THRESHOLD_IN_SECONDS) {
|
|
47
|
+
this.backgroundRefresh();
|
|
48
|
+
return cached.token;
|
|
49
|
+
}
|
|
50
|
+
const token = await this.refresh();
|
|
51
|
+
return token.token;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Clears the cached token and marks the next {@link getToken} as a forced
|
|
55
|
+
* refresh, so the underlying provider bypasses any on-disk freshness check.
|
|
56
|
+
* Called after a 401 — the server has just told us the token is bad even
|
|
57
|
+
* if its `expires_at` still looks fresh.
|
|
58
|
+
*/
|
|
59
|
+
invalidate() {
|
|
60
|
+
this.cached = null;
|
|
61
|
+
this.nextForce = true;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Mandatory refresh. Joins any in-flight refresh unless forced — a forced
|
|
65
|
+
* refresh must not coalesce into a non-forced one that may re-serve the
|
|
66
|
+
* same stale disk token.
|
|
67
|
+
*/
|
|
68
|
+
refresh(force = false) {
|
|
69
|
+
if (this.pendingRefresh && !force) {
|
|
70
|
+
return this.pendingRefresh;
|
|
71
|
+
}
|
|
72
|
+
return this.doRefresh(force);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Advisory background refresh. Shares the same in-flight promise as
|
|
76
|
+
* mandatory refreshes for deduplication, but swallows errors so the
|
|
77
|
+
* stale cached token keeps being served. Backs off for
|
|
78
|
+
* {@link ADVISORY_REFRESH_BACKOFF_IN_SECONDS} after a failure so an
|
|
79
|
+
* outage during the advisory window doesn't hammer the token endpoint.
|
|
80
|
+
*/
|
|
81
|
+
backgroundRefresh() {
|
|
82
|
+
if (this.pendingRefresh) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if ((0, time_1.nowAsSeconds)() - this.lastAdvisoryError < types_1.ADVISORY_REFRESH_BACKOFF_IN_SECONDS) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
this.doRefresh().catch((err) => {
|
|
89
|
+
this.lastAdvisoryError = (0, time_1.nowAsSeconds)();
|
|
90
|
+
// Advisory failure: keep serving the stale cached token, but surface
|
|
91
|
+
// the error to the caller-provided hook so it can be logged.
|
|
92
|
+
this.onAdvisoryRefreshError?.(err);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Core refresh. Sets {@link pendingRefresh} so concurrent callers
|
|
97
|
+
* (both advisory and mandatory) coalesce into a single provider call.
|
|
98
|
+
*/
|
|
99
|
+
doRefresh(force = false) {
|
|
100
|
+
this.pendingRefresh = this.provider(force ? { forceRefresh: true } : undefined).then((token) => {
|
|
101
|
+
this.cached = token;
|
|
102
|
+
this.pendingRefresh = null;
|
|
103
|
+
return token;
|
|
104
|
+
}, (err) => {
|
|
105
|
+
this.pendingRefresh = null;
|
|
106
|
+
throw err;
|
|
107
|
+
});
|
|
108
|
+
return this.pendingRefresh;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
exports.TokenCache = TokenCache;
|
|
112
|
+
//# sourceMappingURL=token-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-cache.js","sourceRoot":"","sources":["../../src/lib/credentials/token-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { ADVISORY_REFRESH_BACKOFF_IN_SECONDS, ADVISORY_REFRESH_THRESHOLD_IN_SECONDS, MANDATORY_REFRESH_THRESHOLD_IN_SECONDS, } from "./types.mjs";
|
|
2
|
+
import { nowAsSeconds } from "../../internal/utils/time.mjs";
|
|
3
|
+
/**
|
|
4
|
+
* Wraps an {@link AccessTokenProvider} with two-tier proactive refresh
|
|
5
|
+
* and concurrent deduplication.
|
|
6
|
+
*
|
|
7
|
+
* Refresh policy on each {@link getToken} call:
|
|
8
|
+
*
|
|
9
|
+
* - No cached token → call provider (blocking), cache, return.
|
|
10
|
+
* - Cached with `expiresAt == null` → return cached forever.
|
|
11
|
+
* - More than 120s remaining → return cached.
|
|
12
|
+
* - 30–120s remaining (advisory window) → return stale token immediately,
|
|
13
|
+
* kick off background refresh. On failure, log and keep stale.
|
|
14
|
+
* - Less than 30s remaining or expired (mandatory) → block and refresh.
|
|
15
|
+
* On failure, throw.
|
|
16
|
+
*
|
|
17
|
+
* Concurrent mandatory callers coalesce into a single provider call.
|
|
18
|
+
*/
|
|
19
|
+
export class TokenCache {
|
|
20
|
+
constructor(provider, onAdvisoryRefreshError) {
|
|
21
|
+
this.cached = null;
|
|
22
|
+
this.pendingRefresh = null;
|
|
23
|
+
this.nextForce = false;
|
|
24
|
+
this.lastAdvisoryError = 0;
|
|
25
|
+
this.provider = provider;
|
|
26
|
+
this.onAdvisoryRefreshError = onAdvisoryRefreshError;
|
|
27
|
+
}
|
|
28
|
+
async getToken() {
|
|
29
|
+
const force = this.nextForce;
|
|
30
|
+
this.nextForce = false;
|
|
31
|
+
const cached = this.cached;
|
|
32
|
+
if (force || cached == null) {
|
|
33
|
+
const token = await this.refresh(force);
|
|
34
|
+
return token.token;
|
|
35
|
+
}
|
|
36
|
+
if (cached.expiresAt == null) {
|
|
37
|
+
return cached.token;
|
|
38
|
+
}
|
|
39
|
+
const remaining = cached.expiresAt - nowAsSeconds();
|
|
40
|
+
if (remaining > ADVISORY_REFRESH_THRESHOLD_IN_SECONDS) {
|
|
41
|
+
return cached.token;
|
|
42
|
+
}
|
|
43
|
+
if (remaining > MANDATORY_REFRESH_THRESHOLD_IN_SECONDS) {
|
|
44
|
+
this.backgroundRefresh();
|
|
45
|
+
return cached.token;
|
|
46
|
+
}
|
|
47
|
+
const token = await this.refresh();
|
|
48
|
+
return token.token;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Clears the cached token and marks the next {@link getToken} as a forced
|
|
52
|
+
* refresh, so the underlying provider bypasses any on-disk freshness check.
|
|
53
|
+
* Called after a 401 — the server has just told us the token is bad even
|
|
54
|
+
* if its `expires_at` still looks fresh.
|
|
55
|
+
*/
|
|
56
|
+
invalidate() {
|
|
57
|
+
this.cached = null;
|
|
58
|
+
this.nextForce = true;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Mandatory refresh. Joins any in-flight refresh unless forced — a forced
|
|
62
|
+
* refresh must not coalesce into a non-forced one that may re-serve the
|
|
63
|
+
* same stale disk token.
|
|
64
|
+
*/
|
|
65
|
+
refresh(force = false) {
|
|
66
|
+
if (this.pendingRefresh && !force) {
|
|
67
|
+
return this.pendingRefresh;
|
|
68
|
+
}
|
|
69
|
+
return this.doRefresh(force);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Advisory background refresh. Shares the same in-flight promise as
|
|
73
|
+
* mandatory refreshes for deduplication, but swallows errors so the
|
|
74
|
+
* stale cached token keeps being served. Backs off for
|
|
75
|
+
* {@link ADVISORY_REFRESH_BACKOFF_IN_SECONDS} after a failure so an
|
|
76
|
+
* outage during the advisory window doesn't hammer the token endpoint.
|
|
77
|
+
*/
|
|
78
|
+
backgroundRefresh() {
|
|
79
|
+
if (this.pendingRefresh) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (nowAsSeconds() - this.lastAdvisoryError < ADVISORY_REFRESH_BACKOFF_IN_SECONDS) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
this.doRefresh().catch((err) => {
|
|
86
|
+
this.lastAdvisoryError = nowAsSeconds();
|
|
87
|
+
// Advisory failure: keep serving the stale cached token, but surface
|
|
88
|
+
// the error to the caller-provided hook so it can be logged.
|
|
89
|
+
this.onAdvisoryRefreshError?.(err);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Core refresh. Sets {@link pendingRefresh} so concurrent callers
|
|
94
|
+
* (both advisory and mandatory) coalesce into a single provider call.
|
|
95
|
+
*/
|
|
96
|
+
doRefresh(force = false) {
|
|
97
|
+
this.pendingRefresh = this.provider(force ? { forceRefresh: true } : undefined).then((token) => {
|
|
98
|
+
this.cached = token;
|
|
99
|
+
this.pendingRefresh = null;
|
|
100
|
+
return token;
|
|
101
|
+
}, (err) => {
|
|
102
|
+
this.pendingRefresh = null;
|
|
103
|
+
throw err;
|
|
104
|
+
});
|
|
105
|
+
return this.pendingRefresh;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=token-cache.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-cache.mjs","sourceRoot":"","sources":["../../src/lib/credentials/token-cache.ts"],"names":[],"mappings":"OACO,EACL,mCAAmC,EACnC,qCAAqC,EACrC,sCAAsC,GACvC;OACM,EAAE,YAAY,EAAE;AAEvB;;;;;;;;;;;;;;;GAeG;AACH,MAAM,OAAO,UAAU;IAQrB,YAAY,QAA6B,EAAE,sBAA+C;QANlF,WAAM,GAAuB,IAAI,CAAC;QAClC,mBAAc,GAAgC,IAAI,CAAC;QACnD,cAAS,GAAG,KAAK,CAAC;QAClB,sBAAiB,GAAG,CAAC,CAAC;QAI5B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,sBAAsB,GAAG,sBAAsB,CAAC;IACvD,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC;QAC7B,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC;QACvB,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAE3B,IAAI,KAAK,IAAI,MAAM,IAAI,IAAI,EAAE,CAAC;YAC5B,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YACxC,OAAO,KAAK,CAAC,KAAK,CAAC;QACrB,CAAC;QAED,IAAI,MAAM,CAAC,SAAS,IAAI,IAAI,EAAE,CAAC;YAC7B,OAAO,MAAM,CAAC,KAAK,CAAC;QACtB,CAAC;QAED,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,GAAG,YAAY,EAAE,CAAC;QAEpD,IAAI,SAAS,GAAG,qCAAqC,EAAE,CAAC;YACtD,OAAO,MAAM,CAAC,KAAK,CAAC;QACtB,CAAC;QAED,IAAI,SAAS,GAAG,sCAAsC,EAAE,CAAC;YACvD,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACzB,OAAO,MAAM,CAAC,KAAK,CAAC;QACtB,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACnC,OAAO,KAAK,CAAC,KAAK,CAAC;IACrB,CAAC;IAED;;;;;OAKG;IACH,UAAU;QACR,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;IACxB,CAAC;IAED;;;;OAIG;IACK,OAAO,CAAC,KAAK,GAAG,KAAK;QAC3B,IAAI,IAAI,CAAC,cAAc,IAAI,CAAC,KAAK,EAAE,CAAC;YAClC,OAAO,IAAI,CAAC,cAAc,CAAC;QAC7B,CAAC;QACD,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IAED;;;;;;OAMG;IACK,iBAAiB;QACvB,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,OAAO;QACT,CAAC;QACD,IAAI,YAAY,EAAE,GAAG,IAAI,CAAC,iBAAiB,GAAG,mCAAmC,EAAE,CAAC;YAClF,OAAO;QACT,CAAC;QACD,IAAI,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;YAC7B,IAAI,CAAC,iBAAiB,GAAG,YAAY,EAAE,CAAC;YACxC,qEAAqE;YACrE,6DAA6D;YAC7D,IAAI,CAAC,sBAAsB,EAAE,CAAC,GAAG,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;OAGG;IACK,SAAS,CAAC,KAAK,GAAG,KAAK;QAC7B,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAClF,CAAC,KAAK,EAAE,EAAE;YACR,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;YACpB,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;YAC3B,OAAO,KAAK,CAAC;QACf,CAAC,EACD,CAAC,GAAG,EAAE,EAAE;YACN,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;YAC3B,MAAM,GAAG,CAAC;QACZ,CAAC,CACF,CAAC;QACF,OAAO,IAAI,CAAC,cAAc,CAAC;IAC7B,CAAC;CACF"}
|