import { CookieAttributes } from 'js-cookie' import { allowedCookieDomain, getCookie, removeCookie, setCookie } from './cookies' import { topDomain } from './top-domain' let domain: string | undefined = undefined try { domain = topDomain(new URL(window.location.href)) } catch (_) { domain = undefined } export type SessionOptions = { cookie_defaults?: CookieAttributes } export function hostName(domain: string | undefined) { domain = domain || '' try { const withProtocol = domain.startsWith('http') ? domain : `https://${domain}` const url = new URL(withProtocol) return url.hostname } catch (_) { return domain } } const MIN_TTL = 20 * 60 * 1000 // 20 minutes const MAX_TTL = 4 * 60 * 60 * 1000 // 4 hours const cookieDefaults: CookieAttributes = { expires: 1, // 1 day domain, path: '/', sameSite: 'lax' } function sessionId(): Session | undefined { const sesh = window.localStorage.getItem('ko_sid') || getCookie('ko_sid') if (sesh) { try { return JSON.parse(sesh) } catch (err) { console.warn('Failed to parse session', err) } } } export interface Session { id: string lastTouched: number } function setSession(session: Session, options?: SessionOptions) { const value = JSON.stringify(session) // do not set cookie if the domain is not at least the same top level domain if (allowedCookieDomain(domain, options?.cookie_defaults?.domain)) { setCookie('ko_sid', value, { ...cookieDefaults, ...options?.cookie_defaults }) } else { removeCookie('ko_sid', cookieDefaults) } window.localStorage.setItem('ko_sid', value) return session } function start(options?: SessionOptions): Session { const now = new Date().getTime() const session: Session = { id: now.toString(), lastTouched: now } return setSession(session, options) } function maxLength(sessionId: string) { const now = new Date().getTime() const sessionStart = parseInt(sessionId, 10) return now - sessionStart > MAX_TTL } function valid(session: Session) { const now = new Date().getTime() const delta = now - session.lastTouched return delta < MIN_TTL } function touch(sesh: Session, options?: SessionOptions) { const session: Session = { id: sesh.id, lastTouched: new Date().getTime() } return setSession(session, options) } /** * Initialize a session if one doesn't exist, or return the existing session * if it hasn't expired or exceeded the TTL. * @returns the current session */ function fetch(options?: SessionOptions): Session { const existing = sessionId() if (existing && valid(existing) && !maxLength(existing.id)) { return touch(existing, options) } return start(options) } function init(options?: SessionOptions): Session { return fetch(options) } function reset(options?: SessionOptions): Session { return start(options) } function clear() { removeCookie('ko_sid', cookieDefaults) window.localStorage.removeItem('ko_sid') } export const session = { init, fetch, reset, sessionId, clear }