From 5b9aaab8476d99e5e36a8232d57345a775b02d88 Mon Sep 17 00:00:00 2001 From: jubnl Date: Tue, 25 Aug 2026 23:16:18 +0200 Subject: [PATCH] fix(client): mint upload idempotency keys via randomId() uploadQueue called crypto.randomUUID() bare, which is undefined on non-secure origins (plain-http localhost and LAN installs) and threw instead of uploading. Route it through randomId() like the other two idempotency-key producers so it falls back to getRandomValues, and update randomId's header comment to name all three producers. --- client/src/utils/randomId.ts | 5 +++-- client/src/utils/uploadQueue.ts | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/client/src/utils/randomId.ts b/client/src/utils/randomId.ts index f9ae2ea1c..b17e3f65b 100644 --- a/client/src/utils/randomId.ts +++ b/client/src/utils/randomId.ts @@ -1,8 +1,9 @@ /** * The one place idempotency keys are minted. * - * Both producers — the offline queue's mutation id and the axios interceptor's - * per-request key — end up in the same `X-Idempotency-Key` header, which the + * Every producer — the offline queue's mutation id, the axios interceptor's + * per-request key and the upload queue's per-file key — ends up in the same + * `X-Idempotency-Key` header, which the * server caches on (key, user_id, method, path). Two writes that share a key * therefore collapse into one: the second gets the first one's cached response * instead of being applied, and the user loses an edit with no error anywhere. diff --git a/client/src/utils/uploadQueue.ts b/client/src/utils/uploadQueue.ts index 721cc2d77..fa7bdb298 100644 --- a/client/src/utils/uploadQueue.ts +++ b/client/src/utils/uploadQueue.ts @@ -1,4 +1,5 @@ import type { AxiosProgressEvent } from 'axios' +import { randomId } from './randomId' export interface UploadProgress { done: number @@ -66,7 +67,7 @@ export async function uploadFilesResilient( const i = idx++ if (i >= files.length) break const file = files[i] - const idempotencyKey = crypto.randomUUID() + const idempotencyKey = randomId() loadedMap.set(i, 0) let items: T[] | null = null