Skip to content

Commit eb893e5

Browse files
tnorlingCopilot
andauthored
Track online/offline status change (#8410)
This pull request enhances the MSAL browser library's ability to track and measure browser state changes related to both page visibility and online/offline status. It introduces new telemetry fields, refactors event listener management for improved maintainability, and updates tests and API documentation accordingly. **Browser state change tracking and telemetry improvements:** * Added tracking for online/offline status changes alongside existing visibility change tracking in `StandardController`, including incrementing a new `onlineStatusChangeCount` field in performance measurements. [[1]](diffhunk://#diff-d8bde128bad64cb357b230e9a738968be4d62b4e3d7c78e889f3e38d4689f73fL295-R309) [[2]](diffhunk://#diff-d8bde128bad64cb357b230e9a738968be4d62b4e3d7c78e889f3e38d4689f73fL981-R1023) [[3]](diffhunk://#diff-d8bde128bad64cb357b230e9a738968be4d62b4e3d7c78e889f3e38d4689f73fR1139-R1142) [[4]](diffhunk://#diff-d8bde128bad64cb357b230e9a738968be4d62b4e3d7c78e889f3e38d4689f73fR1392-R1394) [[5]](diffhunk://#diff-d8bde128bad64cb357b230e9a738968be4d62b4e3d7c78e889f3e38d4689f73fL2306-R2333) * Refactored event listener management by introducing `addStateChangeListeners` and `removeStateChangeListeners` methods to handle both visibility and online/offline events, improving code maintainability and reducing duplication. [[1]](diffhunk://#diff-d8bde128bad64cb357b230e9a738968be4d62b4e3d7c78e889f3e38d4689f73fL981-R1023) [[2]](diffhunk://#diff-d8bde128bad64cb357b230e9a738968be4d62b4e3d7c78e889f3e38d4689f73fR1139-R1142) [[3]](diffhunk://#diff-d8bde128bad64cb357b230e9a738968be4d62b4e3d7c78e889f3e38d4689f73fL1190-R1217) [[4]](diffhunk://#diff-d8bde128bad64cb357b230e9a738968be4d62b4e3d7c78e889f3e38d4689f73fR1392-R1394) [[5]](diffhunk://#diff-d8bde128bad64cb357b230e9a738968be4d62b4e3d7c78e889f3e38d4689f73fL1394-R1418) [[6]](diffhunk://#diff-d8bde128bad64cb357b230e9a738968be4d62b4e3d7c78e889f3e38d4689f73fL2291-R2314) [[7]](diffhunk://#diff-d8bde128bad64cb357b230e9a738968be4d62b4e3d7c78e889f3e38d4689f73fL2457-R2479) **Performance measurement enhancements:** * Updated `BrowserPerformanceClient` to capture the browser's online status at the start of a measurement and include it in the telemetry event as `startOnlineStatus`. [[1]](diffhunk://#diff-7bf3f5ead1c2093dd056541885a7effa8b79d493ebfac484c2bce9e132cb42e6R103-R106) [[2]](diffhunk://#diff-7bf3f5ead1c2093dd056541885a7effa8b79d493ebfac484c2bce9e132cb42e6R147) [[3]](diffhunk://#diff-7bf3f5ead1c2093dd056541885a7effa8b79d493ebfac484c2bce9e132cb42e6R179) **Testing and API documentation:** * Added a new unit test to verify that online status is correctly captured at the start of a performance measurement. * Extended the public API type `PerformanceEvent` to include the new `startOnlineStatus` and `onlineStatusChangeCount` fields. [[1]](diffhunk://#diff-09087b913ebbfa828e5f36b7476a400328e0a7131db84f622cc5f6994759a117R3441) [[2]](diffhunk://#diff-09087b913ebbfa828e5f36b7476a400328e0a7131db84f622cc5f6994759a117R3462) **Minor API cleanup:** * Updated the signature of `generateAppMetadataKey` to use a single input parameter for clarity. --------- Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: tnorling <5307810+tnorling@users.noreply.github.com>
1 parent d221f4e commit eb893e5

File tree

8 files changed

+878
-2495
lines changed

8 files changed

+878
-2495
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "patch",
3+
"comment": "Track online/offline status change [#8410](https://github.com/AzureAD/microsoft-authentication-library-for-js/pull/8410)",
4+
"packageName": "@azure/msal-browser",
5+
"email": "thomas.norling@microsoft.com",
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": "patch",
3+
"comment": "Track online/offline status change [#8410](https://github.com/AzureAD/microsoft-authentication-library-for-js/pull/8410)",
4+
"packageName": "@azure/msal-common",
5+
"email": "thomas.norling@microsoft.com",
6+
"dependentChangeType": "patch"
7+
}

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

Lines changed: 74 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -276,11 +276,8 @@ export class StandardController implements IController {
276276
this.activeSilentTokenRequests = new Map();
277277

278278
// Register listener functions
279-
this.trackPageVisibility = this.trackPageVisibility.bind(this);
280-
281-
// Register listener functions
282-
this.trackPageVisibilityWithMeasurement =
283-
this.trackPageVisibilityWithMeasurement.bind(this);
279+
this.trackStateChangeWithMeasurement =
280+
this.trackStateChangeWithMeasurement.bind(this);
284281
}
285282

286283
static async createController(
@@ -292,15 +289,30 @@ export class StandardController implements IController {
292289
return controller;
293290
}
294291

295-
private trackPageVisibility(correlationId?: string): void {
292+
private trackStateChange(correlationId: string, event: Event): void {
296293
if (!correlationId) {
297294
return;
298295
}
299-
this.logger.info("Perf: Visibility change detected");
300-
this.performanceClient.incrementFields(
301-
{ visibilityChangeCount: 1 },
302-
correlationId
303-
);
296+
297+
if (event.type === "visibilitychange") {
298+
this.logger.info("Perf: Visibility change detected");
299+
this.performanceClient.incrementFields(
300+
{ visibilityChangeCount: 1 },
301+
correlationId
302+
);
303+
} else if (event.type === "online") {
304+
this.logger.info("Perf: Online status change detected");
305+
this.performanceClient.incrementFields(
306+
{ onlineStatusChangeCount: 1 },
307+
correlationId
308+
);
309+
} else if (event.type === "offline") {
310+
this.logger.info("Perf: Offline status change detected");
311+
this.performanceClient.incrementFields(
312+
{ onlineStatusChangeCount: 1 },
313+
correlationId
314+
);
315+
}
304316
}
305317

306318
/**
@@ -978,21 +990,51 @@ export class StandardController implements IController {
978990
});
979991
}
980992

981-
private trackPageVisibilityWithMeasurement(): void {
993+
private trackStateChangeWithMeasurement(event: Event): void {
982994
const measurement =
983995
this.ssoSilentMeasurement ||
984996
this.acquireTokenByCodeAsyncMeasurement;
985997
if (!measurement) {
986998
return;
987999
}
9881000

989-
this.logger.info(
990-
"Perf: Visibility change detected in ",
991-
measurement.event.name
992-
);
993-
measurement.increment({
994-
visibilityChangeCount: 1,
995-
});
1001+
if (event.type === "visibilitychange") {
1002+
this.logger.info(
1003+
"Perf: Visibility change detected in ",
1004+
measurement.event.name
1005+
);
1006+
measurement.increment({
1007+
visibilityChangeCount: 1,
1008+
});
1009+
} else if (event.type === "online") {
1010+
this.logger.info(
1011+
"Perf: Online status change detected in ",
1012+
measurement.event.name
1013+
);
1014+
measurement.increment({
1015+
onlineStatusChangeCount: 1,
1016+
});
1017+
} else if (event.type === "offline") {
1018+
this.logger.info(
1019+
"Perf: Offline status change detected in ",
1020+
measurement.event.name
1021+
);
1022+
measurement.increment({
1023+
onlineStatusChangeCount: 1,
1024+
});
1025+
}
1026+
}
1027+
1028+
private addStateChangeListeners(listener: (event: Event) => void): void {
1029+
document.addEventListener("visibilitychange", listener);
1030+
window.addEventListener("online", listener);
1031+
window.addEventListener("offline", listener);
1032+
}
1033+
1034+
private removeStateChangeListeners(listener: (event: Event) => void): void {
1035+
document.removeEventListener("visibilitychange", listener);
1036+
window.removeEventListener("online", listener);
1037+
window.removeEventListener("offline", listener);
9961038
}
9971039

9981040
/**
@@ -1108,12 +1150,10 @@ export class StandardController implements IController {
11081150
);
11091151
this.ssoSilentMeasurement?.increment({
11101152
visibilityChangeCount: 0,
1153+
onlineStatusChangeCount: 0,
11111154
});
11121155

1113-
document.addEventListener(
1114-
"visibilitychange",
1115-
this.trackPageVisibilityWithMeasurement
1116-
);
1156+
this.addStateChangeListeners(this.trackStateChangeWithMeasurement);
11171157
this.logger.verbose("ssoSilent called", correlationId);
11181158
this.eventHandler.emitEvent(
11191159
EventType.SSO_SILENT_START,
@@ -1187,9 +1227,8 @@ export class StandardController implements IController {
11871227
throw e;
11881228
})
11891229
.finally(() => {
1190-
document.removeEventListener(
1191-
"visibilitychange",
1192-
this.trackPageVisibilityWithMeasurement
1230+
this.removeStateChangeListeners(
1231+
this.trackStateChangeWithMeasurement
11931232
);
11941233
});
11951234
}
@@ -1364,11 +1403,9 @@ export class StandardController implements IController {
13641403
);
13651404
this.acquireTokenByCodeAsyncMeasurement?.increment({
13661405
visibilityChangeCount: 0,
1406+
onlineStatusChangeCount: 0,
13671407
});
1368-
document.addEventListener(
1369-
"visibilitychange",
1370-
this.trackPageVisibilityWithMeasurement
1371-
);
1408+
this.addStateChangeListeners(this.trackStateChangeWithMeasurement);
13721409
const silentAuthCodeClient = this.createSilentAuthCodeClient(
13731410
request.correlationId
13741411
);
@@ -1391,9 +1428,8 @@ export class StandardController implements IController {
13911428
throw tokenRenewalError;
13921429
})
13931430
.finally(() => {
1394-
document.removeEventListener(
1395-
"visibilitychange",
1396-
this.trackPageVisibilityWithMeasurement
1431+
this.removeStateChangeListeners(
1432+
this.trackStateChangeWithMeasurement
13971433
);
13981434
});
13991435
return silentTokenResult;
@@ -2288,8 +2324,8 @@ export class StandardController implements IController {
22882324
request: SilentRequest & { correlationId: string },
22892325
account: AccountInfo
22902326
): Promise<AuthenticationResult> {
2291-
const trackPageVisibility = () =>
2292-
this.trackPageVisibility(request.correlationId);
2327+
const trackStateChange = (event: Event) =>
2328+
this.trackStateChange(request.correlationId, event);
22932329
this.performanceClient.addQueueMeasurement(
22942330
PerformanceEvents.AcquireTokenSilentAsync,
22952331
request.correlationId
@@ -2303,12 +2339,12 @@ export class StandardController implements IController {
23032339

23042340
if (request.correlationId) {
23052341
this.performanceClient.incrementFields(
2306-
{ visibilityChangeCount: 0 },
2342+
{ visibilityChangeCount: 0, onlineStatusChangeCount: 0 },
23072343
request.correlationId
23082344
);
23092345
}
23102346

2311-
document.addEventListener("visibilitychange", trackPageVisibility);
2347+
this.addStateChangeListeners(trackStateChange);
23122348

23132349
const silentRequest = await invokeAsync(
23142350
initializeSilentRequest,
@@ -2454,10 +2490,7 @@ export class StandardController implements IController {
24542490
throw tokenRenewalError;
24552491
})
24562492
.finally(() => {
2457-
document.removeEventListener(
2458-
"visibilitychange",
2459-
trackPageVisibility
2460-
);
2493+
this.removeStateChangeListeners(trackStateChange);
24612494
});
24622495
}
24632496

lib/msal-browser/src/telemetry/BrowserPerformanceClient.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ export class BrowserPerformanceClient
101101
return document.visibilityState?.toString() || null;
102102
}
103103

104+
private getOnlineStatus(): boolean | null {
105+
return typeof navigator !== "undefined" ? navigator.onLine : null;
106+
}
107+
104108
private deleteIncompleteSubMeasurements(
105109
inProgressEvent: InProgressPerformanceEvent
106110
): void {
@@ -141,6 +145,7 @@ export class BrowserPerformanceClient
141145
): InProgressPerformanceEvent {
142146
// Capture page visibilityState and then invoke start/end measurement
143147
const startPageVisibility = this.getPageVisibility();
148+
const startOnlineStatus = this.getOnlineStatus();
144149
const inProgressEvent = super.startMeasurement(
145150
measureName,
146151
correlationId
@@ -173,6 +178,7 @@ export class BrowserPerformanceClient
173178
{
174179
...event,
175180
startPageVisibility,
181+
startOnlineStatus,
176182
endPageVisibility: this.getPageVisibility(),
177183
durationMs: getPerfDurationMs(startTime),
178184
networkEffectiveType: networkInfo.effectiveType,

lib/msal-browser/test/telemetry/BrowserPerformanceClient.spec.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,27 @@ describe("BrowserPerformanceClient.ts", () => {
9292
expect(result?.startPageVisibility).toBe("visible");
9393
expect(result?.endPageVisibility).toBe("visible");
9494
});
95+
96+
it("captures online status at measurement start", () => {
97+
jest.spyOn(
98+
Object.getPrototypeOf(navigator),
99+
"onLine",
100+
"get"
101+
).mockReturnValue(true);
102+
103+
const browserPerfClient = new BrowserPerformanceClient(
104+
testAppConfig
105+
);
106+
107+
const measurement = browserPerfClient.startMeasurement(
108+
PerformanceEvents.AcquireTokenSilent,
109+
correlationId
110+
);
111+
112+
const result = measurement.end();
113+
114+
expect(result?.startOnlineStatus).toBe(true);
115+
});
95116
});
96117

97118
describe("setPreQueueTime", () => {

0 commit comments

Comments
 (0)