@azure/msal-browser 3.0.1 → 3.1.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.
Files changed (257) hide show
  1. package/README.md +5 -5
  2. package/dist/app/IPublicClientApplication.d.ts +6 -2
  3. package/dist/app/IPublicClientApplication.d.ts.map +1 -1
  4. package/dist/app/IPublicClientApplication.mjs +4 -1
  5. package/dist/app/IPublicClientApplication.mjs.map +1 -1
  6. package/dist/app/PublicClientApplication.d.ts +11 -3
  7. package/dist/app/PublicClientApplication.d.ts.map +1 -1
  8. package/dist/app/PublicClientApplication.mjs +10 -2
  9. package/dist/app/PublicClientApplication.mjs.map +1 -1
  10. package/dist/broker/nativeBroker/NativeMessageHandler.d.ts.map +1 -1
  11. package/dist/broker/nativeBroker/NativeMessageHandler.mjs +5 -4
  12. package/dist/broker/nativeBroker/NativeMessageHandler.mjs.map +1 -1
  13. package/dist/cache/AsyncMemoryStorage.mjs +4 -3
  14. package/dist/cache/AsyncMemoryStorage.mjs.map +1 -1
  15. package/dist/cache/BrowserCacheManager.d.ts +13 -3
  16. package/dist/cache/BrowserCacheManager.d.ts.map +1 -1
  17. package/dist/cache/BrowserCacheManager.mjs +54 -26
  18. package/dist/cache/BrowserCacheManager.mjs.map +1 -1
  19. package/dist/cache/BrowserStorage.mjs +1 -1
  20. package/dist/cache/BrowserStorage.mjs.map +1 -1
  21. package/dist/cache/CryptoKeyStore.mjs +1 -1
  22. package/dist/cache/CryptoKeyStore.mjs.map +1 -1
  23. package/dist/cache/DatabaseStorage.d.ts.map +1 -1
  24. package/dist/cache/DatabaseStorage.mjs +9 -8
  25. package/dist/cache/DatabaseStorage.mjs.map +1 -1
  26. package/dist/cache/MemoryStorage.mjs +1 -1
  27. package/dist/cache/MemoryStorage.mjs.map +1 -1
  28. package/dist/cache/TokenCache.d.ts.map +1 -1
  29. package/dist/cache/TokenCache.mjs +30 -30
  30. package/dist/cache/TokenCache.mjs.map +1 -1
  31. package/dist/config/Configuration.d.ts +3 -0
  32. package/dist/config/Configuration.d.ts.map +1 -1
  33. package/dist/config/Configuration.mjs +7 -11
  34. package/dist/config/Configuration.mjs.map +1 -1
  35. package/dist/controllers/ControllerFactory.mjs +1 -1
  36. package/dist/controllers/ControllerFactory.mjs.map +1 -1
  37. package/dist/controllers/IController.d.ts +11 -18
  38. package/dist/controllers/IController.d.ts.map +1 -1
  39. package/dist/controllers/StandardController.d.ts +7 -26
  40. package/dist/controllers/StandardController.d.ts.map +1 -1
  41. package/dist/controllers/StandardController.mjs +28 -52
  42. package/dist/controllers/StandardController.mjs.map +1 -1
  43. package/dist/crypto/BrowserCrypto.d.ts.map +1 -1
  44. package/dist/crypto/BrowserCrypto.mjs +4 -3
  45. package/dist/crypto/BrowserCrypto.mjs.map +1 -1
  46. package/dist/crypto/CryptoOps.d.ts +0 -3
  47. package/dist/crypto/CryptoOps.d.ts.map +1 -1
  48. package/dist/crypto/CryptoOps.mjs +14 -17
  49. package/dist/crypto/CryptoOps.mjs.map +1 -1
  50. package/dist/crypto/ModernBrowserCrypto.mjs +1 -1
  51. package/dist/crypto/ModernBrowserCrypto.mjs.map +1 -1
  52. package/dist/crypto/PkceGenerator.d.ts +0 -1
  53. package/dist/crypto/PkceGenerator.d.ts.map +1 -1
  54. package/dist/crypto/PkceGenerator.mjs +8 -8
  55. package/dist/crypto/PkceGenerator.mjs.map +1 -1
  56. package/dist/crypto/SignedHttpRequest.mjs +1 -1
  57. package/dist/crypto/SignedHttpRequest.mjs.map +1 -1
  58. package/dist/encode/Base64Decode.d.ts +6 -19
  59. package/dist/encode/Base64Decode.d.ts.map +1 -1
  60. package/dist/encode/Base64Decode.mjs +17 -70
  61. package/dist/encode/Base64Decode.mjs.map +1 -1
  62. package/dist/encode/Base64Encode.d.ts +15 -27
  63. package/dist/encode/Base64Encode.d.ts.map +1 -1
  64. package/dist/encode/Base64Encode.mjs +36 -75
  65. package/dist/encode/Base64Encode.mjs.map +1 -1
  66. package/dist/error/BrowserAuthError.d.ts +54 -195
  67. package/dist/error/BrowserAuthError.d.ts.map +1 -1
  68. package/dist/error/BrowserAuthError.mjs +150 -381
  69. package/dist/error/BrowserAuthError.mjs.map +1 -1
  70. package/dist/error/BrowserAuthErrorCodes.d.ts +45 -0
  71. package/dist/error/BrowserAuthErrorCodes.d.ts.map +1 -0
  72. package/dist/error/BrowserAuthErrorCodes.mjs +53 -0
  73. package/dist/error/BrowserAuthErrorCodes.mjs.map +1 -0
  74. package/dist/error/BrowserConfigurationAuthError.mjs +1 -1
  75. package/dist/error/BrowserConfigurationAuthError.mjs.map +1 -1
  76. package/dist/error/NativeAuthError.d.ts.map +1 -1
  77. package/dist/error/NativeAuthError.mjs +5 -4
  78. package/dist/error/NativeAuthError.mjs.map +1 -1
  79. package/dist/event/EventHandler.mjs +1 -1
  80. package/dist/event/EventHandler.mjs.map +1 -1
  81. package/dist/event/EventMessage.mjs +1 -1
  82. package/dist/event/EventMessage.mjs.map +1 -1
  83. package/dist/event/EventType.mjs +1 -1
  84. package/dist/event/EventType.mjs.map +1 -1
  85. package/dist/index.d.ts +9 -6
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.mjs +5 -3
  88. package/dist/index.mjs.map +1 -1
  89. package/dist/interaction_client/BaseInteractionClient.d.ts +2 -1
  90. package/dist/interaction_client/BaseInteractionClient.d.ts.map +1 -1
  91. package/dist/interaction_client/BaseInteractionClient.mjs +1 -1
  92. package/dist/interaction_client/BaseInteractionClient.mjs.map +1 -1
  93. package/dist/interaction_client/HybridSpaAuthorizationCodeClient.mjs +1 -1
  94. package/dist/interaction_client/HybridSpaAuthorizationCodeClient.mjs.map +1 -1
  95. package/dist/interaction_client/NativeInteractionClient.d.ts +4 -10
  96. package/dist/interaction_client/NativeInteractionClient.d.ts.map +1 -1
  97. package/dist/interaction_client/NativeInteractionClient.mjs +22 -30
  98. package/dist/interaction_client/NativeInteractionClient.mjs.map +1 -1
  99. package/dist/interaction_client/PopupClient.d.ts.map +1 -1
  100. package/dist/interaction_client/PopupClient.mjs +16 -15
  101. package/dist/interaction_client/PopupClient.mjs.map +1 -1
  102. package/dist/interaction_client/RedirectClient.d.ts.map +1 -1
  103. package/dist/interaction_client/RedirectClient.mjs +7 -6
  104. package/dist/interaction_client/RedirectClient.mjs.map +1 -1
  105. package/dist/interaction_client/SilentAuthCodeClient.d.ts.map +1 -1
  106. package/dist/interaction_client/SilentAuthCodeClient.mjs +5 -4
  107. package/dist/interaction_client/SilentAuthCodeClient.mjs.map +1 -1
  108. package/dist/interaction_client/SilentCacheClient.d.ts +4 -2
  109. package/dist/interaction_client/SilentCacheClient.d.ts.map +1 -1
  110. package/dist/interaction_client/SilentCacheClient.mjs +10 -8
  111. package/dist/interaction_client/SilentCacheClient.mjs.map +1 -1
  112. package/dist/interaction_client/SilentIframeClient.d.ts.map +1 -1
  113. package/dist/interaction_client/SilentIframeClient.mjs +20 -47
  114. package/dist/interaction_client/SilentIframeClient.mjs.map +1 -1
  115. package/dist/interaction_client/SilentRefreshClient.d.ts.map +1 -1
  116. package/dist/interaction_client/SilentRefreshClient.mjs +4 -3
  117. package/dist/interaction_client/SilentRefreshClient.mjs.map +1 -1
  118. package/dist/interaction_client/StandardInteractionClient.d.ts.map +1 -1
  119. package/dist/interaction_client/StandardInteractionClient.mjs +8 -7
  120. package/dist/interaction_client/StandardInteractionClient.mjs.map +1 -1
  121. package/dist/interaction_handler/InteractionHandler.d.ts.map +1 -1
  122. package/dist/interaction_handler/InteractionHandler.mjs +9 -9
  123. package/dist/interaction_handler/InteractionHandler.mjs.map +1 -1
  124. package/dist/interaction_handler/RedirectHandler.d.ts.map +1 -1
  125. package/dist/interaction_handler/RedirectHandler.mjs +11 -10
  126. package/dist/interaction_handler/RedirectHandler.mjs.map +1 -1
  127. package/dist/interaction_handler/SilentHandler.d.ts.map +1 -1
  128. package/dist/interaction_handler/SilentHandler.mjs +10 -9
  129. package/dist/interaction_handler/SilentHandler.mjs.map +1 -1
  130. package/dist/navigation/NavigationClient.mjs +1 -1
  131. package/dist/navigation/NavigationClient.mjs.map +1 -1
  132. package/dist/network/FetchClient.d.ts.map +1 -1
  133. package/dist/network/FetchClient.mjs +9 -8
  134. package/dist/network/FetchClient.mjs.map +1 -1
  135. package/dist/operatingcontext/BaseOperatingContext.mjs +1 -1
  136. package/dist/operatingcontext/BaseOperatingContext.mjs.map +1 -1
  137. package/dist/operatingcontext/StandardOperatingContext.mjs +1 -1
  138. package/dist/operatingcontext/StandardOperatingContext.mjs.map +1 -1
  139. package/dist/operatingcontext/TeamsAppOperatingContext.mjs +1 -1
  140. package/dist/operatingcontext/TeamsAppOperatingContext.mjs.map +1 -1
  141. package/dist/packageMetadata.d.ts +1 -1
  142. package/dist/packageMetadata.mjs +2 -2
  143. package/dist/packageMetadata.mjs.map +1 -1
  144. package/dist/request/ClearCacheRequest.d.ts +11 -0
  145. package/dist/request/ClearCacheRequest.d.ts.map +1 -0
  146. package/dist/telemetry/BrowserPerformanceClient.d.ts +2 -4
  147. package/dist/telemetry/BrowserPerformanceClient.d.ts.map +1 -1
  148. package/dist/telemetry/BrowserPerformanceClient.mjs +2 -6
  149. package/dist/telemetry/BrowserPerformanceClient.mjs.map +1 -1
  150. package/dist/telemetry/BrowserPerformanceMeasurement.mjs +1 -1
  151. package/dist/telemetry/BrowserPerformanceMeasurement.mjs.map +1 -1
  152. package/dist/utils/BrowserConstants.mjs +1 -1
  153. package/dist/utils/BrowserConstants.mjs.map +1 -1
  154. package/dist/utils/BrowserProtocolUtils.d.ts.map +1 -1
  155. package/dist/utils/BrowserProtocolUtils.mjs +3 -3
  156. package/dist/utils/BrowserProtocolUtils.mjs.map +1 -1
  157. package/dist/utils/BrowserStringUtils.mjs +1 -1
  158. package/dist/utils/BrowserStringUtils.mjs.map +1 -1
  159. package/dist/utils/BrowserUtils.d.ts +0 -9
  160. package/dist/utils/BrowserUtils.d.ts.map +1 -1
  161. package/dist/utils/BrowserUtils.mjs +8 -33
  162. package/dist/utils/BrowserUtils.mjs.map +1 -1
  163. package/lib/msal-browser.cjs +6601 -7104
  164. package/lib/msal-browser.cjs.map +1 -1
  165. package/lib/msal-browser.js +6601 -7104
  166. package/lib/msal-browser.js.map +1 -1
  167. package/lib/msal-browser.min.js +65 -62
  168. package/package.json +7 -5
  169. package/src/app/IPublicClientApplication.ts +199 -0
  170. package/src/app/PublicClientApplication.ts +405 -0
  171. package/src/broker/nativeBroker/NativeMessageHandler.ts +419 -0
  172. package/src/broker/nativeBroker/NativeRequest.ts +52 -0
  173. package/src/broker/nativeBroker/NativeResponse.ts +56 -0
  174. package/src/cache/AsyncMemoryStorage.ts +158 -0
  175. package/src/cache/BrowserCacheManager.ts +1994 -0
  176. package/src/cache/BrowserStorage.ts +54 -0
  177. package/src/cache/CryptoKeyStore.ts +63 -0
  178. package/src/cache/DatabaseStorage.ts +287 -0
  179. package/src/cache/IAsyncMemoryStorage.ts +36 -0
  180. package/src/cache/ITokenCache.ts +21 -0
  181. package/src/cache/IWindowStorage.ts +36 -0
  182. package/src/cache/MemoryStorage.ts +42 -0
  183. package/src/cache/TokenCache.ts +484 -0
  184. package/src/config/Configuration.ts +375 -0
  185. package/src/controllers/ControllerFactory.ts +55 -0
  186. package/src/controllers/IController.ts +131 -0
  187. package/src/controllers/StandardController.ts +2107 -0
  188. package/src/crypto/BrowserCrypto.ts +135 -0
  189. package/src/crypto/CryptoOps.ts +247 -0
  190. package/src/crypto/ISubtleCrypto.ts +30 -0
  191. package/src/crypto/ModernBrowserCrypto.ts +69 -0
  192. package/src/crypto/PkceGenerator.ts +76 -0
  193. package/src/crypto/SignedHttpRequest.ts +76 -0
  194. package/src/encode/Base64Decode.ts +26 -0
  195. package/src/encode/Base64Encode.ts +52 -0
  196. package/src/error/BrowserAuthError.ts +340 -0
  197. package/src/error/BrowserAuthErrorCodes.ts +56 -0
  198. package/src/error/BrowserConfigurationAuthError.ts +133 -0
  199. package/src/error/NativeAuthError.ts +141 -0
  200. package/src/event/EventHandler.ts +176 -0
  201. package/src/event/EventMessage.ts +125 -0
  202. package/src/event/EventType.ts +33 -0
  203. package/src/index.ts +152 -0
  204. package/src/interaction_client/BaseInteractionClient.ts +301 -0
  205. package/src/interaction_client/HybridSpaAuthorizationCodeClient.ts +16 -0
  206. package/src/interaction_client/NativeInteractionClient.ts +907 -0
  207. package/src/interaction_client/PopupClient.ts +926 -0
  208. package/src/interaction_client/RedirectClient.ts +616 -0
  209. package/src/interaction_client/SilentAuthCodeClient.ts +163 -0
  210. package/src/interaction_client/SilentCacheClient.ts +133 -0
  211. package/src/interaction_client/SilentIframeClient.ts +301 -0
  212. package/src/interaction_client/SilentRefreshClient.ts +120 -0
  213. package/src/interaction_client/StandardInteractionClient.ts +481 -0
  214. package/src/interaction_handler/InteractionHandler.ts +246 -0
  215. package/src/interaction_handler/RedirectHandler.ts +225 -0
  216. package/src/interaction_handler/SilentHandler.ts +266 -0
  217. package/src/navigation/INavigationClient.ts +23 -0
  218. package/src/navigation/NavigationClient.ts +55 -0
  219. package/src/navigation/NavigationOptions.ts +18 -0
  220. package/src/network/FetchClient.ts +130 -0
  221. package/src/operatingcontext/BaseOperatingContext.ts +82 -0
  222. package/src/operatingcontext/StandardOperatingContext.ts +49 -0
  223. package/src/operatingcontext/TeamsAppOperatingContext.ts +48 -0
  224. package/src/packageMetadata.ts +3 -0
  225. package/src/request/AuthorizationCodeRequest.ts +19 -0
  226. package/src/request/AuthorizationUrlRequest.ts +17 -0
  227. package/src/request/ClearCacheRequest.ts +16 -0
  228. package/src/request/EndSessionPopupRequest.ts +26 -0
  229. package/src/request/EndSessionRequest.ts +23 -0
  230. package/src/request/PopupRequest.ts +51 -0
  231. package/src/request/PopupWindowAttributes.ts +22 -0
  232. package/src/request/RedirectRequest.ts +51 -0
  233. package/src/request/SilentRequest.ts +49 -0
  234. package/src/request/SsoSilentRequest.ts +44 -0
  235. package/src/response/AuthenticationResult.ts +13 -0
  236. package/src/telemetry/BrowserPerformanceClient.ts +226 -0
  237. package/src/telemetry/BrowserPerformanceMeasurement.ts +144 -0
  238. package/src/utils/BrowserConstants.ts +241 -0
  239. package/src/utils/BrowserProtocolUtils.ts +57 -0
  240. package/src/utils/BrowserStringUtils.ts +143 -0
  241. package/src/utils/BrowserUtils.ts +156 -0
  242. package/dist/crypto/GuidGenerator.d.ts +0 -13
  243. package/dist/crypto/GuidGenerator.d.ts.map +0 -1
  244. package/dist/crypto/GuidGenerator.mjs +0 -104
  245. package/dist/crypto/GuidGenerator.mjs.map +0 -1
  246. package/dist/internals.d.ts +0 -23
  247. package/dist/internals.d.ts.map +0 -1
  248. package/dist/internals.mjs +0 -28
  249. package/dist/internals.mjs.map +0 -1
  250. package/dist/network/XhrClient.d.ts +0 -41
  251. package/dist/network/XhrClient.d.ts.map +0 -1
  252. package/dist/network/XhrClient.mjs +0 -125
  253. package/dist/network/XhrClient.mjs.map +0 -1
  254. package/dist/utils/MathUtils.d.ts +0 -12
  255. package/dist/utils/MathUtils.d.ts.map +0 -1
  256. package/dist/utils/MathUtils.mjs +0 -26
  257. package/dist/utils/MathUtils.mjs.map +0 -1
@@ -0,0 +1,2107 @@
1
+ /*
2
+ * Copyright (c) Microsoft Corporation. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { CryptoOps } from "../crypto/CryptoOps";
7
+ import {
8
+ InteractionRequiredAuthError,
9
+ AccountInfo,
10
+ Constants,
11
+ INetworkModule,
12
+ Logger,
13
+ CommonSilentFlowRequest,
14
+ ICrypto,
15
+ DEFAULT_CRYPTO_IMPLEMENTATION,
16
+ AuthError,
17
+ PerformanceEvents,
18
+ PerformanceCallbackFunction,
19
+ IPerformanceClient,
20
+ BaseAuthRequest,
21
+ PromptValue,
22
+ ClientAuthError,
23
+ InProgressPerformanceEvent,
24
+ RequestThumbprint,
25
+ ServerError,
26
+ AccountEntity,
27
+ ServerResponseType,
28
+ UrlString,
29
+ invokeAsync,
30
+ } from "@azure/msal-common";
31
+ import {
32
+ BrowserCacheManager,
33
+ DEFAULT_BROWSER_CACHE_MANAGER,
34
+ } from "../cache/BrowserCacheManager";
35
+ import { BrowserConfiguration, CacheOptions } from "../config/Configuration";
36
+ import {
37
+ InteractionType,
38
+ ApiId,
39
+ BrowserCacheLocation,
40
+ WrapperSKU,
41
+ TemporaryCacheKeys,
42
+ CacheLookupPolicy,
43
+ DEFAULT_REQUEST,
44
+ BrowserConstants,
45
+ } from "../utils/BrowserConstants";
46
+ import { BrowserUtils } from "../utils/BrowserUtils";
47
+ import { RedirectRequest } from "../request/RedirectRequest";
48
+ import { PopupRequest } from "../request/PopupRequest";
49
+ import { SsoSilentRequest } from "../request/SsoSilentRequest";
50
+ import { EventCallbackFunction, EventError } from "../event/EventMessage";
51
+ import { EventType } from "../event/EventType";
52
+ import { EndSessionRequest } from "../request/EndSessionRequest";
53
+ import { BrowserConfigurationAuthError } from "../error/BrowserConfigurationAuthError";
54
+ import { EndSessionPopupRequest } from "../request/EndSessionPopupRequest";
55
+ import { INavigationClient } from "../navigation/INavigationClient";
56
+ import { EventHandler } from "../event/EventHandler";
57
+ import { PopupClient } from "../interaction_client/PopupClient";
58
+ import { RedirectClient } from "../interaction_client/RedirectClient";
59
+ import { SilentIframeClient } from "../interaction_client/SilentIframeClient";
60
+ import { SilentRefreshClient } from "../interaction_client/SilentRefreshClient";
61
+ import { TokenCache } from "../cache/TokenCache";
62
+ import { ITokenCache } from "../cache/ITokenCache";
63
+ import { NativeInteractionClient } from "../interaction_client/NativeInteractionClient";
64
+ import { NativeMessageHandler } from "../broker/nativeBroker/NativeMessageHandler";
65
+ import { SilentRequest } from "../request/SilentRequest";
66
+ import { NativeAuthError } from "../error/NativeAuthError";
67
+ import { SilentCacheClient } from "../interaction_client/SilentCacheClient";
68
+ import { SilentAuthCodeClient } from "../interaction_client/SilentAuthCodeClient";
69
+ import {
70
+ createBrowserAuthError,
71
+ BrowserAuthErrorCodes,
72
+ } from "../error/BrowserAuthError";
73
+ import { AuthorizationCodeRequest } from "../request/AuthorizationCodeRequest";
74
+ import { NativeTokenRequest } from "../broker/nativeBroker/NativeRequest";
75
+ import { StandardOperatingContext } from "../operatingcontext/StandardOperatingContext";
76
+ import { BaseOperatingContext } from "../operatingcontext/BaseOperatingContext";
77
+ import { IController } from "./IController";
78
+ import { AuthenticationResult } from "../response/AuthenticationResult";
79
+ import { ClearCacheRequest } from "../request/ClearCacheRequest";
80
+
81
+ export class StandardController implements IController {
82
+ // OperatingContext
83
+ protected readonly operatingContext: StandardOperatingContext;
84
+
85
+ // Crypto interface implementation
86
+ protected readonly browserCrypto: ICrypto;
87
+
88
+ // Storage interface implementation
89
+ protected readonly browserStorage: BrowserCacheManager;
90
+
91
+ // Native Cache in memory storage implementation
92
+ protected readonly nativeInternalStorage: BrowserCacheManager;
93
+
94
+ // Network interface implementation
95
+ protected readonly networkClient: INetworkModule;
96
+
97
+ // Navigation interface implementation
98
+ protected navigationClient: INavigationClient;
99
+
100
+ // Input configuration by developer/user
101
+ protected readonly config: BrowserConfiguration;
102
+
103
+ // Token cache implementation
104
+ private tokenCache: TokenCache;
105
+
106
+ // Logger
107
+ protected logger: Logger;
108
+
109
+ // Flag to indicate if in browser environment
110
+ protected isBrowserEnvironment: boolean;
111
+
112
+ protected readonly eventHandler: EventHandler;
113
+
114
+ // Redirect Response Object
115
+ protected readonly redirectResponse: Map<
116
+ string,
117
+ Promise<AuthenticationResult | null>
118
+ >;
119
+
120
+ // Native Extension Provider
121
+ protected nativeExtensionProvider: NativeMessageHandler | undefined;
122
+
123
+ // Hybrid auth code responses
124
+ private hybridAuthCodeResponses: Map<string, Promise<AuthenticationResult>>;
125
+
126
+ // Performance telemetry client
127
+ protected readonly performanceClient: IPerformanceClient;
128
+
129
+ // Flag representing whether or not the initialize API has been called and completed
130
+ protected initialized: boolean;
131
+
132
+ // Active requests
133
+ private activeSilentTokenRequests: Map<
134
+ string,
135
+ Promise<AuthenticationResult>
136
+ >;
137
+ private atsAsyncMeasurement?: InProgressPerformanceEvent = undefined;
138
+
139
+ private ssoSilentMeasurement?: InProgressPerformanceEvent;
140
+ private acquireTokenByCodeAsyncMeasurement?: InProgressPerformanceEvent;
141
+ /**
142
+ * @constructor
143
+ * Constructor for the PublicClientApplication used to instantiate the PublicClientApplication object
144
+ *
145
+ * Important attributes in the Configuration object for auth are:
146
+ * - clientID: the application ID of your application. You can obtain one by registering your application with our Application registration portal : https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredAppsPreview
147
+ * - authority: the authority URL for your application.
148
+ * - redirect_uri: the uri of your application registered in the portal.
149
+ *
150
+ * In Azure AD, authority is a URL indicating the Azure active directory that MSAL uses to obtain tokens.
151
+ * It is of the form https://login.microsoftonline.com/{Enter_the_Tenant_Info_Here}
152
+ * If your application supports Accounts in one organizational directory, replace "Enter_the_Tenant_Info_Here" value with the Tenant Id or Tenant name (for example, contoso.microsoft.com).
153
+ * If your application supports Accounts in any organizational directory, replace "Enter_the_Tenant_Info_Here" value with organizations.
154
+ * If your application supports Accounts in any organizational directory and personal Microsoft accounts, replace "Enter_the_Tenant_Info_Here" value with common.
155
+ * To restrict support to Personal Microsoft accounts only, replace "Enter_the_Tenant_Info_Here" value with consumers.
156
+ *
157
+ * In Azure B2C, authority is of the form https://{instance}/tfp/{tenant}/{policyName}/
158
+ * Full B2C functionality will be available in this library in future versions.
159
+ *
160
+ * @param configuration Object for the MSAL PublicClientApplication instance
161
+ */
162
+ constructor(operatingContext: StandardOperatingContext) {
163
+ this.operatingContext = operatingContext;
164
+ this.isBrowserEnvironment =
165
+ this.operatingContext.isBrowserEnvironment();
166
+ // Set the configuration.
167
+ this.config = operatingContext.getConfig();
168
+ this.initialized = false;
169
+
170
+ // Initialize logger
171
+ this.logger = this.operatingContext.getLogger();
172
+
173
+ // Initialize the network module class.
174
+ this.networkClient = this.config.system.networkClient;
175
+
176
+ // Initialize the navigation client class.
177
+ this.navigationClient = this.config.system.navigationClient;
178
+
179
+ // Initialize redirectResponse Map
180
+ this.redirectResponse = new Map();
181
+
182
+ // Initial hybrid spa map
183
+ this.hybridAuthCodeResponses = new Map();
184
+
185
+ // Initialize performance client
186
+ this.performanceClient = this.config.telemetry.client;
187
+
188
+ // Initialize the crypto class.
189
+ this.browserCrypto = this.isBrowserEnvironment
190
+ ? new CryptoOps(this.logger, this.performanceClient)
191
+ : DEFAULT_CRYPTO_IMPLEMENTATION;
192
+
193
+ this.eventHandler = new EventHandler(this.logger, this.browserCrypto);
194
+
195
+ // Initialize the browser storage class.
196
+ this.browserStorage = this.isBrowserEnvironment
197
+ ? new BrowserCacheManager(
198
+ this.config.auth.clientId,
199
+ this.config.cache,
200
+ this.browserCrypto,
201
+ this.logger
202
+ )
203
+ : DEFAULT_BROWSER_CACHE_MANAGER(
204
+ this.config.auth.clientId,
205
+ this.logger
206
+ );
207
+
208
+ // initialize in memory storage for native flows
209
+ const nativeCacheOptions: Required<CacheOptions> = {
210
+ cacheLocation: BrowserCacheLocation.MemoryStorage,
211
+ temporaryCacheLocation: BrowserCacheLocation.MemoryStorage,
212
+ storeAuthStateInCookie: false,
213
+ secureCookies: false,
214
+ cacheMigrationEnabled: false,
215
+ claimsBasedCachingEnabled: false,
216
+ };
217
+ this.nativeInternalStorage = new BrowserCacheManager(
218
+ this.config.auth.clientId,
219
+ nativeCacheOptions,
220
+ this.browserCrypto,
221
+ this.logger
222
+ );
223
+
224
+ // Initialize the token cache
225
+ this.tokenCache = new TokenCache(
226
+ this.config,
227
+ this.browserStorage,
228
+ this.logger,
229
+ this.browserCrypto
230
+ );
231
+
232
+ this.activeSilentTokenRequests = new Map();
233
+
234
+ // Register listener functions
235
+ this.trackPageVisibility = this.trackPageVisibility.bind(this);
236
+
237
+ // Register listener functions
238
+ this.trackPageVisibilityWithMeasurement =
239
+ this.trackPageVisibilityWithMeasurement.bind(this);
240
+ }
241
+
242
+ static async createController(
243
+ operatingContext: BaseOperatingContext
244
+ ): Promise<IController> {
245
+ const controller = new StandardController(operatingContext);
246
+ await controller.initialize();
247
+ return controller;
248
+ }
249
+
250
+ private trackPageVisibility(): void {
251
+ if (!this.atsAsyncMeasurement) {
252
+ return;
253
+ }
254
+ this.logger.info("Perf: Visibility change detected");
255
+ this.atsAsyncMeasurement.increment({
256
+ visibilityChangeCount: 1,
257
+ });
258
+ }
259
+
260
+ /**
261
+ * Initializer function to perform async startup tasks such as connecting to WAM extension
262
+ */
263
+ async initialize(): Promise<void> {
264
+ this.logger.trace("initialize called");
265
+ if (this.initialized) {
266
+ this.logger.info(
267
+ "initialize has already been called, exiting early."
268
+ );
269
+ return;
270
+ }
271
+
272
+ const allowNativeBroker = this.config.system.allowNativeBroker;
273
+ const initMeasurement = this.performanceClient.startMeasurement(
274
+ PerformanceEvents.InitializeClientApplication
275
+ );
276
+ this.eventHandler.emitEvent(EventType.INITIALIZE_START);
277
+
278
+ if (allowNativeBroker) {
279
+ try {
280
+ this.nativeExtensionProvider =
281
+ await NativeMessageHandler.createProvider(
282
+ this.logger,
283
+ this.config.system.nativeBrokerHandshakeTimeout,
284
+ this.performanceClient,
285
+ this.browserCrypto
286
+ );
287
+ } catch (e) {
288
+ this.logger.verbose(e as string);
289
+ }
290
+ }
291
+
292
+ if (!this.config.cache.claimsBasedCachingEnabled) {
293
+ this.logger.verbose(
294
+ "Claims-based caching is disabled. Clearing the previous cache with claims"
295
+ );
296
+
297
+ await invokeAsync(
298
+ this.browserStorage.clearTokensAndKeysWithClaims.bind(
299
+ this.browserStorage
300
+ ),
301
+ PerformanceEvents.ClearTokensAndKeysWithClaims,
302
+ this.logger,
303
+ this.performanceClient
304
+ )();
305
+ }
306
+
307
+ this.initialized = true;
308
+ this.eventHandler.emitEvent(EventType.INITIALIZE_END);
309
+
310
+ initMeasurement.end({ allowNativeBroker, success: true });
311
+ }
312
+
313
+ // #region Redirect Flow
314
+
315
+ /**
316
+ * Event handler function which allows users to fire events after the PublicClientApplication object
317
+ * has loaded during redirect flows. This should be invoked on all page loads involved in redirect
318
+ * auth flows.
319
+ * @param hash Hash to process. Defaults to the current value of window.location.hash. Only needs to be provided explicitly if the response to be handled is not contained in the current value.
320
+ * @returns Token response or null. If the return value is null, then no auth redirect was detected.
321
+ */
322
+ async handleRedirectPromise(
323
+ hash?: string
324
+ ): Promise<AuthenticationResult | null> {
325
+ this.logger.verbose("handleRedirectPromise called");
326
+ // Block token acquisition before initialize has been called
327
+ BrowserUtils.blockAPICallsBeforeInitialize(this.initialized);
328
+
329
+ let foundServerResponse = hash;
330
+
331
+ if (
332
+ this.config.auth.OIDCOptions?.serverResponseType ===
333
+ ServerResponseType.QUERY
334
+ ) {
335
+ const url = window.location.href;
336
+ foundServerResponse = UrlString.parseQueryServerResponse(url);
337
+ }
338
+
339
+ const loggedInAccounts = this.getAllAccounts();
340
+ if (this.isBrowserEnvironment) {
341
+ /**
342
+ * Store the promise on the PublicClientApplication instance if this is the first invocation of handleRedirectPromise,
343
+ * otherwise return the promise from the first invocation. Prevents race conditions when handleRedirectPromise is called
344
+ * several times concurrently.
345
+ */
346
+ const redirectResponseKey =
347
+ foundServerResponse || Constants.EMPTY_STRING;
348
+ let response = this.redirectResponse.get(redirectResponseKey);
349
+ if (typeof response === "undefined") {
350
+ this.eventHandler.emitEvent(
351
+ EventType.HANDLE_REDIRECT_START,
352
+ InteractionType.Redirect
353
+ );
354
+ this.logger.verbose(
355
+ "handleRedirectPromise has been called for the first time, storing the promise"
356
+ );
357
+
358
+ const request: NativeTokenRequest | null =
359
+ this.browserStorage.getCachedNativeRequest();
360
+ let redirectResponse: Promise<AuthenticationResult | null>;
361
+ if (
362
+ request &&
363
+ NativeMessageHandler.isNativeAvailable(
364
+ this.config,
365
+ this.logger,
366
+ this.nativeExtensionProvider
367
+ ) &&
368
+ this.nativeExtensionProvider &&
369
+ !foundServerResponse
370
+ ) {
371
+ this.logger.trace(
372
+ "handleRedirectPromise - acquiring token from native platform"
373
+ );
374
+ const nativeClient = new NativeInteractionClient(
375
+ this.config,
376
+ this.browserStorage,
377
+ this.browserCrypto,
378
+ this.logger,
379
+ this.eventHandler,
380
+ this.navigationClient,
381
+ ApiId.handleRedirectPromise,
382
+ this.performanceClient,
383
+ this.nativeExtensionProvider,
384
+ request.accountId,
385
+ this.nativeInternalStorage,
386
+ request.correlationId
387
+ );
388
+ redirectResponse = nativeClient.handleRedirectPromise();
389
+ } else {
390
+ this.logger.trace(
391
+ "handleRedirectPromise - acquiring token from web flow"
392
+ );
393
+ const correlationId =
394
+ this.browserStorage.getTemporaryCache(
395
+ TemporaryCacheKeys.CORRELATION_ID,
396
+ true
397
+ ) || Constants.EMPTY_STRING;
398
+ const redirectClient =
399
+ this.createRedirectClient(correlationId);
400
+ redirectResponse =
401
+ redirectClient.handleRedirectPromise(
402
+ foundServerResponse
403
+ );
404
+ }
405
+
406
+ response = redirectResponse
407
+ .then((result: AuthenticationResult | null) => {
408
+ if (result) {
409
+ // Emit login event if number of accounts change
410
+
411
+ const isLoggingIn =
412
+ loggedInAccounts.length <
413
+ this.getAllAccounts().length;
414
+ if (isLoggingIn) {
415
+ this.eventHandler.emitEvent(
416
+ EventType.LOGIN_SUCCESS,
417
+ InteractionType.Redirect,
418
+ result
419
+ );
420
+ this.logger.verbose(
421
+ "handleRedirectResponse returned result, login success"
422
+ );
423
+ } else {
424
+ this.eventHandler.emitEvent(
425
+ EventType.ACQUIRE_TOKEN_SUCCESS,
426
+ InteractionType.Redirect,
427
+ result
428
+ );
429
+ this.logger.verbose(
430
+ "handleRedirectResponse returned result, acquire token success"
431
+ );
432
+ }
433
+ }
434
+ this.eventHandler.emitEvent(
435
+ EventType.HANDLE_REDIRECT_END,
436
+ InteractionType.Redirect
437
+ );
438
+
439
+ return result;
440
+ })
441
+ .catch((e) => {
442
+ // Emit login event if there is an account
443
+ if (loggedInAccounts.length > 0) {
444
+ this.eventHandler.emitEvent(
445
+ EventType.ACQUIRE_TOKEN_FAILURE,
446
+ InteractionType.Redirect,
447
+ null,
448
+ e as EventError
449
+ );
450
+ } else {
451
+ this.eventHandler.emitEvent(
452
+ EventType.LOGIN_FAILURE,
453
+ InteractionType.Redirect,
454
+ null,
455
+ e as EventError
456
+ );
457
+ }
458
+ this.eventHandler.emitEvent(
459
+ EventType.HANDLE_REDIRECT_END,
460
+ InteractionType.Redirect
461
+ );
462
+
463
+ throw e;
464
+ });
465
+ this.redirectResponse.set(redirectResponseKey, response);
466
+ } else {
467
+ this.logger.verbose(
468
+ "handleRedirectPromise has been called previously, returning the result from the first call"
469
+ );
470
+ }
471
+
472
+ return response;
473
+ }
474
+ this.logger.verbose(
475
+ "handleRedirectPromise returns null, not browser environment"
476
+ );
477
+ return null;
478
+ }
479
+
480
+ /**
481
+ * Use when you want to obtain an access_token for your API by redirecting the user's browser window to the authorization endpoint. This function redirects
482
+ * the page, so any code that follows this function will not execute.
483
+ *
484
+ * IMPORTANT: It is NOT recommended to have code that is dependent on the resolution of the Promise. This function will navigate away from the current
485
+ * browser window. It currently returns a Promise in order to reflect the asynchronous nature of the code running in this function.
486
+ *
487
+ * @param request
488
+ */
489
+ async acquireTokenRedirect(request: RedirectRequest): Promise<void> {
490
+ // Preflight request
491
+ const correlationId = this.getRequestCorrelationId(request);
492
+ this.logger.verbose("acquireTokenRedirect called", correlationId);
493
+ this.preflightBrowserEnvironmentCheck(InteractionType.Redirect);
494
+
495
+ // If logged in, emit acquire token events
496
+ const isLoggedIn = this.getAllAccounts().length > 0;
497
+ if (isLoggedIn) {
498
+ this.eventHandler.emitEvent(
499
+ EventType.ACQUIRE_TOKEN_START,
500
+ InteractionType.Redirect,
501
+ request
502
+ );
503
+ } else {
504
+ this.eventHandler.emitEvent(
505
+ EventType.LOGIN_START,
506
+ InteractionType.Redirect,
507
+ request
508
+ );
509
+ }
510
+
511
+ let result: Promise<void>;
512
+
513
+ if (this.nativeExtensionProvider && this.canUseNative(request)) {
514
+ const nativeClient = new NativeInteractionClient(
515
+ this.config,
516
+ this.browserStorage,
517
+ this.browserCrypto,
518
+ this.logger,
519
+ this.eventHandler,
520
+ this.navigationClient,
521
+ ApiId.acquireTokenRedirect,
522
+ this.performanceClient,
523
+ this.nativeExtensionProvider,
524
+ this.getNativeAccountId(request),
525
+ this.nativeInternalStorage,
526
+ request.correlationId
527
+ );
528
+ result = nativeClient
529
+ .acquireTokenRedirect(request)
530
+ .catch((e: AuthError) => {
531
+ if (e instanceof NativeAuthError && e.isFatal()) {
532
+ this.nativeExtensionProvider = undefined; // If extension gets uninstalled during session prevent future requests from continuing to attempt
533
+ const redirectClient = this.createRedirectClient(
534
+ request.correlationId
535
+ );
536
+ return redirectClient.acquireToken(request);
537
+ } else if (e instanceof InteractionRequiredAuthError) {
538
+ this.logger.verbose(
539
+ "acquireTokenRedirect - Resolving interaction required error thrown by native broker by falling back to web flow"
540
+ );
541
+ const redirectClient = this.createRedirectClient(
542
+ request.correlationId
543
+ );
544
+ return redirectClient.acquireToken(request);
545
+ }
546
+ this.getBrowserStorage().setInteractionInProgress(false);
547
+ throw e;
548
+ });
549
+ } else {
550
+ const redirectClient = this.createRedirectClient(
551
+ request.correlationId
552
+ );
553
+ result = redirectClient.acquireToken(request);
554
+ }
555
+
556
+ return result.catch((e) => {
557
+ // If logged in, emit acquire token events
558
+ if (isLoggedIn) {
559
+ this.eventHandler.emitEvent(
560
+ EventType.ACQUIRE_TOKEN_FAILURE,
561
+ InteractionType.Redirect,
562
+ null,
563
+ e
564
+ );
565
+ } else {
566
+ this.eventHandler.emitEvent(
567
+ EventType.LOGIN_FAILURE,
568
+ InteractionType.Redirect,
569
+ null,
570
+ e
571
+ );
572
+ }
573
+ throw e;
574
+ });
575
+ }
576
+
577
+ // #endregion
578
+
579
+ // #region Popup Flow
580
+
581
+ /**
582
+ * Use when you want to obtain an access_token for your API via opening a popup window in the user's browser
583
+ *
584
+ * @param request
585
+ *
586
+ * @returns A promise that is fulfilled when this function has completed, or rejected if an error was raised.
587
+ */
588
+ acquireTokenPopup(request: PopupRequest): Promise<AuthenticationResult> {
589
+ const correlationId = this.getRequestCorrelationId(request);
590
+ const atPopupMeasurement = this.performanceClient.startMeasurement(
591
+ PerformanceEvents.AcquireTokenPopup,
592
+ correlationId
593
+ );
594
+
595
+ try {
596
+ this.logger.verbose("acquireTokenPopup called", correlationId);
597
+ this.preflightBrowserEnvironmentCheck(InteractionType.Popup);
598
+ } catch (e) {
599
+ // Since this function is syncronous we need to reject
600
+ return Promise.reject(e);
601
+ }
602
+
603
+ // If logged in, emit acquire token events
604
+ const loggedInAccounts = this.getAllAccounts();
605
+ if (loggedInAccounts.length > 0) {
606
+ this.eventHandler.emitEvent(
607
+ EventType.ACQUIRE_TOKEN_START,
608
+ InteractionType.Popup,
609
+ request
610
+ );
611
+ } else {
612
+ this.eventHandler.emitEvent(
613
+ EventType.LOGIN_START,
614
+ InteractionType.Popup,
615
+ request
616
+ );
617
+ }
618
+
619
+ let result: Promise<AuthenticationResult>;
620
+
621
+ if (this.canUseNative(request)) {
622
+ result = this.acquireTokenNative(request, ApiId.acquireTokenPopup)
623
+ .then((response) => {
624
+ this.getBrowserStorage().setInteractionInProgress(false);
625
+ atPopupMeasurement.end({
626
+ success: true,
627
+ isNativeBroker: true,
628
+ requestId: response.requestId,
629
+ });
630
+ return response;
631
+ })
632
+ .catch((e: AuthError) => {
633
+ if (e instanceof NativeAuthError && e.isFatal()) {
634
+ this.nativeExtensionProvider = undefined; // If extension gets uninstalled during session prevent future requests from continuing to attempt
635
+ const popupClient = this.createPopupClient(
636
+ request.correlationId
637
+ );
638
+ return popupClient.acquireToken(request);
639
+ } else if (e instanceof InteractionRequiredAuthError) {
640
+ this.logger.verbose(
641
+ "acquireTokenPopup - Resolving interaction required error thrown by native broker by falling back to web flow"
642
+ );
643
+ const popupClient = this.createPopupClient(
644
+ request.correlationId
645
+ );
646
+ return popupClient.acquireToken(request);
647
+ }
648
+ this.getBrowserStorage().setInteractionInProgress(false);
649
+ throw e;
650
+ });
651
+ } else {
652
+ const popupClient = this.createPopupClient(request.correlationId);
653
+ result = popupClient.acquireToken(request);
654
+ }
655
+
656
+ return result
657
+ .then((result) => {
658
+ /*
659
+ * If logged in, emit acquire token events
660
+ */
661
+ const isLoggingIn =
662
+ loggedInAccounts.length < this.getAllAccounts().length;
663
+ if (isLoggingIn) {
664
+ this.eventHandler.emitEvent(
665
+ EventType.LOGIN_SUCCESS,
666
+ InteractionType.Popup,
667
+ result
668
+ );
669
+ } else {
670
+ this.eventHandler.emitEvent(
671
+ EventType.ACQUIRE_TOKEN_SUCCESS,
672
+ InteractionType.Popup,
673
+ result
674
+ );
675
+ }
676
+
677
+ atPopupMeasurement.add({
678
+ accessTokenSize: result.accessToken.length,
679
+ idTokenSize: result.idToken.length,
680
+ });
681
+ atPopupMeasurement.end({
682
+ success: true,
683
+ requestId: result.requestId,
684
+ });
685
+ return result;
686
+ })
687
+ .catch((e: AuthError) => {
688
+ if (loggedInAccounts.length > 0) {
689
+ this.eventHandler.emitEvent(
690
+ EventType.ACQUIRE_TOKEN_FAILURE,
691
+ InteractionType.Popup,
692
+ null,
693
+ e
694
+ );
695
+ } else {
696
+ this.eventHandler.emitEvent(
697
+ EventType.LOGIN_FAILURE,
698
+ InteractionType.Popup,
699
+ null,
700
+ e
701
+ );
702
+ }
703
+
704
+ atPopupMeasurement.end({
705
+ errorCode: e.errorCode,
706
+ subErrorCode: e.subError,
707
+ success: false,
708
+ });
709
+ // Since this function is syncronous we need to reject
710
+ return Promise.reject(e);
711
+ });
712
+ }
713
+
714
+ private trackPageVisibilityWithMeasurement(): void {
715
+ const measurement =
716
+ this.ssoSilentMeasurement ||
717
+ this.acquireTokenByCodeAsyncMeasurement;
718
+ if (!measurement) {
719
+ return;
720
+ }
721
+
722
+ this.logger.info(
723
+ "Perf: Visibility change detected in ",
724
+ measurement.event.name
725
+ );
726
+ measurement.increment({
727
+ visibilityChangeCount: 1,
728
+ });
729
+ }
730
+ // #endregion
731
+
732
+ // #region Silent Flow
733
+
734
+ /**
735
+ * This function uses a hidden iframe to fetch an authorization code from the eSTS. There are cases where this may not work:
736
+ * - Any browser using a form of Intelligent Tracking Prevention
737
+ * - If there is not an established session with the service
738
+ *
739
+ * In these cases, the request must be done inside a popup or full frame redirect.
740
+ *
741
+ * For the cases where interaction is required, you cannot send a request with prompt=none.
742
+ *
743
+ * If your refresh token has expired, you can use this function to fetch a new set of tokens silently as long as
744
+ * you session on the server still exists.
745
+ * @param request {@link SsoSilentRequest}
746
+ *
747
+ * @returns A promise that is fulfilled when this function has completed, or rejected if an error was raised.
748
+ */
749
+ async ssoSilent(request: SsoSilentRequest): Promise<AuthenticationResult> {
750
+ const correlationId = this.getRequestCorrelationId(request);
751
+ const validRequest = {
752
+ ...request,
753
+ // will be PromptValue.NONE or PromptValue.NO_SESSION
754
+ prompt: request.prompt,
755
+ correlationId: correlationId,
756
+ };
757
+ this.preflightBrowserEnvironmentCheck(InteractionType.Silent);
758
+ this.ssoSilentMeasurement = this.performanceClient.startMeasurement(
759
+ PerformanceEvents.SsoSilent,
760
+ correlationId
761
+ );
762
+ this.ssoSilentMeasurement?.increment({
763
+ visibilityChangeCount: 0,
764
+ });
765
+ document.addEventListener(
766
+ "visibilitychange",
767
+ this.trackPageVisibilityWithMeasurement
768
+ );
769
+ this.logger.verbose("ssoSilent called", correlationId);
770
+ this.eventHandler.emitEvent(
771
+ EventType.SSO_SILENT_START,
772
+ InteractionType.Silent,
773
+ validRequest
774
+ );
775
+
776
+ let result: Promise<AuthenticationResult>;
777
+
778
+ if (this.canUseNative(validRequest)) {
779
+ result = this.acquireTokenNative(
780
+ validRequest,
781
+ ApiId.ssoSilent
782
+ ).catch((e: AuthError) => {
783
+ // If native token acquisition fails for availability reasons fallback to standard flow
784
+ if (e instanceof NativeAuthError && e.isFatal()) {
785
+ this.nativeExtensionProvider = undefined; // If extension gets uninstalled during session prevent future requests from continuing to attempt
786
+ const silentIframeClient = this.createSilentIframeClient(
787
+ validRequest.correlationId
788
+ );
789
+ return silentIframeClient.acquireToken(validRequest);
790
+ }
791
+ throw e;
792
+ });
793
+ } else {
794
+ const silentIframeClient = this.createSilentIframeClient(
795
+ validRequest.correlationId
796
+ );
797
+ result = silentIframeClient.acquireToken(validRequest);
798
+ }
799
+
800
+ return result
801
+ .then((response) => {
802
+ this.eventHandler.emitEvent(
803
+ EventType.SSO_SILENT_SUCCESS,
804
+ InteractionType.Silent,
805
+ response
806
+ );
807
+ this.ssoSilentMeasurement?.add({
808
+ accessTokenSize: response.accessToken.length,
809
+ idTokenSize: response.idToken.length,
810
+ });
811
+ this.ssoSilentMeasurement?.end({
812
+ success: true,
813
+ isNativeBroker: response.fromNativeBroker,
814
+ requestId: response.requestId,
815
+ });
816
+ return response;
817
+ })
818
+ .catch((e: AuthError) => {
819
+ this.eventHandler.emitEvent(
820
+ EventType.SSO_SILENT_FAILURE,
821
+ InteractionType.Silent,
822
+ null,
823
+ e
824
+ );
825
+ this.ssoSilentMeasurement?.end({
826
+ errorCode: e.errorCode,
827
+ subErrorCode: e.subError,
828
+ success: false,
829
+ });
830
+ throw e;
831
+ })
832
+ .finally(() => {
833
+ document.removeEventListener(
834
+ "visibilitychange",
835
+ this.trackPageVisibilityWithMeasurement
836
+ );
837
+ });
838
+ }
839
+
840
+ /**
841
+ * This function redeems an authorization code (passed as code) from the eSTS token endpoint.
842
+ * This authorization code should be acquired server-side using a confidential client to acquire a spa_code.
843
+ * This API is not indended for normal authorization code acquisition and redemption.
844
+ *
845
+ * Redemption of this authorization code will not require PKCE, as it was acquired by a confidential client.
846
+ *
847
+ * @param request {@link AuthorizationCodeRequest}
848
+ * @returns A promise that is fulfilled when this function has completed, or rejected if an error was raised.
849
+ */
850
+ async acquireTokenByCode(
851
+ request: AuthorizationCodeRequest
852
+ ): Promise<AuthenticationResult> {
853
+ const correlationId = this.getRequestCorrelationId(request);
854
+ this.preflightBrowserEnvironmentCheck(InteractionType.Silent);
855
+ this.logger.trace("acquireTokenByCode called", correlationId);
856
+ this.eventHandler.emitEvent(
857
+ EventType.ACQUIRE_TOKEN_BY_CODE_START,
858
+ InteractionType.Silent,
859
+ request
860
+ );
861
+ const atbcMeasurement = this.performanceClient.startMeasurement(
862
+ PerformanceEvents.AcquireTokenByCode,
863
+ request.correlationId
864
+ );
865
+
866
+ try {
867
+ if (request.code && request.nativeAccountId) {
868
+ // Throw error in case server returns both spa_code and spa_accountid in exchange for auth code.
869
+ throw createBrowserAuthError(
870
+ BrowserAuthErrorCodes.spaCodeAndNativeAccountIdPresent
871
+ );
872
+ } else if (request.code) {
873
+ const hybridAuthCode = request.code;
874
+ let response = this.hybridAuthCodeResponses.get(hybridAuthCode);
875
+ if (!response) {
876
+ this.logger.verbose(
877
+ "Initiating new acquireTokenByCode request",
878
+ correlationId
879
+ );
880
+ response = this.acquireTokenByCodeAsync({
881
+ ...request,
882
+ correlationId,
883
+ })
884
+ .then((result: AuthenticationResult) => {
885
+ this.eventHandler.emitEvent(
886
+ EventType.ACQUIRE_TOKEN_BY_CODE_SUCCESS,
887
+ InteractionType.Silent,
888
+ result
889
+ );
890
+ this.hybridAuthCodeResponses.delete(hybridAuthCode);
891
+ atbcMeasurement.add({
892
+ accessTokenSize: result.accessToken.length,
893
+ idTokenSize: result.idToken.length,
894
+ });
895
+ atbcMeasurement.end({
896
+ success: true,
897
+ isNativeBroker: result.fromNativeBroker,
898
+ requestId: result.requestId,
899
+ });
900
+ return result;
901
+ })
902
+ .catch((error: AuthError) => {
903
+ this.hybridAuthCodeResponses.delete(hybridAuthCode);
904
+ this.eventHandler.emitEvent(
905
+ EventType.ACQUIRE_TOKEN_BY_CODE_FAILURE,
906
+ InteractionType.Silent,
907
+ null,
908
+ error
909
+ );
910
+ atbcMeasurement.end({
911
+ errorCode: error.errorCode,
912
+ subErrorCode: error.subError,
913
+ success: false,
914
+ });
915
+ throw error;
916
+ });
917
+ this.hybridAuthCodeResponses.set(hybridAuthCode, response);
918
+ } else {
919
+ this.logger.verbose(
920
+ "Existing acquireTokenByCode request found",
921
+ request.correlationId
922
+ );
923
+ atbcMeasurement.discard();
924
+ }
925
+ return response;
926
+ } else if (request.nativeAccountId) {
927
+ if (this.canUseNative(request, request.nativeAccountId)) {
928
+ return this.acquireTokenNative(
929
+ request,
930
+ ApiId.acquireTokenByCode,
931
+ request.nativeAccountId
932
+ ).catch((e: AuthError) => {
933
+ // If native token acquisition fails for availability reasons fallback to standard flow
934
+ if (e instanceof NativeAuthError && e.isFatal()) {
935
+ this.nativeExtensionProvider = undefined; // If extension gets uninstalled during session prevent future requests from continuing to attempt
936
+ }
937
+ throw e;
938
+ });
939
+ } else {
940
+ throw createBrowserAuthError(
941
+ BrowserAuthErrorCodes.unableToAcquireTokenFromNativePlatform
942
+ );
943
+ }
944
+ } else {
945
+ throw createBrowserAuthError(
946
+ BrowserAuthErrorCodes.authCodeOrNativeAccountIdRequired
947
+ );
948
+ }
949
+ } catch (e) {
950
+ this.eventHandler.emitEvent(
951
+ EventType.ACQUIRE_TOKEN_BY_CODE_FAILURE,
952
+ InteractionType.Silent,
953
+ null,
954
+ e as EventError
955
+ );
956
+ atbcMeasurement.end({
957
+ errorCode: (e instanceof AuthError && e.errorCode) || undefined,
958
+ subErrorCode:
959
+ (e instanceof AuthError && e.subError) || undefined,
960
+ success: false,
961
+ });
962
+ throw e;
963
+ }
964
+ }
965
+
966
+ /**
967
+ * Creates a SilentAuthCodeClient to redeem an authorization code.
968
+ * @param request
969
+ * @returns Result of the operation to redeem the authorization code
970
+ */
971
+ private async acquireTokenByCodeAsync(
972
+ request: AuthorizationCodeRequest
973
+ ): Promise<AuthenticationResult> {
974
+ this.logger.trace(
975
+ "acquireTokenByCodeAsync called",
976
+ request.correlationId
977
+ );
978
+ this.acquireTokenByCodeAsyncMeasurement =
979
+ this.performanceClient.startMeasurement(
980
+ PerformanceEvents.AcquireTokenByCodeAsync,
981
+ request.correlationId
982
+ );
983
+ this.acquireTokenByCodeAsyncMeasurement?.increment({
984
+ visibilityChangeCount: 0,
985
+ });
986
+ document.addEventListener(
987
+ "visibilitychange",
988
+ this.trackPageVisibilityWithMeasurement
989
+ );
990
+ const silentAuthCodeClient = this.createSilentAuthCodeClient(
991
+ request.correlationId
992
+ );
993
+ const silentTokenResult = await silentAuthCodeClient
994
+ .acquireToken(request)
995
+ .then((response) => {
996
+ this.acquireTokenByCodeAsyncMeasurement?.end({
997
+ success: true,
998
+ fromCache: response.fromCache,
999
+ isNativeBroker: response.fromNativeBroker,
1000
+ requestId: response.requestId,
1001
+ });
1002
+ return response;
1003
+ })
1004
+ .catch((tokenRenewalError: AuthError) => {
1005
+ this.acquireTokenByCodeAsyncMeasurement?.end({
1006
+ errorCode: tokenRenewalError.errorCode,
1007
+ subErrorCode: tokenRenewalError.subError,
1008
+ success: false,
1009
+ });
1010
+ throw tokenRenewalError;
1011
+ })
1012
+ .finally(() => {
1013
+ document.removeEventListener(
1014
+ "visibilitychange",
1015
+ this.trackPageVisibilityWithMeasurement
1016
+ );
1017
+ });
1018
+ return silentTokenResult;
1019
+ }
1020
+
1021
+ /**
1022
+ * Attempt to acquire an access token from the cache
1023
+ * @param silentCacheClient SilentCacheClient
1024
+ * @param commonRequest CommonSilentFlowRequest
1025
+ * @param silentRequest SilentRequest
1026
+ * @returns A promise that, when resolved, returns the access token
1027
+ */
1028
+ protected async acquireTokenFromCache(
1029
+ silentCacheClient: SilentCacheClient,
1030
+ commonRequest: CommonSilentFlowRequest,
1031
+ silentRequest: SilentRequest
1032
+ ): Promise<AuthenticationResult> {
1033
+ this.performanceClient.addQueueMeasurement(
1034
+ PerformanceEvents.AcquireTokenFromCache,
1035
+ commonRequest.correlationId
1036
+ );
1037
+ switch (silentRequest.cacheLookupPolicy) {
1038
+ case CacheLookupPolicy.Default:
1039
+ case CacheLookupPolicy.AccessToken:
1040
+ case CacheLookupPolicy.AccessTokenAndRefreshToken:
1041
+ return silentCacheClient.acquireToken(commonRequest);
1042
+ default:
1043
+ throw ClientAuthError.createRefreshRequiredError();
1044
+ }
1045
+ }
1046
+
1047
+ /**
1048
+ * Attempt to acquire an access token via a refresh token
1049
+ * @param commonRequest CommonSilentFlowRequest
1050
+ * @param silentRequest SilentRequest
1051
+ * @returns A promise that, when resolved, returns the access token
1052
+ */
1053
+ public async acquireTokenByRefreshToken(
1054
+ commonRequest: CommonSilentFlowRequest,
1055
+ silentRequest: SilentRequest
1056
+ ): Promise<AuthenticationResult> {
1057
+ this.performanceClient.addQueueMeasurement(
1058
+ PerformanceEvents.AcquireTokenByRefreshToken,
1059
+ commonRequest.correlationId
1060
+ );
1061
+ switch (silentRequest.cacheLookupPolicy) {
1062
+ case CacheLookupPolicy.Default:
1063
+ case CacheLookupPolicy.AccessTokenAndRefreshToken:
1064
+ case CacheLookupPolicy.RefreshToken:
1065
+ case CacheLookupPolicy.RefreshTokenAndNetwork:
1066
+ const silentRefreshClient = this.createSilentRefreshClient(
1067
+ commonRequest.correlationId
1068
+ );
1069
+
1070
+ this.performanceClient.setPreQueueTime(
1071
+ PerformanceEvents.SilentRefreshClientAcquireToken,
1072
+ commonRequest.correlationId
1073
+ );
1074
+ return silentRefreshClient.acquireToken(commonRequest);
1075
+ default:
1076
+ throw ClientAuthError.createRefreshRequiredError();
1077
+ }
1078
+ }
1079
+
1080
+ /**
1081
+ * Attempt to acquire an access token via an iframe
1082
+ * @param request CommonSilentFlowRequest
1083
+ * @returns A promise that, when resolved, returns the access token
1084
+ */
1085
+ protected async acquireTokenBySilentIframe(
1086
+ request: CommonSilentFlowRequest
1087
+ ): Promise<AuthenticationResult> {
1088
+ this.performanceClient.addQueueMeasurement(
1089
+ PerformanceEvents.AcquireTokenBySilentIframe,
1090
+ request.correlationId
1091
+ );
1092
+
1093
+ const silentIframeClient = this.createSilentIframeClient(
1094
+ request.correlationId
1095
+ );
1096
+
1097
+ return invokeAsync(
1098
+ silentIframeClient.acquireToken.bind(silentIframeClient),
1099
+ PerformanceEvents.SilentIframeClientAcquireToken,
1100
+ this.logger,
1101
+ this.performanceClient,
1102
+ request.correlationId
1103
+ )(request);
1104
+ }
1105
+
1106
+ // #endregion
1107
+
1108
+ // #region Logout
1109
+
1110
+ /**
1111
+ * Deprecated logout function. Use logoutRedirect or logoutPopup instead
1112
+ * @param logoutRequest
1113
+ * @deprecated
1114
+ */
1115
+ async logout(logoutRequest?: EndSessionRequest): Promise<void> {
1116
+ const correlationId = this.getRequestCorrelationId(logoutRequest);
1117
+ this.logger.warning(
1118
+ "logout API is deprecated and will be removed in msal-browser v3.0.0. Use logoutRedirect instead.",
1119
+ correlationId
1120
+ );
1121
+ return this.logoutRedirect({
1122
+ correlationId,
1123
+ ...logoutRequest,
1124
+ });
1125
+ }
1126
+
1127
+ /**
1128
+ * Use to log out the current user, and redirect the user to the postLogoutRedirectUri.
1129
+ * Default behaviour is to redirect the user to `window.location.href`.
1130
+ * @param logoutRequest
1131
+ */
1132
+ async logoutRedirect(logoutRequest?: EndSessionRequest): Promise<void> {
1133
+ const correlationId = this.getRequestCorrelationId(logoutRequest);
1134
+ this.preflightBrowserEnvironmentCheck(InteractionType.Redirect);
1135
+
1136
+ const redirectClient = this.createRedirectClient(correlationId);
1137
+ return redirectClient.logout(logoutRequest);
1138
+ }
1139
+
1140
+ /**
1141
+ * Clears local cache for the current user then opens a popup window prompting the user to sign-out of the server
1142
+ * @param logoutRequest
1143
+ */
1144
+ logoutPopup(logoutRequest?: EndSessionPopupRequest): Promise<void> {
1145
+ try {
1146
+ const correlationId = this.getRequestCorrelationId(logoutRequest);
1147
+ this.preflightBrowserEnvironmentCheck(InteractionType.Popup);
1148
+ const popupClient = this.createPopupClient(correlationId);
1149
+ return popupClient.logout(logoutRequest);
1150
+ } catch (e) {
1151
+ // Since this function is syncronous we need to reject
1152
+ return Promise.reject(e);
1153
+ }
1154
+ }
1155
+
1156
+ /**
1157
+ * Creates a cache interaction client to clear broswer cache.
1158
+ * @param logoutRequest
1159
+ */
1160
+ async clearCache(logoutRequest?: ClearCacheRequest): Promise<void> {
1161
+ const correlationId = this.getRequestCorrelationId(logoutRequest);
1162
+ const cacheClient = this.createSilentCacheClient(correlationId);
1163
+ return cacheClient.logout(logoutRequest);
1164
+ }
1165
+
1166
+ // #endregion
1167
+
1168
+ // #region Account APIs
1169
+
1170
+ /**
1171
+ * Returns all accounts that MSAL currently has data for.
1172
+ * (the account object is created at the time of successful login)
1173
+ * or empty array when no accounts are found
1174
+ * @returns Array of account objects in cache
1175
+ */
1176
+ getAllAccounts(): AccountInfo[] {
1177
+ this.logger.verbose("getAllAccounts called");
1178
+ return this.isBrowserEnvironment
1179
+ ? this.browserStorage.getAllAccounts()
1180
+ : [];
1181
+ }
1182
+
1183
+ /**
1184
+ * Returns the signed in account matching username.
1185
+ * (the account object is created at the time of successful login)
1186
+ * or null when no matching account is found.
1187
+ * This API is provided for convenience but getAccountById should be used for best reliability
1188
+ * @param username
1189
+ * @returns The account object stored in MSAL
1190
+ */
1191
+ getAccountByUsername(username: string): AccountInfo | null {
1192
+ this.logger.trace("getAccountByUsername called");
1193
+ if (!username) {
1194
+ this.logger.warning("getAccountByUsername: No username provided");
1195
+ return null;
1196
+ }
1197
+
1198
+ const account = this.browserStorage.getAccountInfoFilteredBy({
1199
+ username,
1200
+ });
1201
+ if (account) {
1202
+ this.logger.verbose(
1203
+ "getAccountByUsername: Account matching username found, returning"
1204
+ );
1205
+ this.logger.verbosePii(
1206
+ `getAccountByUsername: Returning signed-in accounts matching username: ${username}`
1207
+ );
1208
+ return account;
1209
+ } else {
1210
+ this.logger.verbose(
1211
+ "getAccountByUsername: No matching account found, returning null"
1212
+ );
1213
+ return null;
1214
+ }
1215
+ }
1216
+
1217
+ /**
1218
+ * Returns the signed in account matching homeAccountId.
1219
+ * (the account object is created at the time of successful login)
1220
+ * or null when no matching account is found
1221
+ * @param homeAccountId
1222
+ * @returns The account object stored in MSAL
1223
+ */
1224
+ getAccountByHomeId(homeAccountId: string): AccountInfo | null {
1225
+ this.logger.trace("getAccountByHomeId called");
1226
+ if (!homeAccountId) {
1227
+ this.logger.warning(
1228
+ "getAccountByHomeId: No homeAccountId provided"
1229
+ );
1230
+ return null;
1231
+ }
1232
+
1233
+ const account = this.browserStorage.getAccountInfoFilteredBy({
1234
+ homeAccountId,
1235
+ });
1236
+ if (account) {
1237
+ this.logger.verbose(
1238
+ "getAccountByHomeId: Account matching homeAccountId found, returning"
1239
+ );
1240
+ this.logger.verbosePii(
1241
+ `getAccountByHomeId: Returning signed-in accounts matching homeAccountId: ${homeAccountId}`
1242
+ );
1243
+ return account;
1244
+ } else {
1245
+ this.logger.verbose(
1246
+ "getAccountByHomeId: No matching account found, returning null"
1247
+ );
1248
+ return null;
1249
+ }
1250
+ }
1251
+
1252
+ /**
1253
+ * Returns the signed in account matching localAccountId.
1254
+ * (the account object is created at the time of successful login)
1255
+ * or null when no matching account is found
1256
+ * @param localAccountId
1257
+ * @returns The account object stored in MSAL
1258
+ */
1259
+ getAccountByLocalId(localAccountId: string): AccountInfo | null {
1260
+ this.logger.trace("getAccountByLocalId called");
1261
+ if (!localAccountId) {
1262
+ this.logger.warning(
1263
+ "getAccountByLocalId: No localAccountId provided"
1264
+ );
1265
+ return null;
1266
+ }
1267
+
1268
+ const account = this.browserStorage.getAccountInfoFilteredBy({
1269
+ localAccountId,
1270
+ });
1271
+ if (account) {
1272
+ this.logger.verbose(
1273
+ "getAccountByLocalId: Account matching localAccountId found, returning"
1274
+ );
1275
+ this.logger.verbosePii(
1276
+ `getAccountByLocalId: Returning signed-in accounts matching localAccountId: ${localAccountId}`
1277
+ );
1278
+ return account;
1279
+ } else {
1280
+ this.logger.verbose(
1281
+ "getAccountByLocalId: No matching account found, returning null"
1282
+ );
1283
+ return null;
1284
+ }
1285
+ }
1286
+
1287
+ /**
1288
+ * Sets the account to use as the active account. If no account is passed to the acquireToken APIs, then MSAL will use this active account.
1289
+ * @param account
1290
+ */
1291
+ setActiveAccount(account: AccountInfo | null): void {
1292
+ this.browserStorage.setActiveAccount(account);
1293
+ }
1294
+
1295
+ /**
1296
+ * Gets the currently active account
1297
+ */
1298
+ getActiveAccount(): AccountInfo | null {
1299
+ return this.browserStorage.getActiveAccount();
1300
+ }
1301
+
1302
+ // #endregion
1303
+
1304
+ /**
1305
+ * Hydrates the cache with the tokens from an AuthenticationResult
1306
+ * @param result
1307
+ * @param request
1308
+ * @returns
1309
+ */
1310
+ async hydrateCache(
1311
+ result: AuthenticationResult,
1312
+ request:
1313
+ | SilentRequest
1314
+ | SsoSilentRequest
1315
+ | RedirectRequest
1316
+ | PopupRequest
1317
+ ): Promise<void> {
1318
+ this.logger.verbose("hydrateCache called");
1319
+
1320
+ // Account gets saved to browser storage regardless of native or not
1321
+ const accountEntity = AccountEntity.createFromAccountInfo(
1322
+ result.account,
1323
+ result.cloudGraphHostName,
1324
+ result.msGraphHost
1325
+ );
1326
+ this.browserStorage.setAccount(accountEntity);
1327
+
1328
+ if (result.fromNativeBroker) {
1329
+ this.logger.verbose(
1330
+ "Response was from native broker, storing in-memory"
1331
+ );
1332
+ // Tokens from native broker are stored in-memory
1333
+ return this.nativeInternalStorage.hydrateCache(result, request);
1334
+ } else {
1335
+ return this.browserStorage.hydrateCache(result, request);
1336
+ }
1337
+ }
1338
+
1339
+ // #region Helpers
1340
+
1341
+ /**
1342
+ * Helper to validate app environment before making an auth request
1343
+ *
1344
+ * @protected
1345
+ * @param {InteractionType} interactionType What kind of interaction is being used
1346
+ * @param {boolean} [isAppEmbedded=false] Whether to set interaction in progress temp cache flag
1347
+ */
1348
+ public preflightBrowserEnvironmentCheck(
1349
+ interactionType: InteractionType,
1350
+ isAppEmbedded: boolean = false
1351
+ ): void {
1352
+ this.logger.verbose("preflightBrowserEnvironmentCheck started");
1353
+ // Block request if not in browser environment
1354
+ BrowserUtils.blockNonBrowserEnvironment(this.isBrowserEnvironment);
1355
+
1356
+ // Block redirects if in an iframe
1357
+ BrowserUtils.blockRedirectInIframe(
1358
+ interactionType,
1359
+ this.config.system.allowRedirectInIframe
1360
+ );
1361
+
1362
+ // Block auth requests inside a hidden iframe
1363
+ BrowserUtils.blockReloadInHiddenIframes();
1364
+
1365
+ // Block redirectUri opened in a popup from calling MSAL APIs
1366
+ BrowserUtils.blockAcquireTokenInPopups();
1367
+
1368
+ // Block token acquisition before initialize has been called
1369
+ BrowserUtils.blockAPICallsBeforeInitialize(this.initialized);
1370
+
1371
+ // Block redirects if memory storage is enabled but storeAuthStateInCookie is not
1372
+ if (
1373
+ interactionType === InteractionType.Redirect &&
1374
+ this.config.cache.cacheLocation ===
1375
+ BrowserCacheLocation.MemoryStorage &&
1376
+ !this.config.cache.storeAuthStateInCookie
1377
+ ) {
1378
+ throw BrowserConfigurationAuthError.createInMemoryRedirectUnavailableError();
1379
+ }
1380
+
1381
+ if (
1382
+ interactionType === InteractionType.Redirect ||
1383
+ interactionType === InteractionType.Popup
1384
+ ) {
1385
+ this.preflightInteractiveRequest(!isAppEmbedded);
1386
+ }
1387
+ }
1388
+
1389
+ /**
1390
+ * Preflight check for interactive requests
1391
+ *
1392
+ * @protected
1393
+ * @param {boolean} setInteractionInProgress Whether to set interaction in progress temp cache flag
1394
+ */
1395
+ protected preflightInteractiveRequest(
1396
+ setInteractionInProgress: boolean
1397
+ ): void {
1398
+ this.logger.verbose(
1399
+ "preflightInteractiveRequest called, validating app environment"
1400
+ );
1401
+ // block the reload if it occurred inside a hidden iframe
1402
+ BrowserUtils.blockReloadInHiddenIframes();
1403
+
1404
+ // Set interaction in progress temporary cache or throw if alread set.
1405
+ if (setInteractionInProgress) {
1406
+ this.getBrowserStorage().setInteractionInProgress(true);
1407
+ }
1408
+ }
1409
+
1410
+ /**
1411
+ * Acquire a token from native device (e.g. WAM)
1412
+ * @param request
1413
+ */
1414
+ public async acquireTokenNative(
1415
+ request: PopupRequest | SilentRequest | SsoSilentRequest,
1416
+ apiId: ApiId,
1417
+ accountId?: string
1418
+ ): Promise<AuthenticationResult> {
1419
+ this.logger.trace("acquireTokenNative called");
1420
+ if (!this.nativeExtensionProvider) {
1421
+ throw createBrowserAuthError(
1422
+ BrowserAuthErrorCodes.nativeConnectionNotEstablished
1423
+ );
1424
+ }
1425
+
1426
+ const nativeClient = new NativeInteractionClient(
1427
+ this.config,
1428
+ this.browserStorage,
1429
+ this.browserCrypto,
1430
+ this.logger,
1431
+ this.eventHandler,
1432
+ this.navigationClient,
1433
+ apiId,
1434
+ this.performanceClient,
1435
+ this.nativeExtensionProvider,
1436
+ accountId || this.getNativeAccountId(request),
1437
+ this.nativeInternalStorage,
1438
+ request.correlationId
1439
+ );
1440
+
1441
+ return nativeClient.acquireToken(request);
1442
+ }
1443
+
1444
+ /**
1445
+ * Returns boolean indicating if this request can use the native broker
1446
+ * @param request
1447
+ */
1448
+ public canUseNative(
1449
+ request: RedirectRequest | PopupRequest | SsoSilentRequest,
1450
+ accountId?: string
1451
+ ): boolean {
1452
+ this.logger.trace("canUseNative called");
1453
+ if (
1454
+ !NativeMessageHandler.isNativeAvailable(
1455
+ this.config,
1456
+ this.logger,
1457
+ this.nativeExtensionProvider,
1458
+ request.authenticationScheme
1459
+ )
1460
+ ) {
1461
+ this.logger.trace(
1462
+ "canUseNative: isNativeAvailable returned false, returning false"
1463
+ );
1464
+ return false;
1465
+ }
1466
+
1467
+ if (request.prompt) {
1468
+ switch (request.prompt) {
1469
+ case PromptValue.NONE:
1470
+ case PromptValue.CONSENT:
1471
+ case PromptValue.LOGIN:
1472
+ this.logger.trace(
1473
+ "canUseNative: prompt is compatible with native flow"
1474
+ );
1475
+ break;
1476
+ default:
1477
+ this.logger.trace(
1478
+ `canUseNative: prompt = ${request.prompt} is not compatible with native flow, returning false`
1479
+ );
1480
+ return false;
1481
+ }
1482
+ }
1483
+
1484
+ if (!accountId && !this.getNativeAccountId(request)) {
1485
+ this.logger.trace(
1486
+ "canUseNative: nativeAccountId is not available, returning false"
1487
+ );
1488
+ return false;
1489
+ }
1490
+
1491
+ return true;
1492
+ }
1493
+
1494
+ /**
1495
+ * Get the native accountId from the account
1496
+ * @param request
1497
+ * @returns
1498
+ */
1499
+ public getNativeAccountId(
1500
+ request: RedirectRequest | PopupRequest | SsoSilentRequest
1501
+ ): string {
1502
+ const account =
1503
+ request.account ||
1504
+ this.browserStorage.getAccountInfoByHints(
1505
+ request.loginHint,
1506
+ request.sid
1507
+ ) ||
1508
+ this.getActiveAccount();
1509
+
1510
+ return (account && account.nativeAccountId) || "";
1511
+ }
1512
+
1513
+ /**
1514
+ * Returns new instance of the Popup Interaction Client
1515
+ * @param correlationId
1516
+ */
1517
+ public createPopupClient(correlationId?: string): PopupClient {
1518
+ return new PopupClient(
1519
+ this.config,
1520
+ this.browserStorage,
1521
+ this.browserCrypto,
1522
+ this.logger,
1523
+ this.eventHandler,
1524
+ this.navigationClient,
1525
+ this.performanceClient,
1526
+ this.nativeInternalStorage,
1527
+ this.nativeExtensionProvider,
1528
+ correlationId
1529
+ );
1530
+ }
1531
+
1532
+ /**
1533
+ * Returns new instance of the Redirect Interaction Client
1534
+ * @param correlationId
1535
+ */
1536
+ protected createRedirectClient(correlationId?: string): RedirectClient {
1537
+ return new RedirectClient(
1538
+ this.config,
1539
+ this.browserStorage,
1540
+ this.browserCrypto,
1541
+ this.logger,
1542
+ this.eventHandler,
1543
+ this.navigationClient,
1544
+ this.performanceClient,
1545
+ this.nativeInternalStorage,
1546
+ this.nativeExtensionProvider,
1547
+ correlationId
1548
+ );
1549
+ }
1550
+
1551
+ /**
1552
+ * Returns new instance of the Silent Iframe Interaction Client
1553
+ * @param correlationId
1554
+ */
1555
+ public createSilentIframeClient(
1556
+ correlationId?: string
1557
+ ): SilentIframeClient {
1558
+ return new SilentIframeClient(
1559
+ this.config,
1560
+ this.browserStorage,
1561
+ this.browserCrypto,
1562
+ this.logger,
1563
+ this.eventHandler,
1564
+ this.navigationClient,
1565
+ ApiId.ssoSilent,
1566
+ this.performanceClient,
1567
+ this.nativeInternalStorage,
1568
+ this.nativeExtensionProvider,
1569
+ correlationId
1570
+ );
1571
+ }
1572
+
1573
+ /**
1574
+ * Returns new instance of the Silent Cache Interaction Client
1575
+ */
1576
+ protected createSilentCacheClient(
1577
+ correlationId?: string
1578
+ ): SilentCacheClient {
1579
+ return new SilentCacheClient(
1580
+ this.config,
1581
+ this.browserStorage,
1582
+ this.browserCrypto,
1583
+ this.logger,
1584
+ this.eventHandler,
1585
+ this.navigationClient,
1586
+ this.performanceClient,
1587
+ this.nativeExtensionProvider,
1588
+ correlationId
1589
+ );
1590
+ }
1591
+
1592
+ /**
1593
+ * Returns new instance of the Silent Refresh Interaction Client
1594
+ */
1595
+ protected createSilentRefreshClient(
1596
+ correlationId?: string
1597
+ ): SilentRefreshClient {
1598
+ return new SilentRefreshClient(
1599
+ this.config,
1600
+ this.browserStorage,
1601
+ this.browserCrypto,
1602
+ this.logger,
1603
+ this.eventHandler,
1604
+ this.navigationClient,
1605
+ this.performanceClient,
1606
+ this.nativeExtensionProvider,
1607
+ correlationId
1608
+ );
1609
+ }
1610
+
1611
+ /**
1612
+ * Returns new instance of the Silent AuthCode Interaction Client
1613
+ */
1614
+ protected createSilentAuthCodeClient(
1615
+ correlationId?: string
1616
+ ): SilentAuthCodeClient {
1617
+ return new SilentAuthCodeClient(
1618
+ this.config,
1619
+ this.browserStorage,
1620
+ this.browserCrypto,
1621
+ this.logger,
1622
+ this.eventHandler,
1623
+ this.navigationClient,
1624
+ ApiId.acquireTokenByCode,
1625
+ this.performanceClient,
1626
+ this.nativeExtensionProvider,
1627
+ correlationId
1628
+ );
1629
+ }
1630
+
1631
+ /**
1632
+ * Adds event callbacks to array
1633
+ * @param callback
1634
+ */
1635
+ addEventCallback(callback: EventCallbackFunction): string | null {
1636
+ return this.eventHandler.addEventCallback(callback);
1637
+ }
1638
+
1639
+ /**
1640
+ * Removes callback with provided id from callback array
1641
+ * @param callbackId
1642
+ */
1643
+ removeEventCallback(callbackId: string): void {
1644
+ this.eventHandler.removeEventCallback(callbackId);
1645
+ }
1646
+
1647
+ /**
1648
+ * Registers a callback to receive performance events.
1649
+ *
1650
+ * @param {PerformanceCallbackFunction} callback
1651
+ * @returns {string}
1652
+ */
1653
+ addPerformanceCallback(callback: PerformanceCallbackFunction): string {
1654
+ return this.performanceClient.addPerformanceCallback(callback);
1655
+ }
1656
+
1657
+ /**
1658
+ * Removes a callback registered with addPerformanceCallback.
1659
+ *
1660
+ * @param {string} callbackId
1661
+ * @returns {boolean}
1662
+ */
1663
+ removePerformanceCallback(callbackId: string): boolean {
1664
+ return this.performanceClient.removePerformanceCallback(callbackId);
1665
+ }
1666
+
1667
+ /**
1668
+ * Adds event listener that emits an event when a user account is added or removed from localstorage in a different browser tab or window
1669
+ */
1670
+ enableAccountStorageEvents(): void {
1671
+ this.eventHandler.enableAccountStorageEvents();
1672
+ }
1673
+
1674
+ /**
1675
+ * Removes event listener that emits an event when a user account is added or removed from localstorage in a different browser tab or window
1676
+ */
1677
+ disableAccountStorageEvents(): void {
1678
+ this.eventHandler.disableAccountStorageEvents();
1679
+ }
1680
+
1681
+ /**
1682
+ * Gets the token cache for the application.
1683
+ */
1684
+ getTokenCache(): ITokenCache {
1685
+ return this.tokenCache;
1686
+ }
1687
+
1688
+ /**
1689
+ * Returns the logger instance
1690
+ */
1691
+ public getLogger(): Logger {
1692
+ return this.logger;
1693
+ }
1694
+
1695
+ /**
1696
+ * Replaces the default logger set in configurations with new Logger with new configurations
1697
+ * @param logger Logger instance
1698
+ */
1699
+ setLogger(logger: Logger): void {
1700
+ this.logger = logger;
1701
+ }
1702
+
1703
+ /**
1704
+ * Called by wrapper libraries (Angular & React) to set SKU and Version passed down to telemetry, logger, etc.
1705
+ * @param sku
1706
+ * @param version
1707
+ */
1708
+ initializeWrapperLibrary(sku: WrapperSKU, version: string): void {
1709
+ // Validate the SKU passed in is one we expect
1710
+ this.browserStorage.setWrapperMetadata(sku, version);
1711
+ }
1712
+
1713
+ /**
1714
+ * Sets navigation client
1715
+ * @param navigationClient
1716
+ */
1717
+ setNavigationClient(navigationClient: INavigationClient): void {
1718
+ this.navigationClient = navigationClient;
1719
+ }
1720
+
1721
+ /**
1722
+ * Returns the configuration object
1723
+ */
1724
+ public getConfiguration(): BrowserConfiguration {
1725
+ return this.config;
1726
+ }
1727
+
1728
+ /**
1729
+ * Returns the performance client
1730
+ */
1731
+ public getPerformanceClient(): IPerformanceClient {
1732
+ return this.performanceClient;
1733
+ }
1734
+
1735
+ /**
1736
+ * Returns the browser storage
1737
+ */
1738
+ public getBrowserStorage(): BrowserCacheManager {
1739
+ return this.browserStorage;
1740
+ }
1741
+
1742
+ /**
1743
+ * Returns the browser env indicator
1744
+ */
1745
+ public isBrowserEnv(): boolean {
1746
+ return this.isBrowserEnvironment;
1747
+ }
1748
+
1749
+ /**
1750
+ * Returns the event handler
1751
+ */
1752
+ getEventHandler(): EventHandler {
1753
+ return this.eventHandler;
1754
+ }
1755
+
1756
+ /**
1757
+ * Generates a correlation id for a request if none is provided.
1758
+ *
1759
+ * @protected
1760
+ * @param {?Partial<BaseAuthRequest>} [request]
1761
+ * @returns {string}
1762
+ */
1763
+ protected getRequestCorrelationId(
1764
+ request?: Partial<BaseAuthRequest>
1765
+ ): string {
1766
+ if (request?.correlationId) {
1767
+ return request.correlationId;
1768
+ }
1769
+
1770
+ if (this.isBrowserEnvironment) {
1771
+ return this.browserCrypto.createNewGuid();
1772
+ }
1773
+
1774
+ /*
1775
+ * Included for fallback for non-browser environments,
1776
+ * and to ensure this method always returns a string.
1777
+ */
1778
+ return Constants.EMPTY_STRING;
1779
+ }
1780
+
1781
+ // #endregion
1782
+
1783
+ /**
1784
+ * Use when initiating the login process by redirecting the user's browser to the authorization endpoint. This function redirects the page, so
1785
+ * any code that follows this function will not execute.
1786
+ *
1787
+ * IMPORTANT: It is NOT recommended to have code that is dependent on the resolution of the Promise. This function will navigate away from the current
1788
+ * browser window. It currently returns a Promise in order to reflect the asynchronous nature of the code running in this function.
1789
+ *
1790
+ * @param request
1791
+ */
1792
+ async loginRedirect(request?: RedirectRequest): Promise<void> {
1793
+ const correlationId: string = this.getRequestCorrelationId(request);
1794
+ this.logger.verbose("loginRedirect called", correlationId);
1795
+ return this.acquireTokenRedirect({
1796
+ correlationId,
1797
+ ...(request || DEFAULT_REQUEST),
1798
+ });
1799
+ }
1800
+
1801
+ /**
1802
+ * Use when initiating the login process via opening a popup window in the user's browser
1803
+ *
1804
+ * @param request
1805
+ *
1806
+ * @returns A promise that is fulfilled when this function has completed, or rejected if an error was raised.
1807
+ */
1808
+ loginPopup(request?: PopupRequest): Promise<AuthenticationResult> {
1809
+ const correlationId: string = this.getRequestCorrelationId(request);
1810
+ this.logger.verbose("loginPopup called", correlationId);
1811
+ return this.acquireTokenPopup({
1812
+ correlationId,
1813
+ ...(request || DEFAULT_REQUEST),
1814
+ });
1815
+ }
1816
+
1817
+ /**
1818
+ * Silently acquire an access token for a given set of scopes. Returns currently processing promise if parallel requests are made.
1819
+ *
1820
+ * @param {@link (SilentRequest:type)}
1821
+ * @returns {Promise.<AuthenticationResult>} - a promise that is fulfilled when this function has completed, or rejected if an error was raised. Returns the {@link AuthResponse} object
1822
+ */
1823
+ async acquireTokenSilent(
1824
+ request: SilentRequest
1825
+ ): Promise<AuthenticationResult> {
1826
+ const correlationId = this.getRequestCorrelationId(request);
1827
+ const atsMeasurement = this.performanceClient.startMeasurement(
1828
+ PerformanceEvents.AcquireTokenSilent,
1829
+ correlationId
1830
+ );
1831
+ atsMeasurement.add({
1832
+ cacheLookupPolicy: request.cacheLookupPolicy,
1833
+ });
1834
+
1835
+ this.preflightBrowserEnvironmentCheck(InteractionType.Silent);
1836
+ this.logger.verbose("acquireTokenSilent called", correlationId);
1837
+
1838
+ const account = request.account || this.getActiveAccount();
1839
+ if (!account) {
1840
+ throw createBrowserAuthError(BrowserAuthErrorCodes.noAccountError);
1841
+ }
1842
+
1843
+ const thumbprint: RequestThumbprint = {
1844
+ clientId: this.config.auth.clientId,
1845
+ authority: request.authority || Constants.EMPTY_STRING,
1846
+ scopes: request.scopes,
1847
+ homeAccountIdentifier: account.homeAccountId,
1848
+ claims: request.claims,
1849
+ authenticationScheme: request.authenticationScheme,
1850
+ resourceRequestMethod: request.resourceRequestMethod,
1851
+ resourceRequestUri: request.resourceRequestUri,
1852
+ shrClaims: request.shrClaims,
1853
+ sshKid: request.sshKid,
1854
+ };
1855
+ const silentRequestKey = JSON.stringify(thumbprint);
1856
+
1857
+ const cachedResponse =
1858
+ this.activeSilentTokenRequests.get(silentRequestKey);
1859
+ if (typeof cachedResponse === "undefined") {
1860
+ this.logger.verbose(
1861
+ "acquireTokenSilent called for the first time, storing active request",
1862
+ correlationId
1863
+ );
1864
+
1865
+ this.performanceClient.setPreQueueTime(
1866
+ PerformanceEvents.AcquireTokenSilentAsync,
1867
+ correlationId
1868
+ );
1869
+ const response = this.acquireTokenSilentAsync(
1870
+ {
1871
+ ...request,
1872
+ correlationId,
1873
+ },
1874
+ account
1875
+ )
1876
+ .then((result) => {
1877
+ this.activeSilentTokenRequests.delete(silentRequestKey);
1878
+ atsMeasurement.add({
1879
+ accessTokenSize: result.accessToken.length,
1880
+ idTokenSize: result.idToken.length,
1881
+ });
1882
+ atsMeasurement.end({
1883
+ success: true,
1884
+ fromCache: result.fromCache,
1885
+ isNativeBroker: result.fromNativeBroker,
1886
+ cacheLookupPolicy: request.cacheLookupPolicy,
1887
+ requestId: result.requestId,
1888
+ });
1889
+ return result;
1890
+ })
1891
+ .catch((error: AuthError) => {
1892
+ this.activeSilentTokenRequests.delete(silentRequestKey);
1893
+ atsMeasurement.end({
1894
+ errorCode: error.errorCode,
1895
+ subErrorCode: error.subError,
1896
+ success: false,
1897
+ });
1898
+ throw error;
1899
+ });
1900
+ this.activeSilentTokenRequests.set(silentRequestKey, response);
1901
+ return response;
1902
+ } else {
1903
+ this.logger.verbose(
1904
+ "acquireTokenSilent has been called previously, returning the result from the first call",
1905
+ correlationId
1906
+ );
1907
+ // Discard measurements for memoized calls, as they are usually only a couple of ms and will artificially deflate metrics
1908
+ atsMeasurement.discard();
1909
+ return cachedResponse;
1910
+ }
1911
+ }
1912
+
1913
+ /**
1914
+ * Silently acquire an access token for a given set of scopes. Will use cached token if available, otherwise will attempt to acquire a new token from the network via refresh token.
1915
+ * @param {@link (SilentRequest:type)}
1916
+ * @param {@link (AccountInfo:type)}
1917
+ * @returns {Promise.<AuthenticationResult>} - a promise that is fulfilled when this function has completed, or rejected if an error was raised. Returns the {@link AuthResponse}
1918
+ */
1919
+ protected async acquireTokenSilentAsync(
1920
+ request: SilentRequest,
1921
+ account: AccountInfo
1922
+ ): Promise<AuthenticationResult> {
1923
+ this.performanceClient.addQueueMeasurement(
1924
+ PerformanceEvents.AcquireTokenSilentAsync,
1925
+ request.correlationId
1926
+ );
1927
+
1928
+ this.eventHandler.emitEvent(
1929
+ EventType.ACQUIRE_TOKEN_START,
1930
+ InteractionType.Silent,
1931
+ request
1932
+ );
1933
+ this.atsAsyncMeasurement = this.performanceClient.startMeasurement(
1934
+ PerformanceEvents.AcquireTokenSilentAsync,
1935
+ request.correlationId
1936
+ );
1937
+ this.atsAsyncMeasurement?.increment({
1938
+ visibilityChangeCount: 0,
1939
+ });
1940
+ document.addEventListener("visibilitychange", this.trackPageVisibility);
1941
+ let result: Promise<AuthenticationResult>;
1942
+ if (
1943
+ NativeMessageHandler.isNativeAvailable(
1944
+ this.config,
1945
+ this.logger,
1946
+ this.nativeExtensionProvider,
1947
+ request.authenticationScheme
1948
+ ) &&
1949
+ account.nativeAccountId
1950
+ ) {
1951
+ this.logger.verbose(
1952
+ "acquireTokenSilent - attempting to acquire token from native platform"
1953
+ );
1954
+ const silentRequest: SilentRequest = {
1955
+ ...request,
1956
+ account,
1957
+ };
1958
+ result = this.acquireTokenNative(
1959
+ silentRequest,
1960
+ ApiId.acquireTokenSilent_silentFlow
1961
+ ).catch(async (e: AuthError) => {
1962
+ // If native token acquisition fails for availability reasons fallback to web flow
1963
+ if (e instanceof NativeAuthError && e.isFatal()) {
1964
+ this.logger.verbose(
1965
+ "acquireTokenSilent - native platform unavailable, falling back to web flow"
1966
+ );
1967
+ this.nativeExtensionProvider = undefined; // Prevent future requests from continuing to attempt
1968
+
1969
+ // Cache will not contain tokens, given that previous WAM requests succeeded. Skip cache and RT renewal and go straight to iframe renewal
1970
+ const silentIframeClient = this.createSilentIframeClient(
1971
+ request.correlationId
1972
+ );
1973
+ return silentIframeClient.acquireToken(request);
1974
+ }
1975
+ throw e;
1976
+ });
1977
+ } else {
1978
+ this.logger.verbose(
1979
+ "acquireTokenSilent - attempting to acquire token from web flow"
1980
+ );
1981
+
1982
+ const silentCacheClient = this.createSilentCacheClient(
1983
+ request.correlationId
1984
+ );
1985
+
1986
+ this.performanceClient.setPreQueueTime(
1987
+ PerformanceEvents.InitializeSilentRequest,
1988
+ request.correlationId
1989
+ );
1990
+ const silentRequest =
1991
+ await silentCacheClient.initializeSilentRequest(
1992
+ request,
1993
+ account
1994
+ );
1995
+
1996
+ const requestWithCLP = {
1997
+ ...request,
1998
+ // set the request's CacheLookupPolicy to Default if it was not optionally passed in
1999
+ cacheLookupPolicy:
2000
+ request.cacheLookupPolicy || CacheLookupPolicy.Default,
2001
+ };
2002
+
2003
+ this.performanceClient.setPreQueueTime(
2004
+ PerformanceEvents.AcquireTokenFromCache,
2005
+ silentRequest.correlationId
2006
+ );
2007
+ result = this.acquireTokenFromCache(
2008
+ silentCacheClient,
2009
+ silentRequest,
2010
+ requestWithCLP
2011
+ ).catch((cacheError: AuthError) => {
2012
+ if (
2013
+ requestWithCLP.cacheLookupPolicy ===
2014
+ CacheLookupPolicy.AccessToken
2015
+ ) {
2016
+ throw cacheError;
2017
+ }
2018
+
2019
+ // block the reload if it occurred inside a hidden iframe
2020
+ BrowserUtils.blockReloadInHiddenIframes();
2021
+ this.eventHandler.emitEvent(
2022
+ EventType.ACQUIRE_TOKEN_NETWORK_START,
2023
+ InteractionType.Silent,
2024
+ silentRequest
2025
+ );
2026
+
2027
+ this.performanceClient.setPreQueueTime(
2028
+ PerformanceEvents.AcquireTokenByRefreshToken,
2029
+ silentRequest.correlationId
2030
+ );
2031
+ return this.acquireTokenByRefreshToken(
2032
+ silentRequest,
2033
+ requestWithCLP
2034
+ ).catch((refreshTokenError: AuthError) => {
2035
+ const isServerError =
2036
+ refreshTokenError instanceof ServerError;
2037
+ const isInteractionRequiredError =
2038
+ refreshTokenError instanceof
2039
+ InteractionRequiredAuthError;
2040
+ const isInvalidGrantError =
2041
+ refreshTokenError.errorCode ===
2042
+ BrowserConstants.INVALID_GRANT_ERROR;
2043
+
2044
+ if (
2045
+ (!isServerError ||
2046
+ !isInvalidGrantError ||
2047
+ isInteractionRequiredError ||
2048
+ requestWithCLP.cacheLookupPolicy ===
2049
+ CacheLookupPolicy.AccessTokenAndRefreshToken ||
2050
+ requestWithCLP.cacheLookupPolicy ===
2051
+ CacheLookupPolicy.RefreshToken) &&
2052
+ requestWithCLP.cacheLookupPolicy !==
2053
+ CacheLookupPolicy.Skip
2054
+ ) {
2055
+ throw refreshTokenError;
2056
+ }
2057
+
2058
+ this.logger.verbose(
2059
+ "Refresh token expired/invalid or CacheLookupPolicy is set to Skip, attempting acquire token by iframe.",
2060
+ request.correlationId
2061
+ );
2062
+ this.performanceClient.setPreQueueTime(
2063
+ PerformanceEvents.AcquireTokenBySilentIframe,
2064
+ silentRequest.correlationId
2065
+ );
2066
+ return this.acquireTokenBySilentIframe(silentRequest);
2067
+ });
2068
+ });
2069
+ }
2070
+
2071
+ return result
2072
+ .then((response) => {
2073
+ this.eventHandler.emitEvent(
2074
+ EventType.ACQUIRE_TOKEN_SUCCESS,
2075
+ InteractionType.Silent,
2076
+ response
2077
+ );
2078
+ this.atsAsyncMeasurement?.end({
2079
+ success: true,
2080
+ fromCache: response.fromCache,
2081
+ isNativeBroker: response.fromNativeBroker,
2082
+ requestId: response.requestId,
2083
+ });
2084
+ return response;
2085
+ })
2086
+ .catch((tokenRenewalError: AuthError) => {
2087
+ this.eventHandler.emitEvent(
2088
+ EventType.ACQUIRE_TOKEN_FAILURE,
2089
+ InteractionType.Silent,
2090
+ null,
2091
+ tokenRenewalError
2092
+ );
2093
+ this.atsAsyncMeasurement?.end({
2094
+ errorCode: tokenRenewalError.errorCode,
2095
+ subErrorCode: tokenRenewalError.subError,
2096
+ success: false,
2097
+ });
2098
+ throw tokenRenewalError;
2099
+ })
2100
+ .finally(() => {
2101
+ document.removeEventListener(
2102
+ "visibilitychange",
2103
+ this.trackPageVisibility
2104
+ );
2105
+ });
2106
+ }
2107
+ }