diff --git a/change/@azure-msal-browser-v5-telemetry-cherry-pick.json b/change/@azure-msal-browser-v5-telemetry-cherry-pick.json new file mode 100644 index 0000000000..63b921d4b6 --- /dev/null +++ b/change/@azure-msal-browser-v5-telemetry-cherry-pick.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "[v5]Add Platform Telemetry [#8175](https://github.com/AzureAD/microsoft-authentication-library-for-js/pull/8175)", + "packageName": "@azure/msal-browser", + "email": "sameera.gajjarapu@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@azure-msal-common-v5-telemetry-cherry-pick.json b/change/@azure-msal-common-v5-telemetry-cherry-pick.json new file mode 100644 index 0000000000..ee992f4f33 --- /dev/null +++ b/change/@azure-msal-common-v5-telemetry-cherry-pick.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "[v5]Add Platform Telemetry [#8175](https://github.com/AzureAD/microsoft-authentication-library-for-js/pull/8175)" + "packageName": "@azure/msal-common", + "email": "sameera.gajjarapu@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/lib/msal-browser/src/controllers/StandardController.ts b/lib/msal-browser/src/controllers/StandardController.ts index b4e55f996a..bcf125f036 100644 --- a/lib/msal-browser/src/controllers/StandardController.ts +++ b/lib/msal-browser/src/controllers/StandardController.ts @@ -462,6 +462,9 @@ export class StandardController implements IController { "handleRedirectPromise - acquiring token from native platform", correlationId ); + rootMeasurement.add({ + isPlatformBrokerRequest: true, + }); const nativeClient = new PlatformAuthInteractionClient( this.config, this.browserStorage, @@ -650,6 +653,9 @@ export class StandardController implements IController { this.platformAuthProvider && this.canUsePlatformBroker(request) ) { + atrMeasurement.add({ + isPlatformBrokerRequest: true, + }); const nativeClient = new PlatformAuthInteractionClient( this.config, this.browserStorage, @@ -664,14 +670,19 @@ export class StandardController implements IController { this.nativeInternalStorage, correlationId ); + result = nativeClient .acquireTokenRedirect(request, atrMeasurement) .catch((e: AuthError) => { + atrMeasurement.add({ + brokerErrorName: e.name, + brokerErrorCode: e.errorCode, + }); if ( e instanceof NativeAuthError && isFatalNativeAuthError(e) ) { - this.platformAuthProvider = undefined; // If extension gets uninstalled during session prevent future requests from continuing to attempt + this.platformAuthProvider = undefined; // If extension gets uninstalled during session prevent future requests from continuing to attempt platform broker calls const redirectClient = this.createRedirectClient(correlationId); return redirectClient.acquireToken(request); @@ -771,6 +782,9 @@ export class StandardController implements IController { const pkce = this.getPreGeneratedPkceCodes(correlationId); if (this.canUsePlatformBroker(request)) { + atPopupMeasurement.add({ + isPlatformBrokerRequest: true, + }); result = this.acquireTokenNative( { ...request, @@ -790,11 +804,15 @@ export class StandardController implements IController { return response; }) .catch((e: AuthError) => { + atPopupMeasurement.add({ + brokerErrorName: e.name, + brokerErrorCode: e.errorCode, + }); if ( e instanceof NativeAuthError && isFatalNativeAuthError(e) ) { - this.platformAuthProvider = undefined; // If extension gets uninstalled during session prevent future requests from continuing to attempt + this.platformAuthProvider = undefined; // If extension gets uninstalled during session prevent future requests from continuing to attempt platform broker calls const popupClient = this.createPopupClient(correlationId); return popupClient.acquireToken(request, pkce); @@ -942,10 +960,17 @@ export class StandardController implements IController { let result: Promise; if (this.canUsePlatformBroker(validRequest)) { + this.ssoSilentMeasurement?.add({ + isPlatformBrokerRequest: true, + }); result = this.acquireTokenNative( validRequest, ApiId.ssoSilent ).catch((e: AuthError) => { + this.ssoSilentMeasurement?.add({ + brokerErrorName: e.name, + brokerErrorCode: e.errorCode, + }); // If native token acquisition fails for availability reasons fallback to standard flow if (e instanceof NativeAuthError && isFatalNativeAuthError(e)) { this.platformAuthProvider = undefined; // If extension gets uninstalled during session prevent future requests from continuing to attempt @@ -1108,6 +1133,9 @@ export class StandardController implements IController { if ( this.canUsePlatformBroker(request, request.nativeAccountId) ) { + atbcMeasurement.add({ + isPlatformBrokerRequest: true, + }); const result = await this.acquireTokenNative( { ...request, @@ -1123,6 +1151,10 @@ export class StandardController implements IController { ) { this.platformAuthProvider = undefined; // If extension gets uninstalled during session prevent future requests from continuing to attempt } + atbcMeasurement.add({ + brokerErrorName: e.name, + brokerErrorCode: e.errorCode, + }); throw e; }); atbcMeasurement.end( @@ -1190,7 +1222,6 @@ export class StandardController implements IController { this.acquireTokenByCodeAsyncMeasurement?.end({ success: true, fromCache: response.fromCache, - isNativeBroker: response.fromPlatformBroker, }); return response; }) @@ -1878,7 +1909,6 @@ export class StandardController implements IController { { success: true, fromCache: result.fromCache, - isNativeBroker: result.fromPlatformBroker, accessTokenSize: result.accessToken.length, idTokenSize: result.idToken.length, }, @@ -2117,7 +2147,6 @@ export class StandardController implements IController { this.performanceClient.addFields( { fromCache: response.fromCache, - isNativeBroker: response.fromPlatformBroker, }, request.correlationId ); @@ -2167,12 +2196,23 @@ export class StandardController implements IController { "acquireTokenSilent - attempting to acquire token from native platform", silentRequest.correlationId ); + this.performanceClient.addFields( + { isPlatformBrokerRequest: true }, + silentRequest.correlationId + ); return this.acquireTokenNative( silentRequest, ApiId.acquireTokenSilent_silentFlow, silentRequest.account.nativeAccountId, cacheLookupPolicy ).catch(async (e: AuthError) => { + this.performanceClient.addFields( + { + brokerErrorName: e.name, + brokerErrorCode: e.errorCode, + }, + silentRequest.correlationId + ); // If native token acquisition fails for availability reasons fallback to web flow if (e instanceof NativeAuthError && isFatalNativeAuthError(e)) { this.logger.verbose( diff --git a/lib/msal-browser/src/error/NativeAuthError.ts b/lib/msal-browser/src/error/NativeAuthError.ts index 381f250c7d..bdeab1c52b 100644 --- a/lib/msal-browser/src/error/NativeAuthError.ts +++ b/lib/msal-browser/src/error/NativeAuthError.ts @@ -48,8 +48,7 @@ export function isFatalNativeAuthError(error: NativeAuthError): boolean { if ( error.ext && error.ext.status && - (error.ext.status === NativeStatusCodes.PERSISTENT_ERROR || - error.ext.status === NativeStatusCodes.DISABLED) + error.ext.status === NativeStatusCodes.DISABLED ) { return true; } diff --git a/lib/msal-browser/src/interaction_client/PlatformAuthInteractionClient.ts b/lib/msal-browser/src/interaction_client/PlatformAuthInteractionClient.ts index e41e7d4369..85121fb2ce 100644 --- a/lib/msal-browser/src/interaction_client/PlatformAuthInteractionClient.ts +++ b/lib/msal-browser/src/interaction_client/PlatformAuthInteractionClient.ts @@ -166,7 +166,7 @@ export class PlatformAuthInteractionClient extends BaseInteractionClient { // start the perf measurement const nativeATMeasurement = this.performanceClient.startMeasurement( BrowserPerformanceEvents.NativeInteractionClientAcquireToken, - request.correlationId + this.correlationId ); const reqTimestamp = TimeUtils.nowSeconds(); @@ -200,6 +200,9 @@ export class PlatformAuthInteractionClient extends BaseInteractionClient { "MSAL internal Cache does not contain tokens, return error as per cache policy", this.correlationId ); + nativeATMeasurement.end({ + success: false, + }); throw e; } // continue with a native call for any and all errors @@ -220,10 +223,15 @@ export class PlatformAuthInteractionClient extends BaseInteractionClient { .then((result: AuthenticationResult) => { nativeATMeasurement.end({ success: true, - isNativeBroker: true, requestId: result.requestId, }); serverTelemetryManager.clearNativeBrokerErrorCode(); + if (this.performanceClient && this.correlationId) { + this.performanceClient.addFields( + { isNativeBroker: true }, + this.correlationId + ); + } return result; }) .catch((error: AuthError) => { @@ -231,7 +239,6 @@ export class PlatformAuthInteractionClient extends BaseInteractionClient { success: false, errorCode: error.errorCode, subErrorCode: error.subError, - isNativeBroker: true, }); throw error; }); @@ -239,6 +246,13 @@ export class PlatformAuthInteractionClient extends BaseInteractionClient { if (e instanceof NativeAuthError) { serverTelemetryManager.setNativeBrokerErrorCode(e.errorCode); } + nativeATMeasurement.end({ + success: false, + ...(e instanceof NativeAuthError && { + brokerErrorName: e.name, + brokerErrorCode: e.errorCode, + }), + }); throw e; } } @@ -284,7 +298,7 @@ export class PlatformAuthInteractionClient extends BaseInteractionClient { { nativeAccountId, }, - request.correlationId + this.correlationId ); if (!account) { @@ -1144,7 +1158,7 @@ export class PlatformAuthInteractionClient extends BaseInteractionClient { embeddedClientId: child_client_id, embeddedRedirectUri: child_redirect_uri, }, - request.correlationId + this.correlationId ); } } diff --git a/lib/msal-browser/src/protocol/Authorize.ts b/lib/msal-browser/src/protocol/Authorize.ts index d8864d7c32..f39b275d2d 100644 --- a/lib/msal-browser/src/protocol/Authorize.ts +++ b/lib/msal-browser/src/protocol/Authorize.ts @@ -83,6 +83,14 @@ async function getStandardParameters( // signal ests that this is a WAM call RequestParameterBuilder.addNativeBroker(parameters); + // instrument JS-platform bridge specific fields + performanceClient.addFields( + { + isPlatformAuthorizeRequest: true, + }, + request.correlationId + ); + // pass the req_cnf for POP if ( request.authenticationScheme === Constants.AuthenticationScheme.POP diff --git a/lib/msal-browser/test/app/PublicClientApplication.spec.ts b/lib/msal-browser/test/app/PublicClientApplication.spec.ts index 30019d4bc0..7bd512fcfb 100644 --- a/lib/msal-browser/test/app/PublicClientApplication.spec.ts +++ b/lib/msal-browser/test/app/PublicClientApplication.spec.ts @@ -116,6 +116,7 @@ import { PlatformAuthDOMHandler } from "../../src/broker/nativeBroker/PlatformAu import * as BrowserRootPerformanceEvents from "../../src/telemetry/BrowserRootPerformanceEvents.js"; import * as CacheKeys from "../../src/cache/CacheKeys.js"; import { getAccountKeysCacheKey } from "../../src/cache/CacheKeys.js"; +import exp from "constants"; const cacheConfig = { cacheLocation: BrowserCacheLocation.SessionStorage, @@ -1570,7 +1571,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { done(); }); }); - it("goes directly to the native broker if nativeAccountId is present", async () => { + it("goes directly to the platform broker if nativeAccountId is present and emits platform telemetry", async () => { const config = { auth: { clientId: TEST_CONFIG.MSAL_CLIENT_ID, @@ -1578,6 +1579,17 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { system: { allowPlatformBroker: true, }, + telemetry: { + client: new BrowserPerformanceClient({ + auth: { + clientId: TEST_CONFIG.MSAL_CLIENT_ID, + }, + }), + application: { + appName: TEST_CONFIG.applicationName, + appVersion: TEST_CONFIG.applicationVersion, + }, + }, }; pca = new PublicClientApplication(config); @@ -1588,6 +1600,21 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { pca = (pca as any).controller; const testAccount = BASIC_NATIVE_TEST_ACCOUNT_INFO; + const testTokenResponse: AuthenticationResult = { + authority: TEST_CONFIG.validAuthority, + uniqueId: testAccount.localAccountId, + tenantId: testAccount.tenantId, + scopes: TEST_CONFIG.DEFAULT_SCOPES, + idToken: "test-idToken", + idTokenClaims: {}, + accessToken: "test-accessToken", + fromCache: false, + correlationId: RANDOM_TEST_GUID, + expiresOn: TestTimeUtils.nowDateWithOffset(3600), + account: testAccount, + tokenType: Constants.AuthenticationScheme.BEARER, + fromNativeBroker: true, + }; jest.spyOn(BrowserCrypto, "createNewGuid").mockReturnValue( RANDOM_TEST_GUID @@ -1599,14 +1626,53 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { "acquireTokenRedirect" ) .mockResolvedValue(); + + const nativeHandleRedirectPromiseSpy = jest + .spyOn(StandardController.prototype, "handleRedirectPromise") + .mockImplementation( + async (req: any, measurementName?: string) => { + // Find existing performance event by correlationId and modify it + const performanceClient = + pca.getConfiguration().telemetry?.client; + if (performanceClient && measurementName) { + const events = (performanceClient as any) + .eventsByCorrelationId; + const existingEvent = events.get(req.correlationId); + if (existingEvent) { + existingEvent.isNativeBroker = true; + } + } + return testTokenResponse; + } + ); + const redirectSpy: jest.SpyInstance = jest .spyOn(RedirectClient.prototype, "acquireToken") .mockResolvedValue(); + + // Mock the acquireTokensFromCache method to simulate a cache miss (rejects first), then WAM success (resolves) + jest.spyOn( + PlatformAuthInteractionClient.prototype as any, + "acquireTokensFromCache" + ) + // First call rejects (simulates cache miss), second call resolves (simulates WAM success) + .mockRejectedValueOnce(new Error("No cached tokens")) + .mockResolvedValue(testTokenResponse); + + // Add performance callback + const callbackId = pca.addPerformanceCallback((events) => { + expect(events[0].isNativeBroker).toBe(true); + expect(events[0].isPlatformBrokerRequest).toBe(true); + pca.removePerformanceCallback(callbackId); + }); + await pca.acquireTokenRedirect({ scopes: ["User.Read"], account: testAccount, }); + const response = pca.handleRedirectPromise(); + expect(nativeAcquireTokenSpy).toHaveBeenCalledTimes(1); expect(redirectSpy).toHaveBeenCalledTimes(0); }); diff --git a/lib/msal-browser/test/error/NativeAuthError.spec.ts b/lib/msal-browser/test/error/NativeAuthError.spec.ts index 60efbb3e58..6613e1628a 100644 --- a/lib/msal-browser/test/error/NativeAuthError.spec.ts +++ b/lib/msal-browser/test/error/NativeAuthError.spec.ts @@ -3,12 +3,12 @@ import { NativeAuthErrorCodes, createNativeAuthError, isFatalNativeAuthError, -} from "../../src/error/NativeAuthError"; +} from "../../src/error/NativeAuthError.js"; import { InteractionRequiredAuthError, InteractionRequiredAuthErrorCodes, } from "@azure/msal-common"; -import * as NativeStatusCode from "../../src/broker/nativeBroker/NativeStatusCodes"; +import * as NativeStatusCode from "../../src/broker/nativeBroker/NativeStatusCodes.js"; import { BrowserAuthErrorCodes, BrowserAuthError, @@ -17,7 +17,7 @@ import { describe("NativeAuthError Unit Tests", () => { describe("NativeAuthError", () => { describe("isFatal tests", () => { - it("should return true for isFatal when WAM status is PERSISTENT_ERROR", () => { + it("should return false for isFatal when WAM status is PERSISTENT_ERROR", () => { const error = new NativeAuthError( "testError", "testErrorDescription", @@ -28,7 +28,7 @@ describe("NativeAuthError Unit Tests", () => { status: NativeStatusCode.PERSISTENT_ERROR, } ); - expect(isFatalNativeAuthError(error)).toBe(true); + expect(isFatalNativeAuthError(error)).toBe(false); }); it("should return true for isFatal when WAM status is DISABLED", () => { diff --git a/lib/msal-browser/test/interaction_client/PlatformAuthInteractionClient.spec.ts b/lib/msal-browser/test/interaction_client/PlatformAuthInteractionClient.spec.ts index 6f95f3e80d..f4db5f61c8 100644 --- a/lib/msal-browser/test/interaction_client/PlatformAuthInteractionClient.spec.ts +++ b/lib/msal-browser/test/interaction_client/PlatformAuthInteractionClient.spec.ts @@ -15,16 +15,22 @@ import { InProgressPerformanceEvent, AccountEntityUtils, Constants, + CacheHelpers, + AuthError, } from "@azure/msal-common"; +import { OpenIdConfigResponse } from "../../../msal-common/src/authority/OpenIdConfigResponse.js"; import { PlatformAuthExtensionHandler } from "../../src/broker/nativeBroker/PlatformAuthExtensionHandler.js"; import { ApiId } from "../../src/utils/BrowserConstants.js"; import { PlatformAuthInteractionClient } from "../../src/interaction_client/PlatformAuthInteractionClient.js"; import { PublicClientApplication } from "../../src/app/PublicClientApplication.js"; import { + DEFAULT_OPENID_CONFIG_RESPONSE, + DEFAULT_TENANT_DISCOVERY_RESPONSE, ID_TOKEN_CLAIMS, RANDOM_TEST_GUID, TEST_CONFIG, TEST_DATA_CLIENT_INFO, + TEST_HASHES, TEST_TOKENS, } from "../utils/StringConstants.js"; import { NavigationClient } from "../../src/navigation/NavigationClient.js"; @@ -33,6 +39,7 @@ import { getDefaultErrorMessage, } from "../../src/error/BrowserAuthError.js"; import { + createNativeAuthError, NativeAuthError, NativeAuthErrorCodes, } from "../../src/error/NativeAuthError.js"; @@ -46,6 +53,7 @@ import { BrowserConstants } from "../../src/utils/BrowserConstants.js"; import * as NativeStatusCodes from "../../src/broker/nativeBroker/NativeStatusCodes.js"; import { PlatformAuthResponse } from "../../src/broker/nativeBroker/PlatformAuthResponse.js"; import { PlatformAuthDOMHandler } from "../../src/broker/nativeBroker/PlatformAuthDOMHandler.js"; +import * as SilentHandler from "../../src/interaction_handler/SilentHandler.js"; const MOCK_WAM_RESPONSE: PlatformAuthResponse = { access_token: TEST_TOKENS.ACCESS_TOKEN, @@ -120,7 +128,7 @@ describe("PlatformAuthInteractionClient Tests", () => { let wamProvider: PlatformAuthExtensionHandler; let postMessageSpy: jest.SpyInstance; - let mcPort: MessagePort; + let mcPort: MessagePort | undefined; let perfClient: IPerformanceClient; let perfMeasurement: InProgressPerformanceEvent; @@ -187,7 +195,7 @@ describe("PlatformAuthInteractionClient Tests", () => { ); // source property not set by jsdom window messaging APIs perfMeasurement = perfClient.startMeasurement( "test-measurement", - "test-correlation-id" + RANDOM_TEST_GUID ); }); @@ -1713,4 +1721,271 @@ describe("PlatformAuthInteractionClient Tests", () => { expect(nativeRequest.redirectUri).toEqual("localhost"); }); }); + + describe("Performance Event Validation", () => { + let performanceSpy: jest.SpyInstance; + let startMeasurementSpy: jest.SpyInstance; + + beforeEach(() => { + // Create mock measurement with spies + const mockMeasurement = { + add: jest.fn(), + end: jest.fn(), + increment: jest.fn(), + discard: jest.fn(), + }; + + // Spy on performance client methods + startMeasurementSpy = jest + .spyOn(perfClient, "startMeasurement") + .mockReturnValue(mockMeasurement as any); + performanceSpy = jest.spyOn(perfClient, "addFields"); + }); + + afterEach(() => { + performanceSpy.mockRestore(); + startMeasurementSpy.mockRestore(); + }); + + it("emits isNativeBroker=true for acquireToken success", async () => { + jest.spyOn( + PlatformAuthExtensionHandler.prototype, + "sendMessage" + ).mockImplementation((): Promise => { + return Promise.resolve(MOCK_WAM_RESPONSE); + }); + + // Mock successful token acquisition from cache first + const mockAuthResult = { + accessToken: "mock_access_token", + idToken: "mock_id_token", + account: { + homeAccountId: "test-home-account-id", + environment: "login.microsoftonline.com", + nativeAccountId: "test-native-account-id", + tenantId: "test-tenant-id", + username: "test@example.com", + localAccountId: "test-local-account-id", + name: "Test User", + idTokenClaims: {}, + }, + scopes: ["User.Read"], + expiresOn: new Date(Date.now() + 3600000), + tenantId: "test-tenant-id", + uniqueId: "test-unique-id", + tokenType: "Bearer" as const, + correlationId: RANDOM_TEST_GUID, + extExpiresOn: new Date(Date.now() + 7200000), + state: "", + fromCache: false, + }; + + // Mock the acquireTokensFromCache method to simulate a cache miss (rejects first), then WAM success (resolves) + jest.spyOn( + platformAuthInteractionClient as any, + "acquireTokensFromCache" + ) + // First call rejects (simulates cache miss), second call resolves (simulates WAM success) + .mockRejectedValueOnce(new Error("No cached tokens")) + .mockResolvedValue(mockAuthResult); + + await platformAuthInteractionClient.acquireToken({ + scopes: ["User.Read"], + account: mockAuthResult.account, + correlationId: RANDOM_TEST_GUID, + }); + + // Get the latest mock measurement object + const mockMeasurement = + startMeasurementSpy.mock.results[ + startMeasurementSpy.mock.results.length - 1 + ].value; + + // Verify isNativeBroker is set during successful completion + expect(mockMeasurement.end).toHaveBeenCalledWith( + expect.objectContaining({ + success: true, + isNativeBroker: true, + }) + ); + + // Verify the measurement was started with correct correlation ID + expect(startMeasurementSpy).toHaveBeenCalledWith( + expect.any(String), + RANDOM_TEST_GUID + ); + }); + + it("should emit isNativeBroker=true for handleRedirectPromise", async () => { + // @ts-ignore + pca.browserStorage.setInteractionInProgress(true); + // @ts-ignore + pca.browserStorage.setTemporaryCache( + "request.native", + JSON.stringify({ + scopes: ["User.Read"], + correlationId: RANDOM_TEST_GUID, + }), + true + ); + + jest.spyOn( + PlatformAuthExtensionHandler.prototype, + "sendMessage" + ).mockImplementation((): Promise => { + return Promise.resolve(MOCK_WAM_RESPONSE); + }); + + // Mock the protected handleNativeResponse method + jest.spyOn( + platformAuthInteractionClient as any, + "handleNativeResponse" + ).mockResolvedValue({ + accessToken: "mock_access_token", + idToken: "mock_id_token", + account: null, + scopes: ["User.Read"], + expiresOn: new Date(Date.now() + 3600000), + tenantId: "mock_tenant_id", + uniqueId: "mock_unique_id", + tokenType: "Bearer", + correlationId: RANDOM_TEST_GUID, + }); + + jest.spyOn( + platformAuthInteractionClient as any, + "initializeServerTelemetryManager" + ).mockReturnValue({ + clearNativeBrokerErrorCode: jest.fn(), + }); + + // Spy on performanceClient.addFields since handleRedirectPromise uses it directly + const addFieldsSpy = jest.spyOn(perfClient, "addFields"); + + await platformAuthInteractionClient.handleRedirectPromise( + perfClient, + RANDOM_TEST_GUID + ); + + // Verify that addFields was called with isNativeBroker: true + expect(addFieldsSpy).toHaveBeenCalledWith( + { isNativeBroker: true }, + RANDOM_TEST_GUID + ); + }); + + it("should use synchronized correlationId in performance measurements", async () => { + const customCorrelationId = "custom-correlation-123"; + + platformAuthInteractionClient = new PlatformAuthInteractionClient( + // @ts-ignore + pca.config, + // @ts-ignore + pca.browserStorage, + // @ts-ignore + pca.browserCrypto, + pca.getLogger(), + // @ts-ignore + pca.eventHandler, + // @ts-ignore + pca.navigationClient, + ApiId.acquireTokenRedirect, + perfClient, + wamProvider, + "nativeAccountId", + // @ts-ignore + pca.nativeInternalStorage, + customCorrelationId + ); + + jest.spyOn( + PlatformAuthExtensionHandler.prototype, + "sendMessage" + ).mockImplementation((): Promise => { + return Promise.resolve(MOCK_WAM_RESPONSE); + }); + + await platformAuthInteractionClient.acquireToken({ + scopes: ["User.Read"], + correlationId: customCorrelationId, + }); + + // Should use the synchronized correlationId, which should be the customCorrelationId + // since synchronizeCorrelationId should update this.correlationId + expect(startMeasurementSpy).toHaveBeenCalledWith( + expect.any(String), + customCorrelationId + ); + }); + + it("should emit success=false and errorCode for failed acquireToken", async () => { + // Mock sendMessage to succeed but handleNativeResponse to fail + jest.spyOn( + PlatformAuthExtensionHandler.prototype, + "sendMessage" + ).mockImplementation((): Promise => { + return Promise.resolve(MOCK_WAM_RESPONSE); + }); + + // Mock handleNativeResponse to throw an error + const authError = new AuthError("test_error", "Test error message"); + jest.spyOn( + platformAuthInteractionClient as any, + "handleNativeResponse" + ).mockRejectedValue(authError); + + try { + await platformAuthInteractionClient.acquireToken({ + scopes: ["User.Read"], + }); + } catch (error) { + // Expected to throw + } + + // Get the mock measurement object that was returned + const mockMeasurement = startMeasurementSpy.mock.results[0].value; + + // Check that measurement.end was called with success: false and errorCode + expect(mockMeasurement.end).toHaveBeenCalledWith( + expect.objectContaining({ + success: false, + errorCode: "test_error", + }) + ); + }); + + it("should emit success=false and errorCode when sendMessage fails", async () => { + // Test that measurement.end is now called when sendMessage fails (bug fix) + const nativeError = createNativeAuthError( + "ContentError", + "problem getting response from extension" + ); + + jest.spyOn( + PlatformAuthExtensionHandler.prototype, + "sendMessage" + ).mockImplementation((): Promise => { + return Promise.reject(nativeError); + }); + + try { + await platformAuthInteractionClient.acquireToken({ + scopes: ["User.Read"], + }); + } catch (error) { + // Expected to throw + expect(error).toBe(nativeError); + } + + // Get the mock measurement object that was returned + const mockMeasurement = startMeasurementSpy.mock.results[0].value; + + // After the fix, measurement.end should now be called when sendMessage fails + expect(mockMeasurement.end).toHaveBeenCalledWith( + expect.objectContaining({ + success: false, + }) + ); + }); + }); }); diff --git a/lib/msal-common/apiReview/msal-common.api.md b/lib/msal-common/apiReview/msal-common.api.md index fc65054d5e..cb11477c04 100644 --- a/lib/msal-common/apiReview/msal-common.api.md +++ b/lib/msal-common/apiReview/msal-common.api.md @@ -3425,6 +3425,10 @@ export type PerformanceEvent = { libraryVersion: string; previousLibraryVersion?: string; isNativeBroker?: boolean; + isPlatformAuthorizeRequest?: boolean; + isPlatformBrokerRequest?: boolean; + brokerErrorName?: string; + brokerErrorCode?: string; requestId?: string; cacheLookupPolicy?: number | undefined; cacheOutcome?: number; @@ -4857,32 +4861,32 @@ const X_MS_LIB_CAPABILITY_VALUE: string; // src/telemetry/performance/PerformanceEvent.ts:162:23 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag // src/telemetry/performance/PerformanceEvent.ts:162:14 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" // src/telemetry/performance/PerformanceEvent.ts:162:8 - (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration -// src/telemetry/performance/PerformanceEvent.ts:169:22 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag -// src/telemetry/performance/PerformanceEvent.ts:169:14 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" -// src/telemetry/performance/PerformanceEvent.ts:169:8 - (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration -// src/telemetry/performance/PerformanceEvent.ts:176:22 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag -// src/telemetry/performance/PerformanceEvent.ts:176:14 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" -// src/telemetry/performance/PerformanceEvent.ts:176:8 - (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration -// src/telemetry/performance/PerformanceEvent.ts:182:22 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag -// src/telemetry/performance/PerformanceEvent.ts:182:14 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" -// src/telemetry/performance/PerformanceEvent.ts:182:8 - (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration -// src/telemetry/performance/PerformanceEvent.ts:202:21 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/telemetry/performance/PerformanceEvent.ts:189:22 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/telemetry/performance/PerformanceEvent.ts:189:14 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/telemetry/performance/PerformanceEvent.ts:189:8 - (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration +// src/telemetry/performance/PerformanceEvent.ts:196:22 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/telemetry/performance/PerformanceEvent.ts:196:14 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/telemetry/performance/PerformanceEvent.ts:196:8 - (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration +// src/telemetry/performance/PerformanceEvent.ts:202:22 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag // src/telemetry/performance/PerformanceEvent.ts:202:14 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" // src/telemetry/performance/PerformanceEvent.ts:202:8 - (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration -// src/telemetry/performance/PerformanceEvent.ts:210:21 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag -// src/telemetry/performance/PerformanceEvent.ts:210:14 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" -// src/telemetry/performance/PerformanceEvent.ts:210:8 - (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration -// src/telemetry/performance/PerformanceEvent.ts:219:21 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag -// src/telemetry/performance/PerformanceEvent.ts:219:14 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" -// src/telemetry/performance/PerformanceEvent.ts:219:8 - (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration -// src/telemetry/performance/PerformanceEvent.ts:227:22 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag -// src/telemetry/performance/PerformanceEvent.ts:227:14 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" -// src/telemetry/performance/PerformanceEvent.ts:227:8 - (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration -// src/telemetry/performance/PerformanceEvent.ts:234:22 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag -// src/telemetry/performance/PerformanceEvent.ts:234:14 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" -// src/telemetry/performance/PerformanceEvent.ts:234:8 - (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration -// src/telemetry/performance/PerformanceEvent.ts:308:21 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag -// src/telemetry/performance/PerformanceEvent.ts:308:14 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" -// src/telemetry/performance/PerformanceEvent.ts:308:8 - (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration +// src/telemetry/performance/PerformanceEvent.ts:222:21 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/telemetry/performance/PerformanceEvent.ts:222:14 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/telemetry/performance/PerformanceEvent.ts:222:8 - (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration +// src/telemetry/performance/PerformanceEvent.ts:230:21 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/telemetry/performance/PerformanceEvent.ts:230:14 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/telemetry/performance/PerformanceEvent.ts:230:8 - (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration +// src/telemetry/performance/PerformanceEvent.ts:239:21 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/telemetry/performance/PerformanceEvent.ts:239:14 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/telemetry/performance/PerformanceEvent.ts:239:8 - (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration +// src/telemetry/performance/PerformanceEvent.ts:247:22 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/telemetry/performance/PerformanceEvent.ts:247:14 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/telemetry/performance/PerformanceEvent.ts:247:8 - (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration +// src/telemetry/performance/PerformanceEvent.ts:254:22 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/telemetry/performance/PerformanceEvent.ts:254:14 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/telemetry/performance/PerformanceEvent.ts:254:8 - (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration +// src/telemetry/performance/PerformanceEvent.ts:328:21 - (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag +// src/telemetry/performance/PerformanceEvent.ts:328:14 - (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" +// src/telemetry/performance/PerformanceEvent.ts:328:8 - (tsdoc-undefined-tag) The TSDoc tag "@type" is not defined in this configuration ``` diff --git a/lib/msal-common/src/telemetry/performance/PerformanceEvent.ts b/lib/msal-common/src/telemetry/performance/PerformanceEvent.ts index 64e8ae83ef..1e778a8708 100644 --- a/lib/msal-common/src/telemetry/performance/PerformanceEvent.ts +++ b/lib/msal-common/src/telemetry/performance/PerformanceEvent.ts @@ -163,6 +163,26 @@ export type PerformanceEvent = { */ isNativeBroker?: boolean; + /** + * Platform-specific fields, when calling STS and/or broker for token requests + */ + /** + * Set to true when making authorize requests to STS with native broker parameter + */ + isPlatformAuthorizeRequest?: boolean; + /** + * Set to true when making requests directly to the platform broker + */ + isPlatformBrokerRequest?: boolean; + /** + * Error name when broker requests fail + */ + brokerErrorName?: string; + /** + * Error code when broker requests fail + */ + brokerErrorCode?: string; + /** * Request ID returned from the response *