msal
Version:
Microsoft Authentication Library for js
132 lines (109 loc) • 4.78 kB
text/typescript
import TelemetryEvent from "./TelemetryEvent";
import {
CompletedEvents,
EventCount,
EventCountByCorrelationId,
InProgressEvents,
TelemetryConfig,
TelemetryPlatform,
TelemetryEmitter
} from "./TelemetryTypes";
import DefaultEvent from "./DefaultEvent";
// for use in cache events
const MSAL_CACHE_EVENT_VALUE_PREFIX = "msal.token";
const MSAL_CACHE_EVENT_NAME = "msal.cache_event";
const createEventKey = (event: TelemetryEvent): string => (
`${event.telemetryCorrelationId}-${event.eventId}-${event.eventName}`
);
export default class TelemetryManager {
// correlation Id to list of events
private completedEvents: CompletedEvents = {};
// event key to event
private inProgressEvents: InProgressEvents = {};
// correlation id to map of eventname to count
private eventCountByCorrelationId: EventCountByCorrelationId = {};
//Implement after API EVENT
private onlySendFailureTelemetry: boolean = false;
private telemetryPlatform: TelemetryPlatform;
private clientId: string;
private telemetryEmitter: TelemetryEmitter;
constructor(config: TelemetryConfig, telemetryEmitter: TelemetryEmitter) {
// TODO THROW if bad options
this.telemetryPlatform = config.platform;
this.clientId = config.clientId;
this.onlySendFailureTelemetry = config.onlySendFailureTelemetry;
// TODO, when i get to wiring this through, think about what it means if
// a developer does not implement telem at all, we still instrument, but telemetryEmitter can be
// optional?
this.telemetryEmitter = telemetryEmitter;
}
startEvent(event: TelemetryEvent) {
if (!this.telemetryEmitter) {
return;
}
const eventKey = createEventKey(event);
this.inProgressEvents[eventKey] = event;
}
stopEvent(event: TelemetryEvent) {
const eventKey = createEventKey(event);
if (!this.telemetryEmitter || !this.inProgressEvents[eventKey]) {
return;
}
event.stop();
this.incrementEventCount(event);
const completedEvents = this.completedEvents[event.telemetryCorrelationId];
this.completedEvents[event.telemetryCorrelationId] = [...(completedEvents || []), event];
delete this.inProgressEvents[eventKey];
}
flush(correlationId: string): void {
// If there is only unfinished events should this still return them?
if (!this.telemetryEmitter || !this.completedEvents[correlationId]) {
return;
}
const orphanedEvents = this.getOrphanedEvents(correlationId);
orphanedEvents.forEach(event => this.incrementEventCount(event));
const eventsToFlush: Array<TelemetryEvent> = [
...this.completedEvents[correlationId],
...orphanedEvents
];
delete this.completedEvents[correlationId];
const eventCountsToFlush: EventCount = this.eventCountByCorrelationId[correlationId];
delete this.eventCountByCorrelationId[correlationId];
// TODO add funcitonality for onlyFlushFailures after implementing api event? ??
if (!eventsToFlush || !eventsToFlush.length) {
return;
}
const defaultEvent: DefaultEvent = new DefaultEvent(
this.telemetryPlatform,
correlationId,
this.clientId,
eventCountsToFlush
);
const eventsWithDefaultEvent = [ ...eventsToFlush, defaultEvent ];
this.telemetryEmitter(eventsWithDefaultEvent.map(e => e.get()));
}
private incrementEventCount(event: TelemetryEvent): void {
// TODO, name cache event different?
// if type is cache event, change name
const eventName = event.eventName;
const eventCount = this.eventCountByCorrelationId[event.telemetryCorrelationId];
if (!eventCount) {
this.eventCountByCorrelationId[event.telemetryCorrelationId] = {
[eventName]: 1
};
} else {
eventCount[eventName] = eventCount[eventName] ? eventCount[eventName] + 1 : 1;
}
}
private getOrphanedEvents(correlationId: string): Array<TelemetryEvent> {
return Object.keys(this.inProgressEvents)
.reduce((memo, eventKey) => {
if (eventKey.indexOf(correlationId) !== -1) {
const event = this.inProgressEvents[eventKey];
delete this.inProgressEvents[eventKey];
return [...memo, event];
}
return memo;
}, []);
}
}