const PROJECT_EVENTS_CHANNEL = 'jgjs-project-events' const PROJECT_DELETED_STORAGE_KEY = 'jgjs-project-event:project-deleted' const RESET_ALL_STORAGE_KEY = 'jgjs-project-event:reset-all' const PROJECT_EVENT_SESSION_ID_KEY = 'jgjs-project-event-session-id' type ProjectDeletedPayload = { type: 'project-deleted' projectId: string sourceSessionId: string at: number } type ResetAllPayload = { type: 'reset-all' sourceSessionId: string at: number } const randomSessionId = () => `${Date.now()}-${Math.random().toString(36).slice(2, 10)}` const getOrCreateSessionId = () => { try { const existing = String(window.sessionStorage.getItem(PROJECT_EVENT_SESSION_ID_KEY) || '').trim() if (existing) return existing const next = randomSessionId() window.sessionStorage.setItem(PROJECT_EVENT_SESSION_ID_KEY, next) return next } catch { return randomSessionId() } } const parseProjectDeletedPayload = (raw: string | null): ProjectDeletedPayload | null => { if (!raw) return null try { const parsed = JSON.parse(raw) as Partial if (!parsed || typeof parsed !== 'object') return null if (parsed.type !== 'project-deleted') return null if (typeof parsed.projectId !== 'string' || !parsed.projectId.trim()) return null if (typeof parsed.sourceSessionId !== 'string' || !parsed.sourceSessionId.trim()) return null if (typeof parsed.at !== 'number' || !Number.isFinite(parsed.at)) return null return { type: 'project-deleted', projectId: parsed.projectId.trim(), sourceSessionId: parsed.sourceSessionId.trim(), at: parsed.at } } catch { return null } } const parseResetAllPayload = (raw: string | null): ResetAllPayload | null => { if (!raw) return null try { const parsed = JSON.parse(raw) as Partial if (!parsed || typeof parsed !== 'object') return null if (parsed.type !== 'reset-all') return null if (typeof parsed.sourceSessionId !== 'string' || !parsed.sourceSessionId.trim()) return null if (typeof parsed.at !== 'number' || !Number.isFinite(parsed.at)) return null return { type: 'reset-all', sourceSessionId: parsed.sourceSessionId.trim(), at: parsed.at } } catch { return null } } export const emitProjectDeleted = (projectIdRaw: string) => { const projectId = String(projectIdRaw || '').trim() if (!projectId) return const payload: ProjectDeletedPayload = { type: 'project-deleted', projectId, sourceSessionId: getOrCreateSessionId(), at: Date.now() } try { if (typeof BroadcastChannel !== 'undefined') { const bc = new BroadcastChannel(PROJECT_EVENTS_CHANNEL) bc.postMessage(payload) bc.close() } } catch { // ignore } try { localStorage.setItem(PROJECT_DELETED_STORAGE_KEY, JSON.stringify(payload)) } catch { // ignore } } export const listenProjectDeleted = (onDeleted: (projectId: string) => void) => { const localSessionId = getOrCreateSessionId() let bc: BroadcastChannel | null = null const handlePayload = (payload: ProjectDeletedPayload | null) => { if (!payload) return if (payload.sourceSessionId === localSessionId) return onDeleted(payload.projectId) } const onStorage = (event: StorageEvent) => { if (event.key !== PROJECT_DELETED_STORAGE_KEY) return handlePayload(parseProjectDeletedPayload(event.newValue)) } if (typeof BroadcastChannel !== 'undefined') { bc = new BroadcastChannel(PROJECT_EVENTS_CHANNEL) bc.onmessage = (event: MessageEvent) => { handlePayload(parseProjectDeletedPayload(JSON.stringify(event.data))) } } window.addEventListener('storage', onStorage) return () => { window.removeEventListener('storage', onStorage) if (bc) { bc.close() bc = null } } } export const emitResetAll = () => { const payload: ResetAllPayload = { type: 'reset-all', sourceSessionId: getOrCreateSessionId(), at: Date.now() } try { if (typeof BroadcastChannel !== 'undefined') { const bc = new BroadcastChannel(PROJECT_EVENTS_CHANNEL) bc.postMessage(payload) bc.close() } } catch { // ignore } try { localStorage.setItem(RESET_ALL_STORAGE_KEY, JSON.stringify(payload)) } catch { // ignore } } export const listenResetAll = (onResetAll: () => void) => { const localSessionId = getOrCreateSessionId() let bc: BroadcastChannel | null = null const handlePayload = (payload: ResetAllPayload | null) => { if (!payload) return if (payload.sourceSessionId === localSessionId) return onResetAll() } const onStorage = (event: StorageEvent) => { if (event.key !== RESET_ALL_STORAGE_KEY) return handlePayload(parseResetAllPayload(event.newValue)) } if (typeof BroadcastChannel !== 'undefined') { bc = new BroadcastChannel(PROJECT_EVENTS_CHANNEL) bc.onmessage = (event: MessageEvent) => { handlePayload(parseResetAllPayload(JSON.stringify(event.data))) } } window.addEventListener('storage', onStorage) return () => { window.removeEventListener('storage', onStorage) if (bc) { bc.close() bc = null } } }