fix(m3): uuid secure-context fallback + middle-tier SKU business rules
- frontend: uuid() helper (crypto.randomUUID -> getRandomValues) for plain-HTTP LAN UAT - slabs.py: empty SKU at create -> NULL (no-SKU-yet is transient, filled in Review) - review.py: ready-gate enforces REVIEW-45 (SKU 3-24 alnum+hyphen) in the middle tier - DB CHECK stays as backstop only (referential/structural), per layering principle
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount, onDestroy } from 'svelte';
|
||||
import { supported } from './browser';
|
||||
import { supported, uuid } from './browser';
|
||||
import { createCaptureSession, type CaptureSession } from './capture/session';
|
||||
import type { Capture, Measures } from './capture/capture.types';
|
||||
import { eighthsToIn } from './capture/capture.types';
|
||||
@@ -89,7 +89,7 @@
|
||||
saveError = '';
|
||||
startSaving();
|
||||
try {
|
||||
const id = crypto.randomUUID();
|
||||
const id = uuid();
|
||||
await createSlab({
|
||||
id,
|
||||
sku: '',
|
||||
@@ -111,7 +111,7 @@
|
||||
for (let i = 0; i < pngItems.length; i++) {
|
||||
const it = pngItems[i];
|
||||
await uploadPhoto(id, {
|
||||
photoId: crypto.randomUUID(),
|
||||
photoId: uuid(),
|
||||
seq: i + 1,
|
||||
isCalibration: it.role === 'calibration',
|
||||
original: it.blob,
|
||||
|
||||
@@ -24,3 +24,25 @@ export function supported(): string | null {
|
||||
if (!isIOS && !isAndroid && (isChrome || isEdge || isFirefox)) return null;
|
||||
return 'unsupported browser';
|
||||
}
|
||||
|
||||
// v4 UUID. crypto.randomUUID() is only guaranteed on secure contexts
|
||||
// (HTTPS/localhost); the UAT host is plain HTTP on a LAN IP, where it is
|
||||
// undefined. Fall back to crypto.getRandomValues (available on HTTP) so the
|
||||
// client-generated ids (APP-20) work in both contexts.
|
||||
export function uuid(): string {
|
||||
const c = globalThis.crypto;
|
||||
if (c && typeof c.randomUUID === 'function') return c.randomUUID();
|
||||
if (c && typeof c.getRandomValues === 'function') {
|
||||
const b = new Uint8Array(16);
|
||||
c.getRandomValues(b);
|
||||
b[6] = (b[6] & 0x0f) | 0x40; // version 4
|
||||
b[8] = (b[8] & 0x3f) | 0x80; // variant 10
|
||||
const h = Array.from(b, (x) => x.toString(16).padStart(2, '0')).join('');
|
||||
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
|
||||
}
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (ch) => {
|
||||
const r = (Math.random() * 16) | 0;
|
||||
const v = ch === 'x' ? r : (r & 0x3) | 0x8;
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { uuid } from '../../browser';
|
||||
|
||||
let slabId = '';
|
||||
let photos: {
|
||||
@@ -38,7 +39,7 @@
|
||||
busy = true;
|
||||
message = '';
|
||||
const form = new FormData();
|
||||
form.append('photo_id', photoId || crypto.randomUUID());
|
||||
form.append('photo_id', photoId || uuid());
|
||||
form.append('is_calibration', isCalibration ? '1' : '0');
|
||||
form.append('original', originalFile, originalFile.name);
|
||||
if (processedFile) form.append('processed', processedFile, processedFile.name);
|
||||
|
||||
Reference in New Issue
Block a user