spb/company-atlas
Public
Python 66.3%
TypeScript 22.7%
JavaScript 8.6%
HTML 1.4%
CSS 0.7%
1'use client';2/**3 * Owner token for watchlists and alerts (no account, spec §55): a random string generated once in the browser4 * (`crypto.randomUUID()` twice → 72 chars) and kept in localStorage. Sent as `X-CA-Owner-Token`; the API stores a hash.5 * Losing the browser storage loses the watchlist — the /watchlist page says so and offers export/import of the token.6 */7import { useEffect, useState } from 'react';89export const OWNER_KEY = 'ca-owner-token';10const EVENT = 'ca-owner-change';1112export function readOwnerToken(): string | null {13 if (typeof window === 'undefined') return null;14 try {15 const v = window.localStorage.getItem(OWNER_KEY);16 return v && v.length >= 24 ? v : null;17 } catch {18 return null;19 }20}2122export function ensureOwnerToken(): string {23 const cur = readOwnerToken();24 if (cur) return cur;25 const t = `${crypto.randomUUID()}${crypto.randomUUID()}`.replace(/-/g, '');26 try {27 window.localStorage.setItem(OWNER_KEY, t);28 window.dispatchEvent(new CustomEvent(EVENT));29 } catch {30 /* storage disabled: token lives for this page only */31 }32 return t;33}3435export function setOwnerToken(t: string): boolean {36 if (t.trim().length < 24) return false;37 try {38 window.localStorage.setItem(OWNER_KEY, t.trim());39 window.dispatchEvent(new CustomEvent(EVENT));40 return true;41 } catch {42 return false;43 }44}4546/** Token after mount (null during SSR / first paint). `create` generates one when absent. */47export function useOwnerToken(create = false): string | null {48 const [token, setToken] = useState<string | null>(null);49 useEffect(() => {50 const read = () => setToken(create ? ensureOwnerToken() : readOwnerToken());51 read();52 window.addEventListener(EVENT, read);53 window.addEventListener('storage', read);54 return () => {55 window.removeEventListener(EVENT, read);56 window.removeEventListener('storage', read);57 };58 }, [create]);59 return token;60}6162/** Local mirror of watched slugs so watch buttons render instantly and work when the API is briefly unavailable. */63export const WATCHED_KEY = 'ca-watched';64const WATCHED_EVENT = 'ca-watched-change';65export function readWatched(): string[] {66 if (typeof window === 'undefined') return [];67 try {68 const arr = JSON.parse(window.localStorage.getItem(WATCHED_KEY) ?? '[]') as unknown;69 return Array.isArray(arr) ? arr.filter((x): x is string => typeof x === 'string') : [];70 } catch {71 return [];72 }73}74export function writeWatched(slugs: string[]) {75 try {76 window.localStorage.setItem(WATCHED_KEY, JSON.stringify([...new Set(slugs)]));77 } catch {78 /* ignore */79 }80 window.dispatchEvent(new CustomEvent(WATCHED_EVENT));81}82export function useWatched(): string[] {83 const [w, setW] = useState<string[]>([]);84 useEffect(() => {85 const read = () => setW(readWatched());86 read();87 window.addEventListener(WATCHED_EVENT, read);88 window.addEventListener('storage', read);89 return () => {90 window.removeEventListener(WATCHED_EVENT, read);91 window.removeEventListener('storage', read);92 };93 }, []);94 return w;95}96