fix(presence) : count visitors per browser
CI / Verify (push) Successful in 1m12s
CI / Build immutable images and deploy (push) Successful in 2m18s

This commit is contained in:
2026-09-11 16:09:33 +07:00 Unverified
parent f3b2b0be21
commit 98025810e8
+34 -10
View File
@@ -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<T>({
endpoint,
guideId,
section,
identifyBy,
}: {
endpoint: string;
guideId: string | null;
section?: string | null;
identifyBy: "browser" | "tab";
}) {
const [value, setValue] = useState<T | null>(null);
const send = useEffectEvent(async (active = true) => {
@@ -38,7 +55,7 @@ function useHeartbeat<T>({
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<T>({
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<T>({
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<VisitorCounts>({ endpoint: "/api/presence/visitors", guideId });
return useHeartbeat<VisitorCounts>({
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",
});
}