@@ -24,12 +24,26 @@ export const getABTestAssignment = async (
24
24
25
25
if ( ! testConfig || ! testConfig . enabled ) return null
26
26
27
- // Create deterministic assignment using IP + User-Agent fingerprint
27
+ // Create deterministic assignment using enhanced fingerprint
28
28
const headers = await import ( "next/headers" ) . then ( ( m ) => m . headers ( ) )
29
- const userAgent = headers . get ( "user-agent" ) || ""
29
+
30
+ // Get IP and user agent (primary identifier)
30
31
const forwardedFor =
31
32
headers . get ( "x-forwarded-for" ) || headers . get ( "x-real-ip" ) || "unknown"
32
- const fingerprint = `${ forwardedFor } -${ userAgent } `
33
+ const userAgent = headers . get ( "user-agent" ) || ""
34
+
35
+ // Add privacy-preserving entropy sources
36
+ const acceptLanguage = headers . get ( "accept-language" ) || ""
37
+ const acceptEncoding = headers . get ( "accept-encoding" ) || ""
38
+
39
+ // Create enhanced fingerprint with more entropy
40
+ const fingerprint = [
41
+ forwardedFor ,
42
+ userAgent ,
43
+ acceptLanguage ,
44
+ acceptEncoding ,
45
+ testKey , // Include test key to ensure different tests get different distributions
46
+ ] . join ( "|" )
33
47
34
48
const variantIndex = assignVariantIndexDeterministic ( testConfig , fingerprint )
35
49
const variant = testConfig . variants [ variantIndex ]
@@ -56,23 +70,22 @@ const assignVariantIndexDeterministic = (
56
70
// Handle case where total weight is 0
57
71
if ( totalWeight === 0 ) return 0
58
72
59
- // Use a better hash function for more uniform distribution
60
- // This is a simple implementation of djb2 hash algorithm
61
- let hash = 5381
73
+ // Hash function to evenly distribute fingerprints amongst assignments
74
+ // Implementation of FNV-1a hash algorithm
75
+ let hash = 2166136261 // FNV offset basis
62
76
for ( let i = 0 ; i < fingerprint . length ; i ++ ) {
63
- hash = ( hash << 5 ) + hash + fingerprint . charCodeAt ( i )
77
+ hash ^= fingerprint . charCodeAt ( i ) // XOR
78
+ hash = ( hash * 16777619 ) >>> 0 // FNV prime, ensure 32-bit unsigned
64
79
}
65
80
66
- // Ensure positive value and create uniform distribution
67
- const normalized = Math . abs ( hash ) / 0x7fffffff // Max 32-bit signed int
81
+ // Convert to uniform distribution [0, 1)
82
+ const normalized = hash / 0x100000000 // 2^32 for full 32-bit range
68
83
const weighted = normalized * totalWeight
69
84
70
85
let cumulativeWeight = 0
71
86
for ( let i = 0 ; i < config . variants . length ; i ++ ) {
72
87
cumulativeWeight += config . variants [ i ] . weight
73
- if ( weighted <= cumulativeWeight ) {
74
- return i
75
- }
88
+ if ( weighted <= cumulativeWeight ) return i
76
89
}
77
90
78
91
return 0
0 commit comments