fix hashing function to avoid collisions

This commit is contained in:
Josh Hawkins 2025-11-07 09:54:05 -06:00
parent ab3ded38e6
commit d76335096a

View File

@ -21,20 +21,30 @@ export const capitalizeAll = (text: string): string => {
* @returns A valid camera identifier (lowercase, alphanumeric, max 8 chars) * @returns A valid camera identifier (lowercase, alphanumeric, max 8 chars)
*/ */
export function generateFixedHash(name: string, prefix: string = "id"): string { export function generateFixedHash(name: string, prefix: string = "id"): string {
// Safely encode Unicode as UTF-8 bytes // Use the full UTF-8 bytes of the name and compute an FNV-1a 32-bit hash.
// This is deterministic, fast, works with Unicode and avoids collisions from
// simple truncation of base64 output.
const utf8Bytes = new TextEncoder().encode(name); const utf8Bytes = new TextEncoder().encode(name);
// Convert to base64 manually // FNV-1a 32-bit hash algorithm
let binary = ""; let hash = 0x811c9dc5; // FNV offset basis
for (const byte of utf8Bytes) { for (let i = 0; i < utf8Bytes.length; i++) {
binary += String.fromCharCode(byte); hash ^= utf8Bytes[i];
// Multiply by FNV prime (0x01000193) with 32-bit overflow
hash = (hash >>> 0) * 0x01000193;
// Ensure 32-bit unsigned integer
hash >>>= 0;
} }
const base64 = btoa(binary);
// Strip out non-alphanumeric characters and truncate // Convert to an 8-character lowercase hex string
const cleanHash = base64.replace(/[^a-zA-Z0-9]/g, "").substring(0, 8); const hashHex = (hash >>> 0).toString(16).padStart(8, "0").toLowerCase();
return `${prefix}_${cleanHash.toLowerCase()}`; // Ensure the first character is a letter to avoid an identifier that's purely
// numeric (isValidId forbids all-digit IDs). If it starts with a digit,
// replace with 'a'. This is extremely unlikely but a simple safeguard.
const safeHash = /^[0-9]/.test(hashHex[0]) ? `a${hashHex.slice(1)}` : hashHex;
return `${prefix}_${safeHash}`;
} }
/** /**