From 8130884783e88e6923b6e6c2361050334874b4f7 Mon Sep 17 00:00:00 2001 From: Konstantin Date: Wed, 17 Jul 2024 16:53:23 -0400 Subject: [PATCH] Track MSAL SKU for broker flows (#7182) - Track MSAL SKU for broker flows - Enable server telemetry platform fields propagation - Propagate broker error to server telemetry --- ...-61afe86b-a787-4083-96f1-b8c531de0bee.json | 7 + ...-9eff86b6-1b02-4445-94e1-56a77a96990d.json | 7 + .../NativeInteractionClient.ts | 174 ++++++--- .../test/app/PublicClientApplication.spec.ts | 14 +- .../NativeInteractionClient.spec.ts | 347 ++++++++++++++++++ lib/msal-common/apiReview/msal-common.api.md | 15 +- .../cache/entities/ServerTelemetryEntity.ts | 1 + .../src/constants/AADServerParamKeys.ts | 1 + .../server/ServerTelemetryManager.ts | 29 +- .../telemetry/ServerTelemetryManager.spec.ts | 23 ++ 10 files changed, 560 insertions(+), 58 deletions(-) create mode 100644 change/@azure-msal-browser-61afe86b-a787-4083-96f1-b8c531de0bee.json create mode 100644 change/@azure-msal-common-9eff86b6-1b02-4445-94e1-56a77a96990d.json diff --git a/change/@azure-msal-browser-61afe86b-a787-4083-96f1-b8c531de0bee.json b/change/@azure-msal-browser-61afe86b-a787-4083-96f1-b8c531de0bee.json new file mode 100644 index 0000000000..3dd4a9d267 --- /dev/null +++ b/change/@azure-msal-browser-61afe86b-a787-4083-96f1-b8c531de0bee.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Track MSAL SKU for broker flows #7182", + "packageName": "@azure/msal-browser", + "email": "kshabelko@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@azure-msal-common-9eff86b6-1b02-4445-94e1-56a77a96990d.json b/change/@azure-msal-common-9eff86b6-1b02-4445-94e1-56a77a96990d.json new file mode 100644 index 0000000000..a8d17780ca --- /dev/null +++ b/change/@azure-msal-common-9eff86b6-1b02-4445-94e1-56a77a96990d.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Track MSAL SKU for broker flows #7182", + "packageName": "@azure/msal-common", + "email": "kshabelko@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/lib/msal-browser/src/interaction_client/NativeInteractionClient.ts b/lib/msal-browser/src/interaction_client/NativeInteractionClient.ts index ad3c48911e..c6ae34a7b3 100644 --- a/lib/msal-browser/src/interaction_client/NativeInteractionClient.ts +++ b/lib/msal-browser/src/interaction_client/NativeInteractionClient.ts @@ -36,6 +36,7 @@ import { CacheHelpers, buildAccountToCache, InProgressPerformanceEvent, + ServerTelemetryManager, } from "@azure/msal-common"; import { BaseInteractionClient } from "./BaseInteractionClient"; import { BrowserConfiguration } from "../config/Configuration"; @@ -50,6 +51,7 @@ import { ApiId, TemporaryCacheKeys, NativeConstants, + BrowserConstants, } from "../utils/BrowserConstants"; import { NativeExtensionRequestBody, @@ -72,18 +74,49 @@ import { import { SilentCacheClient } from "./SilentCacheClient"; import { AuthenticationResult } from "../response/AuthenticationResult"; import { base64Decode } from "../encode/Base64Decode"; +import { version } from "../packageMetadata"; const BrokerServerParamKeys = { BROKER_CLIENT_ID: "brk_client_id", BROKER_REDIRECT_URI: "brk_redirect_uri", }; +/** + * Provides MSAL and browser extension SKUs + * @param messageHandler {NativeMessageHandler} + */ +function getSKUs(messageHandler: NativeMessageHandler): string { + const groupSeparator = ","; + const valueSeparator = "|"; + const skus = Array.from({ length: 4 }, () => valueSeparator); + // Report MSAL SKU + skus[0] = [BrowserConstants.MSAL_SKU, version].join(valueSeparator); + + // Report extension SKU + const extensionName = + messageHandler.getExtensionId() === + NativeConstants.PREFERRED_EXTENSION_ID + ? "chrome" + : messageHandler.getExtensionId()?.length + ? "unknown" + : undefined; + + if (extensionName) { + skus[2] = [extensionName, messageHandler.getExtensionVersion()].join( + valueSeparator + ); + } + return skus.join(groupSeparator); +} + export class NativeInteractionClient extends BaseInteractionClient { protected apiId: ApiId; protected accountId: string; protected nativeMessageHandler: NativeMessageHandler; protected silentCacheClient: SilentCacheClient; protected nativeStorageManager: BrowserCacheManager; + protected skus: string; + protected serverTelemetryManager: ServerTelemetryManager; constructor( config: BrowserConfiguration, @@ -125,6 +158,22 @@ export class NativeInteractionClient extends BaseInteractionClient { provider, correlationId ); + this.serverTelemetryManager = this.initializeServerTelemetryManager( + this.apiId + ); + this.skus = getSKUs(this.nativeMessageHandler); + } + + /** + * Adds SKUs to request extra query parameters + * @param request {NativeTokenRequest} + * @private + */ + private addRequestSKUs(request: NativeTokenRequest) { + request.extraParameters = { + ...request.extraParameters, + [AADServerParamKeys.X_CLIENT_EXTRA_SKU]: this.skus, + }; } /** @@ -147,64 +196,73 @@ export class NativeInteractionClient extends BaseInteractionClient { ); const reqTimestamp = TimeUtils.nowSeconds(); - // initialize native request - const nativeRequest = await this.initializeNativeRequest(request); - - // check if the tokens can be retrieved from internal cache try { - const result = await this.acquireTokensFromCache( - this.accountId, - nativeRequest - ); - nativeATMeasurement.end({ - success: true, - isNativeBroker: false, // Should be true only when the result is coming directly from the broker - fromCache: true, - }); - return result; - } catch (e) { - // continue with a native call for any and all errors - this.logger.info( - "MSAL internal Cache does not contain tokens, proceed to make a native call" - ); - } + // initialize native request + const nativeRequest = await this.initializeNativeRequest(request); - const { ...nativeTokenRequest } = nativeRequest; - - // fall back to native calls - const messageBody: NativeExtensionRequestBody = { - method: NativeExtensionMethod.GetToken, - request: nativeTokenRequest, - }; - - const response: object = await this.nativeMessageHandler.sendMessage( - messageBody - ); - const validatedResponse: NativeResponse = - this.validateNativeResponse(response); - - return this.handleNativeResponse( - validatedResponse, - nativeRequest, - reqTimestamp - ) - .then((result: AuthenticationResult) => { + // check if the tokens can be retrieved from internal cache + try { + const result = await this.acquireTokensFromCache( + this.accountId, + nativeRequest + ); nativeATMeasurement.end({ success: true, - isNativeBroker: true, - requestId: result.requestId, + isNativeBroker: false, // Should be true only when the result is coming directly from the broker + fromCache: true, }); return result; - }) - .catch((error: AuthError) => { - nativeATMeasurement.end({ - success: false, - errorCode: error.errorCode, - subErrorCode: error.subError, - isNativeBroker: true, + } catch (e) { + // continue with a native call for any and all errors + this.logger.info( + "MSAL internal Cache does not contain tokens, proceed to make a native call" + ); + } + + const { ...nativeTokenRequest } = nativeRequest; + + // fall back to native calls + const messageBody: NativeExtensionRequestBody = { + method: NativeExtensionMethod.GetToken, + request: nativeTokenRequest, + }; + + const response: object = + await this.nativeMessageHandler.sendMessage(messageBody); + const validatedResponse: NativeResponse = + this.validateNativeResponse(response); + + return await this.handleNativeResponse( + validatedResponse, + nativeRequest, + reqTimestamp + ) + .then((result: AuthenticationResult) => { + nativeATMeasurement.end({ + success: true, + isNativeBroker: true, + requestId: result.requestId, + }); + this.serverTelemetryManager.clearNativeBrokerErrorCode(); + return result; + }) + .catch((error: AuthError) => { + nativeATMeasurement.end({ + success: false, + errorCode: error.errorCode, + subErrorCode: error.subError, + isNativeBroker: true, + }); + throw error; }); - throw error; - }); + } catch (e) { + if (e instanceof NativeAuthError) { + this.serverTelemetryManager.setNativeBrokerErrorCode( + e.errorCode + ); + } + throw e; + } } /** @@ -307,8 +365,13 @@ export class NativeInteractionClient extends BaseInteractionClient { this.validateNativeResponse(response); } catch (e) { // Only throw fatal errors here to allow application to fallback to regular redirect. Otherwise proceed and the error will be thrown in handleRedirectPromise - if (e instanceof NativeAuthError && isFatalNativeAuthError(e)) { - throw e; + if (e instanceof NativeAuthError) { + this.serverTelemetryManager.setNativeBrokerErrorCode( + e.errorCode + ); + if (isFatalNativeAuthError(e)) { + throw e; + } } } this.browserStorage.setTemporaryCache( @@ -399,7 +462,9 @@ export class NativeInteractionClient extends BaseInteractionClient { reqTimestamp ); this.browserStorage.setInteractionInProgress(false); - return await result; + const res = await result; + this.serverTelemetryManager.clearNativeBrokerErrorCode(); + return res; } catch (e) { this.browserStorage.setInteractionInProgress(false); throw e; @@ -980,6 +1045,7 @@ export class NativeInteractionClient extends BaseInteractionClient { // SPAs require whole string to be passed to broker validatedRequest.reqCnf = reqCnfData; } + this.addRequestSKUs(validatedRequest); return validatedRequest; } diff --git a/lib/msal-browser/test/app/PublicClientApplication.spec.ts b/lib/msal-browser/test/app/PublicClientApplication.spec.ts index 6adccb8f79..2bf8b219f7 100644 --- a/lib/msal-browser/test/app/PublicClientApplication.spec.ts +++ b/lib/msal-browser/test/app/PublicClientApplication.spec.ts @@ -3855,14 +3855,24 @@ describe("PublicClientApplication.ts Class Unit Tests", () => { .callsFake(async () => { return testTokenResponse; }); - const response = await pca.acquireTokenSilent({ + const silentRequest = { scopes: ["User.Read"], account: testAccount, - }); + }; + const response = await pca.acquireTokenSilent(silentRequest); expect(response).toEqual(testTokenResponse); expect(nativeAcquireTokenSpy.calledOnce).toBeTruthy(); expect(silentSpy.calledOnce).toBeTruthy(); + expect( + silentSpy.calledWith({ + ...silentRequest, + tokenQueryParameters: { + "x-client-current-telemetry": + "||broker_error=ContentError", + }, + }) + ); }); it("throws error if native broker call fails due to non-fatal error", async () => { diff --git a/lib/msal-browser/test/interaction_client/NativeInteractionClient.spec.ts b/lib/msal-browser/test/interaction_client/NativeInteractionClient.spec.ts index 0b227b8dd0..38f52a20bd 100644 --- a/lib/msal-browser/test/interaction_client/NativeInteractionClient.spec.ts +++ b/lib/msal-browser/test/interaction_client/NativeInteractionClient.spec.ts @@ -45,6 +45,9 @@ import { getDefaultPerformanceClient } from "../utils/TelemetryUtils"; import { BrowserCacheManager } from "../../src/cache/BrowserCacheManager"; import { BrowserPerformanceClient, IPublicClientApplication } from "../../src"; import { buildAccountFromIdTokenClaims, buildIdToken } from "msal-test-utils"; +import { version } from "../../src/packageMetadata"; +import { BrowserConstants } from "../../src/utils/BrowserConstants"; +import * as NativeStatusCodes from "../../src/broker/nativeBroker/NativeStatusCodes"; const MOCK_WAM_RESPONSE = { access_token: TEST_TOKENS.ACCESS_TOKEN, @@ -530,6 +533,216 @@ describe("NativeInteractionClient Tests", () => { expect(response.tokenType).toEqual(AuthenticationScheme.BEARER); }); + it("adds MSAL.js SKU to request extra query parameters", async () => { + sinon + .stub(NativeMessageHandler.prototype, "sendMessage") + .callsFake((message): Promise => { + expect( + message.request?.extraParameters!["x-client-xtra-sku"] + ).toEqual(`${BrowserConstants.MSAL_SKU}|${version},|,|,|`); + return Promise.resolve(MOCK_WAM_RESPONSE); + }); + await nativeInteractionClient.acquireToken({ + scopes: ["User.Read"], + }); + }); + + it("adds MSAL.js and Chrome extension SKUs to request extra query parameters", async () => { + sinon + .stub(NativeMessageHandler.prototype, "sendMessage") + .callsFake((message): Promise => { + expect( + message.request?.extraParameters!["x-client-xtra-sku"] + ).toEqual( + `${BrowserConstants.MSAL_SKU}|${version},|,chrome|1.0.2,|` + ); + return Promise.resolve(MOCK_WAM_RESPONSE); + }); + + sinon + .stub(NativeMessageHandler.prototype, "getExtensionId") + .returns("ppnbnpeolgkicgegkbkbjmhlideopiji"); + sinon + .stub(NativeMessageHandler.prototype, "getExtensionVersion") + .returns("1.0.2"); + + nativeInteractionClient = new NativeInteractionClient( + // @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, + RANDOM_TEST_GUID + ); + + await nativeInteractionClient.acquireToken({ + scopes: ["User.Read"], + }); + }); + + it("adds MSAL.js and unknown extension SKUs to request extra query parameters", async () => { + sinon + .stub(NativeMessageHandler.prototype, "sendMessage") + .callsFake((message): Promise => { + expect( + message.request?.extraParameters!["x-client-xtra-sku"] + ).toEqual( + `${BrowserConstants.MSAL_SKU}|${version},|,unknown|2.3.4,|` + ); + return Promise.resolve(MOCK_WAM_RESPONSE); + }); + + sinon + .stub(NativeMessageHandler.prototype, "getExtensionId") + .returns("random_extension_id"); + sinon + .stub(NativeMessageHandler.prototype, "getExtensionVersion") + .returns("2.3.4"); + + nativeInteractionClient = new NativeInteractionClient( + // @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, + RANDOM_TEST_GUID + ); + + await nativeInteractionClient.acquireToken({ + scopes: ["User.Read"], + }); + }); + + it("does not set native broker error to server telemetry", async () => { + sinon + .stub(NativeMessageHandler.prototype, "sendMessage") + .callsFake((message): Promise => { + return Promise.resolve(MOCK_WAM_RESPONSE); + }); + + await nativeInteractionClient.acquireToken({ + scopes: ["User.Read"], + }); + expect( + JSON.parse( + window.sessionStorage.getItem( + `server-telemetry-${TEST_CONFIG.MSAL_CLIENT_ID}` + ) || "" + ) + ).toEqual({ + cacheHits: 0, + errors: [], + failedRequests: [], + }); + }); + + it("sets native broker error to server telemetry", async () => { + sinon + .stub(NativeMessageHandler.prototype, "sendMessage") + .callsFake((message): Promise => { + return Promise.reject( + new NativeAuthError("test_native_error_code") + ); + }); + try { + await nativeInteractionClient.acquireToken({ + scopes: ["User.Read"], + }); + } catch (e) {} + expect( + JSON.parse( + window.sessionStorage.getItem( + `server-telemetry-${TEST_CONFIG.MSAL_CLIENT_ID}` + ) || "" + ) + ).toEqual({ + cacheHits: 0, + errors: [], + failedRequests: [], + nativeBrokerErrorCode: "test_native_error_code", + }); + }); + + it("resets native broker error in server telemetry", async () => { + const sendMessageStub = sinon.stub( + NativeMessageHandler.prototype, + "sendMessage" + ); + sendMessageStub + .onFirstCall() + .callsFake((message): Promise => { + return Promise.reject( + new NativeAuthError( + "test_native_error_code", + "test_error_desc", + { status: NativeStatusCodes.PERSISTENT_ERROR } + ) + ); + }); + sendMessageStub + .onSecondCall() + .callsFake((message): Promise => { + return Promise.resolve(MOCK_WAM_RESPONSE); + }); + + try { + await nativeInteractionClient.acquireToken({ + scopes: ["User.Read"], + }); + } catch (e) {} + expect( + JSON.parse( + window.sessionStorage.getItem( + `server-telemetry-${TEST_CONFIG.MSAL_CLIENT_ID}` + ) || "" + ) + ).toEqual({ + cacheHits: 0, + errors: [], + failedRequests: [], + nativeBrokerErrorCode: "test_native_error_code", + }); + + await nativeInteractionClient.acquireToken({ + scopes: ["User.Read"], + }); + expect( + JSON.parse( + window.sessionStorage.getItem( + `server-telemetry-${TEST_CONFIG.MSAL_CLIENT_ID}` + ) || "" + ) + ).toEqual({ + cacheHits: 0, + errors: [], + failedRequests: [], + }); + }); + describe("storeInCache tests", () => { //here @@ -686,6 +899,140 @@ describe("NativeInteractionClient Tests", () => { done(); }); }); + + it("adds MSAL.js SKU to request extra query parameters", (done) => { + sinon + .stub(NavigationClient.prototype, "navigateExternal") + .callsFake((url: string) => { + expect(url).toBe(window.location.href); + done(); + return Promise.resolve(true); + }); + sinon + .stub(NativeMessageHandler.prototype, "sendMessage") + .callsFake((message): Promise => { + expect( + message.request?.extraParameters!["x-client-xtra-sku"] + ).toEqual(`${BrowserConstants.MSAL_SKU}|${version},|,|,|`); + return Promise.resolve(MOCK_WAM_RESPONSE); + }); + nativeInteractionClient.acquireTokenRedirect( + { + scopes: ["User.Read"], + }, + perfMeasurement + ); + }); + + it("sets native broker error to server telemetry", (done) => { + sinon + .stub(NavigationClient.prototype, "navigateExternal") + .callsFake((url: string) => { + expect(url).toBe(window.location.href); + expect( + JSON.parse( + window.sessionStorage.getItem( + `server-telemetry-${TEST_CONFIG.MSAL_CLIENT_ID}` + ) || "" + ) + ).toHaveProperty( + "nativeBrokerErrorCode", + "test_native_error_code" + ); + done(); + return Promise.resolve(true); + }); + sinon + .stub(NativeMessageHandler.prototype, "sendMessage") + .callsFake((message): Promise => { + return Promise.reject( + new NativeAuthError("test_native_error_code") + ); + }); + nativeInteractionClient.acquireTokenRedirect( + { + scopes: ["User.Read"], + }, + perfMeasurement + ); + }); + + it("resets native broker error in server telemetry", async () => { + sinon + .stub(NavigationClient.prototype, "navigateExternal") + .callsFake((url: string) => { + return Promise.resolve(true); + }); + const sendMessageStub = sinon.stub( + NativeMessageHandler.prototype, + "sendMessage" + ); + sendMessageStub + .onFirstCall() + .callsFake((message): Promise => { + return Promise.reject( + new NativeAuthError( + "test_native_error_code", + "test_error_desc", + { status: NativeStatusCodes.PERSISTENT_ERROR } + ) + ); + }); + sendMessageStub + .onSecondCall() + .callsFake((message): Promise => { + return Promise.resolve(MOCK_WAM_RESPONSE); + }); + sendMessageStub + .onThirdCall() + .callsFake((message): Promise => { + return Promise.resolve(MOCK_WAM_RESPONSE); + }); + + try { + await nativeInteractionClient.acquireTokenRedirect( + { + scopes: ["User.Read"], + }, + perfMeasurement + ); + } catch (e) {} + + expect( + JSON.parse( + window.sessionStorage.getItem( + `server-telemetry-${TEST_CONFIG.MSAL_CLIENT_ID}` + ) || "" + ) + ).toEqual({ + cacheHits: 0, + errors: [], + failedRequests: [], + nativeBrokerErrorCode: "test_native_error_code", + }); + + await nativeInteractionClient.acquireTokenRedirect( + { + scopes: ["User.Read"], + }, + perfMeasurement + ); + // @ts-ignore + pca.browserStorage.setInteractionInProgress(true); + await nativeInteractionClient.handleRedirectPromise(); + + expect( + JSON.parse( + window.sessionStorage.getItem( + `server-telemetry-${TEST_CONFIG.MSAL_CLIENT_ID}` + ) || "" + ) + ).toEqual({ + cacheHits: 0, + errors: [], + failedRequests: [], + }); + }); }); describe("handleRedirectPromise tests", () => { diff --git a/lib/msal-common/apiReview/msal-common.api.md b/lib/msal-common/apiReview/msal-common.api.md index 40b071fd43..d27e4de2da 100644 --- a/lib/msal-common/apiReview/msal-common.api.md +++ b/lib/msal-common/apiReview/msal-common.api.md @@ -72,7 +72,8 @@ declare namespace AADServerParamKeys { LOGOUT_HINT, SID, LOGIN_HINT, - DOMAIN_HINT + DOMAIN_HINT, + X_CLIENT_EXTRA_SKU } } export { AADServerParamKeys } @@ -3643,6 +3644,7 @@ export type ServerTelemetryEntity = { failedRequests: Array; errors: string[]; cacheHits: number; + nativeBrokerErrorCode?: string; }; // Warning: (ae-internal-missing-underscore) The name "ServerTelemetryManager" should be prefixed with an underscore because the declaration is marked as @internal @@ -3652,15 +3654,21 @@ export class ServerTelemetryManager { constructor(telemetryRequest: ServerTelemetryRequest, cacheManager: CacheManager); // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen cacheFailedRequest(error: unknown): void; + // (undocumented) + clearNativeBrokerErrorCode(): void; clearTelemetryCache(): void; generateCurrentRequestHeaderValue(): string; generateLastRequestHeaderValue(): string; getLastRequests(): ServerTelemetryEntity; + // (undocumented) + getNativeBrokerErrorCode(): string | undefined; getRegionDiscoveryFields(): string; incrementCacheHits(): number; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen static maxErrorsToSend(serverTelemetryEntity: ServerTelemetryEntity): number; setCacheOutcome(cacheOutcome: CacheOutcome): void; + // (undocumented) + setNativeBrokerErrorCode(errorCode: string): void; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen updateRegionDiscoveryMetadata(regionDiscoveryMetadata: RegionDiscoveryMetadata): void; } @@ -4162,6 +4170,11 @@ const X_CLIENT_CPU = "x-client-CPU"; // @public (undocumented) const X_CLIENT_CURR_TELEM = "x-client-current-telemetry"; +// Warning: (ae-missing-release-tag) "X_CLIENT_EXTRA_SKU" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +const X_CLIENT_EXTRA_SKU = "x-client-xtra-sku"; + // Warning: (ae-missing-release-tag) "X_CLIENT_LAST_TELEM" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/lib/msal-common/src/cache/entities/ServerTelemetryEntity.ts b/lib/msal-common/src/cache/entities/ServerTelemetryEntity.ts index 1748cf13ee..783360d54c 100644 --- a/lib/msal-common/src/cache/entities/ServerTelemetryEntity.ts +++ b/lib/msal-common/src/cache/entities/ServerTelemetryEntity.ts @@ -7,4 +7,5 @@ export type ServerTelemetryEntity = { failedRequests: Array; errors: string[]; cacheHits: number; + nativeBrokerErrorCode?: string; }; diff --git a/lib/msal-common/src/constants/AADServerParamKeys.ts b/lib/msal-common/src/constants/AADServerParamKeys.ts index 5b1745a1de..e8753b92f9 100644 --- a/lib/msal-common/src/constants/AADServerParamKeys.ts +++ b/lib/msal-common/src/constants/AADServerParamKeys.ts @@ -55,3 +55,4 @@ export const LOGOUT_HINT = "logout_hint"; export const SID = "sid"; export const LOGIN_HINT = "login_hint"; export const DOMAIN_HINT = "domain_hint"; +export const X_CLIENT_EXTRA_SKU = "x-client-xtra-sku"; diff --git a/lib/msal-common/src/telemetry/server/ServerTelemetryManager.ts b/lib/msal-common/src/telemetry/server/ServerTelemetryManager.ts index 7778ddb2ab..8ad791ed04 100644 --- a/lib/msal-common/src/telemetry/server/ServerTelemetryManager.ts +++ b/lib/msal-common/src/telemetry/server/ServerTelemetryManager.ts @@ -51,7 +51,12 @@ export class ServerTelemetryManager { */ generateCurrentRequestHeaderValue(): string { const request = `${this.apiId}${SERVER_TELEM_CONSTANTS.VALUE_SEPARATOR}${this.cacheOutcome}`; - const platformFields = [this.wrapperSKU, this.wrapperVer].join( + const platformFieldsArr = [this.wrapperSKU, this.wrapperVer]; + const nativeBrokerErrorCode = this.getNativeBrokerErrorCode(); + if (nativeBrokerErrorCode?.length) { + platformFieldsArr.push(`broker_error=${nativeBrokerErrorCode}`); + } + const platformFields = platformFieldsArr.join( SERVER_TELEM_CONSTANTS.VALUE_SEPARATOR ); const regionDiscoveryFields = this.getRegionDiscoveryFields(); @@ -277,4 +282,26 @@ export class ServerTelemetryManager { setCacheOutcome(cacheOutcome: CacheOutcome): void { this.cacheOutcome = cacheOutcome; } + + setNativeBrokerErrorCode(errorCode: string): void { + const lastRequests = this.getLastRequests(); + lastRequests.nativeBrokerErrorCode = errorCode; + this.cacheManager.setServerTelemetry( + this.telemetryCacheKey, + lastRequests + ); + } + + getNativeBrokerErrorCode(): string | undefined { + return this.getLastRequests().nativeBrokerErrorCode; + } + + clearNativeBrokerErrorCode(): void { + const lastRequests = this.getLastRequests(); + delete lastRequests.nativeBrokerErrorCode; + this.cacheManager.setServerTelemetry( + this.telemetryCacheKey, + lastRequests + ); + } } diff --git a/lib/msal-common/test/telemetry/ServerTelemetryManager.spec.ts b/lib/msal-common/test/telemetry/ServerTelemetryManager.spec.ts index 76468a4162..d0d6b5694b 100644 --- a/lib/msal-common/test/telemetry/ServerTelemetryManager.spec.ts +++ b/lib/msal-common/test/telemetry/ServerTelemetryManager.spec.ts @@ -251,6 +251,29 @@ describe("ServerTelemetryManager.ts", () => { `5|${testCacheHits}|${testApiCode},${testCorrelationId}|${testError}|2,1` ); }); + + it("Adds a broker error to platform fields", () => { + const telemetryManager = new ServerTelemetryManager( + testTelemetryPayload, + testCacheManager + ); + telemetryManager.setNativeBrokerErrorCode("native_dummy_error"); + const currHeaderVal = + telemetryManager.generateCurrentRequestHeaderValue(); + expect(currHeaderVal).toEqual( + `5|${testApiCode},0,,,|,,broker_error=native_dummy_error` + ); + }); + + it("Does not add broker error code to platform fields", () => { + const telemetryManager = new ServerTelemetryManager( + testTelemetryPayload, + testCacheManager + ); + const currHeaderVal = + telemetryManager.generateCurrentRequestHeaderValue(); + expect(currHeaderVal).toEqual(`5|${testApiCode},0,,,|,`); + }); }); describe("clear telemetry cache tests", () => {