UNPKG

@azure/msal-browser

Version:
186 lines (169 loc) 6.08 kB
/* * 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; } } }