26 lines
3.2 KiB
JavaScript
26 lines
3.2 KiB
JavaScript
/** Authenticated read with the existing context guard; no token in URL/history. */
|
|
async function verifiedCsvDownload({id,token,sha256,isCurrent,pathForId,fetcher=fetch}){
|
|
if(!Number.isSafeInteger(Number(id))||Number(id)<1||!token||!/^[a-f0-9]{64}$/.test(sha256))throw Error('Resultado ou SHA indisponível. Consulte novamente.');
|
|
const check=()=>{if(!isCurrent())throw Error('O contexto mudou. A descarga anterior foi ignorada.');};check();
|
|
const response=await fetcher(pathForId(Number(id)),{credentials:'same-origin',headers:{Accept:'text/csv','X-SGF-Context':token}});check();
|
|
if(!response.ok){const problem=await response.json().catch(()=>({}));const error=Error(problem.message||'Não foi possível descarregar este resultado revisto.');error.status=response.status;throw error;}
|
|
if(!/^text\/csv(?:;|$)/i.test(response.headers.get('content-type')??'')||response.headers.get('x-content-sha256')!==sha256)throw Error('A resposta não corresponde à exportação congelada.');
|
|
const bytes=await response.arrayBuffer();check();const digest=await crypto.subtle.digest('SHA-256',bytes);check();const actual=[...new Uint8Array(digest)].map(n=>n.toString(16).padStart(2,'0')).join('');if(actual!==sha256)throw Error('O ficheiro não confere com o SHA conservado.');return{bytes,sha256:actual};
|
|
}
|
|
|
|
export const statementDownload=args=>verifiedCsvDownload({...args,pathForId:id=>'/api/general-ledger/statements/exports/'+id});
|
|
export const fiscalMapDownload=args=>verifiedCsvDownload({...args,pathForId:id=>'/api/fiscal-assessments/forms/'+id+'/export'});
|
|
|
|
export const remunerationDownload=args=>verifiedCsvDownload({...args,pathForId:id=>'/api/remunerations/exports/'+id});
|
|
|
|
export const personnelDownload=args=>verifiedCsvDownload({...args,pathForId:id=>'/api/hr-personnel/exports/'+id});
|
|
/** Readable view is derived; the original artifact SHA and render format are checked separately. */
|
|
export async function personnelReadableDownload({id,token,sourceSha256,isCurrent,fetcher=fetch}){
|
|
if(!Number.isSafeInteger(Number(id))||Number(id)<1||!token||!/^[a-f0-9]{64}$/.test(sourceSha256))throw Error('Versão ou fonte indisponível. Consulte novamente.');
|
|
const check=()=>{if(!isCurrent())throw Error('O contexto mudou. O resumo anterior foi ignorado.');};check();
|
|
const r=await fetcher('/api/hr-personnel/readable/'+Number(id),{credentials:'same-origin',headers:{Accept:'text/csv','X-SGF-Context':token}});check();
|
|
if(!r.ok){const problem=await r.json().catch(()=>({})),e=Error(problem.message||'Não foi possível consultar o resumo desta versão.');e.status=r.status;throw e;}
|
|
const expected=r.headers.get('x-content-sha256');if(!/^text\/csv(?:;|$)/i.test(r.headers.get('content-type')??'')||r.headers.get('x-source-sha256')!==sourceSha256||r.headers.get('x-sgf-read-format')!=='SGF-HR-READABLE-CSV-v1'||!/^[a-f0-9]{64}$/.test(expected??''))throw Error('O resumo não corresponde à versão conservada.');
|
|
const bytes=await r.arrayBuffer();check();const digest=await crypto.subtle.digest('SHA-256',bytes);check();const actual=[...new Uint8Array(digest)].map(v=>v.toString(16).padStart(2,'0')).join('');if(actual!==expected)throw Error('O resumo não confere com o SHA da resposta.');return{bytes,sha256:actual,sourceSha256};
|
|
}
|