ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

FingerprintJS 扩展指南:自定义熵组件、指纹重算与 Canvas 稳定化实战

FingerprintJS 扩展指南:自定义熵组件、指纹重算与 Canvas 稳定化实战 FingerprintJS 扩展指南自定义熵组件、指纹重算与 Canvas 稳定化实战【免费下载链接】fingerprintjsThe most advanced free and open-source browser fingerprinting library项目地址: https://gitcode.com/GitHub_Trending/fi/fingerprintjs导读FingerprintJS 内置了 40 余个熵源entropy source每个熵源在fp.get()的结果中对应一个 component组件。但在实际业务中你可能发现某些组件例如languages、audio在你的用户群体中过于不稳定导致指纹频繁变化或者你希望注入自有业务数据如灰度标识、设备标签参与指纹计算。本文基于 docs/extending.md 的官方指引结合仓库源码系统讲解如何排除/新增熵组件、用内置哈希函数重算访客标识以及如何通过Canvas 稳定化在熵与稳定性之间做出取舍。读完本文你将掌握对result.components的增删改操作、FingerprintJS.hashComponents与FingerprintJS.componentsToDebugString的正确用法、canvas组件内部winding/geometry/text三字段的语义以及完整的可运行示例代码。为什么需要扩展组件fp.get()返回的GetResult包含三个关键字段见 src/agent.tsvisitorId访客标识字符串confidence置信度信息components构成访客标识的所有组件集合。components的 TypeScript 类型为BuiltinComponents见 src/sources/index.ts它由内置熵源列表类型推导而来。官方在源码中特别警告该类型不受 Semantic Versioning语义化版本约束浏览器环境不断变化熵源会随之增删类型可能在某个 minor 版本内发生不兼容变更如果希望保证向后兼容应将其当作更通用的UnknownComponents即Recordstring, Componentunknown使用见 src/utils/entropy_source.ts。每个组件的结构是type ComponentT | { value: T } // 成功获取到熵值 | { error: unknown } // 获取失败 { duration: number } // 采集耗时毫秒这决定了扩展时你自定义的组件也必须符合{ value: ... }这样的形态才能被hashComponents正确消费。排除不稳定组件当某个内置组件在你的业务场景中过于不稳定时例如字体列表因用户安装字体变化、音频指纹受后台影响波动可以简单地从组件集合中剔除它再基于剩余组件重算访客标识。// 引入并加载 Agent省略 load() 代码 const result await fp.get() // 剔除 languages 和 audio 两个组件 const { languages, audio, ...components } result.components // 用内置哈希函数基于剩余组件重算访客标识 const visitorId FingerprintJS.hashComponents(components)hashComponents与componentsToDebugString是FingerprintJS公开导出的三个核心 API 之二第三个是load见 src/index.ts 的默认导出{ load, hashComponents, componentsToDebugString }。注意result.visitorId是基于全部内置组件算出的原始标识。剔除组件后你必须调用FingerprintJS.hashComponents(...)重算直接沿用result.visitorId无法反映你自定义的组件集合。新增自定义组件你也可以向组件集合中注入自己的业务数据前提是你自行实现取值函数——getFooComponent/getBarComponent可以是同步或异步的返回值任意。const result await fp.get() // 新组件 foo 和 bar 会被加入组件集合 const components { ...result.components, foo: { value: await getFooComponent() }, bar: { value: await getBarComponent() }, } // 可选基于自定义组件集合重算访客标识 const visitorId FingerprintJS.hashComponents(components)典型应用场景包括把业务侧分配的租户 ID、渠道来源、实验分组等字段并入指纹使同一浏览器在不同业务上下文中产出可区分的标识。同时排除与新增两者的组合操作完全自由先解构剔除再展开合并。const result await fp.get() // 排除 languages 和 audio const { languages, audio, ...components } result.components // 新增 foo 和 bar const extendedComponents { ...components, foo: { value: await getFooComponent() }, bar: { value: await getBarComponent() }, } // 可选重算访客标识 const visitorId FingerprintJS.hashComponents(extendedComponents)深入了解 hashComponents规范化与哈希原理hashComponents并非简单地把组件对象塞进哈希函数它内部先经过componentsToCanonicalString规范化见 src/agent.tsfunction componentsToCanonicalString(components: UnknownComponents) { let result for (const componentKey of Object.keys(components).sort()) { const component components[componentKey] const value error in component ? error : JSON.stringify(component.value) result ${result ? | : }${componentKey.replace(/([:|\\])/g, \\$1)}:${value} } return result } export function hashComponents(components: UnknownComponents): string { return x64hash128(componentsToCanonicalString(components)) }关键细节键排序Object.keys(...).sort()保证组件顺序不影响哈希结果——这正是排除/新增组件后重算标识仍稳定的前提值序列化成功组件用JSON.stringify(component.value)失败组件固定序列化为字符串error转义组件键中的: | \会被转义避免键与值之间、组件与组件之间的分隔符产生歧义哈希算法底层是 MurmurHash3 的 x64 变体x64hash128输出 128 位无符号十六进制字符串。完整实现见 src/utils/hashing.ts实现细节包括x64Add、x64Multiply、x64Rotl、x64Fmix等 64 位算术原语以两个 int32 元组模拟 int64并刻意通过原地修改mutation减少内存分配与 GC 压力。哈希正确性由 src/utils/hashing.test.ts 中的Murmur3测试用例覆盖长文本、短文本、非 ASCII 输入等。另外agent.get()返回的visitorId是通过惰性 getter 实现的见 src/agent.ts首次读取result.visitorId时才调用hashComponents并缓存结果因此即使你不重算标识重复读取也不会产生额外哈希开销。将自定义组件集合格式化为可读文本排查问题或调试时可以用componentsToDebugString把组件集合输出为人类友好的 JSON 文本2 空格缩进并妥善处理组件中的Error对象序列化为errorToObject的普通对象const debugOutput document.querySelector(pre) debugOutput.textContent FingerprintJS.componentsToDebugString(components)实现见 src/agent.tsJSON.stringify的第二个参数是 replacer当值 instanceof Error 时转为可序列化对象。这与load({ debug: true })时控制台打印的 debug 信息src/agent.ts使用的是同一函数。Canvas 稳定化在两幅图之间取舍canvas 组件的内部结构canvas组件由 src/sources/canvas.ts 定义值为interface CanvasFingerprint { winding: boolean // 是否支持 evenodd 环绕规则 geometry: string // 纯几何图形图像的 base64 数据 text: string // 含文本图像的 base64 数据 }熵源由两幅图组成见 src/sources/canvas.tsgeometry几何图renderGeometryImage绘制三个半透明圆形颜色#f2f、#2ff、#ff2叠加并使用evenodd环绕规则填充一个带洞圆环src/sources/canvas.ts。它更稳定因为只依赖 canvas 的图形渲染/混合能力text文本图renderTextImage用11pt Times New Roman和18pt Arial绘制文字Cwm fjordbank gly 并叠加半透明色块src/sources/canvas.ts。它包含更多熵因为文本渲染还受字体栅格化、抗锯齿、emoji 支持等影响。Agent 默认同时使用两幅图。renderImages还会对文本图连续编码两次并比对若两次结果不一致浏览器对 canvas 加了噪声见 issue #791则把geometry和text都标记为unstable从而将整个 canvas 组件从指纹中排除。排除文本图以提升稳定性如果你更看重稳定性而非熵量可以把text置空字符串后重算标识let { components } await fp.get() if (value in components.canvas) { components.canvas.value.text } // 可选基于调整后的组件集合重算访客标识 const visitorId FingerprintJS.hashComponents(components)判断value in components.canvas是必要的当 canvas 采集失败如浏览器不支持、或处于 Safari 17 / Firefox 120 的反指纹模式时组件形态是{ error, duration }而非{ value, duration }此时直接访问.value会报错。底层抗指纹逻辑值得一提的是src/sources/canvas.ts 中的doesBrowserPerformAntiFingerprinting会在以下情况跳过整幅图像的渲染geometry与text均为skippedSafari 17WebKit 616隐私模式下 canvas 图像被加噪Firefox 120Gecko 120隐私浏览与严格 ETP 模式下 canvas 数据被随机化。这是内置熵源自带的稳定性保障与你手动排除文本图并不冲突——前者应对浏览器反指纹后者应对业务稳定性诉求。完整示例一个可复用的扩展工具函数把官方文档中的三个场景整合为一个工具函数方便直接复制到生产代码async function getExtendedVisitorId( fp, { exclude [], add {}, stabilizeCanvas false } {}, ) { const result await fp.get() let components { ...result.components } // 1. 排除不稳定组件 for (const key of exclude) { delete components[key] } // 2. 可选Canvas 稳定化——丢弃文本图仅保留几何图 if (stabilizeCanvas value in components.canvas) { components.canvas.value.text } // 3. 合并自定义组件 for (const [key, valuePromise] of Object.entries(add)) { components[key] { value: await valuePromise } } // 4. 基于最终组件集合重算访客标识 const visitorId FingerprintJS.hashComponents(components) return { visitorId, components } } // 使用示例 const fp await FingerprintJS.load() const { visitorId, components } await getExtendedVisitorId(fp, { exclude: [languages, audio], add: { tenantId: Promise.resolve(tenant-42) }, stabilizeCanvas: true, }) console.log(visitorId) console.log(FingerprintJS.componentsToDebugString(components))注意事项与最佳实践重算是必须的所有扩展操作后只有通过FingerprintJS.hashComponents(...)重算得到的visitorId才与你的自定义组件集合一致result.visitorId永远基于完整的内置组件集合。组件形态要正确自定义组件必须包装为{ value: any }若你的取值函数可能抛错可显式写为{ error }形态hashComponents会将其序列化为error参与哈希保证标识可复现。类型安全TypeScript 用户建议把扩展后的组件集合声明为FingerprintJS.UnknownComponents以避免依赖不受语义化版本约束的BuiltinComponents具体字段见 src/index.ts 的公开类型导出。稳定性与熵量的权衡排除组件、清空text都会降低熵量、提升稳定性。建议在真实流量上评估剔除前后visitorId的波动率再做取舍。调试利器开发阶段可借助componentsToDebugString或FingerprintJS.load({ debug: true })输出全部组件的原始值快速定位是哪个组件在抖动。扩展能力让 FingerprintJS 从开箱即用的指纹库变成可定制指纹引擎你可以只保留业务关心的熵源也可以把业务数据并入指纹。结合本文对hashComponents规范化逻辑与canvas组件结构的源码级拆解你已具备在生产环境中安全、正确地裁剪与扩展指纹组件的能力。【免费下载链接】fingerprintjsThe most advanced free and open-source browser fingerprinting library项目地址: https://gitcode.com/GitHub_Trending/fi/fingerprintjs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表