@@ -16,37 +16,60 @@ const MIN_UUID_V4 = '00000000-0000-4000-8000-000000000000';
1616const MAX_UUID_V4 = 'ffffffff-ffff-4fff-bfff-ffffffffffff' ;
1717const MIN_UUID_V4_BIGINT = uuidStringToBigInt ( MIN_UUID_V4 ) ;
1818const MAX_UUID_V4_BIGINT = uuidStringToBigInt ( MAX_UUID_V4 ) ;
19- const UUID_V4_VALUE_RANGE_BIGINT =
20- MAX_UUID_V4_BIGINT - MIN_UUID_V4_BIGINT ;
19+ const UUID_V4_VALUE_RANGE_BIGINT = MAX_UUID_V4_BIGINT - MIN_UUID_V4_BIGINT ;
2120
2221/**
2322 * Generates a deterministic random number between 0 and 1 based on a metaMetricsId.
2423 * This is useful for A/B testing and feature flag rollouts where we want
2524 * consistent group assignment for the same user.
2625 * @param metaMetricsId - The unique identifier used to generate the deterministic random number. Must be either:
27- * - A UUIDv4 string (e.g., '123e4567-e89b-12d3-a456-426614174000')
28- * - A hex string with '0x' prefix (e.g., '0x86bacb9b2bf9a7e8d2b147eadb95ac9aaa26842327cd24afc8bd4b3c1d136420')
26+ * - A UUIDv4 string (e.g., '123e4567-e89b-12d3-a456-426614174000'
27+ * - A hex string with '0x' prefix (e.g., '0x86bacb9b2bf9a7e8d2b147eadb95ac9aaa26842327cd24afc8bd4b3c1d136420')
2928 * @returns A number between 0 and 1, deterministically generated from the input ID.
30- * The same input will always produce the same output.
29+ * The same input will always produce the same output.
3130 */
3231export function generateDeterministicRandomNumber (
3332 metaMetricsId : string ,
3433) : number {
34+ if ( ! metaMetricsId ) {
35+ throw new Error ( 'MetaMetrics ID cannot be empty' ) ;
36+ }
37+
3538 let idValue : bigint ;
3639 let maxValue : bigint ;
40+
3741 // uuidv4 format
38- if ( uuidValidate ( metaMetricsId ) && uuidVersion ( metaMetricsId ) === 4 ) {
39- // Normalize the UUIDv4 range to start from 0 by subtracting MIN_UUID_V4_BIGINT.
40- // This ensures uniform distribution across the entire range, since UUIDv4
41- // has restricted bits for version (4) and variant (8-b) that would otherwise skew the distribution
42+ if ( uuidValidate ( metaMetricsId ) ) {
43+ if ( uuidVersion ( metaMetricsId ) !== 4 ) {
44+ throw new Error (
45+ `Invalid UUID version. Expected v4, got v${ uuidVersion ( metaMetricsId ) } ` ,
46+ ) ;
47+ }
4248 idValue = uuidStringToBigInt ( metaMetricsId ) - MIN_UUID_V4_BIGINT ;
4349 maxValue = UUID_V4_VALUE_RANGE_BIGINT ;
4450 } else {
4551 // hex format with 0x prefix
52+ if ( ! metaMetricsId . startsWith ( '0x' ) ) {
53+ throw new Error ( 'Hex ID must start with 0x prefix' ) ;
54+ }
55+
4656 const cleanId = metaMetricsId . slice ( 2 ) ;
57+ const EXPECTED_HEX_LENGTH = 64 ; // 32 bytes = 64 hex characters
58+
59+ if ( cleanId . length !== EXPECTED_HEX_LENGTH ) {
60+ throw new Error (
61+ `Invalid hex ID length. Expected ${ EXPECTED_HEX_LENGTH } characters, got ${ cleanId . length } ` ,
62+ ) ;
63+ }
64+
65+ if ( ! / ^ [ 0 - 9 a - f ] + $ / iu. test ( cleanId ) ) {
66+ throw new Error ( 'Hex ID contains invalid characters' ) ;
67+ }
68+
4769 idValue = BigInt ( `0x${ cleanId } ` ) ;
4870 maxValue = BigInt ( `0x${ 'f' . repeat ( cleanId . length ) } ` ) ;
4971 }
72+
5073 // Use BigInt division first, then convert to number to maintain precision
5174 return Number ( ( idValue * BigInt ( 1_000_000 ) ) / maxValue ) / 1_000_000 ;
5275}
0 commit comments