Skip to content

Commit 58efb81

Browse files
committed
fix: switch to node-machine-id, which is supposedly faster and just as accurate
1 parent aecfd7a commit 58efb81

5 files changed

Lines changed: 111 additions & 106 deletions

File tree

bun.lock

Lines changed: 1 addition & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

npm-app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
"dependencies": {
4343
"@codebuff/code-map": "workspace:*",
4444
"@codebuff/common": "workspace:*",
45-
"@fingerprintjs/fingerprintjs": "^4.6.2",
4645
"@types/diff": "5.2.1",
4746
"@types/micromatch": "^4.0.9",
4847
"@vscode/ripgrep": "1.15.9",
@@ -56,6 +55,7 @@
5655
"lodash": "*",
5756
"micromatch": "^4.0.8",
5857
"nanoid": "5.0.7",
58+
"node-machine-id": "^1.1.12",
5959
"onetime": "5.1.2",
6060
"picocolors": "1.1.0",
6161
"pino": "9.4.0",

npm-app/src/__tests__/fingerprint-integration.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { calculateFingerprint } from '../fingerprint'
22

33
describe('Fingerprint Integration Test', () => {
4-
it('should generate fingerprints and test both enhanced and legacy modes', async () => {
5-
console.log('🔍 Testing enhanced fingerprinting implementation...')
4+
it('should generate fingerprints and test both enhanced CLI and legacy modes', async () => {
5+
console.log('🔍 Testing enhanced CLI fingerprinting implementation...')
66

77
// Test multiple fingerprint generations
88
const results = []
@@ -14,7 +14,7 @@ describe('Fingerprint Integration Test', () => {
1414
results.push({
1515
fingerprint,
1616
duration,
17-
isEnhanced: fingerprint.startsWith('fp-'),
17+
isEnhanced: fingerprint.startsWith('enhanced-') || fingerprint.startsWith('fp-'),
1818
isLegacy: fingerprint.startsWith('legacy-')
1919
})
2020

@@ -59,5 +59,5 @@ describe('Fingerprint Integration Test', () => {
5959

6060
// At least one should succeed
6161
expect(results.length).toBeGreaterThan(0)
62-
}, 30000) // 30 second timeout for browser operations
62+
}, 10000) // 10 second timeout for CLI operations
6363
})

npm-app/src/fingerprint.ts

Lines changed: 101 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
// Enhanced fingerprinting with FingerprintJS and backward compatibility
1+
// Enhanced fingerprinting with CLI-only approach and backward compatibility
22
// Modified from: https://github.com/andsmedeiros/hw-fingerprint
33

44
import { createHash, randomBytes } from 'node:crypto'
5+
import { networkInterfaces } from 'node:os'
56
import {
67
bios,
78
cpu,
@@ -11,23 +12,82 @@ import {
1112
system,
1213
// @ts-ignore
1314
} from 'systeminformation'
15+
import { machineId } from 'node-machine-id'
1416

15-
import { findChrome } from './browser-runner'
17+
import { detectShell } from './utils/detect-shell'
18+
import { getSystemInfo } from './utils/system-info'
1619
import { logger } from './utils/logger'
1720

18-
// Type declaration for FingerprintJS result
19-
declare global {
20-
interface Window {
21-
fingerprintResult?: {
22-
visitorId: string
23-
confidence: { score: number }
24-
components: Record<string, any>
25-
}
26-
}
21+
// Enhanced CLI fingerprint implementation using multiple Node.js data sources
22+
const getEnhancedFingerprintInfo = async () => {
23+
// Get essential system information efficiently
24+
const [
25+
systemInfo,
26+
cpuInfo,
27+
osInfo_,
28+
machineIdValue,
29+
systemInfoBasic,
30+
shell,
31+
networkInfo
32+
] = await Promise.all([
33+
system(),
34+
cpu(),
35+
osInfo(),
36+
machineId().catch(() => 'unknown'),
37+
getSystemInfo(),
38+
detectShell(),
39+
Promise.resolve(networkInterfaces())
40+
])
41+
42+
// Extract MAC addresses for additional uniqueness
43+
const macAddresses = Object.values(networkInfo)
44+
.flat()
45+
.filter(iface => iface && !iface.internal && iface.mac && iface.mac !== '00:00:00:00:00:00')
46+
.map(iface => iface!.mac)
47+
.sort()
48+
49+
return {
50+
// Hardware identifiers
51+
system: {
52+
manufacturer: systemInfo.manufacturer,
53+
model: systemInfo.model,
54+
serial: systemInfo.serial,
55+
uuid: systemInfo.uuid,
56+
},
57+
cpu: {
58+
manufacturer: cpuInfo.manufacturer,
59+
brand: cpuInfo.brand,
60+
cores: cpuInfo.cores,
61+
physicalCores: cpuInfo.physicalCores,
62+
},
63+
os: {
64+
platform: osInfo_.platform,
65+
distro: osInfo_.distro,
66+
arch: osInfo_.arch,
67+
hostname: osInfo_.hostname,
68+
},
69+
// CLI-specific identifiers
70+
runtime: {
71+
nodeVersion: systemInfoBasic.nodeVersion,
72+
platform: systemInfoBasic.platform,
73+
arch: systemInfoBasic.arch,
74+
shell,
75+
cpuCount: systemInfoBasic.cpus,
76+
},
77+
// Network identifiers
78+
network: {
79+
macAddresses,
80+
interfaceCount: Object.keys(networkInfo).length,
81+
},
82+
// Machine ID (OS-specific unique identifier)
83+
machineId: machineIdValue,
84+
// Timestamp for version tracking
85+
fingerprintVersion: '2.0',
86+
} as Record<string, any>
2787
}
2888

2989
// Legacy fingerprint implementation (for backward compatibility)
30-
const getFingerprintInfo = async () => {
90+
const getLegacyFingerprintInfo = async () => {
3191
const { manufacturer, model, serial, uuid } = await system()
3292
const { vendor, version: biosVersion, releaseDate } = await bios()
3393
const { manufacturer: cpuManufacturer, brand, speed, cores } = await cpu()
@@ -70,111 +130,56 @@ const getFingerprintInfo = async () => {
70130
} as Record<string, any>
71131
}
72132

73-
// Legacy implementation with random suffix (still needed for collision avoidance)
74-
async function calculateLegacyFingerprint() {
75-
const fingerprintInfo = await getFingerprintInfo()
76-
const fingerprintString = JSON.stringify(fingerprintInfo)
77-
const fingerprintHash = createHash('sha256')
78-
.update(fingerprintString)
79-
.digest()
80-
.toString('base64url')
81-
82-
// Add 8 random characters to make the fingerprint unique even on identical hardware
83-
const randomSuffix = randomBytes(6).toString('base64url').substring(0, 8)
84-
85-
return `legacy-${fingerprintHash}-${randomSuffix}`
86-
}
87-
88-
// Enhanced FingerprintJS implementation using headless browser
133+
// Enhanced CLI-only fingerprint (deterministic, no browser required)
89134
async function calculateEnhancedFingerprint(): Promise<string> {
90135
try {
91-
const puppeteer = await import('puppeteer-core')
92-
93-
const browser = await puppeteer.default.launch({
94-
headless: true,
95-
args: ['--no-sandbox', '--disable-setuid-sandbox'],
96-
executablePath: findChrome(),
97-
})
98-
99-
const page = await browser.newPage()
100-
101-
// Create a minimal HTML page with FingerprintJS
102-
const html = `
103-
<!DOCTYPE html>
104-
<html>
105-
<head>
106-
<script src="https://cdn.jsdelivr.net/npm/@fingerprintjs/fingerprintjs@4/dist/fp.min.js"></script>
107-
</head>
108-
<body>
109-
<script>
110-
(async () => {
111-
// Initialize FingerprintJS
112-
const fp = await FingerprintJS.load()
113-
114-
// Get the visitor identifier
115-
const result = await fp.get()
116-
117-
// Make the result available to Node.js
118-
window.fingerprintResult = {
119-
visitorId: result.visitorId,
120-
confidence: result.confidence,
121-
components: result.components
122-
}
123-
})()
124-
</script>
125-
</body>
126-
</html>
127-
`
128-
129-
await page.setContent(html)
130-
131-
// Wait for FingerprintJS to complete
132-
await page.waitForFunction(() => window.fingerprintResult, { timeout: 10000 })
133-
134-
// Extract the fingerprint result
135-
const result = await page.evaluate(() => window.fingerprintResult)
136-
137-
await browser.close()
138-
139-
// Combine FingerprintJS result with system info for enhanced uniqueness
140-
const systemInfo = await getFingerprintInfo()
141-
const combinedData = {
142-
fingerprintjs: result!.visitorId,
143-
confidence: result!.confidence,
144-
system: systemInfo,
145-
}
146-
147-
const combinedString = JSON.stringify(combinedData)
148-
const combinedHash = createHash('sha256')
149-
.update(combinedString)
136+
const fingerprintInfo = await getEnhancedFingerprintInfo()
137+
const fingerprintString = JSON.stringify(fingerprintInfo)
138+
const fingerprintHash = createHash('sha256')
139+
.update(fingerprintString)
150140
.digest()
151141
.toString('base64url')
152-
153-
// No random suffix needed - FingerprintJS provides sufficient uniqueness
154-
return `fp-${combinedHash}`
142+
143+
// No random suffix needed - comprehensive system data provides sufficient uniqueness
144+
return `enhanced-${fingerprintHash}`
155145
} catch (error) {
156146
logger.warn(
157147
{
158148
errorMessage: error instanceof Error ? error.message : String(error),
159149
fingerprintType: 'enhanced_failed',
160150
},
161-
'Enhanced fingerprinting failed, falling back to legacy'
151+
'Enhanced CLI fingerprinting failed, falling back to legacy'
162152
)
163153
throw error
164154
}
165155
}
166156

167-
// Main fingerprint function with hybrid approach
157+
// Legacy implementation with random suffix (still needed for collision avoidance)
158+
async function calculateLegacyFingerprint() {
159+
const fingerprintInfo = await getLegacyFingerprintInfo()
160+
const fingerprintString = JSON.stringify(fingerprintInfo)
161+
const fingerprintHash = createHash('sha256')
162+
.update(fingerprintString)
163+
.digest()
164+
.toString('base64url')
165+
166+
// Add 8 random characters to make the fingerprint unique even on identical hardware
167+
const randomSuffix = randomBytes(6).toString('base64url').substring(0, 8)
168+
169+
return `legacy-${fingerprintHash}-${randomSuffix}`
170+
}
171+
172+
// Main fingerprint function with CLI-only approach
168173
export async function calculateFingerprint(): Promise<string> {
169174
try {
170-
// Try enhanced fingerprinting first
175+
// Try enhanced CLI fingerprinting first
171176
const fingerprint = await calculateEnhancedFingerprint()
172177
logger.info(
173178
{
174-
fingerprintType: 'enhanced',
179+
fingerprintType: 'enhanced_cli',
175180
fingerprintId: fingerprint,
176181
},
177-
'Enhanced fingerprint generated successfully'
182+
'Enhanced CLI fingerprint generated successfully'
178183
)
179184
return fingerprint
180185
} catch (enhancedError) {
@@ -183,7 +188,7 @@ export async function calculateFingerprint(): Promise<string> {
183188
errorMessage: enhancedError instanceof Error ? enhancedError.message : String(enhancedError),
184189
fingerprintType: 'enhanced_failed_fallback',
185190
},
186-
'Enhanced fingerprinting failed, using legacy fallback'
191+
'Enhanced CLI fingerprinting failed, using legacy fallback'
187192
)
188193

189194
try {

npm-app/src/utils/analytics.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,11 @@ export function identifyUserWithFingerprint(
129129
// Enhanced properties with fingerprint metadata
130130
const enhancedProperties = {
131131
...properties,
132-
fingerprintType: properties?.fingerprintId?.startsWith('fp-') ? 'enhanced' :
132+
fingerprintType: properties?.fingerprintId?.startsWith('enhanced-') ? 'enhanced_cli' :
133+
properties?.fingerprintId?.startsWith('fp-') ? 'enhanced_browser' :
133134
properties?.fingerprintId?.startsWith('legacy-') ? 'legacy' : 'unknown',
134-
hasEnhancedFingerprint: properties?.fingerprintId?.startsWith('fp-') || false,
135+
hasEnhancedFingerprint: properties?.fingerprintId?.startsWith('enhanced-') ||
136+
properties?.fingerprintId?.startsWith('fp-') || false,
135137
}
136138

137139
if (process.env.NEXT_PUBLIC_CB_ENVIRONMENT !== 'prod') {

0 commit comments

Comments
 (0)