/* Shared direct multipart transport for customer originals/corrections and operator finals. */ window.dtfUpload = async (file, {api, prefix='/uploads', startPath=prefix, progress=()=>{}, onBytes=()=>{}, scope='guest', resume=true}) => { const samples=new Blob([file.slice(0,65536),file.slice(Math.max(0,file.size-65536))]); const hash=Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256',await samples.arrayBuffer())),b=>b.toString(16).padStart(2,'0')).join(''); const key='dtf-upload:'+JSON.stringify([scope,file.name,file.size,file.lastModified,hash]); let id=resume?localStorage.getItem(key):null,state; if(id){try{state=await api(prefix+'/'+id);}catch(error){if(![404,410].includes(error.status))throw error;id=null;}} if(!id){state=await api(startPath,{name:file.name,size:file.size});id=state.id;if(resume)localStorage.setItem(key,id);} async function waitForScan(){ onBytes(file.size); // The antivirus streams the whole file: about a second per 2 MB, and at // least two and a half minutes. const attempts=Math.max(150,Math.ceil(file.size/2e6)); for(let attempt=0;attemptsetTimeout(resolve,1000)); } throw new Error('Verificação de segurança pendente. Tente novamente para consultar o resultado.'); } if(state.complete)return waitForScan(); const done=new Set(state.parts||[]),size=state.part_bytes; onBytes(Math.min(file.size,done.size*size)); for(let offset=0,part=1;offset