"sourcesContent": ["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport type { AccessToken, GetTokenOptions, TokenCredential } from \"@azure/core-auth\";\nimport { delay } from \"@azure/core-util\";\n\n/**\n * A function that gets a promise of an access token and allows providing\n * options.\n *\n * @param options - the options to pass to the underlying token provider\n */\nexport type AccessTokenGetter = (\n scopes: string | string[],\n options: GetTokenOptions,\n) => Promise<AccessToken>;\n\nexport interface TokenCyclerOptions {\n /**\n * The window of time before token expiration during which the token will be\n * considered unusable due to risk of the token expiring before sending the\n * request.\n *\n * This will only become meaningful if the refresh fails for over\n * (refreshWindow - forcedRefreshWindow) milliseconds.\n */\n forcedRefreshWindowInMs: number;\n /**\n * Interval in milliseconds to retry failed token refreshes.\n */\n retryIntervalInMs: number;\n /**\n * The window of time before token expiration during which\n * we will attempt to refresh the token.\n */\n refreshWindowInMs: number;\n}\n\n// Default options for the cycler if none are provided\nexport const DEFAULT_CYCLER_OPTIONS: TokenCyclerOptions = {\n forcedRefreshWindowInMs: 1000, // Force waiting for a refresh 1s before the token expires\n retryIntervalInMs: 3000, // Allow refresh attempts every 3s\n refreshWindowInMs: 1000 * 60 * 2, // Start refreshing 2m before expiry\n};\n\n/**\n * Converts an an unreliable access token getter (which may resolve with null)\n * into an AccessTokenGetter by retrying the unreliable getter in a regular\n * interval.\n *\n * @param getAccessToken - A function that produces a promise of an access token that may fail by returning null.\n * @param retryIntervalInMs - The time (in milliseconds) to wait between retry attempts.\n * @param refreshTimeout - The timestamp after which the refresh attempt will fail, throwing an exception.\n * @returns - A promise that, if it resolves, will resolve with an access token.\n */\nasync function beginRefresh(\n getAccessToken: () => Promise<AccessToken | null>,\n retryIntervalInMs: number,\n refreshTimeout: number,\n): Promise<AccessToken> {\n // This wrapper handles exceptions gracefully as long as we haven't exceeded\n // the timeout.\n async function tryGetAccessToken(): Promise<AccessToken | null> {\n if (Date.now() < refreshTimeout) {\n try {\n return await getAccessToken();\n } catch {\n return null;\n }\n } else {\n const finalToken = await getAccessToken();\n\n // Timeout is up, so throw if it's still null\n if (finalToken === null) {\n throw new Error(\"Failed to refresh access token.\");\n }\n\n return finalToken;\n }\n }\n\n let token: AccessToken | null = await tryGetAccessToken();\n\n while (token === null) {\n await delay(retryIntervalInMs);\n\n token = await tryGetAccessToken();\n }\n\n return token;\n}\n\n/**\n * Creates a token cycler from a credential, scopes, and optional settings.\n *\n * A token cycler represents a way to reliably retrieve a valid access token\n * from a TokenCredential. It will handle initializing the token, refreshing it\n * when it nears expiration, and synchronizes refresh attempts to avoid\n * concurrency hazards.\n *\n * @param credential - the underlying TokenCredential that provides the access\n * token\n * @param tokenCyclerOptions - optionally override default settings for the cycler\n *\n * @returns - a function that reliably produces a valid access token\n */\nexport function createTokenCycler(\n credential: TokenCredential,\n tokenCyclerOptions?: Partial<TokenCyclerOptions>,\n): AccessTokenGetter {\n let refreshWorker: Promise<AccessToken> | null = null;\n let token: AccessToken | null = null;\n let tenantId: string | undefined;\n\n const options = {\n ...DEFAULT_CYCLER_OPTIONS,\n ...tokenCyclerOptions,\n };\n\n /**\n * This little holder defines several predicates that we use to construct\n * the rules of refreshing the token.\n */\n const cycler = {\n /**\n * Produces true if a refresh job is currently in progress.\n */\n get isRefreshing(): boolean {\n return refreshWorker !== null;\n },\n /**\n * Produces true if the cycler SHOULD refresh (we are within the refresh\n * window and not already refreshing)\n */\n get shouldRefresh(): boolean {\n if (cycler.isRefreshing) {\n return false;\n }\n if (token?.refreshAfterTimestamp && token.refreshAfterTimestamp < Date.now()) {\n return true;\n }\n\n return (token?.expiresOnTimestamp ?? 0) - options.refreshWindowInMs < Date.now();\n },\n /**\n * Produces true if the cycler MUST refresh (null or nearly-expired\n * token).\n */\n get mustRefresh(): boolean {\n return (\n token === null || token.expiresOnTimestamp - options.forcedRefreshWindowInMs < Date.now()\n );\n },\n };\n\n /**\n * Starts a refresh job or returns the existing job if one is already\n * running.\n */\n function refresh(\n scopes: string | string[],\n getTokenOptions: GetTokenOptions,\n ): Promise<AccessToken> {\n if (!cycler.isRefreshing) {\n // We bind `scopes` here to avoid passing it around a lot\n const tryGetAccessToken = (): Promise<AccessToken | null> =>\n credential.getToken(scopes, getTokenOptions);\n\n // Take advantage of promise chaining to insert an assignment to `token`\n // before the refresh can be considered done.\n refreshWorker = beginRefresh(\n tryGetAccessToken,\n options.retryIntervalInMs,\n // If we don't have a token, then we should timeout immediately\n token?.expiresOnTimestamp ?? Date.now(),\n )\n .then((_token) => {\n refreshWorker = null;\n token = _token;\n tenantId = getTokenOptions.tenantId;\n return token;\n })\n .catch((reason) => {\n // We also should reset the refresher if we enter a failed state. All\n // existing awaiters will throw, but subsequent requests will start a\n // new retry chain.\n refreshWorker = null;\n token = null;\n tenantId = undefined;\n throw reason;\n });\n }\n\n return refreshWorker as Promise<AccessToken>;\n }\n\n return async (scopes: string | string[], tokenOptions: GetTokenOptions): Promise<AccessToken> => {\n //\n // Simple rules:\n // - If we MUST refresh, then return the refresh task, blocking\n // the pipeline until a token is available.\n // - If we SHOULD refresh, then run refresh but don't return it\n // (we can still use the cached token).\n // - Return the token, since it's fine if we didn't return in\n // step 1.\n //\n\n const hasClaimChallenge = Boolean(tokenOptions.claims);\n const tenantIdChanged = tenantId !== tokenOptions.tenantId;\n\n if (hasClaimChallenge) {\n // If we've received a claim, we know the existing token isn't valid\n // We want to clear it so that that refresh worker won't use the old expiration time as a timeout\n token = null;\n }\n\n // If the tenantId passed in token options is different to the one we have\n // Or if we are in claim challenge and the token was rejected and a new access token need to be issued, we need to\n // refresh the token with the new tenantId or token.\n const mustRefresh = tenantIdChanged || hasClaimChallenge || cycler.mustRefresh;\n\n if (mustRefresh) {\n return refresh(scopes, tokenOptions);\n }\n\n if (cycler.shouldRefresh) {\n refresh(scopes, tokenOptions);\n }\n\n return token as AccessToken;\n };\n}\n"],
|
"mappings": ";;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAIA,uBAAsB;AAmCf,MAAM,yBAA6C;AAAA,EACxD,yBAAyB;AAAA;AAAA,EACzB,mBAAmB;AAAA;AAAA,EACnB,mBAAmB,MAAO,KAAK;AAAA;AACjC;AAYA,eAAe,aACb,gBACA,mBACA,gBACsB;AAGtB,iBAAe,oBAAiD;AAC9D,QAAI,KAAK,IAAI,IAAI,gBAAgB;AAC/B,UAAI;AACF,eAAO,MAAM,eAAe;AAAA,MAC9B,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF,OAAO;AACL,YAAM,aAAa,MAAM,eAAe;AAGxC,UAAI,eAAe,MAAM;AACvB,cAAM,IAAI,MAAM,iCAAiC;AAAA,MACnD;AAEA,aAAO;AAAA,IACT;AAAA,EACF;AAEA,MAAI,QAA4B,MAAM,kBAAkB;AAExD,SAAO,UAAU,MAAM;AACrB,cAAM,wBAAM,iBAAiB;AAE7B,YAAQ,MAAM,kBAAkB;AAAA,EAClC;AAEA,SAAO;AACT;AAgBO,SAAS,kBACd,YACA,oBACmB;AACnB,MAAI,gBAA6C;AACjD,MAAI,QAA4B;AAChC,MAAI;AAEJ,QAAM,UAAU;AAAA,IACd,GAAG;AAAA,IACH,GAAG;AAAA,EACL;AAMA,QAAM,SAAS;AAAA;AAAA;AAAA;AAAA,IAIb,IAAI,eAAwB;AAC1B,aAAO,kBAAkB;AAAA,IAC3B;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA,IAAI,gBAAyB;AAC3B,UAAI,OAAO,cAAc;AACvB,eAAO;AAAA,MACT;AACA,UAAI,OAAO,yBAAyB,MAAM,wBAAwB,KAAK,IAAI,GAAG;AAC5E,eAAO;AAAA,MACT;AAEA,cAAQ,OAAO,sBAAsB,KAAK,QAAQ,oBAAoB,KAAK,IAAI;AAAA,IACjF;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA,IAAI,cAAuB;AACzB,aACE,UAAU,QAAQ,MAAM,qBAAqB,QAAQ,0BAA0B,KAAK,IAAI;AAAA,IAE5F;AAAA,EACF;AAMA,WAAS,QACP,QACA,iBACsB;AACtB,QAAI,CAAC,OAAO,cAAc;AAExB,YAAM,oBAAoB,MACxB,WAAW,SAAS,QAAQ,eAAe;AAI7C,sBAAgB;AAAA,QACd;AAAA,QACA,QAAQ;AAAA;AAAA,QAER,OAAO,sBAAsB,KAAK,IAAI;AAAA,MACxC,EACG,KAAK,CAAC,WAAW;AAChB,wBAAgB;AAChB,gBAAQ;AACR,mBAAW,gBAAgB;AAC3B,eAAO;AAAA,MACT,CAAC,EACA,MAAM,CAAC,WAAW;AAIjB,wBAAgB;AAChB,gBAAQ;AACR,mBAAW;AACX,cAAM;AAAA,MACR,CAAC;AAAA,IACL;AAEA,WAAO;AAAA,EACT;AAEA,SAAO,OAAO,QAA2B,iBAAwD;AAW/F,UAAM,oBAAoB,QAAQ,aAAa,MAAM;AACrD,UAAM,kBAAkB,aAAa,aAAa;AAElD,QAAI,mBAAmB;AAGrB,cAAQ;AAAA,IACV;AAKA,UAAM,cAAc,mBAAmB,qBAAqB,OAAO;AAEnE,QAAI,aAAa;AACf,aAAO,QAAQ,QAAQ,YAAY;AAAA,IACrC;AAEA,QAAI,OAAO,eAAe;AACxB,cAAQ,QAAQ,YAAY;AAAA,IAC9B;AAEA,WAAO;AAAA,EACT;AACF;",
|