diff --git a/web/src/utils/cameraUtil.ts b/web/src/utils/cameraUtil.ts index f85966660..6b5d9e584 100644 --- a/web/src/utils/cameraUtil.ts +++ b/web/src/utils/cameraUtil.ts @@ -1,20 +1,30 @@ /** * Generates a fixed-length hash from a camera name for use as a valid camera identifier. - * Used when user enters a display name with spaces or special characters. + * Works safely with Unicode input while outputting Latin-only identifiers. * * @param name - The original camera name/display name * @returns A valid camera identifier (lowercase, alphanumeric, max 8 chars) */ export function generateFixedHash(name: string): string { - const encoded = encodeURIComponent(name); - const base64 = btoa(encoded); + // Safely encode Unicode as UTF-8 bytes + const utf8Bytes = new TextEncoder().encode(name); + + // Convert to base64 manually + let binary = ""; + for (const byte of utf8Bytes) { + binary += String.fromCharCode(byte); + } + const base64 = btoa(binary); + + // Strip out non-alphanumeric characters and truncate const cleanHash = base64.replace(/[^a-zA-Z0-9]/g, "").substring(0, 8); + return `cam_${cleanHash.toLowerCase()}`; } /** * Checks if a string is a valid camera name identifier. - * Valid camera names contain only letters, numbers, underscores, and hyphens. + * Valid camera names contain only ASCII letters, numbers, underscores, and hyphens. * * @param name - The camera name to validate * @returns True if the name is valid, false otherwise @@ -34,7 +44,7 @@ export function processCameraName(userInput: string): { finalCameraName: string; friendlyName?: string; } { - const normalizedInput = userInput.replace(/\s+/g, "_"); + const normalizedInput = userInput.replace(/\s+/g, "_").toLowerCase(); if (isValidCameraName(normalizedInput)) { return {