diff --git a/src/stores/auth/auth-actors.ts b/src/stores/auth/auth-actors.ts index 32c788d8..1e71df64 100644 --- a/src/stores/auth/auth-actors.ts +++ b/src/stores/auth/auth-actors.ts @@ -238,7 +238,8 @@ export const guestLoginActor = fromPromise(async () => { // ========== 启动 / 恢复时检查登录态 actor(只查不创) ========== // 由 在 mount 时派发(一次性)。 // 流程(纯只读): -// 1. deviceIdentifier.getDeviceId() —— 无则通过 fingerprintjs 生成 + 落盘 +// 1. deviceIdentifier.getDeviceId() —— 无则生成 + 落盘 +// (生产环境使用 fingerprintjs;非生产环境使用本地 UUID) // (给后续 OAuth/email 登录准备 deviceId,不在 splash 首次访问就 auto-create 游客) // 2. AuthStorage.getLoginToken() —— 有 → "email" // 3. AuthStorage.getFacebookId() —— 有 → facebookIdLogin → "facebook" diff --git a/src/utils/device_identifier.ts b/src/utils/device_identifier.ts index 5502cfd3..b42b11c5 100644 --- a/src/utils/device_identifier.ts +++ b/src/utils/device_identifier.ts @@ -3,14 +3,16 @@ /** * 设备 ID 工具类 * - * 使用 fingerprintjs 生成客户端设备指纹。 + * 生产环境使用 fingerprintjs 生成客户端设备指纹。 + * 非生产环境使用本地 UUID,避免开发 / 预发布调试依赖真实设备指纹。 * 原始 Dart: lib/data/services/device_id_service_impl.dart * * 注:仅在浏览器环境可用;SSR/Server Components 中调用会抛错。 */ import { AuthStorage } from "../data/storage/auth/auth_storage"; -import { SpAsyncUtil } from "./storage"; import { StorageKeys } from "../data/storage/storage_keys"; +import { AppEnvUtil } from "./app-env"; +import { SpAsyncUtil } from "./storage"; export class DeviceIdentifier { private cachedId: string | null = null; @@ -19,8 +21,8 @@ export class DeviceIdentifier { /** * 获取或生成设备 ID * - * 优先级:内存缓存 → localStorage 缓存 → fingerprintjs 生成 - * 并发安全:多个并发调用会共享同一个 fingerprintjs 加载 Promise + * 优先级:内存缓存 → localStorage 缓存 → 非生产 UUID / 生产 fingerprintjs + * 并发安全:多个并发调用会共享同一个初始化 Promise */ async getDeviceId(): Promise { if (this.cachedId) return this.cachedId; @@ -55,17 +57,51 @@ export class DeviceIdentifier { ); } - // 3. 动态加载 fingerprintjs(避免 Next.js SSR 打包分析时执行) + // 3. 非生产环境不使用真实设备指纹,生成并持久化一个本地 UUID 即可。 + if (!AppEnvUtil.isProduction()) { + const deviceId = createUuidV4(); + await AuthStorage.getInstance().setDeviceId(deviceId); + return deviceId; + } + + // 4. 生产环境动态加载 fingerprintjs(避免 Next.js SSR 打包分析时执行) const { load } = await import("@fingerprintjs/fingerprintjs"); const fp = await load(); const result = await fp.get(); - // 4. 持久化 + // 5. 持久化 await AuthStorage.getInstance().setDeviceId(result.visitorId); return result.visitorId; } } +function createUuidV4(): string { + if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") { + return crypto.randomUUID(); + } + + const bytes = new Uint8Array(16); + if (typeof crypto !== "undefined" && crypto.getRandomValues) { + crypto.getRandomValues(bytes); + } else { + for (let i = 0; i < bytes.length; i += 1) { + bytes[i] = Math.floor(Math.random() * 256); + } + } + + bytes[6] = (bytes[6] & 0x0f) | 0x40; + bytes[8] = (bytes[8] & 0x3f) | 0x80; + + const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")); + return [ + hex.slice(0, 4).join(""), + hex.slice(4, 6).join(""), + hex.slice(6, 8).join(""), + hex.slice(8, 10).join(""), + hex.slice(10, 16).join(""), + ].join("-"); +} + /** * 全局单例 */