npm package: @m.said/device-fingerprint
Description: High-accuracy client-side device fingerprinting SDK (90-95% accuracy)
Author: m.said
Latest version: 1.0.0

# Device Fingerprint SDK

[![npm version](https://img.shields.io/npm/v/@yourorg/device-fingerprint.svg)](https://www.npmjs.com/package/@yourorg/device-fingerprint)
[![Test Coverage](https://img.shields.io/badge/coverage-80%25-brightgreen.svg)](./coverage)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

High-accuracy (90-95%) client-side device fingerprinting with zero dependencies.

**Features:** Multi-layer persistence • Fuzzy matching • Behavioral tracking • User linking • TypeScript • 22KB bundle

## Install

```bash
npm install @yourorg/device-fingerprint
```

### Local Installation (Copy SDK to Your Project)

If you want to use the SDK locally without npm:

**Step 1: Build the SDK**
```bash
cd Device-fingerprint
npm install
npm run build
```

**Step 2: Copy to Your Project**
```bash
# Copy the entire dist folder to your project
cp -r dist/ /path/to/your-project/libs/device-fingerprint/
```

**Step 3: Import in Your Code**

```javascript
// ESM (ES Modules)
import Fingerprint from './libs/device-fingerprint/index.esm.js';

// CommonJS
const Fingerprint = require('./libs/device-fingerprint/index.js');

// Browser (UMD)
<script src="./libs/device-fingerprint/fingerprint.min.js"></script>
<script>
  DeviceFingerprint.get().then(result => {
    console.log(result.device.id);
  });
</script>
```

**Your Project Structure:**
```
your-project/
├── libs/
│   └── device-fingerprint/
│       ├── index.esm.js      (ESM build)
│       ├── index.js          (CommonJS build)
│       ├── fingerprint.min.js (UMD/Browser build)
│       └── index.d.ts        (TypeScript types)
├── src/
│   └── app.js
└── package.json
```

### Development with npm link

For active development and testing:

```bash
# In the SDK directory
cd Device-fingerprint
npm link

# In your project directory
cd your-project
npm link @yourorg/device-fingerprint
```

Now you can import normally and changes to the SDK will reflect immediately:
```javascript
import Fingerprint from '@yourorg/device-fingerprint';
```

## Quick Start

```javascript
import Fingerprint from '@yourorg/device-fingerprint';

const result = await Fingerprint.get();
console.log(result.id);               // "fp_abc123..." (main fingerprint)
console.log(result.device.id);        // "dev_xyz789..." (stable across browsers)
console.log(result.browser.id);       // "dev_xyz789_chrome_a1b2" (browser-specific)
console.log(result.browser.type);     // "chrome"
console.log(result.confidence);       // 0.92
```

### Dual-Fingerprint Architecture

**Device Fingerprint** - Ultra-stable, identifies physical device:
- Same ID across Chrome, Firefox, Edge, Safari
- Survives resolution changes, timezone changes
- Survives clearing site data (regenerates identically)
- Based on: CPU, memory, GPU, platform

**Browser Fingerprint** - Identifies specific browser:
- Unique to each browser on the device
- Differentiates Chrome vs Firefox on same device
- Includes browser-specific signals (Canvas, Audio)
- Format: `{deviceId}_{browserType}_{hash}`

```javascript
// Analytics: Track device + browser separately
const result = await Fingerprint.get();
analytics.track('page_view', {
  deviceId: result.device.id,    // Same across browsers
  browserId: result.browser.id,  // Unique to browser
  browserType: result.browser.type
});
```

## API

### `Fingerprint.get(options?)`

Generate device fingerprint.

```javascript
const result = await Fingerprint.get({
  enableBehavioral: true,     // +3-5% accuracy (default: false)
  behavioralDelay: 10000,     // Collect for 10s
  enableFuzzyMatching: true,  // Handle browser updates (default: true)
  timeout: 5000,              // Max generation time
  debug: false                // Log to console
});
```

**Returns:**
```typescript
{
  id: string;              // Main fingerprint ID
  confidence: number;      // 0.0-1.0
  source: 'cache' | 'generated';
  device: {                // Device-level fingerprint
    id: string;            // Stable across browsers
    confidence: number;    // 0.95
  };
  browser: {               // Browser-level fingerprint
    id: string;            // Unique to browser
    confidence: number;    // 0.90
    type: 'chrome' | 'firefox' | 'safari' | 'edge' | 'opera' | 'unknown';
  };
  components?: object;     // Raw data
  behavioral?: object;     // If enabled
  timestamp: number;
}
```

### `Fingerprint.linkToUser(fingerprintId, userData)`

Link fingerprint to user account (handles multi-user devices).

```javascript
const result = await Fingerprint.get();

// Recommended: Link by device ID (works across browsers)
await Fingerprint.linkToUser(result.device.id, {
  userId: 'user123',
  email: 'john@example.com'
});

// Alternative: Link by browser ID (browser-specific)
await Fingerprint.linkToUser(result.browser.id, {
  userId: 'user456',
  email: 'jane@example.com'
});
```

### `Fingerprint.verify({ userId })`

Verify if current device is known for user.

```javascript
const verification = await Fingerprint.verify({ userId: 'user123' });

if (verification.isNewDevice) {
  // Require MFA
}
```

**Returns:**
```typescript
{
  isNewDevice: boolean;
  trustScore: number;      // 0.0-1.0
  confidence: number;
  requiresMFA: boolean;
  matchType: 'exact' | 'fuzzy' | 'new';
}
```

### `Fingerprint.clear()`

Clear all stored data.

```javascript
await Fingerprint.clear();
```

## Use Cases

### Device-Level Analytics

Track users across browsers on the same device:

```javascript
const result = await Fingerprint.get();

// User has 3 browsers on same device: all share same device.id
analytics.identify(result.device.id, {
  browsers: [result.browser.type],
  lastSeen: Date.now()
});
```

### Fraud Detection

Detect suspicious activity across browsers:

```javascript
const result = await Fingerprint.get();

if (result.device.isNewDevice) {
  // Completely new physical device
  requireMFA();
} else if (result.browser.isNewBrowser) {
  // Same device, different browser - moderate risk
  sendNotification('New browser detected');
}

// Check if device is associated with multiple accounts
const deviceAccounts = await getAccountsByDevice(result.device.id);
if (deviceAccounts.length > 5) {
  flagForReview('Device used by multiple accounts');
}
```

### Cross-Browser Session Management

Recognize users switching browsers:

```javascript
const result = await Fingerprint.get();

// Check all browsers on this device
const sessions = await getSessionsByDevice(result.device.id);

if (sessions.length > 0) {
  // User has active sessions in other browsers
  showMessage('You are logged in on Chrome. Continue session?');
}
```

### Smart MFA

Reduce MFA prompts for known devices:

```javascript
const verification = await Fingerprint.verify({ userId: 'user123' });

if (verification.matchType === 'exact') {
  // Same browser on known device - no MFA
  allowLogin();
} else if (verification.matchType === 'cross-browser') {
  // Different browser, same device - light MFA
  requireEmailCode();
} else {
  // New device - full MFA
  requireAuthenticatorApp();
}
```

## Integration Examples

### React

```jsx
import { useEffect, useState } from 'react';
import Fingerprint from '@yourorg/device-fingerprint';

function App() {
  const [fingerprint, setFingerprint] = useState(null);

  useEffect(() => {
    async function identify() {
      const result = await Fingerprint.get({
        enableBehavioral: true,
        behavioralDelay: 2000
      });

      setFingerprint(result);

      // Send to backend
      await fetch('/api/track-device', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          deviceId: result.device.id,
          browserId: result.browser.id,
          browserType: result.browser.type,
          confidence: result.confidence
        })
      });
    }

    identify();
  }, []);

  return (
    <div>
      <p>Device: {fingerprint?.device.id}</p>
      <p>Browser: {fingerprint?.browser.type}</p>
    </div>
  );
}
```

### Vue 3

```vue
<script setup>
import { ref, onMounted } from 'vue';
import Fingerprint from '@yourorg/device-fingerprint';

const fingerprint = ref(null);

onMounted(async () => {
  const result = await Fingerprint.get();
  fingerprint.value = result;

  // Send to analytics
  window.analytics?.identify(result.device.id, {
    browserType: result.browser.type,
    confidence: result.confidence
  });
});
</script>

<template>
  <div v-if="fingerprint">
    <p>Device: {{ fingerprint.device.id }}</p>
    <p>Browser: {{ fingerprint.browser.type }}</p>
  </div>
</template>
```

### Node.js + Express Backend

```javascript
import express from 'express';
import { db } from './database';

const app = express();
app.use(express.json());

// Track device fingerprints
app.post('/api/track-device', async (req, res) => {
  const { deviceId, browserId, browserType, confidence } = req.body;

  // Store in database
  await db.query(`
    INSERT INTO device_fingerprints (device_id, browser_id, browser_type, confidence, last_seen)
    VALUES ($1, $2, $3, $4, NOW())
    ON CONFLICT (device_id, browser_id)
    DO UPDATE SET last_seen = NOW(), visit_count = device_fingerprints.visit_count + 1
  `, [deviceId, browserId, browserType, confidence]);

  res.json({ success: true });
});

// Check for suspicious activity
app.post('/api/check-fraud', async (req, res) => {
  const { deviceId, userId } = req.body;

  // Check if device is linked to multiple users
  const result = await db.query(`
    SELECT COUNT(DISTINCT user_id) as user_count
    FROM user_devices
    WHERE device_id = $1
  `, [deviceId]);

  const isSuspicious = result.rows[0].user_count > 3;

  res.json({
    suspicious: isSuspicious,
    userCount: result.rows[0].user_count
  });
});
```

### Next.js (App Router)

```typescript
// app/actions.ts (Server Action)
'use server';

import { db } from '@/lib/database';

export async function trackFingerprint(data: {
  deviceId: string;
  browserId: string;
  browserType: string;
}) {
  await db.deviceFingerprint.upsert({
    where: {
      deviceId_browserId: {
        deviceId: data.deviceId,
        browserId: data.browserId
      }
    },
    update: {
      lastSeen: new Date(),
      visitCount: { increment: 1 }
    },
    create: {
      deviceId: data.deviceId,
      browserId: data.browserId,
      browserType: data.browserType,
      lastSeen: new Date(),
      visitCount: 1
    }
  });
}

// app/components/FingerprintTracker.tsx
'use client';

import { useEffect } from 'react';
import Fingerprint from '@yourorg/device-fingerprint';
import { trackFingerprint } from '@/app/actions';

export function FingerprintTracker() {
  useEffect(() => {
    Fingerprint.get().then(result => {
      trackFingerprint({
        deviceId: result.device.id,
        browserId: result.browser.id,
        browserType: result.browser.type
      });
    });
  }, []);

  return null;
}
```

### CDN (Browser)

```html
<script src="https://unpkg.com/@yourorg/device-fingerprint/dist/fingerprint.min.js"></script>
<script>
  DeviceFingerprint.get().then(result => {
    console.log('Device:', result.device.id);
    console.log('Browser:', result.browser.id);

    // Send to backend
    fetch('/api/track', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(result)
    });
  });
</script>
```

### Python FastAPI Backend

```python
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from datetime import datetime
import psycopg2

app = FastAPI()

class FingerprintData(BaseModel):
    deviceId: str
    browserId: str
    browserType: str
    confidence: float

@app.post("/api/track-device")
async def track_device(data: FingerprintData):
    conn = psycopg2.connect(DATABASE_URL)
    cur = conn.cursor()

    cur.execute("""
        INSERT INTO device_fingerprints
        (device_id, browser_id, browser_type, confidence, last_seen)
        VALUES (%s, %s, %s, %s, %s)
        ON CONFLICT (device_id, browser_id)
        DO UPDATE SET last_seen = %s, visit_count = device_fingerprints.visit_count + 1
    """, (data.deviceId, data.browserId, data.browserType,
          data.confidence, datetime.now(), datetime.now()))

    conn.commit()
    cur.close()
    conn.close()

    return {"success": True}

@app.get("/api/device-info/{device_id}")
async def get_device_info(device_id: str):
    conn = psycopg2.connect(DATABASE_URL)
    cur = conn.cursor()

    cur.execute("""
        SELECT browser_id, browser_type, visit_count, last_seen
        FROM device_fingerprints
        WHERE device_id = %s
        ORDER BY last_seen DESC
    """, (device_id,))

    browsers = cur.fetchall()
    cur.close()
    conn.close()

    return {
        "deviceId": device_id,
        "browsers": [
            {
                "browserId": b[0],
                "browserType": b[1],
                "visitCount": b[2],
                "lastSeen": b[3]
            } for b in browsers
        ]
    }
```

## Database Schema

### PostgreSQL

```sql
-- Device fingerprints table
CREATE TABLE device_fingerprints (
  id SERIAL PRIMARY KEY,
  device_id VARCHAR(255) NOT NULL,
  browser_id VARCHAR(255) NOT NULL,
  browser_type VARCHAR(50),
  confidence DECIMAL(3,2),
  first_seen TIMESTAMP DEFAULT NOW(),
  last_seen TIMESTAMP DEFAULT NOW(),
  visit_count INTEGER DEFAULT 1,
  ip_address INET,
  user_agent TEXT,
  UNIQUE(device_id, browser_id)
);

CREATE INDEX idx_device_id ON device_fingerprints(device_id);
CREATE INDEX idx_browser_id ON device_fingerprints(browser_id);
CREATE INDEX idx_last_seen ON device_fingerprints(last_seen);

-- User-Device linking table
CREATE TABLE user_devices (
  id SERIAL PRIMARY KEY,
  user_id VARCHAR(255) NOT NULL,
  device_id VARCHAR(255) NOT NULL,
  browser_id VARCHAR(255),
  trust_score DECIMAL(3,2),
  is_trusted BOOLEAN DEFAULT false,
  linked_at TIMESTAMP DEFAULT NOW(),
  last_verified TIMESTAMP DEFAULT NOW(),
  UNIQUE(user_id, device_id, browser_id)
);

CREATE INDEX idx_user_id ON user_devices(user_id);
CREATE INDEX idx_user_device ON user_devices(user_id, device_id);

-- Fraud detection events
CREATE TABLE fraud_events (
  id SERIAL PRIMARY KEY,
  device_id VARCHAR(255) NOT NULL,
  user_id VARCHAR(255),
  event_type VARCHAR(50) NOT NULL, -- 'multi_account', 'new_device', 'suspicious_activity'
  severity VARCHAR(20), -- 'low', 'medium', 'high'
  details JSONB,
  created_at TIMESTAMP DEFAULT NOW()
);

CREATE INDEX idx_fraud_device ON fraud_events(device_id);
CREATE INDEX idx_fraud_user ON fraud_events(user_id);
CREATE INDEX idx_fraud_created ON fraud_events(created_at);
```

### MySQL

```sql
CREATE TABLE device_fingerprints (
  id INT AUTO_INCREMENT PRIMARY KEY,
  device_id VARCHAR(255) NOT NULL,
  browser_id VARCHAR(255) NOT NULL,
  browser_type VARCHAR(50),
  confidence DECIMAL(3,2),
  first_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  visit_count INT DEFAULT 1,
  ip_address VARCHAR(45),
  user_agent TEXT,
  UNIQUE KEY unique_device_browser (device_id, browser_id),
  INDEX idx_device_id (device_id),
  INDEX idx_last_seen (last_seen)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```

### MongoDB

```javascript
// device_fingerprints collection
db.device_fingerprints.createIndex({ "deviceId": 1, "browserId": 1 }, { unique: true });
db.device_fingerprints.createIndex({ "deviceId": 1 });
db.device_fingerprints.createIndex({ "lastSeen": -1 });

// Example document
{
  _id: ObjectId("..."),
  deviceId: "dev_xyz789...",
  browserId: "dev_xyz789_chrome_a1b2",
  browserType: "chrome",
  confidence: 0.92,
  firstSeen: ISODate("2025-01-15T10:30:00Z"),
  lastSeen: ISODate("2025-01-20T14:22:00Z"),
  visitCount: 47,
  ipAddresses: ["192.168.1.1", "10.0.0.1"],
  userAgent: "Mozilla/5.0..."
}

// user_devices collection
db.user_devices.createIndex({ "userId": 1, "deviceId": 1 });

{
  _id: ObjectId("..."),
  userId: "user123",
  deviceId: "dev_xyz789...",
  browsers: [
    {
      browserId: "dev_xyz789_chrome_a1b2",
      browserType: "chrome",
      trustScore: 0.95,
      linkedAt: ISODate("2025-01-15T10:30:00Z")
    },
    {
      browserId: "dev_xyz789_firefox_c3d4",
      browserType: "firefox",
      trustScore: 0.90,
      linkedAt: ISODate("2025-01-18T09:15:00Z")
    }
  ],
  isTrusted: true
}
```

## Advanced Integration Patterns

### Multi-Account Detection

```javascript
// Frontend
const result = await Fingerprint.get();

const response = await fetch('/api/check-multi-account', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    deviceId: result.device.id,
    userId: currentUser.id
  })
});

const { hasMultipleAccounts, accountCount } = await response.json();

if (hasMultipleAccounts && accountCount > 3) {
  showWarning('Multiple accounts detected on this device');
}

// Backend (Express)
app.post('/api/check-multi-account', async (req, res) => {
  const { deviceId, userId } = req.body;

  const accounts = await db.query(`
    SELECT DISTINCT user_id, COUNT(*) OVER() as total
    FROM user_devices
    WHERE device_id = $1
  `, [deviceId]);

  // Check if current user is already linked
  const isLinked = accounts.some(a => a.user_id === userId);

  if (!isLinked && accounts.length > 0) {
    // Log fraud event
    await db.query(`
      INSERT INTO fraud_events (device_id, user_id, event_type, severity, details)
      VALUES ($1, $2, 'multi_account', 'medium', $3)
    `, [deviceId, userId, JSON.stringify({ accountCount: accounts.length })]);
  }

  res.json({
    hasMultipleAccounts: accounts.length > 1,
    accountCount: accounts.length,
    requiresVerification: accounts.length > 3
  });
});
```

### Session Continuity Across Browsers

```javascript
// Frontend - Login page
async function handleLogin(email, password) {
  const fingerprint = await Fingerprint.get();

  const response = await fetch('/api/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      email,
      password,
      deviceId: fingerprint.device.id,
      browserId: fingerprint.browser.id,
      browserType: fingerprint.browser.type
    })
  });

  const { user, otherSessions } = await response.json();

  if (otherSessions.length > 0) {
    showNotification(
      `You have active sessions in ${otherSessions.map(s => s.browserType).join(', ')}`
    );
  }
}

// Backend
app.post('/api/login', async (req, res) => {
  const { email, password, deviceId, browserId, browserType } = req.body;

  // Authenticate user
  const user = await authenticateUser(email, password);
  if (!user) return res.status(401).json({ error: 'Invalid credentials' });

  // Find all active sessions on this device
  const sessions = await db.query(`
    SELECT DISTINCT browser_type, browser_id, last_seen
    FROM user_devices
    WHERE user_id = $1 AND device_id = $2
    ORDER BY last_seen DESC
  `, [user.id, deviceId]);

  // Link current browser
  await db.query(`
    INSERT INTO user_devices (user_id, device_id, browser_id, browser_type, trust_score)
    VALUES ($1, $2, $3, $4, 0.85)
    ON CONFLICT (user_id, device_id, browser_id)
    DO UPDATE SET last_verified = NOW()
  `, [user.id, deviceId, browserId, browserType]);

  res.json({
    user,
    otherSessions: sessions
      .filter(s => s.browser_id !== browserId)
      .map(s => ({
        browserType: s.browser_type,
        lastSeen: s.last_seen
      }))
  });
});
```

### Risk-Based Authentication

```javascript
// Frontend
async function attemptLogin(credentials) {
  const fingerprint = await Fingerprint.get();

  const response = await fetch('/api/auth/risk-check', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      ...credentials,
      deviceId: fingerprint.device.id,
      browserId: fingerprint.browser.id,
      confidence: fingerprint.confidence
    })
  });

  const { riskLevel, authMethods } = await response.json();

  switch (riskLevel) {
    case 'low':
      // Known device + browser - direct login
      return await login(credentials);

    case 'medium':
      // Known device, new browser - email code
      return await requestEmailCode(credentials);

    case 'high':
      // New device - full MFA
      return await requestMFACode(credentials);
  }
}

// Backend
app.post('/api/auth/risk-check', async (req, res) => {
  const { userId, deviceId, browserId, confidence } = req.body;

  // Check if device is known
  const deviceRecord = await db.query(`
    SELECT trust_score, is_trusted, last_verified
    FROM user_devices
    WHERE user_id = $1 AND device_id = $2 AND browser_id = $3
  `, [userId, deviceId, browserId]);

  let riskLevel = 'high';
  let authMethods = ['password', 'authenticator'];

  if (deviceRecord.rows.length > 0) {
    const device = deviceRecord.rows[0];
    const daysSinceVerified = (Date.now() - device.last_verified) / (1000 * 60 * 60 * 24);

    if (device.is_trusted && daysSinceVerified < 30) {
      riskLevel = 'low';
      authMethods = ['password'];
    } else {
      // Check if same device, different browser
      const sameDeviceOtherBrowser = await db.query(`
        SELECT COUNT(*) as count
        FROM user_devices
        WHERE user_id = $1 AND device_id = $2 AND is_trusted = true
      `, [userId, deviceId]);

      if (sameDeviceOtherBrowser.rows[0].count > 0) {
        riskLevel = 'medium';
        authMethods = ['password', 'email'];
      }
    }
  }

  res.json({ riskLevel, authMethods });
});
```

### Analytics Integration

```javascript
// Google Analytics 4
const fingerprint = await Fingerprint.get();

gtag('config', 'G-XXXXXXXXXX', {
  user_id: fingerprint.device.id,
  custom_map: {
    dimension1: 'device_id',
    dimension2: 'browser_type',
    dimension3: 'device_confidence'
  }
});

gtag('event', 'fingerprint_generated', {
  device_id: fingerprint.device.id,
  browser_type: fingerprint.browser.type,
  device_confidence: fingerprint.device.confidence
});

// Mixpanel
const fingerprint = await Fingerprint.get();

mixpanel.identify(fingerprint.device.id);
mixpanel.people.set({
  $device_id: fingerprint.device.id,
  $browser_id: fingerprint.browser.id,
  $browser_type: fingerprint.browser.type
});

mixpanel.track('Page View', {
  device_confidence: fingerprint.confidence,
  browser_type: fingerprint.browser.type
});

// Segment
const fingerprint = await Fingerprint.get();

analytics.identify(fingerprint.device.id, {
  deviceId: fingerprint.device.id,
  browserId: fingerprint.browser.id,
  browserType: fingerprint.browser.type,
  confidence: fingerprint.confidence
});

analytics.track('Fingerprint Generated', {
  source: fingerprint.source,
  confidence: fingerprint.confidence
});
```

## How It Works

| Technique | Accuracy Boost |
|-----------|----------------|
| Canvas + WebGL + Audio | 82-85% (base) |
| Component stability weighting | +2-3% |
| Fuzzy matching | +2-4% |
| Multi-layer persistence | +2-3% |
| Behavioral biometrics | +3-5% |
| **Total** | **90-95%** |

### Real-World Scenarios

| Scenario | Device ID | Browser ID | Reason |
|----------|-----------|------------|--------|
| Browser update (Chrome 120→121) | ✅ Same | ✅ Same | Device FP excludes browser version |
| User clears cache | ✅ Same | ✅ Same | Fingerprints regenerate identically |
| VPN enabled | ✅ Same | ✅ Same | IP not used |
| Resolution change (1080p→4K) | ✅ Same | ✅ Same | Device FP excludes screen dimensions |
| Timezone change (travel) | ✅ Same | ✅ Same | Device FP excludes timezone |
| Different browser (Chrome→Firefox) | ✅ Same | ❌ Different | Same device, different browser |
| Incognito mode | ✅ Same | ❌ Different | Hardware match, no Canvas/storage match |
| Multiple users, same PC | ✅ Same | ✅ Same | Differentiated via User Linking API |

## Browser Support

| Browser | Support | Notes |
|---------|---------|-------|
| Chrome 90+ | ✅ Full | Best accuracy |
| Firefox 88+ | ✅ Full | Good accuracy |
| Safari 14+ | ⚠️ Partial | Anti-fingerprinting limits accuracy |
| Edge 90+ | ✅ Full | Chromium-based |

## Privacy & GDPR

- ✅ Same-origin only (no cross-site tracking)
- ✅ Clear opt-out via `Fingerprint.clear()`
- ✅ Data minimization
- ✅ Include in privacy policy

**Privacy Policy Template:**
```
We use device fingerprinting for fraud prevention and analytics.
This collects technical information about your browser and device.
Data is not shared with third parties. Opt out by clearing browser data.
```

## Performance

| Operation | Time |
|-----------|------|
| First load | ~150ms |
| Cached load | ~2ms |
| With behavioral | ~10s |

See [PERFORMANCE.md](PERFORMANCE.md) for detailed benchmarks.

## Security

See [SECURITY.md](SECURITY.md) for:
- Threat model
- Vulnerability disclosure
- Security best practices

## Development

```bash
npm install       # Install dependencies
npm run build     # Build all formats
npm test          # Run tests
npm run benchmark # Performance tests
npm run docs      # Generate API docs
```

## Testing

```bash
npm test              # All tests
npm run test:coverage # Coverage report (80%+)
npm run test:watch    # Watch mode
```

## License

MIT - see [LICENSE](LICENSE)

## Links

- [API Documentation](https://yourorg.github.io/device-fingerprint)
- [Performance Analysis](PERFORMANCE.md)
- [Security Policy](SECURITY.md)
- [Contributing Guide](CONTRIBUTING.md)
- [Changelog](CHANGELOG.md)
- [GitHub Issues](https://github.com/yourorg/device-fingerprint/issues)

---

**Built for accurate, privacy-respectful device identification**