From 85fe8cc251a7abe8ccffcc87cbc7ada172ac7656 Mon Sep 17 00:00:00 2001 From: NicolasCantu Date: Fri, 6 Jun 2025 23:05:28 +0200 Subject: [PATCH] Refactor getDocumentValidation --- src/pages/account/document-validation.ts | 297 +++++++++++------------ 1 file changed, 135 insertions(+), 162 deletions(-) diff --git a/src/pages/account/document-validation.ts b/src/pages/account/document-validation.ts index 6b68eb1..b99fca1 100644 --- a/src/pages/account/document-validation.ts +++ b/src/pages/account/document-validation.ts @@ -1,179 +1,152 @@ -function randomHex32(): string { - return Array.from(crypto.getRandomValues(new Uint8Array(32))) - .map(b => b.toString(16).padStart(2, '0')) - .join(''); +import { ProcessState } from '../../../pkg/sdk_client'; +import Services from '../../services/service'; + +interface State { + file: File | null; + fileHash: string | null; + certificate: ProcessState | null; + commitmentHashes: string[]; } export function getDocumentValidation(container: HTMLElement) { - container.innerHTML = ''; // clear previous content - container.style.display = 'block'; - - const state = { - documentFile: null as File | null, - certificateJson: null as Record | null - }; - - const wrapper = document.createElement('div'); - wrapper.style.cssText = 'padding: 2rem; display: flex; flex-direction: column; gap: 2rem; align-items: center;'; - - const header = document.createElement('h2'); - header.textContent = 'Document Validation'; - header.style.cssText = 'font-size: 1.5rem; font-weight: bold;'; - wrapper.appendChild(header); - - const boxesContainer = document.createElement('div'); - boxesContainer.style.cssText = 'display: flex; gap: 2rem;'; - - const createDropBox = (label: string, onDrop: (file: File) => void) => { - const box = document.createElement('div'); - box.style.cssText = ` - width: 250px; height: 150px; - border: 2px dashed #888; - border-radius: 0.5rem; - display: flex; - justify-content: center; - align-items: center; - text-align: center; - padding: 1rem; - cursor: pointer; - background: #fafafa; - `; - box.textContent = `Drop ${label} here`; - box.ondragover = e => { e.preventDefault(); box.style.background = '#eee'; }; - box.ondragleave = () => { box.style.background = '#fafafa'; }; - box.ondrop = async e => { - e.preventDefault(); - const file = e.dataTransfer?.files?.[0]; - if (!file) return; - if (label === 'Document') { - if (file.type === 'application/pdf') { - onDrop(file); - box.textContent = `${label} uploaded: ${file.name}`; - box.style.borderColor = 'green'; - box.style.background = '#e6ffed'; - } else { - alert('Please drop a valid PDF file.'); - } - } else if (label === 'Certificate') { - try { - const text = await file.text(); - const json = JSON.parse(text); - onDrop(file); - state.certificateJson = json; - box.textContent = `Certificate loaded: ${file.name}`; - box.style.borderColor = 'green'; - box.style.background = '#e6ffed'; - } catch (err) { - alert('Invalid certificate file. Must be valid JSON.'); - } - } - }; - return box; - }; - - const docBox = createDropBox('Document', file => { - state.documentFile = file; - checkSuccess(); - }); - - const certBox = createDropBox('Certificate', file => { - checkSuccess(); - }); - - boxesContainer.appendChild(docBox); - boxesContainer.appendChild(certBox); - wrapper.appendChild(boxesContainer); - - container.appendChild(wrapper); - - function checkSuccess() { - if (state.documentFile && state.certificateJson) { - showSuccessScreen(); - } + const state: State = { + file: null, + fileHash: null, + certificate: null, + commitmentHashes: [] } - function showSuccessScreen() { - container.innerHTML = ''; + container.innerHTML = ''; + container.style.display = 'flex'; + container.style.justifyContent = 'center'; + container.style.gap = '2rem'; + container.style.padding = '2rem'; - const successWrapper = document.createElement('div'); - successWrapper.style.cssText = ` - padding: 4rem; - text-align: center; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 1.5rem; + const createDropButton = (label: string, onDrop: (file: File, updateVisuals: (file: File) => void) => void): HTMLElement => { + const button = document.createElement('div'); + button.style.cssText = ` + width: 200px; + height: 100px; + border: 2px dashed #888; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + font-weight: bold; + background: #f8f8f8; + text-align: center; `; - const checkmark = document.createElement('div'); - checkmark.textContent = '✅'; - checkmark.style.cssText = 'font-size: 4rem;'; - successWrapper.appendChild(checkmark); + const title = document.createElement('div'); + title.textContent = label; - const successMsg = document.createElement('div'); - successMsg.textContent = 'Document Verified Successfully!'; - successMsg.style.cssText = 'font-size: 1.25rem; font-weight: bold; color: green;'; - successWrapper.appendChild(successMsg); + const filename = document.createElement('div'); + filename.style.cssText = ` + font-size: 0.85rem; + margin-top: 0.5rem; + color: #444; + word-break: break-word; + text-align: center; + `; - // === Display file names === - const docLabel = document.createElement('div'); - docLabel.textContent = `Document: ${state.documentFile!.name}`; - successWrapper.appendChild(docLabel); + button.appendChild(title); + button.appendChild(filename); - const certLabel = document.createElement('div'); - certLabel.textContent = `Certificate: ${state.certificateJson!.name}`; - successWrapper.appendChild(certLabel); - - // === Mocked verification data === - const documentHash = randomHex32(); - const fieldName = "owner_name"; // mocked field - const transactionId = randomHex32(); - - const dataBlock = (label: string, value: string) => { - const block = document.createElement('div'); - block.style.cssText = 'text-align: left; width: 100%; max-width: 600px;'; - - const title = document.createElement('div'); - title.textContent = label; - title.style.cssText = 'font-weight: bold; margin-top: 1rem;'; - - const code = document.createElement('code'); - code.textContent = value; - code.style.cssText = ` - display: block; - background: #f0f0f0; - padding: 0.5rem; - border-radius: 0.375rem; - font-family: monospace; - word-break: break-all; - `; - - block.appendChild(title); - block.appendChild(code); - return block; + const updateVisuals = (file: File) => { + button.style.borderColor = 'green'; + button.style.background = '#e6ffed'; + filename.textContent = file.name; }; - successWrapper.appendChild(dataBlock('Document Hash', documentHash)); - successWrapper.appendChild(dataBlock('Field Name', fieldName)); - successWrapper.appendChild(dataBlock('Transaction ID', transactionId)); + button.ondragover = e => { + e.preventDefault(); + button.style.background = '#e0e0e0'; + }; - // === Restart button === - const restartBtn = document.createElement('button'); - restartBtn.textContent = 'Verify Another'; - restartBtn.style.cssText = ` - margin-top: 2rem; - padding: 0.5rem 1.5rem; - font-size: 1rem; - background: #4f46e5; - color: white; - border: none; - border-radius: 0.375rem; - cursor: pointer; - `; - restartBtn.onclick = () => getDocumentValidation(container); - successWrapper.appendChild(restartBtn); + button.ondragleave = () => { + button.style.background = '#f8f8f8'; + }; - container.appendChild(successWrapper); + button.ondrop = async e => { + e.preventDefault(); + button.style.background = '#f8f8f8'; + + const file = e.dataTransfer?.files?.[0]; + if (!file) return; + onDrop(file, updateVisuals); + }; + + return button; + }; + + const fileDropButton = createDropButton('Drop file', async (file, updateVisuals) => { + try { + state.file = file; + updateVisuals(file); + console.log('Loaded file:', state.file); + checkReady(); + } catch (err) { + alert('Failed to drop the file.'); + console.error(err); } + }); + + const certDropButton = createDropButton('Drop certificate', async (file, updateVisuals) => { + try { + const text = await file.text(); + const json = JSON.parse(text); + if ( + typeof json === 'object' && + json !== null && + typeof json.pcd_commitment === 'object' && + typeof json.state_id === 'string' + ) { + state.certificate = json as ProcessState; + + state.commitmentHashes = Object.values(json.pcd_commitment).map((h: string) => + h.toLowerCase() + ); + + updateVisuals(file); + console.log('Loaded certificate, extracted hashes:', state.commitmentHashes); + checkReady(); + } else { + alert('Invalid certificate structure.'); + } + } catch (err) { + alert('Failed to parse certificate JSON.'); + console.error(err); + } + }); + + container.appendChild(fileDropButton); + container.appendChild(certDropButton); + + async function checkReady() { + if (state.file && state.certificate && state.commitmentHashes.length > 0) { + // We take the commited_in and all pcd_commitment keys to reconstruct all the possible hash + const fileBlob = { + type: state.file.type, + data: new Uint8Array(await state.file.arrayBuffer()) + }; + const service = await Services.getInstance(); + const commitedIn = state.certificate.commited_in; + let found = false; + for (const label of Object.keys(state.certificate.pcd_commitment)) { + // Compute the hash for this label + console.log(`Computing hash with label ${label}`) + const fileHex = service.getHashForFile(commitedIn, label, fileBlob); + console.log(`Found hash ${fileHex}`); + found = state.commitmentHashes.includes(fileHex); + if (found) break; + } + + if (found) { + alert('✅ Validation successful: file hash found in pcd_commitment.'); + } else { + alert('❌ Validation failed: file hash NOT found in pcd_commitment.'); + } + } + } }