import { createEnclave } from '../dist/enclave.js'; let enclave = null; let lastDatabase = null; let currentResource = 'accounts'; let currentAction = 'list'; const RESOURCE_ACTIONS = { accounts: ['list', 'get', 'sign'], enclaves: ['list', 'get', 'sign', 'rotate', 'archive', 'delete'], credentials: ['list', 'get'], sessions: ['list', 'revoke'], grants: ['list', 'revoke'], delegations: ['list', 'list_received', 'list_command', 'get', 'revoke', 'verify', 'cleanup'], ucans: ['list', 'get', 'revoke', 'verify', 'cleanup'], verification_methods: ['list', 'get', 'delete'], services: ['list', 'get', 'get_by_id'], }; const ACTIONS_REQUIRING_SUBJECT = ['get', 'revoke', 'delete', 'verify', 'rotate', 'archive', 'list_received', 'list_command', 'get_by_id', 'sign']; function log(card, level, message, data = null) { const el = document.getElementById(`log-${card}`); if (!el) return console.log(`[${card}] ${message}`, data ?? ''); const time = new Date().toISOString().slice(11, 23); let entry = `
${time} ${message}`; if (data !== null) { const json = typeof data === 'string' ? data : JSON.stringify(data, null, 2); entry += `${escapeHtml(json)}`; } entry += '
'; el.innerHTML += entry; el.classList.add('visible'); el.scrollTop = el.scrollHeight; console.log(`[${time}] [${card}] ${message}`, data ?? ''); } function escapeHtml(str) { return str.replace(/&/g, '&').replace(//g, '>'); } function setStatus(ready, message) { const dot = document.getElementById('status-dot'); const text = document.getElementById('status-text'); if (dot) { dot.className = 'status-dot' + (ready === true ? ' ready' : ready === false ? ' error' : ''); } if (text) { text.textContent = message; } } function arrayBufferToBase64(buffer) { const bytes = new Uint8Array(buffer); let binary = ''; for (let i = 0; i < bytes.byteLength; i++) { binary += String.fromCharCode(bytes[i]); } return btoa(binary); } async function createWebAuthnCredential() { const userId = crypto.getRandomValues(new Uint8Array(16)); const challenge = crypto.getRandomValues(new Uint8Array(32)); const options = { challenge, rp: { name: "Motr Enclave Demo", id: window.location.hostname, }, user: { id: userId, name: `user-${Date.now()}@motr.local`, displayName: "Motr Demo User", }, pubKeyCredParams: [ { alg: -7, type: "public-key" }, { alg: -257, type: "public-key" }, ], authenticatorSelection: { authenticatorAttachment: "platform", userVerification: "preferred", residentKey: "preferred", }, timeout: 60000, attestation: "none", }; const credential = await navigator.credentials.create({ publicKey: options }); return { id: credential.id, rawId: arrayBufferToBase64(credential.rawId), type: credential.type, response: { clientDataJSON: arrayBufferToBase64(credential.response.clientDataJSON), attestationObject: arrayBufferToBase64(credential.response.attestationObject), }, }; } async function init() { try { setStatus(null, 'Loading...'); enclave = await createEnclave('./enclave.wasm', { debug: true }); setStatus(true, 'Ready'); log('generate', 'ok', 'Plugin loaded successfully'); } catch (err) { setStatus(false, 'Failed'); log('generate', 'err', `Load failed: ${err?.message || String(err)}`); } } window.showSection = function(section) { document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active')); document.querySelectorAll('.nav-btn').forEach(el => el.classList.remove('active')); const sectionEl = document.getElementById(`section-${section}`); if (sectionEl) sectionEl.classList.add('active'); event?.target?.classList.add('active'); }; window.testPing = async function() { if (!enclave) return log('ping', 'err', 'Plugin not loaded'); const message = document.getElementById('ping-msg')?.value || 'hello'; log('ping', 'info', `Sending: "${message}"`); try { const result = await enclave.ping(message); if (result.success) { log('ping', 'ok', `Response: "${result.echo}"`, result); } else { log('ping', 'err', result.message, result); } return result; } catch (err) { log('ping', 'err', err?.message || String(err)); throw err; } }; window.testGenerate = async function() { if (!enclave) return log('generate', 'err', 'Plugin not loaded'); if (!window.PublicKeyCredential) { log('generate', 'err', 'WebAuthn not supported in this browser'); return; } try { log('generate', 'info', 'Requesting WebAuthn credential...'); const credential = await createWebAuthnCredential(); log('generate', 'ok', `Credential created: ${credential.id.slice(0, 20)}...`); const credentialBase64 = btoa(JSON.stringify(credential)); log('generate', 'info', 'Calling enclave.generate()...'); const result = await enclave.generate(credentialBase64); log('generate', 'ok', `DID created: ${result.did}`, { did: result.did, enclaveId: result.enclave_id, publicKey: result.public_key?.slice(0, 20) + '...', accounts: result.accounts?.length ?? 0, dbSize: result.database?.length ?? 0, }); if (result.database) { lastDatabase = result.database; } return result; } catch (err) { if (err.name === 'NotAllowedError') { log('generate', 'err', 'User cancelled or WebAuthn not allowed'); } else { log('generate', 'err', err?.message || String(err)); } throw err; } }; window.testGenerateMock = async function() { if (!enclave) return log('generate', 'err', 'Plugin not loaded'); const mockCredential = btoa(JSON.stringify({ id: `mock-${Date.now()}`, rawId: arrayBufferToBase64(crypto.getRandomValues(new Uint8Array(32))), type: 'public-key', response: { clientDataJSON: arrayBufferToBase64(new TextEncoder().encode('{"mock":true}')), attestationObject: arrayBufferToBase64(crypto.getRandomValues(new Uint8Array(64))), }, })); log('generate', 'info', 'Using mock credential...'); try { const result = await enclave.generate(mockCredential); log('generate', 'ok', `DID created: ${result.did}`, { did: result.did, enclaveId: result.enclave_id, publicKey: result.public_key?.slice(0, 20) + '...', accounts: result.accounts?.length ?? 0, dbSize: result.database?.length ?? 0, }); if (result.database) { lastDatabase = result.database; } return result; } catch (err) { log('generate', 'err', err?.message || String(err)); throw err; } }; window.testLoadFromBytes = async function() { if (!enclave) return log('load', 'err', 'Plugin not loaded'); if (!lastDatabase) { return log('load', 'err', 'No database in memory - run generate first'); } log('load', 'info', `Loading from bytes (${lastDatabase.length} bytes)...`); try { const result = await enclave.load(new Uint8Array(lastDatabase)); if (result.success) { log('load', 'ok', `Loaded DID: ${result.did}`, result); } else { log('load', 'err', result.error, result); } return result; } catch (err) { log('load', 'err', err?.message || String(err)); throw err; } }; window.downloadDatabase = function() { if (!lastDatabase) { alert('No database in memory. Run generate first.'); return; } const blob = new Blob([new Uint8Array(lastDatabase)], { type: 'application/octet-stream' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `enclave-${Date.now()}.db`; a.click(); URL.revokeObjectURL(url); log('load', 'ok', `Downloaded database (${lastDatabase.length} bytes)`); }; window.uploadDatabase = async function(event) { const file = event.target.files?.[0]; if (!file) return; const buffer = await file.arrayBuffer(); lastDatabase = Array.from(new Uint8Array(buffer)); log('load', 'info', `Uploaded ${file.name} (${lastDatabase.length} bytes)`); if (enclave) { await testLoadFromBytes(); } }; window.selectResource = function(resource) { currentResource = resource; currentAction = 'list'; document.querySelectorAll('.resource-btn').forEach(el => el.classList.remove('active')); event?.target?.closest('.resource-btn')?.classList.add('active'); document.getElementById('selected-resource').textContent = resource; const actions = RESOURCE_ACTIONS[resource] || ['list']; const pillsContainer = document.getElementById('action-pills'); pillsContainer.innerHTML = actions.map((action, i) => `` ).join(''); updateSubjectRow(); }; window.selectAction = function(action) { currentAction = action; document.querySelectorAll('.action-pill').forEach(el => el.classList.remove('active')); event?.target?.classList.add('active'); updateSubjectRow(); }; function updateSubjectRow() { const subjectRow = document.getElementById('subject-row'); const subjectInput = document.getElementById('subject-input'); if (ACTIONS_REQUIRING_SUBJECT.includes(currentAction)) { subjectRow.style.display = 'block'; const placeholders = { get: 'ID or address', revoke: 'ID to revoke', delete: 'ID to delete', verify: 'CID to verify', rotate: 'Enclave ID', archive: 'Enclave ID', list_received: 'Audience DID', list_command: 'Command (e.g., msg/send)', get_by_id: 'Service ID (number)', sign: currentResource === 'enclaves' ? 'enclave_id:data_to_sign' : 'Data to sign (text)', }; subjectInput.placeholder = placeholders[currentAction] || 'Subject'; } else { subjectRow.style.display = 'none'; subjectInput.value = ''; } } window.executeAction = async function() { if (!enclave) { showResult('Plugin not loaded', true); return; } const subject = document.getElementById('subject-input')?.value || ''; if (ACTIONS_REQUIRING_SUBJECT.includes(currentAction) && !subject) { showResult(`Subject is required for action: ${currentAction}`, true); return; } let filter = `resource:${currentResource} action:${currentAction}`; if (subject) { filter += ` subject:${subject}`; } try { const result = await enclave.exec(filter); if (result.success) { showResult(JSON.stringify(result.result, null, 2), false); } else { showResult(result.error, true); } } catch (err) { showResult(err?.message || String(err), true); } }; window.quickExec = async function(resource, action) { if (!enclave) { alert('Plugin not loaded'); return; } showSection('explorer'); selectResource(resource); selectAction(action); await executeAction(); }; function showResult(content, isError = false) { const resultEl = document.getElementById('exec-result'); resultEl.textContent = content; resultEl.className = 'result-panel' + (isError ? ' error' : ' success'); } window.clearResult = function() { const resultEl = document.getElementById('exec-result'); resultEl.innerHTML = 'Execute an action to see results'; resultEl.className = 'result-panel'; }; window.testQuery = async function() { if (!enclave) { showQueryResult('Plugin not loaded', true); return; } const did = document.getElementById('did-input')?.value || ''; try { const result = await enclave.query(did); showQueryResult(JSON.stringify(result, null, 2), false); } catch (err) { showQueryResult(err?.message || String(err), true); } }; function showQueryResult(content, isError = false) { const resultEl = document.getElementById('query-result'); resultEl.textContent = content; resultEl.className = 'result-panel' + (isError ? ' error' : ' success'); } window.resetEnclave = async function() { if (!enclave) return; try { await enclave.reset(); lastDatabase = null; log('generate', 'info', 'Enclave state reset'); clearResult(); document.getElementById('query-result').innerHTML = 'Query a DID to see the document'; } catch (err) { log('generate', 'err', `Reset failed: ${err?.message || String(err)}`); } }; window.runAllTests = async function() { log('generate', 'info', '=== Running all tests ==='); try { await testPing(); await testGenerateMock(); await testLoadFromBytes(); showSection('explorer'); await quickExec('accounts', 'list'); await quickExec('enclaves', 'list'); showSection('query'); await testQuery(); log('generate', 'ok', '=== All tests completed ==='); } catch (err) { log('generate', 'err', `Tests failed: ${err?.message || String(err)}`); } }; init();