From fd660ca7969596bd5ef8a43a1c873d356931a86e Mon Sep 17 00:00:00 2001 From: hbh112233abc Date: Tue, 21 Jul 2026 09:21:40 +0800 Subject: [PATCH] =?UTF-8?q?fix(embed):=20=E5=85=BC=E5=AE=B9=E4=B8=8D?= =?UTF-8?q?=E6=94=AF=E6=8C=81=20crypto.randomUUID=20=E7=9A=84=E6=B5=8F?= =?UTF-8?q?=E8=A7=88=E5=99=A8=E4=BB=A5=E5=8F=8Ahttp=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/embed/index.ts | 42 ++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/frontend/src/api/embed/index.ts b/frontend/src/api/embed/index.ts index 942bdbf5..2c6f72e6 100644 --- a/frontend/src/api/embed/index.ts +++ b/frontend/src/api/embed/index.ts @@ -69,20 +69,56 @@ export function embedVisitorStorageKey(channelId: string): string { return `${EMBED_VISITOR_STORAGE_PREFIX}${channelId}` } +/** + * 生成符合 UUID v4 格式的随机 ID。 + * 优先使用 crypto.randomUUID(),在不支持的浏览器中回退到 crypto.getRandomValues(), + * 最终兜底使用 Math.random()。 + */ +export function generateUUID(): string { + // 优先使用原生 API(HTTPS 环境下可用) + if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') { + return crypto.randomUUID() + } + + // 回退:使用 crypto.getRandomValues() + if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') { + const buf = new Uint8Array(16) + crypto.getRandomValues(buf) + // 设置 UUID v4 标志位 + buf[6] = (buf[6] & 0x0f) | 0x40 + buf[8] = (buf[8] & 0x3f) | 0x80 + const hex = Array.from(buf, (b) => b.toString(16).padStart(2, '0')) + return [ + hex[0], hex[1], hex[2], hex[3], '-', + hex[4], hex[5], '-', + hex[6], hex[7], '-', + hex[8], hex[9], '-', + hex[10], hex[11], hex[12], hex[13], hex[14], hex[15], + ].join('') + } + + // 最终兜底:Math.random()(随机性较弱,仅保证格式正确) + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { + const r = (Math.random() * 16) | 0 + const v = c === 'x' ? r : (r & 0x3) | 0x8 + return v.toString(16) + }) +} + /** Stable anonymous id for this browser on the given channel. */ export function getOrCreateEmbedVisitorId(channelId: string): string { if (typeof localStorage === 'undefined' || !channelId) { - return crypto.randomUUID() + return generateUUID() } const key = embedVisitorStorageKey(channelId) try { const existing = localStorage.getItem(key)?.trim() if (existing) return existing - const id = crypto.randomUUID() + const id = generateUUID() localStorage.setItem(key, id) return id } catch { - return crypto.randomUUID() + return generateUUID() } }