import { CognitoIdentityProviderClient } from '@aws-sdk/client-cognito-identity-provider'; import { environment } from '../environments/environment'; import { DEMO_ROLES, demoAppOrigin } from '../utils/demoEntrance'; import { discardDemoKey } from '../utils/dpopKeyStore'; import { revokeRefreshToken } from '../utils/revokeRefreshToken'; import { clearAuthHint } from '../utils/authHintCookie'; import { claimDemoTab, clearDemoTab, readDemoSession, releaseDemoTab } from './demoTab'; import { sendSignOutBeacon } from './signOutBeacon'; export const AKASHIC_WEBSITE_URL = 'https://akashic.health/'; export const DEMO_EXIT_EVENT = 'akashic-demo-exit'; const EXIT_WAIT_MS = 250; /** Leaves this tab only; the clinic is left for the ordinary lease cleanup. */ export const exitDemoToWebsite = (go: (url: string) => void = (url) => window.location.assign(url)): void => { if (window.location.origin !== demoAppOrigin()) { go(AKASHIC_WEBSITE_URL); return; } const store = window.sessionStorage; const session = readDemoSession(store); const prefix = environment.localStoragePrefix; const role = session && Object.prototype.hasOwnProperty.call(DEMO_ROLES, session.role) ? DEMO_ROLES[session.role] : undefined; const prefixes = /_(patient|staff)$/.test(prefix) ? ['patient', 'staff'].map((portal) => prefix.replace(/_(patient|staff)$/, '_' + portal)) : [prefix]; const refreshPrefix = role ? prefix.replace(/_(patient|staff)$/, '_' + (role.mount === 'staff' ? 'staff' : 'patient')) : prefix; const refresh = store.getItem(refreshPrefix + '_refreshToken'); const snapshot = { loggedInAs: store.getItem(refreshPrefix + '_loggedInAs'), accessToken: store.getItem(refreshPrefix + '_accessToken'), expiresAt: Number(store.getItem(refreshPrefix + '_tokenExpiration') ?? '0'), }; const clientId = role ? environment.demoCognitoClients?.[role.mount === 'staff' ? 'staff' : 'patient'] : undefined; const holder = session ? claimDemoTab(session.leaseId).catch(() => 'copy' as const) : Promise.resolve('copy' as const); // Retire entry and standing checks synchronously before they can refill the cleared tab. window.dispatchEvent(new Event(DEMO_EXIT_EVENT)); for (const ownedPrefix of prefixes) clearDemoTab(ownedPrefix, store); clearAuthHint(); let left = false; let ownsLease = false; let released = false; const releaseOwned = () => { if (session && ownsLease && !released) { released = true; releaseDemoTab(session.leaseId); } }; const cleanup = holder.then(async (held) => { if (!session || held !== 'held') return; ownsLease = true; try { if (left) return; if (clientId && refresh) { void revokeRefreshToken(new CognitoIdentityProviderClient({ region: environment.region }), clientId, refresh); } const beaconIssued = role ? sendSignOutBeacon(session.clinicId, snapshot) : Promise.resolve(); const keyDropped = discardDemoKey(session.tabId).catch(() => undefined); await Promise.race([ Promise.allSettled([beaconIssued, keyDropped]), new Promise((resolve) => window.setTimeout(resolve, EXIT_WAIT_MS)), ]); } finally { releaseOwned(); } }); // A slow key store or beacon cannot hold someone in the demo. const leave = () => { left = true; releaseOwned(); go(AKASHIC_WEBSITE_URL); }; void Promise.race([cleanup, new Promise((resolve) => window.setTimeout(resolve, EXIT_WAIT_MS))]) .then(leave, leave); };