Skip to content

Commit 8a8575e

Browse files
Instrument timed out and cancelled redirects (#7989)
- Instrument timed out and cancelled redirects
1 parent 61101e9 commit 8a8575e

File tree

6 files changed

+170
-33
lines changed

6 files changed

+170
-33
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "minor",
3+
"comment": "Instrument timed out and cancelled redirects #7989",
4+
"packageName": "@azure/msal-browser",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "minor",
3+
"comment": "Instrument timed out and cancelled redirects #7989",
4+
"packageName": "@azure/msal-common",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}

lib/msal-browser/src/controllers/StandardController.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -641,11 +641,11 @@ export class StandardController implements IController {
641641
typeof configOnRedirectNavigateCb === "function"
642642
? configOnRedirectNavigateCb(url)
643643
: undefined;
644-
if (navigate !== false) {
645-
atrMeasurement.end({ success: true });
646-
} else {
647-
atrMeasurement.discard();
648-
}
644+
atrMeasurement.add({
645+
navigateCallbackResult: navigate !== false,
646+
});
647+
atrMeasurement.event =
648+
atrMeasurement.end({ success: true }) || atrMeasurement.event;
649649
return navigate;
650650
};
651651

@@ -721,7 +721,21 @@ export class StandardController implements IController {
721721
return await result;
722722
} catch (e) {
723723
this.browserStorage.resetRequestCache();
724-
atrMeasurement.end({ success: false }, e);
724+
/*
725+
* Pre-redirect event completes before navigation occurs.
726+
* Timed out navigation needs to be instrumented separately as a post-redirect event.
727+
*/
728+
if (atrMeasurement.event.status === 2) {
729+
this.performanceClient
730+
.startMeasurement(
731+
BrowserRootPerformanceEvents.AcquireTokenRedirect,
732+
correlationId
733+
)
734+
.end({ success: false }, e);
735+
} else {
736+
atrMeasurement.end({ success: false }, e);
737+
}
738+
725739
if (isLoggedIn) {
726740
this.eventHandler.emitEvent(
727741
EventType.ACQUIRE_TOKEN_FAILURE,

lib/msal-browser/test/app/PublicClientApplication.spec.ts

Lines changed: 133 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2161,6 +2161,107 @@ describe("PublicClientApplication.ts Class Unit Tests", () => {
21612161
});
21622162
});
21632163

2164+
it("emits pre-redirect telemetry event when onRedirectNavigate callback is set in configuration", (done) => {
2165+
const onRedirectNavigate = (url: string) => {
2166+
expect(url).toBeDefined();
2167+
};
2168+
2169+
pca = new PublicClientApplication({
2170+
auth: {
2171+
clientId: TEST_CONFIG.MSAL_CLIENT_ID,
2172+
onRedirectNavigate,
2173+
},
2174+
telemetry: {
2175+
client: new BrowserPerformanceClient(testAppConfig),
2176+
application: {
2177+
appName: TEST_CONFIG.applicationName,
2178+
appVersion: TEST_CONFIG.applicationVersion,
2179+
},
2180+
},
2181+
});
2182+
pca = (pca as any).controller;
2183+
2184+
pca.initialize().then(() => {
2185+
const callbackId = pca.addPerformanceCallback((events) => {
2186+
expect(events[0].success).toBe(true);
2187+
expect(events[0].name).toBe(
2188+
BrowserRootPerformanceEvents.AcquireTokenPreRedirect
2189+
);
2190+
expect(events[0].navigateCallbackResult).toBeTruthy();
2191+
pca.removePerformanceCallback(callbackId);
2192+
done();
2193+
});
2194+
2195+
jest.spyOn(
2196+
NavigationClient.prototype,
2197+
"navigateExternal"
2198+
).mockImplementation(() => Promise.resolve(true));
2199+
2200+
jest.spyOn(
2201+
PkceGenerator,
2202+
"generatePkceCodes"
2203+
).mockResolvedValue({
2204+
challenge: TEST_CONFIG.TEST_CHALLENGE,
2205+
verifier: TEST_CONFIG.TEST_VERIFIER,
2206+
});
2207+
const loginRequest: RedirectRequest = {
2208+
redirectUri: TEST_URIS.TEST_REDIR_URI,
2209+
scopes: ["user.read", "openid", "profile"],
2210+
state: TEST_STATE_VALUES.USER_STATE,
2211+
};
2212+
pca.acquireTokenRedirect(loginRequest);
2213+
});
2214+
});
2215+
2216+
it("instruments pre-redirect telemetry event when navigation times out", (done) => {
2217+
let eventCounter = 0;
2218+
const callbackId = pca.addPerformanceCallback((events) => {
2219+
if (
2220+
events[0].name ===
2221+
BrowserRootPerformanceEvents.AcquireTokenPreRedirect
2222+
) {
2223+
expect(events[0].success).toBe(true);
2224+
eventCounter++;
2225+
}
2226+
2227+
if (
2228+
events[0].name ===
2229+
BrowserRootPerformanceEvents.AcquireTokenRedirect
2230+
) {
2231+
expect(events[0].success).toBe(false);
2232+
expect(events[0].errorCode).toEqual("timed_out");
2233+
eventCounter++;
2234+
}
2235+
2236+
if (eventCounter === 2) {
2237+
pca.removePerformanceCallback(callbackId);
2238+
done();
2239+
}
2240+
});
2241+
2242+
jest.spyOn(
2243+
NavigationClient.prototype,
2244+
"navigateExternal"
2245+
).mockRejectedValue(
2246+
createBrowserAuthError(
2247+
BrowserAuthErrorCodes.timedOut,
2248+
"failed_to_redirect"
2249+
)
2250+
);
2251+
2252+
jest.spyOn(PkceGenerator, "generatePkceCodes").mockResolvedValue({
2253+
challenge: TEST_CONFIG.TEST_CHALLENGE,
2254+
verifier: TEST_CONFIG.TEST_VERIFIER,
2255+
});
2256+
const loginRequest: RedirectRequest = {
2257+
redirectUri: TEST_URIS.TEST_REDIR_URI,
2258+
scopes: ["user.read", "openid", "profile"],
2259+
state: TEST_STATE_VALUES.USER_STATE,
2260+
};
2261+
2262+
pca.acquireTokenRedirect(loginRequest).catch((e) => {});
2263+
});
2264+
21642265
it("emits pre-redirect telemetry event when onRedirectNavigate callback is not set", (done) => {
21652266
const callbackId = pca.addPerformanceCallback((events) => {
21662267
expect(events[0].success).toBe(true);
@@ -2234,7 +2335,7 @@ describe("PublicClientApplication.ts Class Unit Tests", () => {
22342335
await pca.acquireTokenRedirect(loginRequest);
22352336
});
22362337

2237-
it("discards pre-redirect telemetry event when onRedirectNavigate callback returns false", async () => {
2338+
it("discards pre-redirect telemetry event when onRedirectNavigate callback returns false", (done) => {
22382339
const onRedirectNavigate = (url: string) => {
22392340
return false;
22402341
};
@@ -2253,38 +2354,43 @@ describe("PublicClientApplication.ts Class Unit Tests", () => {
22532354
},
22542355
});
22552356
pca = (pca as any).controller;
2256-
await pca.initialize();
2357+
pca.initialize().then(() => {
2358+
const callbackId = pca.addPerformanceCallback((events) => {
2359+
expect(events[0].success).toBe(true);
2360+
expect(events[0].name).toBe(
2361+
BrowserRootPerformanceEvents.AcquireTokenPreRedirect
2362+
);
2363+
expect(events[0].navigateCallbackResult).toBeFalsy();
2364+
pca.removePerformanceCallback(callbackId);
2365+
done();
2366+
});
22572367

2258-
const callbackId = pca.addPerformanceCallback((events) => {
2259-
expect(events[0].success).toBe(true);
2260-
expect(events[0].name).toBe(
2261-
BrowserRootPerformanceEvents.AcquireTokenPreRedirect
2368+
const measurementDiscardSpy = jest.spyOn(
2369+
PerformanceClient.prototype,
2370+
"discardMeasurements"
22622371
);
2263-
pca.removePerformanceCallback(callbackId);
2264-
});
22652372

2266-
const measurementDiscardSpy = jest.spyOn(
2267-
PerformanceClient.prototype,
2268-
"discardMeasurements"
2269-
);
2373+
jest.spyOn(
2374+
NavigationClient.prototype,
2375+
"navigateExternal"
2376+
).mockResolvedValue(true);
22702377

2271-
jest.spyOn(
2272-
NavigationClient.prototype,
2273-
"navigateExternal"
2274-
).mockResolvedValue(true);
2378+
jest.spyOn(
2379+
PkceGenerator,
2380+
"generatePkceCodes"
2381+
).mockResolvedValue({
2382+
challenge: TEST_CONFIG.TEST_CHALLENGE,
2383+
verifier: TEST_CONFIG.TEST_VERIFIER,
2384+
});
2385+
const loginRequest: RedirectRequest = {
2386+
redirectUri: TEST_URIS.TEST_REDIR_URI,
2387+
scopes: ["user.read", "openid", "profile"],
2388+
state: TEST_STATE_VALUES.USER_STATE,
2389+
};
22752390

2276-
jest.spyOn(PkceGenerator, "generatePkceCodes").mockResolvedValue({
2277-
challenge: TEST_CONFIG.TEST_CHALLENGE,
2278-
verifier: TEST_CONFIG.TEST_VERIFIER,
2391+
pca.acquireTokenRedirect(loginRequest);
2392+
expect(measurementDiscardSpy).toHaveBeenCalledTimes(0);
22792393
});
2280-
const loginRequest: RedirectRequest = {
2281-
redirectUri: TEST_URIS.TEST_REDIR_URI,
2282-
scopes: ["user.read", "openid", "profile"],
2283-
state: TEST_STATE_VALUES.USER_STATE,
2284-
};
2285-
2286-
await pca.acquireTokenRedirect(loginRequest);
2287-
expect(measurementDiscardSpy).toHaveBeenCalledTimes(1);
22882394
});
22892395

22902396
it("instruments initialization error", (done) => {

lib/msal-common/apiReview/msal-common.api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3406,6 +3406,7 @@ export type PerformanceEvent = {
34063406
usePreGeneratedPkce?: boolean;
34073407
msalInstanceCount?: number;
34083408
sameClientIdInstanceCount?: number;
3409+
navigateCallbackResult?: boolean;
34093410
};
34103411

34113412
declare namespace PerformanceEvents {

lib/msal-common/src/telemetry/performance/PerformanceEvent.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,8 @@ export type PerformanceEvent = {
329329
msalInstanceCount?: number;
330330
// Number of MSAL JS instances using the same client id in the frame
331331
sameClientIdInstanceCount?: number;
332+
333+
navigateCallbackResult?: boolean;
332334
};
333335

334336
export type PerformanceEventContext = {

0 commit comments

Comments
 (0)