@theseed.dev/fingerprint
v1.1.0
Published
Fingerprinting navigateur léger, sans dépendance — Canvas, Audio, WebGL, Fonts, Media Devices. Compatible Vue 3, React, Next.js, Nuxt, Angular.
Maintainers
Readme
Génère un identifiant visiteur unique (visitorId) à partir de 7 signaux matériels et logiciels du navigateur — sans cookies, sans localStorage, sans serveur.
npm install @theseed.dev/fingerprintSignaux collectés
| Signal | Technique | Sensibilité |
|---|---|---|
| Canvas | Rendu 2D texte + formes | GPU, pilotes, anti-aliasing |
| Audio | OfflineAudioContext oscillateur + compresseur | Carte son, pilotes audio |
| WebGL | Extension WEBGL_debug_renderer_info | Vendor GPU, renderer string |
| Fonts | Détection de largeur Canvas (25 familles) | Polices installées |
| Media Devices | enumerateDevices() — types uniquement | Micros, caméras présents |
| Environnement | Screen, langue, timezone, CPU, RAM… | Profil OS / navigateur |
| Plugins | navigator.plugins | Extensions installées |
Aucune permission utilisateur requise. Aucune donnée ne quitte le navigateur.
Démarrage rapide
import { generateFingerprint } from "@theseed.dev/fingerprint";
const result = await generateFingerprint();
console.log(result?.visitorId); // "1a2b3c4d5e6f"
console.log(result?.components); // { canvas, audio, webgl, fonts, ... }
console.log(result?.generatedAt); // 1716638400000Retourne null si exécuté hors navigateur (SSR, Node.js, Deno…).
Intégrations par framework
Vanilla JS / TypeScript
import { generateFingerprint } from "@theseed.dev/fingerprint";
const result = await generateFingerprint();
if (result) {
document.title = `Visitor: ${result.visitorId}`;
}React
Hook personnalisé useFingerprint :
// hooks/useFingerprint.ts
import { useEffect, useState } from "react";
import { generateFingerprint, FingerprintResult } from "@theseed.dev/fingerprint";
export function useFingerprint() {
const [result, setResult] = useState<FingerprintResult | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
generateFingerprint()
.then(setResult)
.catch(setError)
.finally(() => setLoading(false));
}, []);
return { visitorId: result?.visitorId ?? null, components: result?.components ?? null, loading, error };
}// App.tsx
import { useFingerprint } from "./hooks/useFingerprint";
export default function App() {
const { visitorId, loading } = useFingerprint();
if (loading) return <p>Identification…</p>;
return <p>Visitor ID : <code>{visitorId}</code></p>;
}Vue 3
Le composable useFingerprint() est intégré directement dans le package.
Il génère le fingerprint au montage du composant et expose un état réactif.
<!-- components/VisitorBadge.vue -->
<script setup>
import { useFingerprint } from "@theseed.dev/fingerprint";
const { visitorId, loading, error } = useFingerprint();
</script>
<template>
<p v-if="loading">Identification en cours…</p>
<p v-else-if="error" class="error">Erreur : {{ error.message }}</p>
<p v-else>Visitor ID : <code>{{ visitorId }}</code></p>
</template>Next.js — App Router
Les Browser APIs ne sont disponibles que côté client. Utilise la directive "use client".
// components/VisitorTracker.tsx
"use client";
import { useEffect, useState } from "react";
import { generateFingerprint } from "@theseed.dev/fingerprint";
export function VisitorTracker() {
const [visitorId, setVisitorId] = useState<string | null>(null);
useEffect(() => {
generateFingerprint().then((r) => r && setVisitorId(r.visitorId));
}, []);
return visitorId ? <span data-visitor={visitorId} /> : null;
}Next.js Pages Router :
// pages/index.tsx
import dynamic from "next/dynamic";
// Désactive le SSR — generateFingerprint() retourne null côté serveur
const VisitorTracker = dynamic(() => import("../components/VisitorTracker"), {
ssr: false,
});Nuxt 3
<!-- components/VisitorTracker.vue -->
<script setup>
// ClientOnly garantit que le code s'exécute uniquement dans le navigateur
const visitorId = ref(null);
onMounted(async () => {
const { generateFingerprint } = await import("@theseed.dev/fingerprint");
const result = await generateFingerprint();
if (result) visitorId.value = result.visitorId;
});
</script>
<template>
<span v-if="visitorId" :data-visitor="visitorId" />
</template>Ou avec le composant <ClientOnly> de Nuxt :
<!-- app.vue -->
<template>
<ClientOnly>
<VisitorTracker />
</ClientOnly>
</template>Angular
// services/fingerprint.service.ts
import { Injectable } from "@angular/core";
import { from, of } from "rxjs";
import { catchError } from "rxjs/operators";
import { generateFingerprint } from "@theseed.dev/fingerprint";
@Injectable({ providedIn: "root" })
export class FingerprintService {
readonly visitorId$ = from(generateFingerprint()).pipe(
catchError(() => of(null))
);
}// components/visitor-badge.component.ts
import { Component } from "@angular/core";
import { AsyncPipe } from "@angular/common";
import { FingerprintService } from "../services/fingerprint.service";
@Component({
selector: "app-visitor-badge",
standalone: true,
imports: [AsyncPipe],
template: `
<code>{{ (fp.visitorId$ | async)?.visitorId }}</code>
`,
})
export class VisitorBadgeComponent {
constructor(readonly fp: FingerprintService) {}
}Structure du résultat
interface FingerprintResult {
/** Hash 53-bit en hexadécimal (ex: "1a2b3c4d5e6f") */
visitorId: string;
/** Timestamp de génération (ms depuis epoch) */
generatedAt: number;
/** Composants bruts ayant servi au calcul */
components: FingerprintComponents;
}
interface FingerprintComponents {
// Signaux graphiques
canvas: string | null; // Data URL canvas rendu
webgl: string | null; // "Vendor ~ Renderer"
// Signal audio
audio: string | null; // "<hash>-<sum>"
// Signaux environnement (v1.1+)
fonts: string[]; // Polices détectées
mediaDevices: string | null; // Types de devices (audioinput,videoinput…)
plugins: string[]; // navigator.plugins
// Environnement navigateur
userAgent: string;
language: string;
platform: string;
screenResolution: string; // "1920x1080"
colorDepth: number;
timezone: string; // "Europe/Paris"
touchSupport: boolean;
hardwareConcurrency: number;
deviceMemory?: number; // Go de RAM (approximatif)
// Métadonnées navigateur supplémentaires (v1.1+)
cookiesEnabled: boolean;
doNotTrack: string | null;
sessionStorage: boolean;
localStorage: boolean;
indexedDB: boolean;
openDatabase: boolean;
cpuClass: string | null;
oscpu: string | null;
vendor: string;
vendorSub: string;
productSub: string;
navigatorOnline: boolean;
}API
generateFingerprint()
function generateFingerprint(): Promise<FingerprintResult | null>Collecte tous les signaux en parallèle (Promise.all) et retourne le résultat.
Retourne null si window n'est pas défini (Node.js, SSR…).
useFingerprint() (Vue 3 uniquement)
function useFingerprint(): {
visitorId: Ref<string | null>;
components: ShallowRef<FingerprintComponents | null>;
loading: Ref<boolean>;
error: ShallowRef<Error | null>;
}Composable Vue 3 — génère le fingerprint au montage (onMounted).
Lance une exception si appelé hors d'un projet Vue 3.
Algorithme de hash
Le visitorId est généré par cyrb53 — un hash non-cryptographique 53-bit conçu pour minimiser les collisions sur de petits jeux de données :
visitorId = cyrb53(JSON.stringify(components)).toString(16)- Déterministe : même machine + même navigateur → même
visitorId - Sensible : un changement de signal =
visitorIddifférent - Rapide : O(n) sur la longueur de la chaîne sérialisée
⚠️ Ce n'est pas un hash cryptographique. Ne pas utiliser pour la sécurité.
Notes de confidentialité
Ce module collecte des informations purement techniques sur le navigateur — aucune donnée personnelle identifiable (nom, email, adresse IP).
- ✅ Aucun cookie créé
- ✅ Aucune écriture dans le localStorage
- ✅ Aucune requête réseau
- ✅ Aucune permission demandée à l'utilisateur
- ⚠️ Pour
mediaDevices: seuls les types de devices sont collectés (pas les labels, qui nécessiteraient une permission)
Conformité légale : selon ton usage (analytics, anti-fraude, personnalisation), tu peux être soumis au RGPD, CCPA ou autres réglementations. Assure-toi de mentionner la collecte d'empreinte dans ta politique de confidentialité.
Powered by theseed.dev — License MIT
