/*
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
* Licensed under the MIT License.
|
*/
|
|
import { Logger } from "@azure/msal-common/browser";
|
import { InteractionType } from "../utils/BrowserConstants.js";
|
import {
|
EventCallbackFunction,
|
EventError,
|
EventMessage,
|
EventPayload,
|
} from "./EventMessage.js";
|
import { EventType } from "./EventType.js";
|
import { createGuid } from "../utils/BrowserUtils.js";
|
|
const BROADCAST_CHANNEL_NAME = "msal.broadcast.event";
|
|
export class EventHandler {
|
// Callback for subscribing to events
|
private eventCallbacks: Map<
|
string,
|
[EventCallbackFunction, Array<EventType>]
|
>;
|
private logger: Logger;
|
private broadcastChannel?: BroadcastChannel;
|
|
constructor(logger?: Logger) {
|
this.eventCallbacks = new Map();
|
this.logger = logger || new Logger({});
|
if (typeof BroadcastChannel !== "undefined") {
|
this.broadcastChannel = new BroadcastChannel(
|
BROADCAST_CHANNEL_NAME
|
);
|
}
|
this.invokeCrossTabCallbacks = this.invokeCrossTabCallbacks.bind(this);
|
}
|
|
/**
|
* Adds event callbacks to array
|
* @param callback - callback to be invoked when an event is raised
|
* @param eventTypes - list of events that this callback will be invoked for, if not provided callback will be invoked for all events
|
* @param callbackId - Identifier for the callback, used to locate and remove the callback when no longer required
|
*/
|
addEventCallback(
|
callback: EventCallbackFunction,
|
eventTypes?: Array<EventType>,
|
callbackId?: string
|
): string | null {
|
if (typeof window !== "undefined") {
|
const id = callbackId || createGuid();
|
if (this.eventCallbacks.has(id)) {
|
this.logger.error(
|
`Event callback with id: '${id}' is already registered. Please provide a unique id or remove the existing callback and try again.`,
|
""
|
);
|
return null;
|
}
|
this.eventCallbacks.set(id, [callback, eventTypes || []]);
|
this.logger.verbose(
|
`Event callback registered with id: '${id}'`,
|
""
|
);
|
|
return id;
|
}
|
|
return null;
|
}
|
|
/**
|
* Removes callback with provided id from callback array
|
* @param callbackId
|
*/
|
removeEventCallback(callbackId: string): void {
|
this.eventCallbacks.delete(callbackId);
|
this.logger.verbose(`Event callback '${callbackId}' removed.`, "");
|
}
|
|
/**
|
* Emits events by calling callback with event message
|
* @param eventType
|
* @param interactionType
|
* @param payload
|
* @param error
|
*/
|
emitEvent(
|
eventType: EventType,
|
correlationId: string,
|
interactionType?: InteractionType,
|
payload?: EventPayload,
|
error?: EventError
|
): void {
|
const message: EventMessage = {
|
eventType: eventType,
|
interactionType: interactionType || null,
|
payload: payload || null,
|
error: error || null,
|
correlationId: correlationId,
|
timestamp: Date.now(),
|
};
|
|
switch (eventType) {
|
case EventType.LOGIN_SUCCESS:
|
case EventType.LOGOUT_SUCCESS:
|
case EventType.ACTIVE_ACCOUNT_CHANGED:
|
// Send event to other open tabs / MSAL instances on same domain
|
this.broadcastChannel?.postMessage(message);
|
}
|
// Emit event to callbacks registered in this instance
|
this.invokeCallbacks(message);
|
}
|
|
/**
|
* Invoke registered callbacks
|
* @param message
|
*/
|
private invokeCallbacks(message: EventMessage): void {
|
this.eventCallbacks.forEach(
|
(
|
[callback, eventTypes]: [
|
EventCallbackFunction,
|
Array<EventType>
|
],
|
callbackId: string
|
) => {
|
if (
|
eventTypes.length === 0 ||
|
eventTypes.includes(message.eventType)
|
) {
|
this.logger.verbose(
|
`Emitting event to callback '${callbackId}': '${message.eventType}'`,
|
""
|
);
|
callback.apply(null, [message]);
|
}
|
}
|
);
|
}
|
|
/**
|
* Wrapper around invokeCallbacks to handle broadcast events received from other tabs/instances
|
* @param event
|
*/
|
private invokeCrossTabCallbacks(event: MessageEvent): void {
|
const message = event.data as EventMessage;
|
this.invokeCallbacks(message);
|
}
|
|
/**
|
* Listen for events broadcasted from other tabs/instances
|
*/
|
subscribeCrossTab(): void {
|
this.broadcastChannel?.addEventListener(
|
"message",
|
this.invokeCrossTabCallbacks
|
);
|
}
|
|
/**
|
* Unsubscribe from broadcast events
|
*/
|
unsubscribeCrossTab(): void {
|
this.broadcastChannel?.removeEventListener(
|
"message",
|
this.invokeCrossTabCallbacks
|
);
|
}
|
}
|