Add a local-dev fallback for download device IDs

crypto.randomUUID is unavailable on HTTP, which breaks download counting during local development. Production still uses the native UUID.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jared Schoeny
2026-08-20 11:12:29 -06:00
parent 3ac3a9c4b7
commit 6d2a61f91c
3 changed files with 41 additions and 12 deletions

View File

@@ -13,6 +13,7 @@ import {
isArchiveFile,
isAnyRomExtension,
} from "@/utils/romFile";
import { getOrCreateDeviceId } from "@/utils/deviceId";
import { collectFetchDiagnostics, HTTPError, runConnectivityProbes } from "@/utils/patches/download-telemetry";
import type { SelectablePatch } from "@/types/patcher";
import { applyPatch, patchFormatFromFilename, type PatchFormat } from "@/utils/patching";
@@ -436,12 +437,8 @@ const HackActions: React.FC<HackActionsProps> = ({
try {
const countPatchId = selectedPatch?.id ?? patchId;
if (countPatchId != null) {
const key = "deviceId";
let deviceId = localStorage.getItem(key);
if (!deviceId) {
deviceId = crypto.randomUUID();
localStorage.setItem(key, deviceId);
}
const deviceId = getOrCreateDeviceId();
if (!deviceId) return;
// Defer count update to avoid Safari cancelling the request
setTimeout(async () => {
const deviceIdObscured = deviceId.split("-");

View File

@@ -7,6 +7,7 @@ import { FiEdit2, FiEdit, FiX } from "react-icons/fi";
import VersionActions from "@/components/Hack/VersionActions";
import type { PatchesDownloadPermission } from "@/components/Hack/DownloadPermissionSettings";
import { updatePatchChangelog, updatePatchVersion, getPatchDownloadUrl, updatePatchDownloadCount } from "@/app/hack/[slug]/actions";
import { getOrCreateDeviceId } from "@/utils/deviceId";
import { useRouter } from "next/navigation";
import { createClient } from "@/utils/supabase/client";
import type { Patch } from "@/components/Hack/PatcherVersionManager";
@@ -38,12 +39,8 @@ function PublicPatchDownloadButton({ patchId }: { patchId: number }) {
window.open(result.url, "_blank");
// Best-effort log download for counting
try {
const key = "deviceId";
let deviceId = localStorage.getItem(key);
if (!deviceId) {
deviceId = crypto.randomUUID();
localStorage.setItem(key, deviceId);
}
const deviceId = getOrCreateDeviceId();
if (!deviceId) return;
setTimeout(async () => {
const deviceIdObscured = deviceId.split("-");
const countResult = await updatePatchDownloadCount(patchId, deviceIdObscured);

35
src/utils/deviceId.ts Normal file
View File

@@ -0,0 +1,35 @@
const DEVICE_ID_STORAGE_KEY = "deviceId";
function createDeviceId(): string | null {
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
return crypto.randomUUID();
}
// Insecure-context local dev only. Must stay five hyphen-separated segments.
if (process.env.NODE_ENV === "development") {
return `dev-${Date.now()}-0-0-0`;
}
return null;
}
function isDevFallbackId(id: string): boolean {
return id.startsWith("dev-");
}
export function getOrCreateDeviceId(): string | null {
const existing = localStorage.getItem(DEVICE_ID_STORAGE_KEY);
const isStaleDevFallback =
!!existing && isDevFallbackId(existing) && process.env.NODE_ENV !== "development";
if (existing && !isStaleDevFallback) return existing;
const created = createDeviceId();
if (created) {
localStorage.setItem(DEVICE_ID_STORAGE_KEY, created);
return created;
}
if (isStaleDevFallback) {
localStorage.removeItem(DEVICE_ID_STORAGE_KEY);
}
return null;
}