@azure/msal-browser
Version:
Microsoft Authentication Library for js
290 lines (267 loc) • 9.5 kB
text/typescript
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import {
Constants,
InProgressPerformanceEvent,
IPerformanceClient,
Logger,
PerformanceClient,
PerformanceEvent,
PerformanceEvents,
PreQueueEvent,
SubMeasurement,
} from "@azure/msal-common/browser";
import { Configuration } from "../config/Configuration.js";
import { name, version } from "../packageMetadata.js";
import {
BROWSER_PERF_ENABLED_KEY,
BrowserCacheLocation,
} from "../utils/BrowserConstants.js";
import * as BrowserCrypto from "../crypto/BrowserCrypto.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>,
abbreviations?: Map<string, 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,
abbreviations
);
}
generateId(): string {
return BrowserCrypto.createNewGuid();
}
private getPageVisibility(): string | null {
return document.visibilityState?.toString() || 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 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
): PerformanceEvent | null => {
const res = inProgressEvent.end(
{
...event,
startPageVisibility,
endPageVisibility: this.getPageVisibility(),
durationMs: getPerfDurationMs(startTime),
},
error
);
void browserMeasurement?.then((measurement) =>
measurement.endMeasurement()
);
this.deleteIncompleteSubMeasurements(inProgressEvent);
return res;
},
discard: () => {
inProgressEvent.discard();
void browserMeasurement?.then((measurement) =>
measurement.flushMeasurement()
);
this.deleteIncompleteSubMeasurements(inProgressEvent);
},
};
}
/**
* Adds pre-queue time to preQueueTimeByCorrelationId map.
* @param {PerformanceEvents} eventName
* @param {?string} correlationId
* @returns
*/
setPreQueueTime(
eventName: PerformanceEvents,
correlationId?: string
): void {
if (!supportsBrowserPerformanceNow()) {
this.logger.trace(
`BrowserPerformanceClient: window performance API not available, unable to set telemetry queue time for ${eventName}`
);
return;
}
if (!correlationId) {
this.logger.trace(
`BrowserPerformanceClient: correlationId for ${eventName} not provided, unable to set telemetry queue time`
);
return;
}
const preQueueEvent: PreQueueEvent | undefined =
this.preQueueTimeByCorrelationId.get(correlationId);
/**
* Manually complete queue measurement if there is an incomplete pre-queue event.
* Incomplete pre-queue events are instrumentation bugs that should be fixed.
*/
if (preQueueEvent) {
this.logger.trace(
`BrowserPerformanceClient: Incomplete pre-queue ${preQueueEvent.name} found`,
correlationId
);
this.addQueueMeasurement(
preQueueEvent.name,
correlationId,
undefined,
true
);
}
this.preQueueTimeByCorrelationId.set(correlationId, {
name: eventName,
time: window.performance.now(),
});
}
/**
* Calculates and adds queue time measurement for given performance event.
*
* @param {PerformanceEvents} eventName
* @param {?string} correlationId
* @param {?number} queueTime
* @param {?boolean} manuallyCompleted - indicator for manually completed queue measurements
* @returns
*/
addQueueMeasurement(
eventName: string,
correlationId?: string,
queueTime?: number,
manuallyCompleted?: boolean
): void {
if (!supportsBrowserPerformanceNow()) {
this.logger.trace(
`BrowserPerformanceClient: window performance API not available, unable to add queue measurement for ${eventName}`
);
return;
}
if (!correlationId) {
this.logger.trace(
`BrowserPerformanceClient: correlationId for ${eventName} not provided, unable to add queue measurement`
);
return;
}
const preQueueTime = super.getPreQueueTime(eventName, correlationId);
if (!preQueueTime) {
return;
}
const currentTime = window.performance.now();
const resQueueTime =
queueTime || super.calculateQueuedTime(preQueueTime, currentTime);
return super.addQueueMeasurement(
eventName,
correlationId,
resQueueTime,
manuallyCompleted
);
}
}