Skip to content

Commit 323b79d

Browse files
author
Matthias Kastner
committed
fix(client): avoid crypto.randomUUID() TypeError by adding uuid helper
Add a small, robust UUID helper and replace direct uses of crypto.randomUUID() in the client to avoid a runtime TypeError in browsers/environments that do not implement crypto.randomUUID which caused the frontend to crash and show the "Browser not supported / JavaScript changes.
1 parent bf51a6d commit 323b79d

3 files changed

Lines changed: 28 additions & 2 deletions

File tree

client/src/lib/components/Toggle.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
label: string;
44
checked?: boolean;
55
}
6+
import { uuid } from '$lib/uuid';
67
78
let { label, checked = $bindable(), class: extraClass = '', ...rest }: Props = $props();
89
9-
const id = `toggle-${crypto.randomUUID()}`;
10+
const id = `toggle-${uuid()}`;
1011
</script>
1112

1213
<div class="flex items-center gap-2 {extraClass}">

client/src/lib/utils.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { uuid } from './uuid';
2+
13
declare const umami: {
24
track: (event_name: string, data?: Record<string, any>) => void;
35
identify: (unique_id: string) => void;
@@ -11,7 +13,7 @@ export function initializeAnalytics(): void {
1113
const umamiIdKey = 'mvw_uuid';
1214
let uniqueId = localStorage.getItem(umamiIdKey);
1315
if (!uniqueId) {
14-
uniqueId = crypto.randomUUID();
16+
uniqueId = uuid();
1517
localStorage.setItem(umamiIdKey, uniqueId);
1618
}
1719
umami.identify(uniqueId);

client/src/lib/uuid.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
export function uuid(): string {
2+
if (typeof globalThis !== 'undefined' && globalThis.crypto && typeof globalThis.crypto.randomUUID === 'function') {
3+
return globalThis.crypto.randomUUID();
4+
}
5+
6+
if (typeof globalThis !== 'undefined' && globalThis.crypto && typeof globalThis.crypto.getRandomValues === 'function') {
7+
const bytes = globalThis.crypto.getRandomValues(new Uint8Array(16));
8+
// RFC4122 v4
9+
bytes[6] = (bytes[6] & 0x0f) | 0x40;
10+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
11+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
12+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
13+
}
14+
15+
// Last-resort fallback (not cryptographically secure)
16+
const hexChars = '0123456789abcdef';
17+
const s: string[] = new Array(36);
18+
for (let i = 0; i < 36; i++) s[i] = hexChars[(Math.random() * 16) | 0];
19+
s[14] = '4';
20+
s[19] = hexChars[(parseInt(s[19], 16) & 0x3) | 0x8];
21+
s[8] = s[13] = s[18] = s[23] = '-';
22+
return s.join('');
23+
}

0 commit comments

Comments
 (0)