Files
SlabUploader/frontend/src/browser.ts
T
Ty e36979617b 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
2026-09-20 21:15:29 -07:00

49 lines
2.3 KiB
TypeScript

// Supported clients: iOS Safari, Android Chrome, PC Chrome/Edge/Firefox.
// Gated on required API presence; anything missing → hard stop.
export function supported(): string | null {
if (typeof window === 'undefined') return 'window unavailable';
if (typeof createImageBitmap !== 'function') return 'createImageBitmap';
if (typeof Worker === 'undefined') return 'Worker';
if (typeof indexedDB === 'undefined') return 'IndexedDB';
const hasOffscreen =
typeof OffscreenCanvas !== 'undefined' &&
typeof OffscreenCanvas.prototype.convertToBlob === 'function';
const hasCanvasBlob =
typeof HTMLCanvasElement !== 'undefined' &&
typeof HTMLCanvasElement.prototype.toBlob === 'function';
if (!hasOffscreen && !hasCanvasBlob) return 'canvas PNG encode';
const ua = navigator.userAgent;
const isIOS = /iPad|iPhone|iPod/.test(ua) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
const isAndroid = /Android/i.test(ua);
const isSafari = /Safari/i.test(ua) && !/Chrome|CriOS|Edg|Firefox|FxiOS/i.test(ua);
const isChrome = /Chrome|CriOS/i.test(ua) && !/Edg/i.test(ua);
const isEdge = /Edg/i.test(ua);
const isFirefox = /Firefox|FxiOS/i.test(ua);
if (isIOS && isSafari) return null;
if (isAndroid && isChrome) return 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);
});
}