mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-21 14:36:31 +00:00
5b9aaab847
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.
41 lines
2.0 KiB
TypeScript
41 lines
2.0 KiB
TypeScript
/**
|
|
* The one place idempotency keys are minted.
|
|
*
|
|
* 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.
|
|
*
|
|
* `crypto.randomUUID` needs a secure context, and TREK's own quickstart is plain
|
|
* http://localhost:3000 with LAN installs on http://192.168.x.x — so on the
|
|
* deployments the README documents, randomUUID is undefined and the fallback is
|
|
* what actually runs. It has to be a real one. `getRandomValues` has no
|
|
* secure-context requirement and covers that gap; the Math.random rung below it
|
|
* exists only for a runtime with no Web Crypto at all.
|
|
*
|
|
* Lives in its own module rather than next to either caller because
|
|
* sync/mutationQueue imports api/client, so the dependency can only go one way.
|
|
*/
|
|
export function randomId(): string {
|
|
const c = globalThis.crypto
|
|
if (c && typeof c.randomUUID === 'function') return c.randomUUID()
|
|
|
|
if (c && typeof c.getRandomValues === 'function') {
|
|
const bytes = new Uint8Array(16)
|
|
c.getRandomValues(bytes)
|
|
// RFC 4122 version + variant bits, so the value still reads as a v4 UUID.
|
|
bytes[6] = (bytes[6] & 0x0f) | 0x40
|
|
bytes[8] = (bytes[8] & 0x3f) | 0x80
|
|
const hex = Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('')
|
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`
|
|
}
|
|
|
|
// Padded on purpose: `Math.random().toString(36).slice(2)` is not
|
|
// length-stable — 0.5 yields a single character and 0 yields the empty
|
|
// string, which the server reads as "no key" and skips deduplication for.
|
|
const rand = () => Math.random().toString(36).slice(2).padStart(10, '0').slice(0, 10)
|
|
return `${Date.now().toString(36)}-${rand()}-${rand()}`
|
|
}
|