import { Identity } from '@schibsted/account-sdk-browser'; import { eventTarget, localStorage } from '@schibsted/niche-utils'; const { getEventTarget } = eventTarget; const { getFromLocalStorage, saveToLocalStorage } = localStorage; export type SchibstedAccountEnv = 'PRE' | 'PRO' | 'PRO_DK' | 'PRO_FI' | 'PRO_NO'; export type User = { accountUrl: string; displayName: string; familyName: string; givenName: string; photo: string; sig: string; tokenExpirationTime: number; userId: number; uuid: string; }; declare global { interface Window { Identity: Identity; } } const WEEK_AGO = new Date(); WEEK_AGO.setDate(WEEK_AGO.getDate() - 7); const THREE_DAYS_AGO = new Date(); THREE_DAYS_AGO.setDate(THREE_DAYS_AGO.getDate() - 3); function getStateString(state: Record) { return btoa(JSON.stringify(state)); } const getRedirectUri = () => window.location.pathname + window.location.search; class SchibstedAccount { get eventTarget() { return this._eventTarget; } get identity() { return this._identity; } get initialized() { return this._initialized; } get user() { return this._user; } private _eventTarget: EventTarget = getEventTarget(); private _identity: Identity | undefined; private _initialized = false; private _user: User | null | undefined; private set initialized(newState: boolean) { this._initialized = newState; this.eventTarget.dispatchEvent(new CustomEvent('initialized', { detail: this.user })); } private set user(newUser: User | null | undefined) { const oldUser = this._user; this._user = newUser; if (newUser !== oldUser) { this.eventTarget.dispatchEvent(new CustomEvent('user', { detail: newUser })); } } checkAndRefreshSchibstedAccountSession = async (): Promise => { if (!this.user || Date.now() >= this.user.tokenExpirationTime) { try { const newSession = await this._identity?.hasSession(); if (!newSession || 'error' in newSession) { throw new Error('No session'); } const newUser = { accountUrl: this._identity?.accountUrl(), displayName: newSession.displayName, familyName: newSession.familyName, givenName: newSession.givenName, photo: newSession.photo, sig: newSession.sig, tokenExpirationTime: (newSession.serverTime + newSession.expiresIn) * 1000, userId: newSession.userId, uuid: newSession.uuid, } as User; this.user = newUser; if (!this.initialized) { this.initialized = true; } } catch (e) { if (this.user !== null) { this.user = null; } if (!this.initialized) { this.initialized = true; } } } return this.user; }; initialize = async ({ autoLogin, autoLoginTimestampLimit = THREE_DAYS_AGO.getTime(), clientId, env, redirectUri, sessionDomain, simplifiedLogin = true, simplifiedLoginTimestampLimit = WEEK_AGO.getTime(), }: { autoLogin?: boolean; autoLoginTimestampLimit?: number; clientId: string; env: SchibstedAccountEnv; redirectUri: string; sessionDomain: string; simplifiedLogin?: boolean; simplifiedLoginTimestampLimit?: number; }) => { if (this._initialized) { throw new Error('Already initialized'); } this._identity = new Identity({ clientId, env, redirectUri, sessionDomain, }); // Core Comments will call window.Identity.logout() directly so to provide redirectUri we need to override the logout function window.Identity = this._identity; const originalLogoutFunction = this._identity.logout.bind(this._identity); const logoutFunction = (redirectUri: string = window.location.href) => { originalLogoutFunction(redirectUri); }; window.Identity.logout = logoutFunction; const isLoggedIn = Boolean(await this.checkAndRefreshSchibstedAccountSession()); if (simplifiedLogin && !isLoggedIn) { await this.runSimplifiedLogin(simplifiedLoginTimestampLimit); } if (!isLoggedIn && autoLogin) { await this.runAutoLogin(autoLoginTimestampLimit); } return this.user; }; login = (state?: Record) => { this._identity?.login({ state: getStateString({ redirectUri: getRedirectUri(), ...state }) }); }; logout = (redirectUri?: string) => { saveToLocalStorage('autoLogin', new Date().getTime().toString()); this._identity?.logout(redirectUri); }; mockUser = (user: User) => { if (this.initialized) { throw new Error('Cannot mock user after initialization'); } this._user = user; }; waitForInitialization = () => new Promise((resolve) => { if (this.initialized) { resolve(this.user); } else { this.eventTarget.addEventListener('initialized', () => { resolve(this.user); }); } }); private runAutoLogin = async (timestampLimit: number) => { try { const widgetShownTimestamp = getFromLocalStorage('autoLogin'); if (!widgetShownTimestamp || Number.parseInt(widgetShownTimestamp, 10) < timestampLimit) { const context = await this._identity?.getUserContextData(); const identifier = context?.identifier; if (identifier) { saveToLocalStorage('autoLogin', new Date().getTime().toString()); const url = new URL(window.location.href); url.searchParams.set('utm_source', 'autologin'); await this._identity?.login({ loginHint: identifier, state: getStateString({ redirectUri: url.toString() }), }); } } } catch (err) { // ignore } }; private runSimplifiedLogin = async (timestampLimit: number) => { try { const widgetShownTimestamp = getFromLocalStorage('simplifiedLogin'); if (!widgetShownTimestamp || Number.parseInt(widgetShownTimestamp, 10) < timestampLimit) { await this._identity?.showSimplifiedLoginWidget({ state: getStateString({ redirectUri: getRedirectUri() }), }); saveToLocalStorage('simplifiedLogin', new Date().getTime().toString()); } } catch (err) { // ignore } }; } export default SchibstedAccount;