feat: integrate Draco compression for GLB models and implement encryption for secure loading
- Added DRACOLoader to starter.js for handling compressed GLB models. - Created a createGltfLoader function to initialize GLTFLoader with DRACOLoader. - Implemented dracoModels plugin in vite.config.js to compress GLB models during build. - Introduced model_loader.js for loading encrypted GLB models with WebCrypto. - Developed encryptModels plugin to encrypt GLB files, ensuring no clear models are exposed. - Updated asset handling to remove uncompressed GLB files post-build.
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
// NON ATTIVO: per ora nessuno lo importa (vedi vite-plugins/encrypt-models.js).
|
||||
//
|
||||
// Carica un modello GLB cifrato in build (vedi vite-plugins/encrypt-models.js): scarica il .bin,
|
||||
// lo decifra in memoria con WebCrypto e lo passa al GLTFLoader. Nessun .glb in chiaro passa in rete.
|
||||
//
|
||||
// WebCrypto richiede un contesto sicuro: https, oppure http://localhost.
|
||||
|
||||
/* global __KREIOS_MK__, __KREIOS_MM__ */
|
||||
|
||||
let keyPromise = null;
|
||||
|
||||
function rawKey() {
|
||||
const masked = __KREIOS_MK__;
|
||||
const mask = __KREIOS_MM__;
|
||||
return Uint8Array.from(masked, (b, i) => b ^ mask[i]);
|
||||
}
|
||||
|
||||
function getKey() {
|
||||
keyPromise ??= (async () => {
|
||||
const raw = rawKey();
|
||||
const cryptoKey = await crypto.subtle.importKey('raw', raw, 'AES-GCM', false, ['decrypt']);
|
||||
return { raw, cryptoKey };
|
||||
})();
|
||||
return keyPromise;
|
||||
}
|
||||
|
||||
async function fileName(raw, relPath) {
|
||||
const data = new Uint8Array([...raw, ...new TextEncoder().encode(relPath)]);
|
||||
const digest = new Uint8Array(await crypto.subtle.digest('SHA-256', data));
|
||||
return [...digest].map((b) => b.toString(16).padStart(2, '0')).join('').slice(0, 32) + '.bin';
|
||||
}
|
||||
|
||||
// Scarica con avanzamento, compatibile con il callback di progresso del GLTFLoader ({ loaded, total }).
|
||||
async function download(url, onProgress) {
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) throw new Error(`Modello non disponibile (${res.status})`);
|
||||
|
||||
const total = Number(res.headers.get('Content-Length')) || 0;
|
||||
if (!res.body || !total) {
|
||||
const buf = await res.arrayBuffer();
|
||||
onProgress?.({ loaded: buf.byteLength, total: buf.byteLength });
|
||||
return new Uint8Array(buf);
|
||||
}
|
||||
|
||||
const out = new Uint8Array(total);
|
||||
const reader = res.body.getReader();
|
||||
let loaded = 0;
|
||||
for (;;) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
out.set(value, loaded);
|
||||
loaded += value.length;
|
||||
onProgress?.({ loaded, total });
|
||||
}
|
||||
return out.subarray(0, loaded);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import('three/examples/jsm/loaders/GLTFLoader').GLTFLoader} loader
|
||||
* @param {string} relPath percorso sotto assets/models, es. "pvc/finestra_1_anta.glb"
|
||||
*/
|
||||
export async function caricaModelloCifrato(loader, relPath, onProgress) {
|
||||
const { raw, cryptoKey } = await getKey();
|
||||
const data = await download(`./assets/m/${await fileName(raw, relPath)}`, onProgress);
|
||||
|
||||
const glb = await crypto.subtle.decrypt(
|
||||
{ name: 'AES-GCM', iv: data.subarray(0, 12), additionalData: new TextEncoder().encode(relPath) },
|
||||
cryptoKey,
|
||||
data.subarray(12),
|
||||
);
|
||||
|
||||
const dir = relPath.includes('/') ? relPath.slice(0, relPath.lastIndexOf('/') + 1) : '';
|
||||
return loader.parseAsync(glb, `./assets/models/${dir}`);
|
||||
}
|
||||
|
||||
/** Stessa firma di GLTFLoader.load, ma su modelli cifrati. */
|
||||
export function loadModello(loader, relPath, onLoad, onProgress, onError) {
|
||||
caricaModelloCifrato(loader, relPath, onProgress).then(onLoad, (err) => (onError ? onError(err) : console.error(err)));
|
||||
}
|
||||
Reference in New Issue
Block a user