From a2a79c72276237330bbc016d6fabdd1f6ae630a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C5=A0vihl=C3=ADk?= Date: Mon, 15 Nov 2021 10:23:26 +0100 Subject: [PATCH 1/6] Proactive token refresh interval made configurable --- .../src/autoRefreshTokenCredential.ts | 17 +++++++++-- .../test/communicationTokenCredential.spec.ts | 28 +++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/sdk/communication/communication-common/src/autoRefreshTokenCredential.ts b/sdk/communication/communication-common/src/autoRefreshTokenCredential.ts index cbeab9a2783a..cabf49c49960 100644 --- a/sdk/communication/communication-common/src/autoRefreshTokenCredential.ts +++ b/sdk/communication/communication-common/src/autoRefreshTokenCredential.ts @@ -24,6 +24,13 @@ export interface CommunicationTokenRefreshOptions { * By default false. */ refreshProactively?: boolean; + + /** + * The time span before token expiry that causes the 'tokenRefresher' to be called if 'refreshProactively' is true. + * For example, setting it to a value equal to 5 minutes means that 5 minutes before the cached token expires, the proactive refresh will request a new token. + * By default, the value is equal to 10 minutes. + */ + refreshTimeBeforeTokenExpiryInMs?: number; } const expiredToken = { token: "", expiresOnTimestamp: -10 }; @@ -33,7 +40,7 @@ const defaultRefreshingInterval = minutesToMs(10); export class AutoRefreshTokenCredential implements TokenCredential { private readonly refresh: (abortSignal?: AbortSignalLike) => Promise; private readonly refreshProactively: boolean; - private readonly refreshingIntervalInMs: number = defaultRefreshingInterval; + private readonly refreshingIntervalInMs: number; private currentToken: AccessToken; private activeTimeout: ReturnType | undefined; @@ -42,11 +49,17 @@ export class AutoRefreshTokenCredential implements TokenCredential { private disposed = false; constructor(refreshArgs: CommunicationTokenRefreshOptions) { - const { tokenRefresher, token, refreshProactively } = refreshArgs; + const { + tokenRefresher, + token, + refreshProactively, + refreshTimeBeforeTokenExpiryInMs + } = refreshArgs; this.refresh = tokenRefresher; this.currentToken = token ? parseToken(token) : expiredToken; this.refreshProactively = refreshProactively ?? false; + this.refreshingIntervalInMs = refreshTimeBeforeTokenExpiryInMs ?? defaultRefreshingInterval; if (this.refreshProactively) { this.scheduleRefresh(); diff --git a/sdk/communication/communication-common/test/communicationTokenCredential.spec.ts b/sdk/communication/communication-common/test/communicationTokenCredential.spec.ts index 2dd2d14b615e..70901cb4b47d 100644 --- a/sdk/communication/communication-common/test/communicationTokenCredential.spec.ts +++ b/sdk/communication/communication-common/test/communicationTokenCredential.spec.ts @@ -111,6 +111,34 @@ describe("CommunicationTokenCredential", () => { sinon.assert.calledOnce(tokenRefresher); }); + it("doesn't proactively refresh a valid token before a custom specified time interval", async () => { + const refreshMinutes = 30; + const tokenValidityMinutes = 60; + const tokenRefresher = sinon.stub().resolves(generateToken(tokenValidityMinutes)); + new AzureCommunicationTokenCredential({ + tokenRefresher, + token: generateToken(tokenValidityMinutes), + refreshProactively: true, + refreshTimeBeforeTokenExpiryInMs: refreshMinutes * 60 * 1000 + }); + clock.tick((refreshMinutes - 5) * 60 * 1000); + sinon.assert.notCalled(tokenRefresher); + }); + + it("proactively refreshes a valid token after a custom specified time interval", async () => { + const refreshMinutes = 30; + const tokenValidityMinutes = 60; + const tokenRefresher = sinon.stub().resolves(generateToken(tokenValidityMinutes)); + new AzureCommunicationTokenCredential({ + tokenRefresher, + token: generateToken(tokenValidityMinutes), + refreshProactively: true, + refreshTimeBeforeTokenExpiryInMs: refreshMinutes * 60 * 1000 + }); + clock.tick((refreshMinutes + 5) * 60 * 1000); + sinon.assert.calledOnce(tokenRefresher); + }); + it("returns expired token when not using a lambda", async () => { const token = generateToken(-1); const tokenCredential = new AzureCommunicationTokenCredential(token); From 6871a085e3ead82969a88446c9cd7ce5189d0b4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C5=A0vihl=C3=ADk?= Date: Mon, 15 Nov 2021 11:10:29 +0100 Subject: [PATCH 2/6] switched default unit to seconds --- .../src/autoRefreshTokenCredential.ts | 12 ++++++++---- .../test/communicationTokenCredential.spec.ts | 4 ++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/sdk/communication/communication-common/src/autoRefreshTokenCredential.ts b/sdk/communication/communication-common/src/autoRefreshTokenCredential.ts index cabf49c49960..706bfb04386f 100644 --- a/sdk/communication/communication-common/src/autoRefreshTokenCredential.ts +++ b/sdk/communication/communication-common/src/autoRefreshTokenCredential.ts @@ -30,11 +30,12 @@ export interface CommunicationTokenRefreshOptions { * For example, setting it to a value equal to 5 minutes means that 5 minutes before the cached token expires, the proactive refresh will request a new token. * By default, the value is equal to 10 minutes. */ - refreshTimeBeforeTokenExpiryInMs?: number; + refreshTimeBeforeTokenExpiryInSeconds?: number; } const expiredToken = { token: "", expiresOnTimestamp: -10 }; -const minutesToMs = (minutes: number): number => minutes * 1000 * 60; +const secondsToMs = (seconds: number): number => seconds * 1000; +const minutesToMs = (minutes: number): number => secondsToMs(minutes * 60); const defaultRefreshingInterval = minutesToMs(10); export class AutoRefreshTokenCredential implements TokenCredential { @@ -53,13 +54,16 @@ export class AutoRefreshTokenCredential implements TokenCredential { tokenRefresher, token, refreshProactively, - refreshTimeBeforeTokenExpiryInMs + refreshTimeBeforeTokenExpiryInSeconds } = refreshArgs; this.refresh = tokenRefresher; this.currentToken = token ? parseToken(token) : expiredToken; this.refreshProactively = refreshProactively ?? false; - this.refreshingIntervalInMs = refreshTimeBeforeTokenExpiryInMs ?? defaultRefreshingInterval; + this.refreshingIntervalInMs = + refreshTimeBeforeTokenExpiryInSeconds !== undefined + ? secondsToMs(refreshTimeBeforeTokenExpiryInSeconds) + : defaultRefreshingInterval; if (this.refreshProactively) { this.scheduleRefresh(); diff --git a/sdk/communication/communication-common/test/communicationTokenCredential.spec.ts b/sdk/communication/communication-common/test/communicationTokenCredential.spec.ts index 70901cb4b47d..edd6bfee9e15 100644 --- a/sdk/communication/communication-common/test/communicationTokenCredential.spec.ts +++ b/sdk/communication/communication-common/test/communicationTokenCredential.spec.ts @@ -119,7 +119,7 @@ describe("CommunicationTokenCredential", () => { tokenRefresher, token: generateToken(tokenValidityMinutes), refreshProactively: true, - refreshTimeBeforeTokenExpiryInMs: refreshMinutes * 60 * 1000 + refreshTimeBeforeTokenExpiryInSeconds: refreshMinutes * 60 }); clock.tick((refreshMinutes - 5) * 60 * 1000); sinon.assert.notCalled(tokenRefresher); @@ -133,7 +133,7 @@ describe("CommunicationTokenCredential", () => { tokenRefresher, token: generateToken(tokenValidityMinutes), refreshProactively: true, - refreshTimeBeforeTokenExpiryInMs: refreshMinutes * 60 * 1000 + refreshTimeBeforeTokenExpiryInSeconds: refreshMinutes * 60 }); clock.tick((refreshMinutes + 5) * 60 * 1000); sinon.assert.calledOnce(tokenRefresher); From 4c4c55f353e3308e8f8c64ed97bdccd4719020e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C5=A0vihl=C3=ADk?= Date: Mon, 22 Nov 2021 13:17:24 +0100 Subject: [PATCH 3/6] added a sample to readme --- sdk/communication/communication-common/README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/sdk/communication/communication-common/README.md b/sdk/communication/communication-common/README.md index f70f5aa82360..b7af7658849e 100644 --- a/sdk/communication/communication-common/README.md +++ b/sdk/communication/communication-common/README.md @@ -62,6 +62,19 @@ const tokenCredential = new AzureCommunicationTokenCredential({ }); ``` +### Create a credential with proactive refreshing and custom refresh time + +Optionally, you can adjust the time span before token expiry that will trigger the proactive refreshing of the token. For example, if the proactive refreshing is enabled (`refreshProactively` is true), setting the time span (`refreshTimeBeforeTokenExpiryInSeconds`) to 5 minutes means that 5 minutes before the cached token expires, the proactive refresh will request a new token by calling the `tokenRefresher` callback. +The default value is 10 minutes. + +```typescript +const tokenCredential = new AzureCommunicationTokenCredential({ + tokenRefresher: async () => fetchTokenFromMyServerForUser("bob@contoso.com"), + refreshProactively: true, + refreshTimeBeforeTokenExpiryInSeconds: 5 * 60 // 5 minutes +}); +``` + ### Create a credential with proactive refreshing and an initial token Passing `initialToken` is an optional optimization to skip the first call to `tokenRefresher`. You can use this to separate the boot from your application from subsequent token refresh cycles. From db53dc681e0cacf3529753ae50cbcfc12f458d3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C5=A0vihl=C3=ADk?= Date: Mon, 29 Nov 2021 09:47:24 +0100 Subject: [PATCH 4/6] extracted API for review --- .../communication-common/review/communication-common.api.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/communication/communication-common/review/communication-common.api.md b/sdk/communication/communication-common/review/communication-common.api.md index ae3575ec1935..6408be13ffcf 100644 --- a/sdk/communication/communication-common/review/communication-common.api.md +++ b/sdk/communication/communication-common/review/communication-common.api.md @@ -16,7 +16,7 @@ export class AzureCommunicationTokenCredential implements CommunicationTokenCred constructor(refreshOptions: CommunicationTokenRefreshOptions); dispose(): void; getToken(options?: CommunicationGetTokenOptions): Promise; - } +} // @public export interface CommunicationGetTokenOptions { @@ -38,6 +38,7 @@ export interface CommunicationTokenCredential { // @public export interface CommunicationTokenRefreshOptions { refreshProactively?: boolean; + refreshTimeBeforeTokenExpiryInSeconds?: number; token?: string; tokenRefresher: (abortSignal?: AbortSignalLike) => Promise; } @@ -162,7 +163,6 @@ export type UrlWithCredential = { credential: TokenCredential | KeyCredential; }; - // (No @packageDocumentation comment for this package) ``` From cd3e6918012c4db750b7738674c2f325e1c7afd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C5=A0vihl=C3=ADk?= Date: Wed, 15 Dec 2021 11:04:40 +0100 Subject: [PATCH 5/6] renames according to the interval api review --- sdk/communication/communication-common/README.md | 4 ++-- .../review/communication-common.api.md | 2 +- .../src/autoRefreshTokenCredential.ts | 12 ++++++------ .../test/communicationTokenCredential.spec.ts | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/sdk/communication/communication-common/README.md b/sdk/communication/communication-common/README.md index b7af7658849e..586885031764 100644 --- a/sdk/communication/communication-common/README.md +++ b/sdk/communication/communication-common/README.md @@ -64,14 +64,14 @@ const tokenCredential = new AzureCommunicationTokenCredential({ ### Create a credential with proactive refreshing and custom refresh time -Optionally, you can adjust the time span before token expiry that will trigger the proactive refreshing of the token. For example, if the proactive refreshing is enabled (`refreshProactively` is true), setting the time span (`refreshTimeBeforeTokenExpiryInSeconds`) to 5 minutes means that 5 minutes before the cached token expires, the proactive refresh will request a new token by calling the `tokenRefresher` callback. +Optionally, you can adjust the time span before token expiry that will trigger the proactive refreshing of the token. For example, if the proactive refreshing is enabled (`refreshProactively` is true), setting the time span (`refreshIntervalBeforeTokenExpiryInSeconds`) to 5 minutes means that 5 minutes before the cached token expires, the proactive refresh will request a new token by calling the `tokenRefresher` callback. The default value is 10 minutes. ```typescript const tokenCredential = new AzureCommunicationTokenCredential({ tokenRefresher: async () => fetchTokenFromMyServerForUser("bob@contoso.com"), refreshProactively: true, - refreshTimeBeforeTokenExpiryInSeconds: 5 * 60 // 5 minutes + refreshIntervalBeforeTokenExpiryInSeconds: 5 * 60 // 5 minutes }); ``` diff --git a/sdk/communication/communication-common/review/communication-common.api.md b/sdk/communication/communication-common/review/communication-common.api.md index 6408be13ffcf..9b4f8e924e34 100644 --- a/sdk/communication/communication-common/review/communication-common.api.md +++ b/sdk/communication/communication-common/review/communication-common.api.md @@ -37,8 +37,8 @@ export interface CommunicationTokenCredential { // @public export interface CommunicationTokenRefreshOptions { + refreshIntervalBeforeTokenExpiryInSeconds?: number; refreshProactively?: boolean; - refreshTimeBeforeTokenExpiryInSeconds?: number; token?: string; tokenRefresher: (abortSignal?: AbortSignalLike) => Promise; } diff --git a/sdk/communication/communication-common/src/autoRefreshTokenCredential.ts b/sdk/communication/communication-common/src/autoRefreshTokenCredential.ts index 706bfb04386f..f5805a7ad6ac 100644 --- a/sdk/communication/communication-common/src/autoRefreshTokenCredential.ts +++ b/sdk/communication/communication-common/src/autoRefreshTokenCredential.ts @@ -28,15 +28,15 @@ export interface CommunicationTokenRefreshOptions { /** * The time span before token expiry that causes the 'tokenRefresher' to be called if 'refreshProactively' is true. * For example, setting it to a value equal to 5 minutes means that 5 minutes before the cached token expires, the proactive refresh will request a new token. - * By default, the value is equal to 10 minutes. + * By default, the value is equal to 4.5 minutes. This value ensures that when the 'tokenRefresher' callback is internally using MSAL, MSAL avoids the cache and gets a fresh token. */ - refreshTimeBeforeTokenExpiryInSeconds?: number; + refreshIntervalBeforeTokenExpiryInSeconds?: number; } const expiredToken = { token: "", expiresOnTimestamp: -10 }; const secondsToMs = (seconds: number): number => seconds * 1000; const minutesToMs = (minutes: number): number => secondsToMs(minutes * 60); -const defaultRefreshingInterval = minutesToMs(10); +const defaultRefreshingInterval = minutesToMs(4.5); export class AutoRefreshTokenCredential implements TokenCredential { private readonly refresh: (abortSignal?: AbortSignalLike) => Promise; @@ -54,15 +54,15 @@ export class AutoRefreshTokenCredential implements TokenCredential { tokenRefresher, token, refreshProactively, - refreshTimeBeforeTokenExpiryInSeconds + refreshIntervalBeforeTokenExpiryInSeconds } = refreshArgs; this.refresh = tokenRefresher; this.currentToken = token ? parseToken(token) : expiredToken; this.refreshProactively = refreshProactively ?? false; this.refreshingIntervalInMs = - refreshTimeBeforeTokenExpiryInSeconds !== undefined - ? secondsToMs(refreshTimeBeforeTokenExpiryInSeconds) + refreshIntervalBeforeTokenExpiryInSeconds !== undefined + ? secondsToMs(refreshIntervalBeforeTokenExpiryInSeconds) : defaultRefreshingInterval; if (this.refreshProactively) { diff --git a/sdk/communication/communication-common/test/communicationTokenCredential.spec.ts b/sdk/communication/communication-common/test/communicationTokenCredential.spec.ts index edd6bfee9e15..ca4a9ce84b08 100644 --- a/sdk/communication/communication-common/test/communicationTokenCredential.spec.ts +++ b/sdk/communication/communication-common/test/communicationTokenCredential.spec.ts @@ -119,7 +119,7 @@ describe("CommunicationTokenCredential", () => { tokenRefresher, token: generateToken(tokenValidityMinutes), refreshProactively: true, - refreshTimeBeforeTokenExpiryInSeconds: refreshMinutes * 60 + refreshIntervalBeforeTokenExpiryInSeconds: refreshMinutes * 60 }); clock.tick((refreshMinutes - 5) * 60 * 1000); sinon.assert.notCalled(tokenRefresher); @@ -133,7 +133,7 @@ describe("CommunicationTokenCredential", () => { tokenRefresher, token: generateToken(tokenValidityMinutes), refreshProactively: true, - refreshTimeBeforeTokenExpiryInSeconds: refreshMinutes * 60 + refreshIntervalBeforeTokenExpiryInSeconds: refreshMinutes * 60 }); clock.tick((refreshMinutes + 5) * 60 * 1000); sinon.assert.calledOnce(tokenRefresher); From 1aeee46e5464614309c17b670890735221d0fec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C5=A0vihl=C3=ADk?= Date: Wed, 15 Dec 2021 11:07:16 +0100 Subject: [PATCH 6/6] adjusted the default proactive refresh interval --- sdk/communication/communication-common/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/communication/communication-common/README.md b/sdk/communication/communication-common/README.md index 586885031764..80c2c487fd2e 100644 --- a/sdk/communication/communication-common/README.md +++ b/sdk/communication/communication-common/README.md @@ -65,7 +65,7 @@ const tokenCredential = new AzureCommunicationTokenCredential({ ### Create a credential with proactive refreshing and custom refresh time Optionally, you can adjust the time span before token expiry that will trigger the proactive refreshing of the token. For example, if the proactive refreshing is enabled (`refreshProactively` is true), setting the time span (`refreshIntervalBeforeTokenExpiryInSeconds`) to 5 minutes means that 5 minutes before the cached token expires, the proactive refresh will request a new token by calling the `tokenRefresher` callback. -The default value is 10 minutes. +The default value is 4.5 minutes. This value ensures that when the 'tokenRefresher' callback is internally using MSAL, MSAL avoids the cache and gets a fresh token. ```typescript const tokenCredential = new AzureCommunicationTokenCredential({