FingerprintJS 是一种浏览器指纹识别工具,可以用来生成设备的唯一标识符。利用浏览器和设备的一系列非敏感数据(如屏幕分辨率、字体、WebGL 信息等)来创建一个高度唯一的指纹,用于追踪和识别用户。通过这种方式,可以实现跨会话、跨设备的用户识别,而不依赖传统的 cookie 方式,尤其适用于防止用户伪装身份或阻止追踪。如果需要更强大的功能(如设备识别的准确性提高或更多分析功能),可以使用 FingerprintJS 的 Pro 版本。
开源部分地址:https://github.com/fingerprintjs/fingerprintjs
npm install @fingerprintjs/fingerprintjs
import React, { useEffect, useState } from "react";
import FingerprintJS from "@fingerprintjs/fingerprintjs";
const App = () => {
const [fingerprint, setFingerprint] = useState("");
useEffect(() => {
// 创建 FingerprintJS 加载器实例
const loadFingerprint = async () => {
const fp = await FingerprintJS.load(); // 加载库
const result = await fp.get(); // 获取指纹数据
setFingerprint(result.visitorId); // 保存指纹 ID
};
loadFingerprint();
}, []);
return (
Device Fingerprint
Your fingerprint is: {fingerprint}
);
};
export default App;
// https://github1s.com/fingerprintjs/fingerprintjs/blob/master/src/sources/index.ts#L51-L102
export const sources = {
// READ FIRST:
// See https://github.com/fingerprintjs/fingerprintjs/blob/master/contributing.md#how-to-add-an-entropy-source
// to learn how entropy source works and how to make your own.
// The sources run in this exact order.
// The asynchronous sources are at the start to run in parallel with other sources.
fonts: getFonts,
domBlockers: getDomBlockers,
fontPreferences: getFontPreferences,
audio: getAudioFingerprint,
screenFrame: getScreenFrame,
canvas: getCanvasFingerprint,
osCpu: getOsCpu,
languages: getLanguages,
colorDepth: getColorDepth,
deviceMemory: getDeviceMemory,
screenResolution: getScreenResolution,
hardwareConcurrency: getHardwareConcurrency,
timezone: getTimezone,
sessionStorage: getSessionStorage,
localStorage: getLocalStorage,
indexedDB: getIndexedDB,
openDatabase: getOpenDatabase,
cpuClass: getCpuClass,
platform: getPlatform,
plugins: getPlugins,
touchSupport: getTouchSupport,
vendor: getVendor,
vendorFlavors: getVendorFlavors,
cookiesEnabled: areCookiesEnabled,
colorGamut: getColorGamut,
invertedColors: areColorsInverted,
forcedColors: areColorsForced,
monochrome: getMonochromeDepth,
contrast: getContrastPreference,
reducedMotion: isMotionReduced,
reducedTransparency: isTransparencyReduced,
hdr: isHDR,
math: getMathFingerprint,
pdfViewerEnabled: isPdfViewerEnabled,
architecture: getArchitecture,
applePay: getApplePayState,
privateClickMeasurement: getPrivateClickMeasurement,
audioBaseLatency: getAudioContextBaseLatency,
// Some sources can affect other sources (e.g. WebGL can affect canvas), so it's important to run these sources
// after other sources.
webGlBasics: getWebGlBasics,
webGlExtensions: getWebGlExtensions,
}
// https://github1s.com/fingerprintjs/fingerprintjs/blob/master/src/sources/os_cpu.ts#L1-L3
export default function getOsCpu(): string | undefined {
return navigator.oscpu // 返回一个字符串,用于标识当前操作系统。https://developer.mozilla.org/zh-CN/docs/Web/API/Navigator/oscpu
}
FingerprintJS
库中的 load
函数用于初始化和加载指纹识别所需的组件(确保所有组件和配置都正确加载),并返回一个 Agent
实例。Promise
,即一个异步操作,最终返回一个 Agent
实例。// https://github1s.com/fingerprintjs/fingerprintjs/blob/master/src/agent.ts#L195-L206
export async function load(options: Readonly<LoadOptions> = {}): Promise<Agent> {
if ((options as { monitoring?: boolean }).monitoring ?? true) {
monitor()
}
const { delayFallback, debug } = options
await prepareForSources(delayFallback)
const getComponents = loadBuiltinSources({ cache: {}, debug })
return makeAgent(getComponents, debug)// 创建并返回Agent实例
}
options
(类型:Readonly
):一个可选的配置对象,包含了在加载时需要的一些选项。 Readonly
限制,意味着 options
对象在传入后不可修改。
monitoring
(类型:boolean
,默认值:true
):如果为 true
,启用实时监控操作功能。delayFallback
(类型:number
或 undefined
,默认值:undefined
):用于设置加载延迟,可能用于解决一些性能问题或延迟加载组件。debug
(类型:boolean
,默认值:false
):是否启用调试模式,输出详细的调试信息。loadBuiltinSources函数:
const getComponents = loadBuiltinSources({ cache: {}, debug })
loadBuiltinSources
是一个加载内置组件的函数。它返回一个函数 getComponents
,这个函数可以用来获取加载的组件。makeAgent
是一个 工厂函数,返回一个包含 get()
方法的 Agent
对象。 Agent
对象负责收集用户的浏览器或设备指纹信息。// https://github1s.com/fingerprintjs/fingerprintjs/blob/master/src/agent.ts#L141-L174
/**
* The function isn't exported from the index file to not allow to call it without `load()`.
* `makeAgent` 这个函数没有直接从库的主入口文件(`index`)导出,目的是防止在没有执行 `load()` 初始化操作的情况下直接调用它。
* 所以,调用这个函数之前,必须先执行某些必要的加载或初始化步骤。
*
* The hiding gives more freedom for future non-breaking updates.
* 通过将这个函数隐藏起来,库可以在未来进行更新时更加灵活。这样,即使库内部做了改变或优化,也不会影响到外部用户的代码,从而减少破坏性更新的风险。
*
* A factory function is used instead of a class to shorten the attribute names in the minified code.
* 库使用了一个 工厂函数(`makeAgent`),而不是使用类(class),目的是在代码压缩(minification)后,能生成更短的变量名,从而减小文件大小。因为类通常会有较长的属性名,而工厂函数可以提供更精简的结构。
*
* Native private class fields could've been used, but TypeScript doesn't allow them with `"target": "es5"`.
* 兼容性:虽然可以使用原生的私有类字段(比如 `#fieldName`)来封装数据,但由于 TypeScript 编译设置为 `target: "es5"`(即编译为 ECMAScript 5 标准),这时不能使用私有字段。为了解决这个问题,库选择了工厂函数而不是类,避免了兼容性问题。
*/
function makeAgent(getComponents: () => Promise<BuiltinComponents>, debug?: boolean): Agent {
const creationTime = Date.now()
return {
async get(options) {
const startTime = Date.now()
const components = await getComponents()
const result = makeLazyGetResult(components)
if (debug || options?.debug) {
// console.log is ok here because it's under a debug clause
// eslint-disable-next-line no-console
console.log(`Copy the text below to get the debug data:
\`\`\`
version: ${result.version}
userAgent: ${navigator.userAgent}
timeBetweenLoadAndGet: ${startTime - creationTime}
visitorId: ${result.visitorId}
components: ${componentsToDebugString(components)}
\`\`\``)
}
return result
},
}
}
makeLazyGetResult
函数创建并返回一个 GetResult
对象,该对象用于获取指纹识别的结果,并在需要时计算和缓存 visitorId
(访客 ID)的哈希值。其设计目的是优化性能,确保只有在真正需要时才计算和存储 visitorId
。function makeLazyGetResult(components: BuiltinComponents): GetResult {
let visitorIdCache: string | undefined
// This function runs very fast, so there is no need to make it lazy
const confidence = getConfidence(components)
// A plain class isn't used because its getters and setters aren't enumerable.
return {
// 一个懒加载的 getter 和 setter,用于访问和设置访客 ID
get visitorId(): string {
if (visitorIdCache === undefined) {
visitorIdCache = hashComponents(this.components)
}
return visitorIdCache
},
set visitorId(visitorId: string) {
visitorIdCache = visitorId
},
confidence, // 计算得到的信心值,表示指纹识别结果的可靠性
components, // 指纹识别过程中使用的组件数据
version, // 版本信息
}
}
该函数创建并返回一个对象:
虽然 JavaScript 中可以使用类来封装对象,这样做的一个原因是,类的 getter 和 setter 不能被枚举,而直接使用对象字面量能确保属性是可枚举的,这对于优化某些操作可能是必要的。
懒加载的visitorId
,用于访问和设置访客 ID:
visitorId
时,首先检查 visitorIdCache
是否已经缓存了该值。如果未缓存,则调用 hashComponents(this.components)
计算并缓存结果。visitorIdCache
的值。