Skip to content

Commit 5bff57b

Browse files
committed
chore(analytics): CR - 修 x-satoken header / storage try/catch / timer 清理等
Copilot CR #277: - lib/analytics: 客户端 → Next /api/analytics 用 x-satoken(之前用 satoken 导致 resolveUserId 解析不到 userId,uniqueUsers 恒为 0) - lib/analytics: localStorage 读取用 try/catch,Safari 隐私模式不崩 - lib/analytics: headers 用 Record<string,string> 而不是 HeadersInit 联合类型,可变安全 - DocsPageViewTracker: sessionStorage 读写 try/catch 降级到内存去重 - DocShareButton: useRef 存 setTimeout id,unmount 时 clearTimeout;加 type='button' 避免 form 内误触发提交 - CustomSearchDialog: 注释修正('搜索结果点击' → '搜索词输入')
1 parent c33c1ea commit 5bff57b

3 files changed

Lines changed: 26 additions & 7 deletions

File tree

app/components/CustomSearchDialog.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export function CustomSearchDialog({
9292
if (!search) return;
9393

9494
const timer = setTimeout(() => {
95-
// Umami 埋点: 搜索结果点击
95+
// Umami 埋点: 搜索词输入(debounce 1s,非搜索结果点击)
9696
if (window.umami) {
9797
window.umami.track("search_query", { query: search });
9898
}

app/components/DocShareButton.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { useState } from "react";
3+
import { useEffect, useRef, useState } from "react";
44
import { trackEvent } from "@/lib/analytics";
55

66
/**
@@ -9,14 +9,23 @@ import { trackEvent } from "@/lib/analytics";
99
*/
1010
export function DocShareButton() {
1111
const [copied, setCopied] = useState(false);
12+
// timer ref:每次新点击 / 组件卸载时清掉旧 timer,避免 setState on unmounted
13+
const resetTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
14+
15+
useEffect(() => {
16+
return () => {
17+
if (resetTimerRef.current) clearTimeout(resetTimerRef.current);
18+
};
19+
}, []);
1220

1321
const handleCopy = async () => {
1422
const url = window.location.href;
1523
try {
1624
await navigator.clipboard.writeText(url);
1725
setCopied(true);
18-
// 2s 后恢复按钮文案
19-
setTimeout(() => setCopied(false), 2000);
26+
// 旧 timer 先清掉,避免连点两次后提前恢复文案
27+
if (resetTimerRef.current) clearTimeout(resetTimerRef.current);
28+
resetTimerRef.current = setTimeout(() => setCopied(false), 2000);
2029
} catch {
2130
// clipboard 不可用时静默失败
2231
}
@@ -27,6 +36,7 @@ export function DocShareButton() {
2736

2837
return (
2938
<button
39+
type="button"
3040
onClick={handleCopy}
3141
className="inline-flex items-center gap-2 rounded-md px-4 h-11 text-base font-medium hover:bg-muted/80 hover:text-foreground"
3242
aria-label="复制页面链接"

app/components/DocsPageViewTracker.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,22 @@ export function DocsPageViewTracker() {
1818
if (!pathname) return;
1919

2020
const dedupeKey = `pv:${pathname}`;
21-
// sessionStorage 去重:同一 session 内同一路径不重复上报
22-
if (sessionStorage.getItem(dedupeKey)) return;
21+
// sessionStorage 可能因为存储禁用 / 配额超限 / Safari 隐私模式抛错;
22+
// 埋点的去重不能因此报错破坏导航,用 try/catch 降级到内存去重
23+
try {
24+
if (sessionStorage.getItem(dedupeKey)) return;
25+
} catch {
26+
// 读失败时跳过 session 去重,后面的内存去重仍然生效
27+
}
2328
// 内存去重:防止 React StrictMode 双重 effect 重复调用
2429
if (lastTrackedRef.current === pathname) return;
2530

2631
lastTrackedRef.current = pathname;
27-
sessionStorage.setItem(dedupeKey, "1");
32+
try {
33+
sessionStorage.setItem(dedupeKey, "1");
34+
} catch {
35+
// 写失败时下一个 session / 刷新会再报一次,可接受
36+
}
2837

2938
trackEvent("page_view", {
3039
path: pathname,

0 commit comments

Comments
 (0)