@azure/msal-browser
Version:
Microsoft Authentication Library for js
200 lines (184 loc) • 6.89 kB
text/typescript
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import {
AccountInfo,
Constants,
InProgressPerformanceEvent,
IPerformanceClient,
Logger,
PerformanceClient,
PerformanceEvent,
SubMeasurement,
} from "@azure/msal-common/browser";
import { Configuration } from "../config/Configuration.js";
import { name, version } from "../packageMetadata.js";
import { BrowserCacheLocation } from "../utils/BrowserConstants.js";
import * as BrowserCrypto from "../crypto/BrowserCrypto.js";
import { BROWSER_PERF_ENABLED_KEY } from "../cache/CacheKeys.js";
import { getNetworkInfo } from "../utils/MsalFrameStatsUtils.js";
/**
* Returns browser performance measurement module if session flag is enabled. Returns undefined otherwise.
*/
function getPerfMeasurementModule() {
let sessionStorage: Storage | undefined;
try {
sessionStorage = window[BrowserCacheLocation.SessionStorage];
const perfEnabled = sessionStorage?.getItem(BROWSER_PERF_ENABLED_KEY);
if (Number(perfEnabled) === 1) {
return import("./BrowserPerformanceMeasurement.js");
}
// Mute errors if it's a non-browser environment or cookies are blocked.
} catch (e) {}
return undefined;
}
/**
* Returns boolean, indicating whether browser supports window.performance.now() function.
*/
function supportsBrowserPerformanceNow(): boolean {
return (
typeof window !== "undefined" &&
typeof window.performance !== "undefined" &&
typeof window.performance.now === "function"
);
}
/**
* Returns event duration in milliseconds using window performance API if available. Returns undefined otherwise.
* @param startTime {DOMHighResTimeStamp | undefined}
* @returns {number | undefined}
*/
function getPerfDurationMs(
startTime: DOMHighResTimeStamp | undefined
): number | undefined {
if (!startTime || !supportsBrowserPerformanceNow()) {
return undefined;
}
return Math.round(window.performance.now() - startTime);
}
export class BrowserPerformanceClient
extends PerformanceClient
implements IPerformanceClient
{
constructor(configuration: Configuration, intFields?: Set<string>) {
super(
configuration.auth.clientId,
configuration.auth.authority || `${Constants.DEFAULT_AUTHORITY}`,
new Logger(
configuration.system?.loggerOptions || {},
name,
version
),
name,
version,
configuration.telemetry?.application || {
appName: "",
appVersion: "",
},
intFields
);
}
generateId(): string {
return BrowserCrypto.createNewGuid();
}
private getPageVisibility(): string | null {
return document.visibilityState?.toString() || null;
}
private getOnlineStatus(): boolean | null {
return typeof navigator !== "undefined" ? navigator.onLine : null;
}
private deleteIncompleteSubMeasurements(
inProgressEvent: InProgressPerformanceEvent
): void {
void getPerfMeasurementModule()?.then((module) => {
const rootEvent = this.eventsByCorrelationId.get(
inProgressEvent.event.correlationId
);
const isRootEvent =
rootEvent &&
rootEvent.eventId === inProgressEvent.event.eventId;
const incompleteMeasurements: SubMeasurement[] = [];
if (isRootEvent && rootEvent?.incompleteSubMeasurements) {
rootEvent.incompleteSubMeasurements.forEach(
(subMeasurement: SubMeasurement) => {
incompleteMeasurements.push({ ...subMeasurement });
}
);
}
// Clean up remaining marks for incomplete sub-measurements
module.BrowserPerformanceMeasurement.flushMeasurements(
inProgressEvent.event.correlationId,
incompleteMeasurements
);
});
}
/**
* Starts measuring performance for a given operation. Returns a function that should be used to end the measurement.
* Also captures browser page visibilityState.
*
* @param {PerformanceEvents} measureName
* @param {?string} [correlationId]
* @returns {((event?: Partial<PerformanceEvent>) => PerformanceEvent| null)}
*/
startMeasurement(
measureName: string,
correlationId?: string
): InProgressPerformanceEvent {
// Capture page visibilityState and then invoke start/end measurement
const startPageVisibility = this.getPageVisibility();
const startOnlineStatus = this.getOnlineStatus();
const inProgressEvent = super.startMeasurement(
measureName,
correlationId
);
const startTime: number | undefined = supportsBrowserPerformanceNow()
? window.performance.now()
: undefined;
const browserMeasurement = getPerfMeasurementModule()?.then(
(module) => {
return new module.BrowserPerformanceMeasurement(
measureName,
inProgressEvent.event.correlationId
);
}
);
void browserMeasurement?.then((measurement) =>
measurement.startMeasurement()
);
return {
...inProgressEvent,
end: (
event?: Partial<PerformanceEvent>,
error?: unknown,
account?: AccountInfo
): PerformanceEvent | null => {
const networkInfo = getNetworkInfo();
const res = inProgressEvent.end(
{
...event,
startPageVisibility,
startOnlineStatus,
endPageVisibility: this.getPageVisibility(),
durationMs: getPerfDurationMs(startTime),
networkEffectiveType: networkInfo.effectiveType,
networkRtt: networkInfo.rtt,
},
error,
account
);
void browserMeasurement?.then((measurement) =>
measurement.endMeasurement()
);
this.deleteIncompleteSubMeasurements(inProgressEvent);
return res;
},
discard: () => {
inProgressEvent.discard();
void browserMeasurement?.then((measurement) =>
measurement.flushMeasurement()
);
this.deleteIncompleteSubMeasurements(inProgressEvent);
},
};
}
}