@azure/msal-browser
Version:
Microsoft Authentication Library for js
186 lines (169 loc) • 6.08 kB
text/typescript
/*
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
import {
ICrypto,
Logger,
AccountEntity,
CacheManager,
PersistentCacheKeys,
} from "@azure/msal-common";
import { InteractionType } from "../utils/BrowserConstants";
import {
EventCallbackFunction,
EventError,
EventMessage,
EventPayload,
} from "./EventMessage";
import { EventType } from "./EventType";
import { createNewGuid } from "../crypto/BrowserCrypto";
export class EventHandler {
// Callback for subscribing to events
private eventCallbacks: Map<string, EventCallbackFunction>;
private logger: Logger;
private browserCrypto: ICrypto;
private listeningToStorageEvents: boolean;
constructor(logger: Logger, browserCrypto: ICrypto) {
this.eventCallbacks = new Map();
this.logger = logger;
this.browserCrypto = browserCrypto;
this.listeningToStorageEvents = false;
this.handleAccountCacheChange =
this.handleAccountCacheChange.bind(this);
}
/**
* Adds event callbacks to array
* @param callback
*/
addEventCallback(callback: EventCallbackFunction): string | null {
if (typeof window !== "undefined") {
const callbackId = createNewGuid();
this.eventCallbacks.set(callbackId, callback);
this.logger.verbose(
`Event callback registered with id: ${callbackId}`
);
return callbackId;
}
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.`);
}
/**
* Adds event listener that emits an event when a user account is added or removed from localstorage in a different browser tab or window
*/
enableAccountStorageEvents(): void {
if (typeof window === "undefined") {
return;
}
if (!this.listeningToStorageEvents) {
this.logger.verbose("Adding account storage listener.");
this.listeningToStorageEvents = true;
window.addEventListener("storage", this.handleAccountCacheChange);
} else {
this.logger.verbose("Account storage listener already registered.");
}
}
/**
* Removes event listener that emits an event when a user account is added or removed from localstorage in a different browser tab or window
*/
disableAccountStorageEvents(): void {
if (typeof window === "undefined") {
return;
}
if (this.listeningToStorageEvents) {
this.logger.verbose("Removing account storage listener.");
window.removeEventListener(
"storage",
this.handleAccountCacheChange
);
this.listeningToStorageEvents = false;
} else {
this.logger.verbose("No account storage listener registered.");
}
}
/**
* Emits events by calling callback with event message
* @param eventType
* @param interactionType
* @param payload
* @param error
*/
emitEvent(
eventType: EventType,
interactionType?: InteractionType,
payload?: EventPayload,
error?: EventError
): void {
if (typeof window !== "undefined") {
const message: EventMessage = {
eventType: eventType,
interactionType: interactionType || null,
payload: payload || null,
error: error || null,
timestamp: Date.now(),
};
this.logger.info(`Emitting event: ${eventType}`);
this.eventCallbacks.forEach(
(callback: EventCallbackFunction, callbackId: string) => {
this.logger.verbose(
`Emitting event to callback ${callbackId}: ${eventType}`
);
callback.apply(null, [message]);
}
);
}
}
/**
* Emit account added/removed events when cached accounts are changed in a different tab or frame
*/
private handleAccountCacheChange(e: StorageEvent): void {
try {
// Handle active account filter change
if (e.key?.includes(PersistentCacheKeys.ACTIVE_ACCOUNT_FILTERS)) {
// This event has no payload, it only signals cross-tab app instances that the results of calling getActiveAccount() will have changed
this.emitEvent(EventType.ACTIVE_ACCOUNT_CHANGED);
}
// Handle account object change
const cacheValue = e.newValue || e.oldValue;
if (!cacheValue) {
return;
}
const parsedValue = JSON.parse(cacheValue);
if (
typeof parsedValue !== "object" ||
!AccountEntity.isAccountEntity(parsedValue)
) {
return;
}
const accountEntity = CacheManager.toObject<AccountEntity>(
new AccountEntity(),
parsedValue
);
const accountInfo = accountEntity.getAccountInfo();
if (!e.oldValue && e.newValue) {
this.logger.info(
"Account was added to cache in a different window"
);
this.emitEvent(EventType.ACCOUNT_ADDED, undefined, accountInfo);
} else if (!e.newValue && e.oldValue) {
this.logger.info(
"Account was removed from cache in a different window"
);
this.emitEvent(
EventType.ACCOUNT_REMOVED,
undefined,
accountInfo
);
}
} catch (e) {
return;
}
}
}