fix(telemetry): idle-based session rotation; faster sse keepalive

- session id rotates after 30 idle minutes so installed pwas produce
  visit-sized sessions instead of one eternal id
- sse ping every 8s, under the idle timeout observed killing streams
This commit is contained in:
2026-08-26 22:43:25 -07:00
parent 96d5535293
commit cb16d9757e
2 changed files with 28 additions and 8 deletions
+2 -1
View File
@@ -39,9 +39,10 @@ export const Route = createFileRoute("/api/events/$")({
serverEvents.on(type, handleEvent);
}
// Under the shortest idle timeout observed on proxies/PWA paths.
const pingInterval = setInterval(() => {
send({ type: "ping", timestamp: Date.now() });
}, 15000);
}, 8000);
cleanup = () => {
if (cleanedUp) return;
+26 -7
View File
@@ -17,19 +17,37 @@ const recentErrors = new Map<string, number>();
const isEnabled = (): boolean => typeof window !== "undefined";
const getSessionId = (): string => {
// A session ends after 30 idle minutes, not when the tab closes — installed
// PWAs keep sessionStorage alive for days.
const SESSION_IDLE_MS = 30 * 60 * 1000;
const SESSION_KEY = "flxn-tsid";
const readSession = (): { id: string; t: number } | null => {
try {
let id = sessionStorage.getItem("flxn-tsid");
if (!id) {
id = crypto.randomUUID();
sessionStorage.setItem("flxn-tsid", id);
const raw = sessionStorage.getItem(SESSION_KEY);
if (!raw) return null;
const parsed = JSON.parse(raw);
if (typeof parsed?.id === "string" && typeof parsed?.t === "number") {
return parsed;
}
return id;
return null;
} catch {
return "anon-session";
return null;
}
};
const touchSession = () => {
try {
const now = Date.now();
const current = readSession();
const id =
current && now - current.t <= SESSION_IDLE_MS ? current.id : crypto.randomUUID();
sessionStorage.setItem(SESSION_KEY, JSON.stringify({ id, t: now }));
} catch {}
};
const getSessionId = (): string => readSession()?.id ?? "anon-session";
const location = (): { path: string; routeId?: string } => ({
path: window.location.pathname,
routeId: lastRouteId,
@@ -64,6 +82,7 @@ const flush = (useBeacon = false) => {
const enqueue = (event: QueuedEvent) => {
if (!isEnabled()) return;
touchSession();
queue.push(event);
if (queue.length >= FLUSH_AT) {
flush();