From 98025810e8a54b0ca0d2a03a51cbffb6cde82363 Mon Sep 17 00:00:00 2001 From: gunshiz Date: Fri, 11 Sep 2026 16:09:33 +0700 Subject: [PATCH] fix(presence) : count visitors per browser --- hooks/use-presence.ts | 44 +++++++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/hooks/use-presence.ts b/hooks/use-presence.ts index 13626f7..54fcc79 100644 --- a/hooks/use-presence.ts +++ b/hooks/use-presence.ts @@ -14,22 +14,39 @@ export type EditorsByGuide = Record< Array<{ userId: string; name: string; sections: string[] }> >; -function tabId(storageKey: string): string { - const existing = window.sessionStorage.getItem(storageKey); - if (existing) return existing; - const created = crypto.randomUUID(); - window.sessionStorage.setItem(storageKey, created); - return created; +const visitorIdStorageKey = "buzz-sheet:visitor-id"; + +let currentTabId: string | undefined; +let fallbackVisitorId: string | undefined; + +function tabId(): string { + currentTabId ??= crypto.randomUUID(); + return currentTabId; +} + +function visitorId(): string { + try { + const existing = window.localStorage.getItem(visitorIdStorageKey); + if (existing) return existing; + const created = crypto.randomUUID(); + window.localStorage.setItem(visitorIdStorageKey, created); + return created; + } catch { + fallbackVisitorId ??= crypto.randomUUID(); + return fallbackVisitorId; + } } function useHeartbeat({ endpoint, guideId, section, + identifyBy, }: { endpoint: string; guideId: string | null; section?: string | null; + identifyBy: "browser" | "tab"; }) { const [value, setValue] = useState(null); const send = useEffectEvent(async (active = true) => { @@ -38,7 +55,7 @@ function useHeartbeat({ method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ - tabId: tabId(`buzz-sheet:${endpoint}:tab`), + tabId: identifyBy === "browser" ? visitorId() : tabId(), guideId, ...(section !== undefined ? { section } : {}), active, @@ -59,7 +76,9 @@ function useHeartbeat({ const refresh = () => { if (document.visibilityState === "visible") void send(); }; - const leave = () => void send(false); + const leave = () => { + if (identifyBy === "tab") void send(false); + }; window.addEventListener("focus", refresh); window.addEventListener("pagehide", leave); document.addEventListener("visibilitychange", refresh); @@ -70,13 +89,17 @@ function useHeartbeat({ window.removeEventListener("pagehide", leave); document.removeEventListener("visibilitychange", refresh); }; - }, [endpoint, guideId, section]); + }, [endpoint, guideId, identifyBy, section]); return value; } export function useVisitorPresence(guideId: string | null) { - return useHeartbeat({ endpoint: "/api/presence/visitors", guideId }); + return useHeartbeat({ + endpoint: "/api/presence/visitors", + guideId, + identifyBy: "browser", + }); } export function useAdminPresence(guideId: string | null, section: string | null = null) { @@ -84,5 +107,6 @@ export function useAdminPresence(guideId: string | null, section: string | null endpoint: "/api/presence/admin", guideId, section, + identifyBy: "tab", }); }