Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions sdk/communication/communication-common/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 (`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 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({
tokenRefresher: async () => fetchTokenFromMyServerForUser("bob@contoso.com"),
refreshProactively: true,
refreshIntervalBeforeTokenExpiryInSeconds: 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class AzureCommunicationTokenCredential implements CommunicationTokenCred
constructor(refreshOptions: CommunicationTokenRefreshOptions);
dispose(): void;
getToken(options?: CommunicationGetTokenOptions): Promise<AccessToken>;
}
}

// @public
export interface CommunicationGetTokenOptions {
Expand All @@ -37,6 +37,7 @@ export interface CommunicationTokenCredential {

// @public
export interface CommunicationTokenRefreshOptions {
refreshIntervalBeforeTokenExpiryInSeconds?: number;
refreshProactively?: boolean;
token?: string;
tokenRefresher: (abortSignal?: AbortSignalLike) => Promise<string>;
Expand Down Expand Up @@ -162,7 +163,6 @@ export type UrlWithCredential = {
credential: TokenCredential | KeyCredential;
};


// (No @packageDocumentation comment for this package)

```
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,24 @@ 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 4.5 minutes. This value ensures that when the 'tokenRefresher' callback is internally using MSAL, MSAL avoids the cache and gets a fresh token.
*/
refreshIntervalBeforeTokenExpiryInSeconds?: number;
}

const expiredToken = { token: "", expiresOnTimestamp: -10 };
const minutesToMs = (minutes: number): number => minutes * 1000 * 60;
const defaultRefreshingInterval = minutesToMs(10);
const secondsToMs = (seconds: number): number => seconds * 1000;
const minutesToMs = (minutes: number): number => secondsToMs(minutes * 60);
const defaultRefreshingInterval = minutesToMs(4.5);

export class AutoRefreshTokenCredential implements TokenCredential {
private readonly refresh: (abortSignal?: AbortSignalLike) => Promise<string>;
private readonly refreshProactively: boolean;
private readonly refreshingIntervalInMs: number = defaultRefreshingInterval;
private readonly refreshingIntervalInMs: number;

private currentToken: AccessToken;
private activeTimeout: ReturnType<typeof setTimeout> | undefined;
Expand All @@ -42,11 +50,20 @@ export class AutoRefreshTokenCredential implements TokenCredential {
private disposed = false;

constructor(refreshArgs: CommunicationTokenRefreshOptions) {
const { tokenRefresher, token, refreshProactively } = refreshArgs;
const {
tokenRefresher,
token,
refreshProactively,
refreshIntervalBeforeTokenExpiryInSeconds
} = refreshArgs;

this.refresh = tokenRefresher;
this.currentToken = token ? parseToken(token) : expiredToken;
this.refreshProactively = refreshProactively ?? false;
this.refreshingIntervalInMs =
refreshIntervalBeforeTokenExpiryInSeconds !== undefined
? secondsToMs(refreshIntervalBeforeTokenExpiryInSeconds)
: defaultRefreshingInterval;

if (this.refreshProactively) {
this.scheduleRefresh();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
refreshIntervalBeforeTokenExpiryInSeconds: refreshMinutes * 60
});
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,
refreshIntervalBeforeTokenExpiryInSeconds: refreshMinutes * 60
});
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);
Expand Down