@azure/communication-common 2.4.2-alpha.20260211.1 → 2.5.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/README.md +47 -1
- package/dist/browser/autoRefreshTokenCredential.d.ts +20 -4
- package/dist/browser/autoRefreshTokenCredential.d.ts.map +1 -1
- package/dist/browser/autoRefreshTokenCredential.js +13 -4
- package/dist/browser/autoRefreshTokenCredential.js.map +1 -1
- package/dist/browser/azureCommunicationTokenCredential.d.ts +3 -2
- package/dist/browser/azureCommunicationTokenCredential.d.ts.map +1 -1
- package/dist/browser/azureCommunicationTokenCredential.js +3 -2
- package/dist/browser/azureCommunicationTokenCredential.js.map +1 -1
- package/dist/browser/credential/communicationAccessKeyCredentialPolicy.d.ts.map +1 -1
- package/dist/browser/credential/communicationAccessKeyCredentialPolicy.js +6 -10
- package/dist/browser/credential/communicationAccessKeyCredentialPolicy.js.map +1 -1
- package/dist/browser/entraTokenCredential.d.ts.map +1 -1
- package/dist/browser/entraTokenCredential.js +12 -7
- package/dist/browser/entraTokenCredential.js.map +1 -1
- package/dist/browser/index.d.ts +4 -3
- package/dist/browser/index.d.ts.map +1 -1
- package/dist/browser/index.js +1 -0
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/logger.d.ts +3 -0
- package/dist/browser/logger.d.ts.map +1 -0
- package/dist/browser/logger.js +6 -0
- package/dist/browser/logger.js.map +1 -0
- package/dist/browser/tokenParser.d.ts +9 -1
- package/dist/browser/tokenParser.d.ts.map +1 -1
- package/dist/browser/tokenParser.js +64 -3
- package/dist/browser/tokenParser.js.map +1 -1
- package/dist/commonjs/autoRefreshTokenCredential.d.ts +20 -4
- package/dist/commonjs/autoRefreshTokenCredential.d.ts.map +1 -1
- package/dist/commonjs/autoRefreshTokenCredential.js +118 -95
- package/dist/commonjs/autoRefreshTokenCredential.js.map +7 -1
- package/dist/commonjs/azureCommunicationTokenCredential.d.ts +3 -2
- package/dist/commonjs/azureCommunicationTokenCredential.d.ts.map +1 -1
- package/dist/commonjs/azureCommunicationTokenCredential.js +64 -47
- package/dist/commonjs/azureCommunicationTokenCredential.js.map +7 -1
- package/dist/commonjs/communicationTokenCredential.js +16 -5
- package/dist/commonjs/communicationTokenCredential.js.map +7 -1
- package/dist/commonjs/credential/clientArguments.js +46 -40
- package/dist/commonjs/credential/clientArguments.js.map +7 -1
- package/dist/commonjs/credential/communicationAccessKeyCredentialPolicy.d.ts.map +1 -1
- package/dist/commonjs/credential/communicationAccessKeyCredentialPolicy.js +53 -42
- package/dist/commonjs/credential/communicationAccessKeyCredentialPolicy.js.map +7 -1
- package/dist/commonjs/credential/communicationAuthPolicy.js +39 -26
- package/dist/commonjs/credential/communicationAuthPolicy.js.map +7 -1
- package/dist/commonjs/credential/connectionString.js +39 -28
- package/dist/commonjs/credential/connectionString.js.map +7 -1
- package/dist/commonjs/credential/index.js +28 -10
- package/dist/commonjs/credential/index.js.map +7 -1
- package/dist/commonjs/entraTokenCredential.d.ts.map +1 -1
- package/dist/commonjs/entraTokenCredential.js +137 -113
- package/dist/commonjs/entraTokenCredential.js.map +7 -1
- package/dist/commonjs/identifierModelSerializer.js +172 -159
- package/dist/commonjs/identifierModelSerializer.js.map +7 -1
- package/dist/commonjs/identifierModels.js +183 -205
- package/dist/commonjs/identifierModels.js.map +7 -1
- package/dist/commonjs/index.d.ts +4 -3
- package/dist/commonjs/index.d.ts.map +1 -1
- package/dist/commonjs/index.js +40 -12
- package/dist/commonjs/index.js.map +7 -1
- package/dist/commonjs/logger.d.ts +3 -0
- package/dist/commonjs/logger.d.ts.map +1 -0
- package/dist/commonjs/logger.js +29 -0
- package/dist/commonjs/logger.js.map +7 -0
- package/dist/commonjs/staticTokenCredential.js +36 -20
- package/dist/commonjs/staticTokenCredential.js.map +7 -1
- package/dist/commonjs/tokenParser.d.ts +9 -1
- package/dist/commonjs/tokenParser.d.ts.map +1 -1
- package/dist/commonjs/tokenParser.js +71 -13
- package/dist/commonjs/tokenParser.js.map +7 -1
- package/dist/commonjs/tsdoc-metadata.json +1 -1
- package/dist/esm/autoRefreshTokenCredential.d.ts +20 -4
- package/dist/esm/autoRefreshTokenCredential.d.ts.map +1 -1
- package/dist/esm/autoRefreshTokenCredential.js +13 -4
- package/dist/esm/autoRefreshTokenCredential.js.map +1 -1
- package/dist/esm/azureCommunicationTokenCredential.d.ts +3 -2
- package/dist/esm/azureCommunicationTokenCredential.d.ts.map +1 -1
- package/dist/esm/azureCommunicationTokenCredential.js +3 -2
- package/dist/esm/azureCommunicationTokenCredential.js.map +1 -1
- package/dist/esm/credential/communicationAccessKeyCredentialPolicy.d.ts.map +1 -1
- package/dist/esm/credential/communicationAccessKeyCredentialPolicy.js +6 -10
- package/dist/esm/credential/communicationAccessKeyCredentialPolicy.js.map +1 -1
- package/dist/esm/entraTokenCredential.d.ts.map +1 -1
- package/dist/esm/entraTokenCredential.js +12 -7
- package/dist/esm/entraTokenCredential.js.map +1 -1
- package/dist/esm/index.d.ts +4 -3
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/logger.d.ts +3 -0
- package/dist/esm/logger.d.ts.map +1 -0
- package/dist/esm/logger.js +6 -0
- package/dist/esm/logger.js.map +1 -0
- package/dist/esm/tokenParser.d.ts +9 -1
- package/dist/esm/tokenParser.d.ts.map +1 -1
- package/dist/esm/tokenParser.js +64 -3
- package/dist/esm/tokenParser.js.map +1 -1
- package/dist/react-native/autoRefreshTokenCredential.d.ts +20 -4
- package/dist/react-native/autoRefreshTokenCredential.d.ts.map +1 -1
- package/dist/react-native/autoRefreshTokenCredential.js +13 -4
- package/dist/react-native/autoRefreshTokenCredential.js.map +1 -1
- package/dist/react-native/azureCommunicationTokenCredential.d.ts +3 -2
- package/dist/react-native/azureCommunicationTokenCredential.d.ts.map +1 -1
- package/dist/react-native/azureCommunicationTokenCredential.js +3 -2
- package/dist/react-native/azureCommunicationTokenCredential.js.map +1 -1
- package/dist/react-native/credential/communicationAccessKeyCredentialPolicy.d.ts.map +1 -1
- package/dist/react-native/credential/communicationAccessKeyCredentialPolicy.js +6 -10
- package/dist/react-native/credential/communicationAccessKeyCredentialPolicy.js.map +1 -1
- package/dist/react-native/entraTokenCredential.d.ts.map +1 -1
- package/dist/react-native/entraTokenCredential.js +12 -7
- package/dist/react-native/entraTokenCredential.js.map +1 -1
- package/dist/react-native/index.d.ts +4 -3
- package/dist/react-native/index.d.ts.map +1 -1
- package/dist/react-native/index.js +1 -0
- package/dist/react-native/index.js.map +1 -1
- package/dist/react-native/logger.d.ts +3 -0
- package/dist/react-native/logger.d.ts.map +1 -0
- package/dist/react-native/logger.js +6 -0
- package/dist/react-native/logger.js.map +1 -0
- package/dist/react-native/tokenParser.d.ts +9 -1
- package/dist/react-native/tokenParser.d.ts.map +1 -1
- package/dist/react-native/tokenParser.js +64 -3
- package/dist/react-native/tokenParser.js.map +1 -1
- package/package.json +34 -41
- package/dist/browser/credential/cryptoUtils-browser.d.mts.map +0 -1
- package/dist/browser/credential/cryptoUtils-browser.mjs.map +0 -1
- package/dist/browser/credential/cryptoUtils.d.ts +0 -3
- package/dist/browser/credential/cryptoUtils.js +0 -19
- package/dist/browser/credential/encodeUtils-browser.d.mts +0 -4
- package/dist/browser/credential/encodeUtils-browser.d.mts.map +0 -1
- package/dist/browser/credential/encodeUtils-browser.mjs +0 -19
- package/dist/browser/credential/encodeUtils-browser.mjs.map +0 -1
- package/dist/commonjs/credential/cryptoUtils.d.ts +0 -3
- package/dist/commonjs/credential/cryptoUtils.d.ts.map +0 -1
- package/dist/commonjs/credential/cryptoUtils.js +0 -14
- package/dist/commonjs/credential/cryptoUtils.js.map +0 -1
- package/dist/esm/credential/cryptoUtils.d.ts +0 -3
- package/dist/esm/credential/cryptoUtils.d.ts.map +0 -1
- package/dist/esm/credential/cryptoUtils.js +0 -9
- package/dist/esm/credential/cryptoUtils.js.map +0 -1
- package/dist/esm/credential/encodeUtils-browser.d.mts +0 -4
- package/dist/esm/credential/encodeUtils-browser.d.mts.map +0 -1
- package/dist/esm/credential/encodeUtils-browser.mjs +0 -19
- package/dist/esm/credential/encodeUtils-browser.mjs.map +0 -1
- package/dist/react-native/credential/cryptoUtils.d.ts +0 -3
- package/dist/react-native/credential/cryptoUtils.d.ts.map +0 -1
- package/dist/react-native/credential/cryptoUtils.js +0 -9
- package/dist/react-native/credential/cryptoUtils.js.map +0 -1
- package/dist/react-native/credential/encodeUtils-browser.d.mts +0 -4
- package/dist/react-native/credential/encodeUtils-browser.d.mts.map +0 -1
- package/dist/react-native/credential/encodeUtils-browser.mjs +0 -19
- package/dist/react-native/credential/encodeUtils-browser.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -111,13 +111,59 @@ const tokenCredential = new AzureCommunicationTokenCredential({
|
|
|
111
111
|
});
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
+
### Create a credential for an encrypted or opaque access token
|
|
115
|
+
|
|
116
|
+
Some access tokens — such as those issued to enterprise Entra users — are encrypted and can't be decoded on the client, so the credential cannot read their expiry. The recommended approach is to return an `AccessToken` (`{ token, expiresOnTimestamp }`) from your `tokenRefresher` (or pass one as the initial `token`), setting `expiresOnTimestamp` from the token response's `expires_in`. This keeps proactive refresh scheduling accurate.
|
|
117
|
+
|
|
118
|
+
```ts snippet:ReadmeSampleCredentialEncryptedToken
|
|
119
|
+
import { AzureCommunicationTokenCredential } from "@azure/communication-common";
|
|
120
|
+
|
|
121
|
+
// Some access tokens (e.g. for enterprise Entra users) are encrypted and cannot be
|
|
122
|
+
// decoded on the client. Return an AccessToken with an explicit expiry so proactive
|
|
123
|
+
// refresh scheduling stays accurate; derive expiresOnTimestamp from the response's expires_in.
|
|
124
|
+
async function fetchEncryptedTokenForUser(user: string): Promise<{
|
|
125
|
+
token: string;
|
|
126
|
+
expiresOnTimestamp: number;
|
|
127
|
+
}> {
|
|
128
|
+
const response = { token: "some-encrypted-token-for-" + user, expiresInSeconds: 8 * 60 * 60 };
|
|
129
|
+
return {
|
|
130
|
+
token: response.token,
|
|
131
|
+
expiresOnTimestamp: Date.now() + response.expiresInSeconds * 1000,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const tokenCredential = new AzureCommunicationTokenCredential({
|
|
136
|
+
tokenRefresher: async () => fetchEncryptedTokenForUser("bob@contoso.com"),
|
|
137
|
+
refreshProactively: true,
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
If you can only provide the token string, the credential accepts it and assumes a fallback lifetime. Set `undecodableTokenExpiryIntervalInSeconds` to your tokens' actual lifetime so they aren't refreshed more often than needed; it defaults to 600 seconds.
|
|
142
|
+
|
|
143
|
+
```ts snippet:ReadmeSampleCredentialUndecodableTokenInterval
|
|
144
|
+
import { AzureCommunicationTokenCredential } from "@azure/communication-common";
|
|
145
|
+
|
|
146
|
+
function fetchOpaqueTokenForUser(user: string): Promise<string> {
|
|
147
|
+
// Your custom implementation returns an opaque token string that can't be decoded.
|
|
148
|
+
return Promise.resolve("some-opaque-token-for-" + user);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const tokenCredential = new AzureCommunicationTokenCredential({
|
|
152
|
+
tokenRefresher: async () => fetchOpaqueTokenForUser("bob@contoso.com"),
|
|
153
|
+
refreshProactively: true,
|
|
154
|
+
// When the token can't be decoded and no explicit expiry is provided, assume this
|
|
155
|
+
// lifetime (in seconds) instead of the 600s default — set it to your tokens' real lifetime.
|
|
156
|
+
undecodableTokenExpiryIntervalInSeconds: 3600,
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
114
160
|
### Create a credential with a token credential capable of obtaining an Entra user token
|
|
115
161
|
|
|
116
162
|
For scenarios where an Entra user can be used with Communication Services, you need to initialize any implementation of [TokenCredential interface](https://learn.microsoft.com/es-mx/javascript/api/@azure/core-auth/tokencredential?view=azure-node-latest) and provide it to the ``EntraCommunicationTokenCredentialOptions``.
|
|
117
163
|
Along with this, you must provide the URI of the Azure Communication Services resource and the scopes required for the Entra user token. These scopes determine the permissions granted to the token.
|
|
118
164
|
|
|
119
165
|
This approach needs to be used for authorizing an Entra user with a Teams license to use Teams Phone Extensibility features through your Azure Communication Services resource.
|
|
120
|
-
This requires providing the `https://auth.msft.communication.azure.com/TeamsExtension.ManageCalls` scope.
|
|
166
|
+
This requires providing the `https://auth.msft.communication.azure.com/TeamsExtension.ManageCalls` scope. For the GCCH cloud environment, use `https://auth.msft.communication.azure.us/TeamsExtension.ManageCalls`.
|
|
121
167
|
|
|
122
168
|
```ts snippet:ReadmeSampleCredentialEntraUserTeamsPhoneExtensibility
|
|
123
169
|
import { InteractiveBrowserCredential } from "@azure/identity";
|
|
@@ -6,25 +6,41 @@ import type { AccessToken } from "@azure/core-auth";
|
|
|
6
6
|
*/
|
|
7
7
|
export interface CommunicationTokenRefreshOptions {
|
|
8
8
|
/**
|
|
9
|
-
* Callback
|
|
9
|
+
* Callback that returns a token acquired from the Communication Identity API.
|
|
10
10
|
* The returned token must be valid (expiration date must be in the future).
|
|
11
|
+
*
|
|
12
|
+
* Return a `string` to have the expiry read from the JWT, or an `AccessToken`
|
|
13
|
+
* to supply the expiry explicitly (required for encrypted access tokens that
|
|
14
|
+
* cannot be decoded).
|
|
11
15
|
*/
|
|
12
|
-
tokenRefresher: (abortSignal?: AbortSignalLike) => Promise<string>;
|
|
16
|
+
tokenRefresher: (abortSignal?: AbortSignalLike) => Promise<string | AccessToken>;
|
|
13
17
|
/**
|
|
14
|
-
* Optional token to initialize.
|
|
18
|
+
* Optional token to initialize. May be a `string` or an `AccessToken` carrying
|
|
19
|
+
* an explicit expiry.
|
|
15
20
|
*/
|
|
16
|
-
token?: string;
|
|
21
|
+
token?: string | AccessToken;
|
|
17
22
|
/**
|
|
18
23
|
* Indicates whether the token should be proactively renewed prior to expiry or only renew on demand.
|
|
19
24
|
* By default false.
|
|
20
25
|
*/
|
|
21
26
|
refreshProactively?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Lifetime, in seconds, assumed for a returned token that cannot be decoded and
|
|
29
|
+
* carries no explicit expiry (e.g. an encrypted access token). Defaults to 600 (10 minutes).
|
|
30
|
+
*
|
|
31
|
+
* Set this to your tokens' real lifetime. It is not capped: a value larger than the token's
|
|
32
|
+
* actual lifetime means the credential refreshes later than it should and the service will
|
|
33
|
+
* reject the expired token. To avoid guessing, prefer returning an `AccessToken` with an
|
|
34
|
+
* explicit `expiresOnTimestamp` (e.g. derived from the token response's `expires_in`).
|
|
35
|
+
*/
|
|
36
|
+
undecodableTokenExpiryIntervalInSeconds?: number;
|
|
22
37
|
}
|
|
23
38
|
export declare class AutoRefreshTokenCredential implements TokenCredential {
|
|
24
39
|
private readonly refresh;
|
|
25
40
|
private readonly refreshProactively;
|
|
26
41
|
private readonly expiringSoonIntervalInMs;
|
|
27
42
|
private readonly refreshAfterLifetimePercentage;
|
|
43
|
+
private readonly undecodableTokenExpiryIntervalInSeconds;
|
|
28
44
|
private currentToken;
|
|
29
45
|
private activeTimeout;
|
|
30
46
|
private activeTokenFetching;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"autoRefreshTokenCredential.d.ts","sourceRoot":"","sources":["../../src/autoRefreshTokenCredential.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,4BAA4B,EAC5B,eAAe,EAChB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGpD;;GAEG;AACH,MAAM,WAAW,gCAAgC;IAC/C
|
|
1
|
+
{"version":3,"file":"autoRefreshTokenCredential.d.ts","sourceRoot":"","sources":["../../src/autoRefreshTokenCredential.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,4BAA4B,EAC5B,eAAe,EAChB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGpD;;GAEG;AACH,MAAM,WAAW,gCAAgC;IAC/C;;;;;;;OAOG;IACH,cAAc,EAAE,CAAC,WAAW,CAAC,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,GAAG,WAAW,CAAC,CAAC;IAEjF;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;IAE7B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B;;;;;;;;OAQG;IACH,uCAAuC,CAAC,EAAE,MAAM,CAAC;CAClD;AASD,qBAAa,0BAA2B,YAAW,eAAe;IAChE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmE;IAC3F,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAU;IAC7C,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAuC;IAChF,OAAO,CAAC,QAAQ,CAAC,8BAA8B,CAAyC;IACxF,OAAO,CAAC,QAAQ,CAAC,uCAAuC,CAAqB;IAE7E,OAAO,CAAC,YAAY,CAAc;IAClC,OAAO,CAAC,aAAa,CAA4C;IACjE,OAAO,CAAC,mBAAmB,CAA8C;IACzE,OAAO,CAAC,mBAAmB,CAA8B;IACzD,OAAO,CAAC,QAAQ,CAAS;gBAEb,WAAW,EAAE,gCAAgC;IAgB5C,QAAQ,CAAC,OAAO,CAAC,EAAE,4BAA4B,GAAG,OAAO,CAAC,WAAW,CAAC;IAa5E,OAAO,IAAI,IAAI;YAUR,wBAAwB;YAexB,yBAAyB;YAazB,YAAY;IAc1B,OAAO,CAAC,eAAe;IAqBvB,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,mBAAmB;CAG5B"}
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import { parseToken } from "./tokenParser.js";
|
|
4
4
|
const expiredToken = { token: "", expiresOnTimestamp: -10 };
|
|
5
5
|
const minutesToMs = (minutes) => minutes * 1000 * 60;
|
|
6
|
+
// Must stay in step with `defaultUndecodableTokenExpiryIntervalInSeconds` (600s) in tokenParser.ts —
|
|
7
|
+
// an opaque token's fallback expiry is expected to land in this expiring-soon window.
|
|
6
8
|
const defaultExpiringSoonInterval = minutesToMs(10);
|
|
7
9
|
const defaultRefreshAfterLifetimePercentage = 0.5;
|
|
8
10
|
export class AutoRefreshTokenCredential {
|
|
@@ -10,15 +12,19 @@ export class AutoRefreshTokenCredential {
|
|
|
10
12
|
refreshProactively;
|
|
11
13
|
expiringSoonIntervalInMs = defaultExpiringSoonInterval;
|
|
12
14
|
refreshAfterLifetimePercentage = defaultRefreshAfterLifetimePercentage;
|
|
15
|
+
undecodableTokenExpiryIntervalInSeconds;
|
|
13
16
|
currentToken;
|
|
14
17
|
activeTimeout;
|
|
15
18
|
activeTokenFetching = null;
|
|
16
19
|
activeTokenUpdating = null;
|
|
17
20
|
disposed = false;
|
|
18
21
|
constructor(refreshArgs) {
|
|
19
|
-
const { tokenRefresher, token, refreshProactively } = refreshArgs;
|
|
22
|
+
const { tokenRefresher, token, refreshProactively, undecodableTokenExpiryIntervalInSeconds } = refreshArgs;
|
|
20
23
|
this.refresh = tokenRefresher;
|
|
21
|
-
this.
|
|
24
|
+
this.undecodableTokenExpiryIntervalInSeconds = undecodableTokenExpiryIntervalInSeconds;
|
|
25
|
+
this.currentToken = token
|
|
26
|
+
? parseToken(token, this.undecodableTokenExpiryIntervalInSeconds)
|
|
27
|
+
: expiredToken;
|
|
22
28
|
this.refreshProactively = refreshProactively ?? false;
|
|
23
29
|
if (this.refreshProactively) {
|
|
24
30
|
this.scheduleRefresh();
|
|
@@ -39,11 +45,14 @@ export class AutoRefreshTokenCredential {
|
|
|
39
45
|
this.activeTokenFetching = null;
|
|
40
46
|
this.activeTokenUpdating = null;
|
|
41
47
|
this.currentToken = expiredToken;
|
|
42
|
-
if (this.activeTimeout) {
|
|
48
|
+
if (this.activeTimeout !== undefined) {
|
|
43
49
|
clearTimeout(this.activeTimeout);
|
|
44
50
|
}
|
|
45
51
|
}
|
|
46
52
|
async updateTokenAndReschedule(abortSignal) {
|
|
53
|
+
if (this.disposed) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
47
56
|
if (this.activeTokenUpdating) {
|
|
48
57
|
return this.activeTokenUpdating;
|
|
49
58
|
}
|
|
@@ -70,7 +79,7 @@ export class AutoRefreshTokenCredential {
|
|
|
70
79
|
if (!this.activeTokenFetching) {
|
|
71
80
|
this.activeTokenFetching = this.refresh(abortSignal);
|
|
72
81
|
}
|
|
73
|
-
return parseToken(await this.activeTokenFetching);
|
|
82
|
+
return parseToken(await this.activeTokenFetching, this.undecodableTokenExpiryIntervalInSeconds);
|
|
74
83
|
}
|
|
75
84
|
finally {
|
|
76
85
|
this.activeTokenFetching = null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"autoRefreshTokenCredential.js","sourceRoot":"","sources":["../../src/autoRefreshTokenCredential.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;AAQlC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAwB9C,MAAM,YAAY,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,kBAAkB,EAAE,CAAC,EAAE,EAAE,CAAC;AAC5D,MAAM,WAAW,GAAG,CAAC,OAAe,EAAU,EAAE,CAAC,OAAO,GAAG,IAAI,GAAG,EAAE,CAAC;AACrE,MAAM,2BAA2B,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;AACpD,MAAM,qCAAqC,GAAG,GAAG,CAAC;AAElD,MAAM,OAAO,0BAA0B;IACpB,OAAO,CAAqD;IAC5D,kBAAkB,CAAU;IAC5B,wBAAwB,GAAW,2BAA2B,CAAC;IAC/D,8BAA8B,GAAG,qCAAqC,CAAC;IAEhF,YAAY,CAAc;IAC1B,aAAa,CAA4C;IACzD,mBAAmB,GAA2B,IAAI,CAAC;IACnD,mBAAmB,GAAyB,IAAI,CAAC;IACjD,QAAQ,GAAG,KAAK,CAAC;IAEzB,YAAY,WAA6C;QACvD,MAAM,EAAE,cAAc,EAAE,KAAK,EAAE,kBAAkB,EAAE,GAAG,WAAW,CAAC;QAElE,IAAI,CAAC,OAAO,GAAG,cAAc,CAAC;QAC9B,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC;QAC7D,IAAI,CAAC,kBAAkB,GAAG,kBAAkB,IAAI,KAAK,CAAC;QAEtD,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC5B,IAAI,CAAC,eAAe,EAAE,CAAC;QACzB,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,QAAQ,CAAC,OAAsC;QAC1D,IAAI,CAAC,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;YACjD,OAAO,IAAI,CAAC,YAAY,CAAC;QAC3B,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;YAC1C,MAAM,aAAa,GAAG,IAAI,CAAC,wBAAwB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;YAC1E,MAAM,aAAa,CAAC;QACtB,CAAC;QAED,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;IAEM,OAAO;QACZ,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC;QAChC,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC;QAChC,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACvB,YAAY,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,wBAAwB,CAAC,WAA6B;QAClE,IAAI,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC7B,OAAO,IAAI,CAAC,mBAAmB,CAAC;QAClC,CAAC;QACD,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC,yBAAyB,CAAC,WAAW,CAAC,CAAC;QACvE,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,mBAAmB,CAAC;QACjC,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC;QAClC,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,yBAAyB,CAAC,WAA6B;QACnE,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;QAEtD,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC;YACjC,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;QAC5E,CAAC;QAED,IAAI,CAAC,YAAY,GAAG,QAAQ,CAAC;QAC7B,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC5B,IAAI,CAAC,eAAe,EAAE,CAAC;QACzB,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,YAAY,CAAC,WAA6B;QACtD,IAAI,CAAC;YACH,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,CAAC;gBAC9B,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;YACvD,CAAC;YACD,OAAO,UAAU,CAAC,MAAM,IAAI,CAAC,mBAAmB,CAAC,CAAC;QACpD,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC;QAClC,CAAC;IACH,CAAC;IAEO,eAAe;QACrB,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,OAAO;QACT,CAAC;QACD,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACvB,YAAY,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACnC,CAAC;QACD,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC,kBAAkB,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvE,IAAI,YAAY,GAAG,IAAI,CAAC;QAExB,IAAI,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;YAChD,gGAAgG;YAChG,YAAY,GAAG,YAAY,GAAG,IAAI,CAAC,8BAA8B,CAAC;QACpE,CAAC;aAAM,CAAC;YACN,8EAA8E;YAC9E,YAAY,GAAG,YAAY,GAAG,IAAI,CAAC,wBAAwB,CAAC;QAC9D,CAAC;QAED,IAAI,CAAC,aAAa,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,wBAAwB,EAAE,EAAE,YAAY,CAAC,CAAC;IACvF,CAAC;IAEO,YAAY,CAAC,KAAkB;QACrC,OAAO,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,kBAAkB,CAAC;IACxD,CAAC;IAEO,mBAAmB,CAAC,KAAkB;QAC5C,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,KAAK,CAAC,kBAAkB,GAAG,IAAI,CAAC,wBAAwB,CAAC;IAC1F,CAAC;CACF","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport type {\n CommunicationGetTokenOptions,\n TokenCredential,\n} from \"./communicationTokenCredential.js\";\nimport type { AbortSignalLike } from \"@azure/abort-controller\";\nimport type { AccessToken } from \"@azure/core-auth\";\nimport { parseToken } from \"./tokenParser.js\";\n\n/**\n * Options for auto-refreshing a Communication Token credential.\n */\nexport interface CommunicationTokenRefreshOptions {\n /**\n * Callback function that returns a string JWT token acquired from the Communication Identity API.\n * The returned token must be valid (expiration date must be in the future).\n */\n tokenRefresher: (abortSignal?: AbortSignalLike) => Promise<string>;\n\n /**\n * Optional token to initialize.\n */\n token?: string;\n\n /**\n * Indicates whether the token should be proactively renewed prior to expiry or only renew on demand.\n * By default false.\n */\n refreshProactively?: boolean;\n}\n\nconst expiredToken = { token: \"\", expiresOnTimestamp: -10 };\nconst minutesToMs = (minutes: number): number => minutes * 1000 * 60;\nconst defaultExpiringSoonInterval = minutesToMs(10);\nconst defaultRefreshAfterLifetimePercentage = 0.5;\n\nexport class AutoRefreshTokenCredential implements TokenCredential {\n private readonly refresh: (abortSignal?: AbortSignalLike) => Promise<string>;\n private readonly refreshProactively: boolean;\n private readonly expiringSoonIntervalInMs: number = defaultExpiringSoonInterval;\n private readonly refreshAfterLifetimePercentage = defaultRefreshAfterLifetimePercentage;\n\n private currentToken: AccessToken;\n private activeTimeout: ReturnType<typeof setTimeout> | undefined;\n private activeTokenFetching: Promise<string> | null = null;\n private activeTokenUpdating: Promise<void> | null = null;\n private disposed = false;\n\n constructor(refreshArgs: CommunicationTokenRefreshOptions) {\n const { tokenRefresher, token, refreshProactively } = refreshArgs;\n\n this.refresh = tokenRefresher;\n this.currentToken = token ? parseToken(token) : expiredToken;\n this.refreshProactively = refreshProactively ?? false;\n\n if (this.refreshProactively) {\n this.scheduleRefresh();\n }\n }\n\n public async getToken(options?: CommunicationGetTokenOptions): Promise<AccessToken> {\n if (!this.isTokenExpiringSoon(this.currentToken)) {\n return this.currentToken;\n }\n\n if (!this.isTokenValid(this.currentToken)) {\n const updatePromise = this.updateTokenAndReschedule(options?.abortSignal);\n await updatePromise;\n }\n\n return this.currentToken;\n }\n\n public dispose(): void {\n this.disposed = true;\n this.activeTokenFetching = null;\n this.activeTokenUpdating = null;\n this.currentToken = expiredToken;\n if (this.activeTimeout) {\n clearTimeout(this.activeTimeout);\n }\n }\n\n private async updateTokenAndReschedule(abortSignal?: AbortSignalLike): Promise<void> {\n if (this.activeTokenUpdating) {\n return this.activeTokenUpdating;\n }\n this.activeTokenUpdating = this.refreshTokenAndReschedule(abortSignal);\n try {\n await this.activeTokenUpdating;\n } finally {\n this.activeTokenUpdating = null;\n }\n }\n\n private async refreshTokenAndReschedule(abortSignal?: AbortSignalLike): Promise<void> {\n const newToken = await this.refreshToken(abortSignal);\n\n if (!this.isTokenValid(newToken)) {\n throw new Error(\"The token returned from the tokenRefresher is expired.\");\n }\n\n this.currentToken = newToken;\n if (this.refreshProactively) {\n this.scheduleRefresh();\n }\n }\n\n private async refreshToken(abortSignal?: AbortSignalLike): Promise<AccessToken> {\n try {\n if (!this.activeTokenFetching) {\n this.activeTokenFetching = this.refresh(abortSignal);\n }\n return parseToken(await this.activeTokenFetching);\n } finally {\n this.activeTokenFetching = null;\n }\n }\n\n private scheduleRefresh(): void {\n if (this.disposed) {\n return;\n }\n if (this.activeTimeout) {\n clearTimeout(this.activeTimeout);\n }\n const tokenTtlInMs = this.currentToken.expiresOnTimestamp - Date.now();\n let timespanInMs = null;\n\n if (this.isTokenExpiringSoon(this.currentToken)) {\n // Schedule the next refresh for when it reaches a certain percentage of the remaining lifetime.\n timespanInMs = tokenTtlInMs * this.refreshAfterLifetimePercentage;\n } else {\n // Schedule the next refresh for when it gets in to the soon-to-expire window.\n timespanInMs = tokenTtlInMs - this.expiringSoonIntervalInMs;\n }\n\n this.activeTimeout = setTimeout(() => this.updateTokenAndReschedule(), timespanInMs);\n }\n\n private isTokenValid(token: AccessToken): boolean {\n return token && Date.now() < token.expiresOnTimestamp;\n }\n\n private isTokenExpiringSoon(token: AccessToken): boolean {\n return !token || Date.now() >= token.expiresOnTimestamp - this.expiringSoonIntervalInMs;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"autoRefreshTokenCredential.js","sourceRoot":"","sources":["../../src/autoRefreshTokenCredential.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport type {\n CommunicationGetTokenOptions,\n TokenCredential,\n} from \"./communicationTokenCredential.js\";\nimport type { AbortSignalLike } from \"@azure/abort-controller\";\nimport type { AccessToken } from \"@azure/core-auth\";\nimport { parseToken } from \"./tokenParser.js\";\n\n/**\n * Options for auto-refreshing a Communication Token credential.\n */\nexport interface CommunicationTokenRefreshOptions {\n /**\n * Callback that returns a token acquired from the Communication Identity API.\n * The returned token must be valid (expiration date must be in the future).\n *\n * Return a `string` to have the expiry read from the JWT, or an `AccessToken`\n * to supply the expiry explicitly (required for encrypted access tokens that\n * cannot be decoded).\n */\n tokenRefresher: (abortSignal?: AbortSignalLike) => Promise<string | AccessToken>;\n\n /**\n * Optional token to initialize. May be a `string` or an `AccessToken` carrying\n * an explicit expiry.\n */\n token?: string | AccessToken;\n\n /**\n * Indicates whether the token should be proactively renewed prior to expiry or only renew on demand.\n * By default false.\n */\n refreshProactively?: boolean;\n\n /**\n * Lifetime, in seconds, assumed for a returned token that cannot be decoded and\n * carries no explicit expiry (e.g. an encrypted access token). Defaults to 600 (10 minutes).\n *\n * Set this to your tokens' real lifetime. It is not capped: a value larger than the token's\n * actual lifetime means the credential refreshes later than it should and the service will\n * reject the expired token. To avoid guessing, prefer returning an `AccessToken` with an\n * explicit `expiresOnTimestamp` (e.g. derived from the token response's `expires_in`).\n */\n undecodableTokenExpiryIntervalInSeconds?: number;\n}\n\nconst expiredToken = { token: \"\", expiresOnTimestamp: -10 };\nconst minutesToMs = (minutes: number): number => minutes * 1000 * 60;\n// Must stay in step with `defaultUndecodableTokenExpiryIntervalInSeconds` (600s) in tokenParser.ts —\n// an opaque token's fallback expiry is expected to land in this expiring-soon window.\nconst defaultExpiringSoonInterval = minutesToMs(10);\nconst defaultRefreshAfterLifetimePercentage = 0.5;\n\nexport class AutoRefreshTokenCredential implements TokenCredential {\n private readonly refresh: (abortSignal?: AbortSignalLike) => Promise<string | AccessToken>;\n private readonly refreshProactively: boolean;\n private readonly expiringSoonIntervalInMs: number = defaultExpiringSoonInterval;\n private readonly refreshAfterLifetimePercentage = defaultRefreshAfterLifetimePercentage;\n private readonly undecodableTokenExpiryIntervalInSeconds: number | undefined;\n\n private currentToken: AccessToken;\n private activeTimeout: ReturnType<typeof setTimeout> | undefined;\n private activeTokenFetching: Promise<string | AccessToken> | null = null;\n private activeTokenUpdating: Promise<void> | null = null;\n private disposed = false;\n\n constructor(refreshArgs: CommunicationTokenRefreshOptions) {\n const { tokenRefresher, token, refreshProactively, undecodableTokenExpiryIntervalInSeconds } =\n refreshArgs;\n\n this.refresh = tokenRefresher;\n this.undecodableTokenExpiryIntervalInSeconds = undecodableTokenExpiryIntervalInSeconds;\n this.currentToken = token\n ? parseToken(token, this.undecodableTokenExpiryIntervalInSeconds)\n : expiredToken;\n this.refreshProactively = refreshProactively ?? false;\n\n if (this.refreshProactively) {\n this.scheduleRefresh();\n }\n }\n\n public async getToken(options?: CommunicationGetTokenOptions): Promise<AccessToken> {\n if (!this.isTokenExpiringSoon(this.currentToken)) {\n return this.currentToken;\n }\n\n if (!this.isTokenValid(this.currentToken)) {\n const updatePromise = this.updateTokenAndReschedule(options?.abortSignal);\n await updatePromise;\n }\n\n return this.currentToken;\n }\n\n public dispose(): void {\n this.disposed = true;\n this.activeTokenFetching = null;\n this.activeTokenUpdating = null;\n this.currentToken = expiredToken;\n if (this.activeTimeout !== undefined) {\n clearTimeout(this.activeTimeout);\n }\n }\n\n private async updateTokenAndReschedule(abortSignal?: AbortSignalLike): Promise<void> {\n if (this.disposed) {\n return;\n }\n if (this.activeTokenUpdating) {\n return this.activeTokenUpdating;\n }\n this.activeTokenUpdating = this.refreshTokenAndReschedule(abortSignal);\n try {\n await this.activeTokenUpdating;\n } finally {\n this.activeTokenUpdating = null;\n }\n }\n\n private async refreshTokenAndReschedule(abortSignal?: AbortSignalLike): Promise<void> {\n const newToken = await this.refreshToken(abortSignal);\n\n if (!this.isTokenValid(newToken)) {\n throw new Error(\"The token returned from the tokenRefresher is expired.\");\n }\n\n this.currentToken = newToken;\n if (this.refreshProactively) {\n this.scheduleRefresh();\n }\n }\n\n private async refreshToken(abortSignal?: AbortSignalLike): Promise<AccessToken> {\n try {\n if (!this.activeTokenFetching) {\n this.activeTokenFetching = this.refresh(abortSignal);\n }\n return parseToken(\n await this.activeTokenFetching,\n this.undecodableTokenExpiryIntervalInSeconds,\n );\n } finally {\n this.activeTokenFetching = null;\n }\n }\n\n private scheduleRefresh(): void {\n if (this.disposed) {\n return;\n }\n if (this.activeTimeout) {\n clearTimeout(this.activeTimeout);\n }\n const tokenTtlInMs = this.currentToken.expiresOnTimestamp - Date.now();\n let timespanInMs = null;\n\n if (this.isTokenExpiringSoon(this.currentToken)) {\n // Schedule the next refresh for when it reaches a certain percentage of the remaining lifetime.\n timespanInMs = tokenTtlInMs * this.refreshAfterLifetimePercentage;\n } else {\n // Schedule the next refresh for when it gets in to the soon-to-expire window.\n timespanInMs = tokenTtlInMs - this.expiringSoonIntervalInMs;\n }\n\n this.activeTimeout = setTimeout(() => this.updateTokenAndReschedule(), timespanInMs);\n }\n\n private isTokenValid(token: AccessToken): boolean {\n return token && Date.now() < token.expiresOnTimestamp;\n }\n\n private isTokenExpiringSoon(token: AccessToken): boolean {\n return !token || Date.now() >= token.expiresOnTimestamp - this.expiringSoonIntervalInMs;\n }\n}\n"]}
|
|
@@ -10,9 +10,10 @@ export declare class AzureCommunicationTokenCredential implements CommunicationT
|
|
|
10
10
|
private disposed;
|
|
11
11
|
/**
|
|
12
12
|
* Creates an instance of CommunicationTokenCredential with a static token and no proactive refreshing.
|
|
13
|
-
* @param token - A user access token issued by Communication Services.
|
|
13
|
+
* @param token - A user access token issued by Communication Services. Pass an `AccessToken`
|
|
14
|
+
* to supply the expiry explicitly when the token cannot be decoded (e.g. an encrypted token).
|
|
14
15
|
*/
|
|
15
|
-
constructor(token: string);
|
|
16
|
+
constructor(token: string | AccessToken);
|
|
16
17
|
/**
|
|
17
18
|
* Creates an instance of CommunicationTokenCredential with a lambda to get a token and options
|
|
18
19
|
* to configure proactive refreshing.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"azureCommunicationTokenCredential.d.ts","sourceRoot":"","sources":["../../src/azureCommunicationTokenCredential.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,gCAAgC,EACtC,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EACV,4BAA4B,EAC5B,4BAA4B,EAE7B,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGpD,OAAO,EACL,KAAK,wCAAwC,EAE9C,MAAM,2BAA2B,CAAC;AAEnC;;GAEG;AAEH,qBAAa,iCAAkC,YAAW,4BAA4B;IACpF,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,QAAQ,CAAS;IAEzB
|
|
1
|
+
{"version":3,"file":"azureCommunicationTokenCredential.d.ts","sourceRoot":"","sources":["../../src/azureCommunicationTokenCredential.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,gCAAgC,EACtC,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EACV,4BAA4B,EAC5B,4BAA4B,EAE7B,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGpD,OAAO,EACL,KAAK,wCAAwC,EAE9C,MAAM,2BAA2B,CAAC;AAEnC;;GAEG;AAEH,qBAAa,iCAAkC,YAAW,4BAA4B;IACpF,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,QAAQ,CAAS;IAEzB;;;;OAIG;gBACS,KAAK,EAAE,MAAM,GAAG,WAAW;IACvC;;;;OAIG;gBACS,cAAc,EAAE,gCAAgC;IAC5D;;;OAGG;gBACS,YAAY,EAAE,wCAAwC;IAsBlE;;;OAGG;IACU,QAAQ,CAAC,OAAO,CAAC,EAAE,4BAA4B,GAAG,OAAO,CAAC,WAAW,CAAC;IAOnF;;OAEG;IACI,OAAO,IAAI,IAAI;IAKtB,OAAO,CAAC,eAAe;CAKxB"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
3
|
import { AutoRefreshTokenCredential, } from "./autoRefreshTokenCredential.js";
|
|
4
4
|
import { StaticTokenCredential } from "./staticTokenCredential.js";
|
|
5
|
-
import { parseToken } from "./tokenParser.js";
|
|
5
|
+
import { isAccessToken, parseToken } from "./tokenParser.js";
|
|
6
6
|
import { EntraTokenCredential, } from "./entraTokenCredential.js";
|
|
7
7
|
/**
|
|
8
8
|
* The CommunicationTokenCredential implementation with support for proactive token refresh.
|
|
@@ -11,7 +11,8 @@ export class AzureCommunicationTokenCredential {
|
|
|
11
11
|
tokenCredential;
|
|
12
12
|
disposed = false;
|
|
13
13
|
constructor(tokenOrRefreshOptionsOrEntraOptions) {
|
|
14
|
-
if (typeof tokenOrRefreshOptionsOrEntraOptions === "string"
|
|
14
|
+
if (typeof tokenOrRefreshOptionsOrEntraOptions === "string" ||
|
|
15
|
+
isAccessToken(tokenOrRefreshOptionsOrEntraOptions)) {
|
|
15
16
|
this.tokenCredential = new StaticTokenCredential(parseToken(tokenOrRefreshOptionsOrEntraOptions));
|
|
16
17
|
}
|
|
17
18
|
else if ("tokenRefresher" in tokenOrRefreshOptionsOrEntraOptions) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"azureCommunicationTokenCredential.js","sourceRoot":"","sources":["../../src/azureCommunicationTokenCredential.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;AAElC,OAAO,EACL,0BAA0B,GAE3B,MAAM,iCAAiC,CAAC;AAOzC,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"azureCommunicationTokenCredential.js","sourceRoot":"","sources":["../../src/azureCommunicationTokenCredential.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;AAElC,OAAO,EACL,0BAA0B,GAE3B,MAAM,iCAAiC,CAAC;AAOzC,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAEL,oBAAoB,GACrB,MAAM,2BAA2B,CAAC;AAEnC;;GAEG;AAEH,MAAM,OAAO,iCAAiC;IAC3B,eAAe,CAAkB;IAC1C,QAAQ,GAAG,KAAK,CAAC;IAmBzB,YACE,mCAI4C;QAE5C,IACE,OAAO,mCAAmC,KAAK,QAAQ;YACvD,aAAa,CAAC,mCAAmC,CAAC,EAClD,CAAC;YACD,IAAI,CAAC,eAAe,GAAG,IAAI,qBAAqB,CAC9C,UAAU,CAAC,mCAAmC,CAAC,CAChD,CAAC;QACJ,CAAC;aAAM,IAAI,gBAAgB,IAAI,mCAAmC,EAAE,CAAC;YACnE,IAAI,CAAC,eAAe,GAAG,IAAI,0BAA0B,CAAC,mCAAmC,CAAC,CAAC;QAC7F,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,eAAe,GAAG,IAAI,oBAAoB,CAAC,mCAAmC,CAAC,CAAC;QACvF,CAAC;IACH,CAAC;IAED;;;OAGG;IACI,KAAK,CAAC,QAAQ,CAAC,OAAsC;QAC1D,IAAI,CAAC,eAAe,EAAE,CAAC;QACvB,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC3D,IAAI,CAAC,eAAe,EAAE,CAAC;QACvB,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;OAEG;IACI,OAAO;QACZ,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,CAAC;IACjC,CAAC;IAEO,eAAe;QACrB,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;QACjD,CAAC;IACH,CAAC;CACF","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport {\n AutoRefreshTokenCredential,\n type CommunicationTokenRefreshOptions,\n} from \"./autoRefreshTokenCredential.js\";\nimport type {\n CommunicationGetTokenOptions,\n CommunicationTokenCredential,\n TokenCredential,\n} from \"./communicationTokenCredential.js\";\nimport type { AccessToken } from \"@azure/core-auth\";\nimport { StaticTokenCredential } from \"./staticTokenCredential.js\";\nimport { isAccessToken, parseToken } from \"./tokenParser.js\";\nimport {\n type EntraCommunicationTokenCredentialOptions,\n EntraTokenCredential,\n} from \"./entraTokenCredential.js\";\n\n/**\n * The CommunicationTokenCredential implementation with support for proactive token refresh.\n */\n\nexport class AzureCommunicationTokenCredential implements CommunicationTokenCredential {\n private readonly tokenCredential: TokenCredential;\n private disposed = false;\n\n /**\n * Creates an instance of CommunicationTokenCredential with a static token and no proactive refreshing.\n * @param token - A user access token issued by Communication Services. Pass an `AccessToken`\n * to supply the expiry explicitly when the token cannot be decoded (e.g. an encrypted token).\n */\n constructor(token: string | AccessToken);\n /**\n * Creates an instance of CommunicationTokenCredential with a lambda to get a token and options\n * to configure proactive refreshing.\n * @param refreshOptions - Options to configure refresh and opt-in to proactive refreshing.\n */\n constructor(refreshOptions: CommunicationTokenRefreshOptions);\n /**\n * Creates an instance of CommunicationTokenCredential with an Entra ID token credential. In most cases, you might want to use InteractiveBrowserCredential to sign in your user.\n * @param entraOptions - Options to configure the Entra ID token credential.\n */\n constructor(entraOptions: EntraCommunicationTokenCredentialOptions);\n constructor(\n tokenOrRefreshOptionsOrEntraOptions:\n | string\n | AccessToken\n | CommunicationTokenRefreshOptions\n | EntraCommunicationTokenCredentialOptions,\n ) {\n if (\n typeof tokenOrRefreshOptionsOrEntraOptions === \"string\" ||\n isAccessToken(tokenOrRefreshOptionsOrEntraOptions)\n ) {\n this.tokenCredential = new StaticTokenCredential(\n parseToken(tokenOrRefreshOptionsOrEntraOptions),\n );\n } else if (\"tokenRefresher\" in tokenOrRefreshOptionsOrEntraOptions) {\n this.tokenCredential = new AutoRefreshTokenCredential(tokenOrRefreshOptionsOrEntraOptions);\n } else {\n this.tokenCredential = new EntraTokenCredential(tokenOrRefreshOptionsOrEntraOptions);\n }\n }\n\n /**\n * Gets an `AccessToken` for the user. Throws if already disposed.\n * @param abortSignal - An implementation of `AbortSignalLike` to cancel the operation.\n */\n public async getToken(options?: CommunicationGetTokenOptions): Promise<AccessToken> {\n this.throwIfDisposed();\n const token = await this.tokenCredential.getToken(options);\n this.throwIfDisposed();\n return token;\n }\n\n /**\n * Disposes the CommunicationTokenCredential and cancels any internal auto-refresh operation.\n */\n public dispose(): void {\n this.disposed = true;\n this.tokenCredential.dispose();\n }\n\n private throwIfDisposed(): void {\n if (this.disposed) {\n throw new Error(\"User credential is disposed\");\n }\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"communicationAccessKeyCredentialPolicy.d.ts","sourceRoot":"","sources":["../../../src/credential/communicationAccessKeyCredentialPolicy.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,cAAc,EAIf,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"communicationAccessKeyCredentialPolicy.d.ts","sourceRoot":"","sources":["../../../src/credential/communicationAccessKeyCredentialPolicy.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,cAAc,EAIf,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAStD;;;;;GAKG;AACH,wBAAgB,4CAA4C,CAC1D,UAAU,EAAE,aAAa,GACxB,cAAc,CA4BhB"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
|
-
import {
|
|
4
|
-
import { isNodeLike } from "@azure/core-util";
|
|
3
|
+
import { computeSha256Hash, computeSha256Hmac } from "@azure/core-util";
|
|
5
4
|
/**
|
|
6
5
|
* CommunicationKeyCredentialPolicy provides a means of signing requests made through
|
|
7
6
|
* the SmsClient.
|
|
@@ -19,19 +18,16 @@ export function createCommunicationAccessKeyCredentialPolicy(credential) {
|
|
|
19
18
|
async sendRequest(request, next) {
|
|
20
19
|
const verb = request.method.toUpperCase();
|
|
21
20
|
const utcNow = new Date().toUTCString();
|
|
22
|
-
const contentHash = await
|
|
21
|
+
const contentHash = await computeSha256Hash(request.body?.toString() || "", "base64");
|
|
23
22
|
const dateHeader = "x-ms-date";
|
|
24
23
|
const signedHeaders = `${dateHeader};host;x-ms-content-sha256`;
|
|
25
24
|
const url = new URL(request.url);
|
|
26
25
|
const query = url.searchParams.toString();
|
|
27
26
|
const urlPathAndQuery = query ? `${url.pathname}?${query}` : url.pathname;
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (isNodeLike) {
|
|
33
|
-
request.headers.set("Host", hostAndPort || "");
|
|
34
|
-
}
|
|
27
|
+
const stringToSign = `${verb}\n${urlPathAndQuery}\n${utcNow};${url.host};${contentHash}`;
|
|
28
|
+
const signature = await computeSha256Hmac(credential.key, stringToSign, "base64");
|
|
29
|
+
// Host is a forbidden header in browsers (silently ignored), but needed in Node.js
|
|
30
|
+
request.headers.set("Host", url.host);
|
|
35
31
|
request.headers.set(dateHeader, utcNow);
|
|
36
32
|
request.headers.set("x-ms-content-sha256", contentHash);
|
|
37
33
|
request.headers.set("Authorization", `HMAC-SHA256 SignedHeaders=${signedHeaders}&Signature=${signature}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"communicationAccessKeyCredentialPolicy.js","sourceRoot":"","sources":["../../../src/credential/communicationAccessKeyCredentialPolicy.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;
|
|
1
|
+
{"version":3,"file":"communicationAccessKeyCredentialPolicy.js","sourceRoot":"","sources":["../../../src/credential/communicationAccessKeyCredentialPolicy.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;AASlC,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAExE;;;GAGG;AACH,MAAM,sCAAsC,GAAG,wCAAwC,CAAC;AAExF;;;;;GAKG;AACH,MAAM,UAAU,4CAA4C,CAC1D,UAAyB;IAEzB,OAAO;QACL,IAAI,EAAE,sCAAsC;QAC5C,KAAK,CAAC,WAAW,CAAC,OAAwB,EAAE,IAAiB;YAC3D,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;YAC1C,MAAM,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YACxC,MAAM,WAAW,GAAG,MAAM,iBAAiB,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,QAAQ,CAAC,CAAC;YACtF,MAAM,UAAU,GAAG,WAAW,CAAC;YAC/B,MAAM,aAAa,GAAG,GAAG,UAAU,2BAA2B,CAAC;YAE/D,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YACjC,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC,QAAQ,EAAE,CAAC;YAC1C,MAAM,eAAe,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,QAAQ,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC;YAE1E,MAAM,YAAY,GAAG,GAAG,IAAI,KAAK,eAAe,KAAK,MAAM,IAAI,GAAG,CAAC,IAAI,IAAI,WAAW,EAAE,CAAC;YACzF,MAAM,SAAS,GAAG,MAAM,iBAAiB,CAAC,UAAU,CAAC,GAAG,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAC;YAElF,mFAAmF;YACnF,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;YACtC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;YACxC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,qBAAqB,EAAE,WAAW,CAAC,CAAC;YACxD,OAAO,CAAC,OAAO,CAAC,GAAG,CACjB,eAAe,EACf,6BAA6B,aAAa,cAAc,SAAS,EAAE,CACpE,CAAC;YACF,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC;QACvB,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport type {\n PipelinePolicy,\n PipelineRequest,\n PipelineResponse,\n SendRequest,\n} from \"@azure/core-rest-pipeline\";\nimport type { KeyCredential } from \"@azure/core-auth\";\nimport { computeSha256Hash, computeSha256Hmac } from \"@azure/core-util\";\n\n/**\n * CommunicationKeyCredentialPolicy provides a means of signing requests made through\n * the SmsClient.\n */\nconst communicationAccessKeyCredentialPolicy = \"CommunicationAccessKeyCredentialPolicy\";\n\n/**\n * Creates an HTTP pipeline policy to authenticate a request using a `KeyCredential`.\n * @hidden\n *\n * @param credential - The key credential.\n */\nexport function createCommunicationAccessKeyCredentialPolicy(\n credential: KeyCredential,\n): PipelinePolicy {\n return {\n name: communicationAccessKeyCredentialPolicy,\n async sendRequest(request: PipelineRequest, next: SendRequest): Promise<PipelineResponse> {\n const verb = request.method.toUpperCase();\n const utcNow = new Date().toUTCString();\n const contentHash = await computeSha256Hash(request.body?.toString() || \"\", \"base64\");\n const dateHeader = \"x-ms-date\";\n const signedHeaders = `${dateHeader};host;x-ms-content-sha256`;\n\n const url = new URL(request.url);\n const query = url.searchParams.toString();\n const urlPathAndQuery = query ? `${url.pathname}?${query}` : url.pathname;\n\n const stringToSign = `${verb}\\n${urlPathAndQuery}\\n${utcNow};${url.host};${contentHash}`;\n const signature = await computeSha256Hmac(credential.key, stringToSign, \"base64\");\n\n // Host is a forbidden header in browsers (silently ignored), but needed in Node.js\n request.headers.set(\"Host\", url.host);\n request.headers.set(dateHeader, utcNow);\n request.headers.set(\"x-ms-content-sha256\", contentHash);\n request.headers.set(\n \"Authorization\",\n `HMAC-SHA256 SignedHeaders=${signedHeaders}&Signature=${signature}`,\n );\n return next(request);\n },\n };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entraTokenCredential.d.ts","sourceRoot":"","sources":["../../src/entraTokenCredential.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EACL,KAAK,eAAe,IAAI,kBAAkB,EAC1C,KAAK,4BAA4B,EAClC,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"entraTokenCredential.d.ts","sourceRoot":"","sources":["../../src/entraTokenCredential.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EACL,KAAK,eAAe,IAAI,kBAAkB,EAC1C,KAAK,4BAA4B,EAClC,MAAM,mCAAmC,CAAC;AAwB3C,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE;QACX,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,wCAAwC;IACvD;;OAEG;IACH,gBAAgB,EAAE,MAAM,CAAC;IACzB;;OAEG;IACH,eAAe,EAAE,eAAe,CAAC;IACjC;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;GAEG;AACH,qBAAa,oBAAqB,YAAW,kBAAkB;IASjD,OAAO,CAAC,OAAO;IAR3B,OAAO,CAAC,SAAS,CAA8B;IAC/C,OAAO,CAAC,MAAM,CAGZ;IACF,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAAa;gBAEX,OAAO,EAAE,wCAAwC;IAYxD,QAAQ,CAAC,OAAO,CAAC,EAAE,4BAA4B,GAAG,OAAO,CAAC,WAAW,CAAC;YAoBrE,gBAAgB;IAmCvB,OAAO,IAAI,IAAI;YAOR,kBAAkB;IA8BhC,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,8BAA8B;CAmBvC"}
|
|
@@ -2,8 +2,15 @@
|
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
3
|
import { getClient } from "@azure-rest/core-client";
|
|
4
4
|
import { createDefaultHttpClient, createHttpHeaders, createPipelineRequest, } from "@azure/core-rest-pipeline";
|
|
5
|
-
const
|
|
5
|
+
const TeamsExtensionScopePrefixes = [
|
|
6
|
+
"https://auth.msft.communication.azure.com/",
|
|
7
|
+
"https://auth.msft.communication.azure.us/",
|
|
8
|
+
];
|
|
6
9
|
const CommunicationClientsScopePrefix = "https://communication.azure.com/clients/";
|
|
10
|
+
const ScopeValidationErrorMessage = `Scopes validation failed. Ensure all scopes start with one of ${[
|
|
11
|
+
...TeamsExtensionScopePrefixes,
|
|
12
|
+
CommunicationClientsScopePrefix,
|
|
13
|
+
].join(", ")}.`;
|
|
7
14
|
const TeamsExtensionEndpoint = "/access/teamsExtension/:exchangeAccessToken";
|
|
8
15
|
const TeamsExtensionApiVersion = "2025-06-30";
|
|
9
16
|
const CommunicationClientsEndpoint = "/access/entra/:exchangeAccessToken";
|
|
@@ -107,17 +114,15 @@ export class EntraTokenCredential {
|
|
|
107
114
|
}
|
|
108
115
|
determineEndpointAndApiVersion() {
|
|
109
116
|
if (!this.options.scopes || this.options.scopes.length === 0) {
|
|
110
|
-
throw new Error(
|
|
117
|
+
throw new Error(ScopeValidationErrorMessage);
|
|
111
118
|
}
|
|
112
|
-
|
|
119
|
+
if (this.options.scopes.every((scope) => TeamsExtensionScopePrefixes.some((prefix) => scope.startsWith(prefix)))) {
|
|
113
120
|
return [TeamsExtensionEndpoint, TeamsExtensionApiVersion];
|
|
114
121
|
}
|
|
115
|
-
|
|
122
|
+
if (this.options.scopes.every((scope) => scope.startsWith(CommunicationClientsScopePrefix))) {
|
|
116
123
|
return [CommunicationClientsEndpoint, CommunicationClientsApiVersion];
|
|
117
124
|
}
|
|
118
|
-
|
|
119
|
-
throw new Error(`Scopes validation failed. Ensure all scopes start with either {TeamsExtensionScopePrefix} or {CommunicationClientsScopePrefix}.`);
|
|
120
|
-
}
|
|
125
|
+
throw new Error(ScopeValidationErrorMessage);
|
|
121
126
|
}
|
|
122
127
|
}
|
|
123
128
|
//# sourceMappingURL=entraTokenCredential.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entraTokenCredential.js","sourceRoot":"","sources":["../../src/entraTokenCredential.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport { type AccessToken, type TokenCredential } from \"@azure/core-auth\";\nimport {\n type TokenCredential as AcsTokenCredential,\n type CommunicationGetTokenOptions,\n} from \"./communicationTokenCredential.js\";\nimport { type AbortSignalLike } from \"@azure/abort-controller\";\nimport { type Client, getClient } from \"@azure-rest/core-client\";\nimport {\n type HttpClient,\n createDefaultHttpClient,\n createHttpHeaders,\n createPipelineRequest,\n} from \"@azure/core-rest-pipeline\";\n\nconst TeamsExtensionScopePrefix = \"https://auth.msft.communication.azure.com/\";\nconst CommunicationClientsScopePrefix = \"https://communication.azure.com/clients/\";\nconst TeamsExtensionEndpoint = \"/access/teamsExtension/:exchangeAccessToken\";\nconst TeamsExtensionApiVersion = \"2025-06-30\";\nconst CommunicationClientsEndpoint = \"/access/entra/:exchangeAccessToken\";\nconst CommunicationClientsApiVersion = \"2025-03-02-preview\";\n\nexport interface ExchangeTokenResponse {\n identity: string;\n accessToken: {\n token: string;\n expiresOn: string;\n };\n}\n\n/**\n * The Entra Communication Token Options.\n */\nexport interface EntraCommunicationTokenCredentialOptions {\n /**\n * The Azure Communication Service resource endpoint URL, e.g. https://myResource.communication.azure.com.\n */\n resourceEndpoint: string;\n /**\n * The Entra ID token credential.\n */\n tokenCredential: TokenCredential;\n /**\n * The scopes for retrieving the Entra ID access token.\n */\n scopes?: string[];\n}\n\n/**\n * Represents a credential that exchanges an Entra token for an Azure Communication Services (ACS) token, enabling access to ACS resources.\n */\nexport class EntraTokenCredential implements AcsTokenCredential {\n private isPending: Promise<AccessToken> | null;\n private result = {\n entraToken: undefined as string | undefined,\n acsToken: { token: \"\", expiresOnTimestamp: 0 },\n };\n private client: Client;\n private httpClient: HttpClient;\n\n constructor(private options: EntraCommunicationTokenCredentialOptions) {\n this.client = getClient(options.resourceEndpoint);\n this.httpClient = createDefaultHttpClient();\n this.options = options;\n this.options.scopes = this.options.scopes || [\n \"https://communication.azure.com/clients/.default\",\n ];\n\n // immediately fetch the token to pre-warm\n this.isPending = this.getToken();\n }\n\n public async getToken(options?: CommunicationGetTokenOptions): Promise<AccessToken> {\n if (options?.abortSignal?.aborted) {\n return { token: \"\", expiresOnTimestamp: 0 };\n }\n\n // we're awaiting the token fetch, so we don't want to start another one\n // however, we're ignoring the new abortSignal, unfortunately\n if (!this.isPending) {\n this.isPending = this.getTokenInternal(options);\n }\n\n try {\n await this.isPending;\n } finally {\n this.isPending = null;\n }\n\n return this.result.acsToken;\n }\n\n private async getTokenInternal(options?: CommunicationGetTokenOptions): Promise<AccessToken> {\n const getTokenOptions = options?.abortSignal ? { abortSignal: options.abortSignal } : undefined;\n const token = await this.options.tokenCredential.getToken(\n this.options.scopes\n ? this.options.scopes\n : [\"https://communication.azure.com/clients/.default\"],\n getTokenOptions,\n );\n const currentDateTime = new Date();\n const tokenExpiresOn = new Date(this.result.acsToken.expiresOnTimestamp);\n\n if (token === null) {\n this.result = {\n entraToken: undefined,\n acsToken: { token: \"\", expiresOnTimestamp: 0 },\n };\n } else if (\n this.result.acsToken.token === \"\" ||\n token.token !== this.result.entraToken ||\n tokenExpiresOn < currentDateTime\n ) {\n const acsToken = await this.exchangeEntraToken(\n this.options.resourceEndpoint,\n token.token,\n getTokenOptions,\n );\n this.result = {\n entraToken: token.token,\n acsToken,\n };\n }\n\n return this.result.acsToken;\n }\n\n public dispose(): void {\n this.result = {\n entraToken: undefined,\n acsToken: { token: \"\", expiresOnTimestamp: 0 },\n };\n }\n\n private async exchangeEntraToken(\n resourceEndpoint: string,\n entraToken: string,\n options?: { abortSignal: AbortSignalLike },\n ): Promise<AccessToken> {\n const request = createPipelineRequest({\n url: this.createRequestUri(resourceEndpoint),\n method: \"POST\",\n headers: createHttpHeaders({\n Authorization: `Bearer ${entraToken}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n }),\n abortSignal: options?.abortSignal,\n body: JSON.stringify({}),\n });\n const response = await this.client.pipeline.sendRequest(this.httpClient, request);\n\n if (response.status !== 200 || !response.bodyAsText) {\n throw new Error(\n `Service request failed. Status: ${response.status}, Body: ${response.bodyAsText}`,\n );\n }\n const json = JSON.parse(response.bodyAsText) as ExchangeTokenResponse;\n return {\n token: json.accessToken.token,\n expiresOnTimestamp: Date.parse(json.accessToken.expiresOn),\n };\n }\n\n private createRequestUri(resourceEndpoint: string): string {\n const [endpoint, apiVersion] = this.determineEndpointAndApiVersion();\n const requestUri = `${resourceEndpoint}${endpoint}?api-version=${apiVersion}`;\n return requestUri;\n }\n\n private determineEndpointAndApiVersion(): [string, string] {\n if (!this.options.scopes || this.options.scopes.length === 0) {\n throw new Error(\n `Scopes validation failed. Ensure all scopes start with either {TeamsExtensionScopePrefix} or {CommunicationClientsScopePrefix}.`,\n );\n } else if (this.options.scopes.every((scope) => scope.startsWith(TeamsExtensionScopePrefix))) {\n return [TeamsExtensionEndpoint, TeamsExtensionApiVersion];\n } else if (\n this.options.scopes.every((scope) => scope.startsWith(CommunicationClientsScopePrefix))\n ) {\n return [CommunicationClientsEndpoint, CommunicationClientsApiVersion];\n } else {\n throw new Error(\n `Scopes validation failed. Ensure all scopes start with either {TeamsExtensionScopePrefix} or {CommunicationClientsScopePrefix}.`,\n );\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"entraTokenCredential.js","sourceRoot":"","sources":["../../src/entraTokenCredential.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;AAQlC,OAAO,EAAe,SAAS,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,EAEL,uBAAuB,EACvB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,2BAA2B,CAAC;AAEnC,MAAM,2BAA2B,GAAG;IAClC,4CAA4C;IAC5C,2CAA2C;CAC5C,CAAC;AACF,MAAM,+BAA+B,GAAG,0CAA0C,CAAC;AACnF,MAAM,2BAA2B,GAAG,iEAAiE;IACnG,GAAG,2BAA2B;IAC9B,+BAA+B;CAChC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;AAChB,MAAM,sBAAsB,GAAG,6CAA6C,CAAC;AAC7E,MAAM,wBAAwB,GAAG,YAAY,CAAC;AAC9C,MAAM,4BAA4B,GAAG,oCAAoC,CAAC;AAC1E,MAAM,8BAA8B,GAAG,oBAAoB,CAAC;AA4B5D;;GAEG;AACH,MAAM,OAAO,oBAAoB;IASX;IARZ,SAAS,CAA8B;IACvC,MAAM,GAAG;QACf,UAAU,EAAE,SAA+B;QAC3C,QAAQ,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,kBAAkB,EAAE,CAAC,EAAE;KAC/C,CAAC;IACM,MAAM,CAAS;IACf,UAAU,CAAa;IAE/B,YAAoB,OAAiD;QAAjD,YAAO,GAAP,OAAO,CAA0C;QACnE,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;QAClD,IAAI,CAAC,UAAU,GAAG,uBAAuB,EAAE,CAAC;QAC5C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI;YAC3C,kDAAkD;SACnD,CAAC;QAEF,0CAA0C;QAC1C,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;IACnC,CAAC;IAEM,KAAK,CAAC,QAAQ,CAAC,OAAsC;QAC1D,IAAI,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC;YAClC,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,kBAAkB,EAAE,CAAC,EAAE,CAAC;QAC9C,CAAC;QAED,wEAAwE;QACxE,6DAA6D;QAC7D,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YACpB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC;QAClD,CAAC;QAED,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,SAAS,CAAC;QACvB,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACxB,CAAC;QAED,OAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;IAC9B,CAAC;IAEO,KAAK,CAAC,gBAAgB,CAAC,OAAsC;QACnE,MAAM,eAAe,GAAG,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QAChG,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,QAAQ,CACvD,IAAI,CAAC,OAAO,CAAC,MAAM;YACjB,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM;YACrB,CAAC,CAAC,CAAC,kDAAkD,CAAC,EACxD,eAAe,CAChB,CAAC;QACF,MAAM,eAAe,GAAG,IAAI,IAAI,EAAE,CAAC;QACnC,MAAM,cAAc,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC;QAEzE,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,IAAI,CAAC,MAAM,GAAG;gBACZ,UAAU,EAAE,SAAS;gBACrB,QAAQ,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,kBAAkB,EAAE,CAAC,EAAE;aAC/C,CAAC;QACJ,CAAC;aAAM,IACL,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,KAAK,EAAE;YACjC,KAAK,CAAC,KAAK,KAAK,IAAI,CAAC,MAAM,CAAC,UAAU;YACtC,cAAc,GAAG,eAAe,EAChC,CAAC;YACD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAC5C,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAC7B,KAAK,CAAC,KAAK,EACX,eAAe,CAChB,CAAC;YACF,IAAI,CAAC,MAAM,GAAG;gBACZ,UAAU,EAAE,KAAK,CAAC,KAAK;gBACvB,QAAQ;aACT,CAAC;QACJ,CAAC;QAED,OAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;IAC9B,CAAC;IAEM,OAAO;QACZ,IAAI,CAAC,MAAM,GAAG;YACZ,UAAU,EAAE,SAAS;YACrB,QAAQ,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,kBAAkB,EAAE,CAAC,EAAE;SAC/C,CAAC;IACJ,CAAC;IAEO,KAAK,CAAC,kBAAkB,CAC9B,gBAAwB,EACxB,UAAkB,EAClB,OAA0C;QAE1C,MAAM,OAAO,GAAG,qBAAqB,CAAC;YACpC,GAAG,EAAE,IAAI,CAAC,gBAAgB,CAAC,gBAAgB,CAAC;YAC5C,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,iBAAiB,CAAC;gBACzB,aAAa,EAAE,UAAU,UAAU,EAAE;gBACrC,cAAc,EAAE,kBAAkB;gBAClC,MAAM,EAAE,kBAAkB;aAC3B,CAAC;YACF,WAAW,EAAE,OAAO,EAAE,WAAW;YACjC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;SACzB,CAAC,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAElF,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;YACpD,MAAM,IAAI,KAAK,CACb,mCAAmC,QAAQ,CAAC,MAAM,WAAW,QAAQ,CAAC,UAAU,EAAE,CACnF,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,UAAU,CAA0B,CAAC;QACtE,OAAO;YACL,KAAK,EAAE,IAAI,CAAC,WAAW,CAAC,KAAK;YAC7B,kBAAkB,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC;SAC3D,CAAC;IACJ,CAAC;IAEO,gBAAgB,CAAC,gBAAwB;QAC/C,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC,GAAG,IAAI,CAAC,8BAA8B,EAAE,CAAC;QACrE,MAAM,UAAU,GAAG,GAAG,gBAAgB,GAAG,QAAQ,gBAAgB,UAAU,EAAE,CAAC;QAC9E,OAAO,UAAU,CAAC;IACpB,CAAC;IAEO,8BAA8B;QACpC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC7D,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;QAC/C,CAAC;QAED,IACE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAClC,2BAA2B,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CACvE,EACD,CAAC;YACD,OAAO,CAAC,sBAAsB,EAAE,wBAAwB,CAAC,CAAC;QAC5D,CAAC;QAED,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,+BAA+B,CAAC,CAAC,EAAE,CAAC;YAC5F,OAAO,CAAC,4BAA4B,EAAE,8BAA8B,CAAC,CAAC;QACxE,CAAC;QAED,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;IAC/C,CAAC;CACF","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport { type AccessToken, type TokenCredential } from \"@azure/core-auth\";\nimport {\n type TokenCredential as AcsTokenCredential,\n type CommunicationGetTokenOptions,\n} from \"./communicationTokenCredential.js\";\nimport { type AbortSignalLike } from \"@azure/abort-controller\";\nimport { type Client, getClient } from \"@azure-rest/core-client\";\nimport {\n type HttpClient,\n createDefaultHttpClient,\n createHttpHeaders,\n createPipelineRequest,\n} from \"@azure/core-rest-pipeline\";\n\nconst TeamsExtensionScopePrefixes = [\n \"https://auth.msft.communication.azure.com/\",\n \"https://auth.msft.communication.azure.us/\",\n];\nconst CommunicationClientsScopePrefix = \"https://communication.azure.com/clients/\";\nconst ScopeValidationErrorMessage = `Scopes validation failed. Ensure all scopes start with one of ${[\n ...TeamsExtensionScopePrefixes,\n CommunicationClientsScopePrefix,\n].join(\", \")}.`;\nconst TeamsExtensionEndpoint = \"/access/teamsExtension/:exchangeAccessToken\";\nconst TeamsExtensionApiVersion = \"2025-06-30\";\nconst CommunicationClientsEndpoint = \"/access/entra/:exchangeAccessToken\";\nconst CommunicationClientsApiVersion = \"2025-03-02-preview\";\n\nexport interface ExchangeTokenResponse {\n identity: string;\n accessToken: {\n token: string;\n expiresOn: string;\n };\n}\n\n/**\n * The Entra Communication Token Options.\n */\nexport interface EntraCommunicationTokenCredentialOptions {\n /**\n * The Azure Communication Service resource endpoint URL, e.g. https://myResource.communication.azure.com.\n */\n resourceEndpoint: string;\n /**\n * The Entra ID token credential.\n */\n tokenCredential: TokenCredential;\n /**\n * The scopes for retrieving the Entra ID access token.\n */\n scopes?: string[];\n}\n\n/**\n * Represents a credential that exchanges an Entra token for an Azure Communication Services (ACS) token, enabling access to ACS resources.\n */\nexport class EntraTokenCredential implements AcsTokenCredential {\n private isPending: Promise<AccessToken> | null;\n private result = {\n entraToken: undefined as string | undefined,\n acsToken: { token: \"\", expiresOnTimestamp: 0 },\n };\n private client: Client;\n private httpClient: HttpClient;\n\n constructor(private options: EntraCommunicationTokenCredentialOptions) {\n this.client = getClient(options.resourceEndpoint);\n this.httpClient = createDefaultHttpClient();\n this.options = options;\n this.options.scopes = this.options.scopes || [\n \"https://communication.azure.com/clients/.default\",\n ];\n\n // immediately fetch the token to pre-warm\n this.isPending = this.getToken();\n }\n\n public async getToken(options?: CommunicationGetTokenOptions): Promise<AccessToken> {\n if (options?.abortSignal?.aborted) {\n return { token: \"\", expiresOnTimestamp: 0 };\n }\n\n // we're awaiting the token fetch, so we don't want to start another one\n // however, we're ignoring the new abortSignal, unfortunately\n if (!this.isPending) {\n this.isPending = this.getTokenInternal(options);\n }\n\n try {\n await this.isPending;\n } finally {\n this.isPending = null;\n }\n\n return this.result.acsToken;\n }\n\n private async getTokenInternal(options?: CommunicationGetTokenOptions): Promise<AccessToken> {\n const getTokenOptions = options?.abortSignal ? { abortSignal: options.abortSignal } : undefined;\n const token = await this.options.tokenCredential.getToken(\n this.options.scopes\n ? this.options.scopes\n : [\"https://communication.azure.com/clients/.default\"],\n getTokenOptions,\n );\n const currentDateTime = new Date();\n const tokenExpiresOn = new Date(this.result.acsToken.expiresOnTimestamp);\n\n if (token === null) {\n this.result = {\n entraToken: undefined,\n acsToken: { token: \"\", expiresOnTimestamp: 0 },\n };\n } else if (\n this.result.acsToken.token === \"\" ||\n token.token !== this.result.entraToken ||\n tokenExpiresOn < currentDateTime\n ) {\n const acsToken = await this.exchangeEntraToken(\n this.options.resourceEndpoint,\n token.token,\n getTokenOptions,\n );\n this.result = {\n entraToken: token.token,\n acsToken,\n };\n }\n\n return this.result.acsToken;\n }\n\n public dispose(): void {\n this.result = {\n entraToken: undefined,\n acsToken: { token: \"\", expiresOnTimestamp: 0 },\n };\n }\n\n private async exchangeEntraToken(\n resourceEndpoint: string,\n entraToken: string,\n options?: { abortSignal: AbortSignalLike },\n ): Promise<AccessToken> {\n const request = createPipelineRequest({\n url: this.createRequestUri(resourceEndpoint),\n method: \"POST\",\n headers: createHttpHeaders({\n Authorization: `Bearer ${entraToken}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n }),\n abortSignal: options?.abortSignal,\n body: JSON.stringify({}),\n });\n const response = await this.client.pipeline.sendRequest(this.httpClient, request);\n\n if (response.status !== 200 || !response.bodyAsText) {\n throw new Error(\n `Service request failed. Status: ${response.status}, Body: ${response.bodyAsText}`,\n );\n }\n const json = JSON.parse(response.bodyAsText) as ExchangeTokenResponse;\n return {\n token: json.accessToken.token,\n expiresOnTimestamp: Date.parse(json.accessToken.expiresOn),\n };\n }\n\n private createRequestUri(resourceEndpoint: string): string {\n const [endpoint, apiVersion] = this.determineEndpointAndApiVersion();\n const requestUri = `${resourceEndpoint}${endpoint}?api-version=${apiVersion}`;\n return requestUri;\n }\n\n private determineEndpointAndApiVersion(): [string, string] {\n if (!this.options.scopes || this.options.scopes.length === 0) {\n throw new Error(ScopeValidationErrorMessage);\n }\n\n if (\n this.options.scopes.every((scope) =>\n TeamsExtensionScopePrefixes.some((prefix) => scope.startsWith(prefix)),\n )\n ) {\n return [TeamsExtensionEndpoint, TeamsExtensionApiVersion];\n }\n\n if (this.options.scopes.every((scope) => scope.startsWith(CommunicationClientsScopePrefix))) {\n return [CommunicationClientsEndpoint, CommunicationClientsApiVersion];\n }\n\n throw new Error(ScopeValidationErrorMessage);\n }\n}\n"]}
|
package/dist/browser/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
export { CommunicationTokenCredential, CommunicationGetTokenOptions, } from "./communicationTokenCredential.js";
|
|
1
|
+
export type { CommunicationTokenCredential, CommunicationGetTokenOptions, } from "./communicationTokenCredential.js";
|
|
2
2
|
export { AzureCommunicationTokenCredential } from "./azureCommunicationTokenCredential.js";
|
|
3
|
-
export { CommunicationTokenRefreshOptions } from "./autoRefreshTokenCredential.js";
|
|
4
|
-
export { EntraCommunicationTokenCredentialOptions } from "./entraTokenCredential.js";
|
|
3
|
+
export type { CommunicationTokenRefreshOptions } from "./autoRefreshTokenCredential.js";
|
|
4
|
+
export type { EntraCommunicationTokenCredentialOptions } from "./entraTokenCredential.js";
|
|
5
5
|
export * from "./credential/index.js";
|
|
6
6
|
export * from "./identifierModels.js";
|
|
7
7
|
export * from "./identifierModelSerializer.js";
|
|
8
|
+
export { RestError, isRestError } from "@azure/core-rest-pipeline";
|
|
8
9
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAGA,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAGA,YAAY,EACV,4BAA4B,EAC5B,4BAA4B,GAC7B,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,iCAAiC,EAAE,MAAM,wCAAwC,CAAC;AAC3F,YAAY,EAAE,gCAAgC,EAAE,MAAM,iCAAiC,CAAC;AACxF,YAAY,EAAE,wCAAwC,EAAE,MAAM,2BAA2B,CAAC;AAC1F,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,gCAAgC,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC"}
|
package/dist/browser/index.js
CHANGED
|
@@ -4,4 +4,5 @@ export { AzureCommunicationTokenCredential } from "./azureCommunicationTokenCred
|
|
|
4
4
|
export * from "./credential/index.js";
|
|
5
5
|
export * from "./identifierModels.js";
|
|
6
6
|
export * from "./identifierModelSerializer.js";
|
|
7
|
+
export { RestError, isRestError } from "@azure/core-rest-pipeline";
|
|
7
8
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;AAMlC,OAAO,EAAE,iCAAiC,EAAE,MAAM,wCAAwC,CAAC;AAG3F,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,gCAAgC,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nexport {\n CommunicationTokenCredential,\n CommunicationGetTokenOptions,\n} from \"./communicationTokenCredential.js\";\nexport { AzureCommunicationTokenCredential } from \"./azureCommunicationTokenCredential.js\";\nexport { CommunicationTokenRefreshOptions } from \"./autoRefreshTokenCredential.js\";\nexport { EntraCommunicationTokenCredentialOptions } from \"./entraTokenCredential.js\";\nexport * from \"./credential/index.js\";\nexport * from \"./identifierModels.js\";\nexport * from \"./identifierModelSerializer.js\";\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;AAMlC,OAAO,EAAE,iCAAiC,EAAE,MAAM,wCAAwC,CAAC;AAG3F,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,gCAAgC,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nexport type {\n CommunicationTokenCredential,\n CommunicationGetTokenOptions,\n} from \"./communicationTokenCredential.js\";\nexport { AzureCommunicationTokenCredential } from \"./azureCommunicationTokenCredential.js\";\nexport type { CommunicationTokenRefreshOptions } from \"./autoRefreshTokenCredential.js\";\nexport type { EntraCommunicationTokenCredentialOptions } from \"./entraTokenCredential.js\";\nexport * from \"./credential/index.js\";\nexport * from \"./identifierModels.js\";\nexport * from \"./identifierModelSerializer.js\";\nexport { RestError, isRestError } from \"@azure/core-rest-pipeline\";\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/logger.ts"],"names":[],"mappings":"AAKA,yDAAyD;AACzD,eAAO,MAAM,MAAM,qCAA6C,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
import { createClientLogger } from "@azure/logger";
|
|
4
|
+
/** The `@azure/logger` instance used by this package. */
|
|
5
|
+
export const logger = createClientLogger("communication-common");
|
|
6
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/logger.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,kCAAkC;AAElC,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAEnD,yDAAyD;AACzD,MAAM,CAAC,MAAM,MAAM,GAAG,kBAAkB,CAAC,sBAAsB,CAAC,CAAC","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport { createClientLogger } from \"@azure/logger\";\n\n/** The `@azure/logger` instance used by this package. */\nexport const logger = createClientLogger(\"communication-common\");\n"]}
|
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
import type { AccessToken } from "@azure/core-auth";
|
|
2
|
-
|
|
2
|
+
/** Type guard for a caller-supplied {@link AccessToken} (carries a valid explicit expiry). */
|
|
3
|
+
export declare const isAccessToken: (value: unknown) => value is AccessToken;
|
|
4
|
+
/**
|
|
5
|
+
* Resolves a token into an {@link AccessToken}. Expiry is taken from a caller-supplied
|
|
6
|
+
* `AccessToken` if present, otherwise from the JWT `exp` claim. A token that cannot be decoded
|
|
7
|
+
* (e.g. an encrypted/opaque token) or that lacks a usable `exp` is accepted as-is with a fallback
|
|
8
|
+
* expiry of `now + undecodableTokenExpiryIntervalInSeconds`; an empty/blank token throws.
|
|
9
|
+
*/
|
|
10
|
+
export declare const parseToken: (token: string | AccessToken, undecodableTokenExpiryIntervalInSeconds?: number) => AccessToken;
|
|
3
11
|
//# sourceMappingURL=tokenParser.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tokenParser.d.ts","sourceRoot":"","sources":["../../src/tokenParser.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"tokenParser.d.ts","sourceRoot":"","sources":["../../src/tokenParser.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAkBpD,8FAA8F;AAC9F,eAAO,MAAM,aAAa,GAAI,OAAO,OAAO,KAAG,KAAK,IAAI,WAMI,CAAC;AA8B7D;;;;;GAKG;AACH,eAAO,MAAM,UAAU,GACrB,OAAO,MAAM,GAAG,WAAW,EAC3B,0CAAyC,MAAuD,KAC/F,WA4BF,CAAC"}
|
|
@@ -1,11 +1,72 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
3
|
import { jwtDecode } from "jwt-decode";
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import { logger } from "./logger.js";
|
|
5
|
+
/**
|
|
6
|
+
* Default lifetime (in seconds) assumed for an encrypted/undecodable token when no expiry is
|
|
7
|
+
* supplied. Must stay in step with `defaultExpiringSoonInterval` (10 min) in
|
|
8
|
+
* autoRefreshTokenCredential.ts: the "refresh an opaque token at half-life" behavior relies on a
|
|
9
|
+
* fresh fallback token immediately landing in the expiring-soon window.
|
|
10
|
+
*/
|
|
11
|
+
const defaultUndecodableTokenExpiryIntervalInSeconds = 10 * 60;
|
|
12
|
+
let undecodableTokenWarningEmitted = false;
|
|
13
|
+
/** Type guard for a caller-supplied {@link AccessToken} (carries a valid explicit expiry). */
|
|
14
|
+
export const isAccessToken = (value) => typeof value === "object" &&
|
|
15
|
+
value !== null &&
|
|
16
|
+
typeof value.token === "string" &&
|
|
17
|
+
value.token.length > 0 &&
|
|
18
|
+
typeof value.expiresOnTimestamp === "number" &&
|
|
19
|
+
Number.isFinite(value.expiresOnTimestamp);
|
|
20
|
+
/**
|
|
21
|
+
* Accepts a token that can't be decoded (encrypted/opaque, or a JWT without a usable `exp`) and
|
|
22
|
+
* assigns a fallback expiry of `now + undecodableTokenExpiryIntervalInSeconds`. Emits a one-time
|
|
23
|
+
* warning; throws if the interval is not a positive, finite number.
|
|
24
|
+
*/
|
|
25
|
+
const resolveOpaqueToken = (token, undecodableTokenExpiryIntervalInSeconds) => {
|
|
26
|
+
if (!Number.isFinite(undecodableTokenExpiryIntervalInSeconds) ||
|
|
27
|
+
undecodableTokenExpiryIntervalInSeconds <= 0) {
|
|
28
|
+
throw new Error("undecodableTokenExpiryIntervalInSeconds must be a positive number.");
|
|
29
|
+
}
|
|
30
|
+
if (!undecodableTokenWarningEmitted) {
|
|
31
|
+
undecodableTokenWarningEmitted = true;
|
|
32
|
+
logger.warning(`Received a token that could not be decoded; treating it as opaque and assuming a ${undecodableTokenExpiryIntervalInSeconds}s lifetime. ` +
|
|
33
|
+
`Supply an AccessToken with an explicit expiry (e.g. from the token response 'expires_in') to control refresh scheduling.`);
|
|
34
|
+
}
|
|
6
35
|
return {
|
|
7
36
|
token,
|
|
8
|
-
expiresOnTimestamp:
|
|
37
|
+
expiresOnTimestamp: Date.now() + undecodableTokenExpiryIntervalInSeconds * 1000,
|
|
9
38
|
};
|
|
10
39
|
};
|
|
40
|
+
/**
|
|
41
|
+
* Resolves a token into an {@link AccessToken}. Expiry is taken from a caller-supplied
|
|
42
|
+
* `AccessToken` if present, otherwise from the JWT `exp` claim. A token that cannot be decoded
|
|
43
|
+
* (e.g. an encrypted/opaque token) or that lacks a usable `exp` is accepted as-is with a fallback
|
|
44
|
+
* expiry of `now + undecodableTokenExpiryIntervalInSeconds`; an empty/blank token throws.
|
|
45
|
+
*/
|
|
46
|
+
export const parseToken = (token, undecodableTokenExpiryIntervalInSeconds = defaultUndecodableTokenExpiryIntervalInSeconds) => {
|
|
47
|
+
// Caller supplied an explicit expiry — use it as-is.
|
|
48
|
+
if (isAccessToken(token)) {
|
|
49
|
+
return token;
|
|
50
|
+
}
|
|
51
|
+
if (typeof token !== "string") {
|
|
52
|
+
throw new Error("Invalid token: expected a string or an AccessToken with a non-empty token and a finite expiresOnTimestamp.");
|
|
53
|
+
}
|
|
54
|
+
if (token.trim().length === 0) {
|
|
55
|
+
throw new Error("Invalid token: token must not be empty.");
|
|
56
|
+
}
|
|
57
|
+
// Prefer the expiry from a decodable JWT.
|
|
58
|
+
try {
|
|
59
|
+
const { exp } = jwtDecode(token);
|
|
60
|
+
if (typeof exp === "number" && Number.isFinite(exp)) {
|
|
61
|
+
return {
|
|
62
|
+
token,
|
|
63
|
+
expiresOnTimestamp: exp * 1000,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// Not decodable — treated as an opaque token below.
|
|
69
|
+
}
|
|
70
|
+
return resolveOpaqueToken(token, undecodableTokenExpiryIntervalInSeconds);
|
|
71
|
+
};
|
|
11
72
|
//# sourceMappingURL=tokenParser.js.map
|