diff --git a/crypto_vault_extension/background.js b/crypto_vault_extension/background.js new file mode 100644 index 0000000..9be989b --- /dev/null +++ b/crypto_vault_extension/background.js @@ -0,0 +1,225 @@ +let vault = null; +let isInitialized = false; +let currentSession = null; + +// Utility function to convert Uint8Array to hex +function toHex(uint8Array) { + return Array.from(uint8Array) + .map(b => b.toString(16).padStart(2, '0')) + .join(''); +} + +// Session persistence functions +async function saveSession(keyspace) { + currentSession = { keyspace, timestamp: Date.now() }; + await chrome.storage.session.set({ cryptoVaultSession: currentSession }); + console.log('Session saved:', currentSession); +} + +async function loadSession() { + try { + const result = await chrome.storage.session.get(['cryptoVaultSession']); + if (result.cryptoVaultSession) { + currentSession = result.cryptoVaultSession; + console.log('Session loaded:', currentSession); + return currentSession; + } + } catch (error) { + console.error('Failed to load session:', error); + } + return null; +} + +async function clearSession() { + currentSession = null; + await chrome.storage.session.remove(['cryptoVaultSession']); + console.log('Session cleared'); +} + +async function restoreSession() { + const session = await loadSession(); + if (session && vault) { + try { + // Check if the session is still valid by testing if vault is unlocked + const isUnlocked = vault.is_unlocked(); + if (isUnlocked) { + console.log('Session restored successfully for keyspace:', session.keyspace); + return session; + } else { + console.log('Session expired, clearing...'); + await clearSession(); + } + } catch (error) { + console.error('Error checking session validity:', error); + await clearSession(); + } + } + return null; +} + +// Import WASM module functions +import init, { + create_keyspace, + init_session, + is_unlocked, + add_keypair, + list_keypairs, + select_keypair, + current_keypair_metadata, + current_keypair_public_key, + sign, + lock_session +} from './wasm/wasm_app.js'; + +// Initialize WASM module +async function initVault() { + try { + if (vault && isInitialized) return vault; + + // Initialize with the WASM file + const wasmUrl = chrome.runtime.getURL('wasm/wasm_app_bg.wasm'); + await init(wasmUrl); + + // Create a vault object with all the imported functions + vault = { + create_keyspace, + init_session, + is_unlocked, + add_keypair, + list_keypairs, + select_keypair, + current_keypair_metadata, + current_keypair_public_key, + sign, + lock_session + }; + + isInitialized = true; + console.log('CryptoVault initialized successfully'); + + // Try to restore previous session + await restoreSession(); + + return vault; + } catch (error) { + console.error('Failed to initialize CryptoVault:', error); + throw error; + } +} + +// Handle messages from popup and content scripts +chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + const handleRequest = async () => { + try { + if (!vault) { + await initVault(); + } + + switch (request.action) { + case 'createKeyspace': + await vault.create_keyspace(request.keyspace, request.password); + return { success: true }; + + case 'initSession': + await vault.init_session(request.keyspace, request.password); + await saveSession(request.keyspace); + return { success: true }; + + case 'isUnlocked': + const unlocked = vault.is_unlocked(); + return { success: true, unlocked }; + + case 'addKeypair': + const result = await vault.add_keypair(request.keyType, request.metadata); + return { success: true, result }; + + case 'listKeypairs': + console.log('Background: listing keypairs...'); + console.log('Background: vault object:', vault); + console.log('Background: vault.list_keypairs function:', vault.list_keypairs); + + // Check if session is unlocked first + const isUnlocked = vault.is_unlocked(); + console.log('Background: is session unlocked?', isUnlocked); + + if (!isUnlocked) { + console.log('Background: Session is not unlocked, cannot list keypairs'); + return { success: false, error: 'Session is not unlocked' }; + } + + try { + const keypairsRaw = await vault.list_keypairs(); + console.log('Background: keypairs raw result:', keypairsRaw); + console.log('Background: keypairs type:', typeof keypairsRaw); + + // Parse JSON string if needed + let keypairs; + if (typeof keypairsRaw === 'string') { + console.log('Background: Parsing JSON string...'); + keypairs = JSON.parse(keypairsRaw); + } else { + keypairs = keypairsRaw; + } + + console.log('Background: parsed keypairs:', keypairs); + console.log('Background: parsed keypairs type:', typeof keypairs); + console.log('Background: keypairs array length:', Array.isArray(keypairs) ? keypairs.length : 'not an array'); + + return { success: true, keypairs }; + } catch (listError) { + console.error('Background: Error calling list_keypairs:', listError); + throw listError; + } + + case 'selectKeypair': + vault.select_keypair(request.keyId); + return { success: true }; + + case 'getCurrentKeypairMetadata': + const metadata = vault.current_keypair_metadata(); + return { success: true, metadata }; + + case 'getCurrentKeypairPublicKey': + const publicKey = vault.current_keypair_public_key(); + const hexKey = toHex(publicKey); + return { success: true, publicKey: hexKey }; + + case 'sign': + const signature = await vault.sign(new Uint8Array(request.message)); + return { success: true, signature }; + + case 'lockSession': + vault.lock_session(); + await clearSession(); + return { success: true }; + + case 'getStatus': + const status = vault ? vault.is_unlocked() : false; + const session = await loadSession(); + return { + success: true, + status, + session: session ? { keyspace: session.keyspace } : null + }; + + default: + throw new Error('Unknown action: ' + request.action); + } + } catch (error) { + console.error('Background script error:', error); + return { success: false, error: error.message }; + } + }; + + handleRequest().then(sendResponse); + return true; // Keep the message channel open for async response +}); + +// Initialize vault when extension starts +chrome.runtime.onStartup.addListener(() => { + initVault(); +}); + +chrome.runtime.onInstalled.addListener(() => { + initVault(); +}); \ No newline at end of file diff --git a/crypto_vault_extension/content.js b/crypto_vault_extension/content.js new file mode 100644 index 0000000..e1041b7 --- /dev/null +++ b/crypto_vault_extension/content.js @@ -0,0 +1,89 @@ +// Content script for potential webpage integration +// This can be used to inject CryptoVault functionality into webpages + +(function() { + 'use strict'; + + // Create a bridge between webpage and extension + const cryptoVaultBridge = { + async isAvailable() { + try { + const response = await chrome.runtime.sendMessage({ action: 'getStatus' }); + return response.success && response.status; + } catch (error) { + return false; + } + }, + + async sign(message) { + try { + const response = await chrome.runtime.sendMessage({ + action: 'sign', + message: Array.from(new TextEncoder().encode(message)) + }); + return response.success ? response.signature : null; + } catch (error) { + console.error('CryptoVault sign error:', error); + return null; + } + }, + + async getPublicKey() { + try { + const response = await chrome.runtime.sendMessage({ action: 'getCurrentKeypairPublicKey' }); + return response.success ? response.publicKey : null; + } catch (error) { + console.error('CryptoVault getPublicKey error:', error); + return null; + } + } + }; + + // Expose to window for webpage access (optional) + if (window.location.protocol === 'https:' || window.location.hostname === 'localhost') { + window.cryptoVault = cryptoVaultBridge; + + // Dispatch event to let webpage know CryptoVault is available + window.dispatchEvent(new CustomEvent('cryptovault-ready', { + detail: { available: true } + })); + } + + // Listen for messages from webpage + window.addEventListener('message', async (event) => { + if (event.source !== window || !event.data.cryptoVault) return; + + const { action, id, data } = event.data; + let result; + + try { + switch (action) { + case 'sign': + result = await cryptoVaultBridge.sign(data.message); + break; + case 'getPublicKey': + result = await cryptoVaultBridge.getPublicKey(); + break; + case 'isAvailable': + result = await cryptoVaultBridge.isAvailable(); + break; + default: + throw new Error('Unknown action: ' + action); + } + + window.postMessage({ + cryptoVaultResponse: true, + id, + success: true, + result + }, '*'); + } catch (error) { + window.postMessage({ + cryptoVaultResponse: true, + id, + success: false, + error: error.message + }, '*'); + } + }); +})(); \ No newline at end of file diff --git a/crypto_vault_extension/icons/icon128.png b/crypto_vault_extension/icons/icon128.png new file mode 100644 index 0000000..e5869e4 Binary files /dev/null and b/crypto_vault_extension/icons/icon128.png differ diff --git a/crypto_vault_extension/icons/icon16.png b/crypto_vault_extension/icons/icon16.png new file mode 100644 index 0000000..5348679 Binary files /dev/null and b/crypto_vault_extension/icons/icon16.png differ diff --git a/crypto_vault_extension/icons/icon32.png b/crypto_vault_extension/icons/icon32.png new file mode 100644 index 0000000..d761cc8 Binary files /dev/null and b/crypto_vault_extension/icons/icon32.png differ diff --git a/crypto_vault_extension/icons/icon48.png b/crypto_vault_extension/icons/icon48.png new file mode 100644 index 0000000..3d34ec4 Binary files /dev/null and b/crypto_vault_extension/icons/icon48.png differ diff --git a/crypto_vault_extension/manifest.json b/crypto_vault_extension/manifest.json new file mode 100644 index 0000000..9602ce8 --- /dev/null +++ b/crypto_vault_extension/manifest.json @@ -0,0 +1,46 @@ +{ + "manifest_version": 3, + "name": "CryptoVault", + "version": "1.0.0", + "description": "Secure cryptographic key management and signing in your browser", + + "permissions": [ + "storage", + "activeTab" + ], + + "background": { + "service_worker": "background.js", + "type": "module" + }, + + "content_scripts": [ + { + "matches": [""], + "js": ["content.js"] + } + ], + + "action": { + "default_popup": "popup.html", + "default_icon": { + "16": "icons/icon16.png", + "48": "icons/icon48.png", + "128": "icons/icon128.png" + } + }, + + "web_accessible_resources": [ + { + "resources": [ + "wasm/*.wasm", + "wasm/*.js" + ], + "matches": [""] + } + ], + + "content_security_policy": { + "extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'; style-src 'self' 'unsafe-inline';" + } +} \ No newline at end of file diff --git a/crypto_vault_extension/popup.html b/crypto_vault_extension/popup.html new file mode 100644 index 0000000..2aaaf66 --- /dev/null +++ b/crypto_vault_extension/popup.html @@ -0,0 +1,138 @@ + + + + + + + +
+
+ +
+
+ Initializing... +
+
+ + +
+
+

Access Your Vault

+
+ + +
+
+ + +
+
+ + +
+
+
+ + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/crypto_vault_extension/popup.js b/crypto_vault_extension/popup.js new file mode 100644 index 0000000..cbc21fa --- /dev/null +++ b/crypto_vault_extension/popup.js @@ -0,0 +1,648 @@ +// Utility functions +function showToast(message, type = 'info') { + const toast = document.getElementById('toast'); + toast.textContent = message; + toast.className = `toast ${type}`; + setTimeout(() => toast.classList.add('hidden'), 3000); +} + +function showLoading(show = true) { + const overlay = document.getElementById('loadingOverlay'); + overlay.classList.toggle('hidden', !show); +} + +// Enhanced loading states for buttons +function setButtonLoading(button, loading = true, originalText = null) { + if (loading) { + button.dataset.originalText = button.textContent; + button.classList.add('loading'); + button.disabled = true; + } else { + button.classList.remove('loading'); + button.disabled = false; + if (originalText) { + button.textContent = originalText; + } else if (button.dataset.originalText) { + button.textContent = button.dataset.originalText; + } + } +} + +// Show inline loading for specific operations +function showInlineLoading(element, message = 'Processing...') { + element.innerHTML = ` +
+
+ ${message} +
+ `; +} + +// Enhanced error handling utility +function getErrorMessage(error, fallback = 'An unexpected error occurred') { + if (!error) return fallback; + + // If it's a string, return it + if (typeof error === 'string') { + return error.trim() || fallback; + } + + // If it's an Error object + if (error instanceof Error) { + return error.message || fallback; + } + + // If it's an object with error property + if (error.error) { + return getErrorMessage(error.error, fallback); + } + + // If it's an object with message property + if (error.message) { + return error.message || fallback; + } + + // Try to stringify if it's an object + if (typeof error === 'object') { + try { + const stringified = JSON.stringify(error); + if (stringified && stringified !== '{}') { + return stringified; + } + } catch (e) { + // Ignore JSON stringify errors + } + } + + return fallback; +} + +// Enhanced response error handling +function getResponseError(response, operation = 'operation') { + if (!response) { + return `Failed to ${operation}: No response received`; + } + + if (response.success === false || response.error) { + const errorMsg = getErrorMessage(response.error, `${operation} failed`); + + // Handle specific error types + if (errorMsg.includes('decryption error') || errorMsg.includes('aead::Error')) { + return 'Invalid password or corrupted keyspace data'; + } + + if (errorMsg.includes('Crypto error')) { + return 'Keyspace not found or corrupted. Try creating a new one.'; + } + + if (errorMsg.includes('not unlocked') || errorMsg.includes('session')) { + return 'Session expired. Please login again.'; + } + + return errorMsg; + } + + return `Failed to ${operation}: Unknown error`; +} + +function showSection(sectionId) { + document.querySelectorAll('.section').forEach(s => s.classList.add('hidden')); + document.getElementById(sectionId).classList.remove('hidden'); +} + +function setStatus(text, isConnected = false) { + document.getElementById('statusText').textContent = text; + const indicator = document.getElementById('statusIndicator'); + indicator.classList.toggle('connected', isConnected); +} + +// Message handling +async function sendMessage(action, data = {}) { + return new Promise((resolve) => { + chrome.runtime.sendMessage({ action, ...data }, resolve); + }); +} + +// Copy to clipboard +async function copyToClipboard(text) { + try { + await navigator.clipboard.writeText(text); + showToast('Copied to clipboard!', 'success'); + } catch (err) { + showToast('Failed to copy', 'error'); + } +} + +// Convert string to Uint8Array +function stringToUint8Array(str) { + if (str.match(/^[0-9a-fA-F]+$/)) { + // Hex string + const bytes = []; + for (let i = 0; i < str.length; i += 2) { + bytes.push(parseInt(str.substr(i, 2), 16)); + } + return bytes; + } else { + // Regular string + return Array.from(new TextEncoder().encode(str)); + } +} + +// DOM Elements +const elements = { + keyspaceInput: document.getElementById('keyspaceInput'), + passwordInput: document.getElementById('passwordInput'), + createKeyspaceBtn: document.getElementById('createKeyspaceBtn'), + loginBtn: document.getElementById('loginBtn'), + lockBtn: document.getElementById('lockBtn'), + toggleAddKeypairBtn: document.getElementById('toggleAddKeypairBtn'), + addKeypairCard: document.getElementById('addKeypairCard'), + keyTypeSelect: document.getElementById('keyTypeSelect'), + keyNameInput: document.getElementById('keyNameInput'), + addKeypairBtn: document.getElementById('addKeypairBtn'), + cancelAddKeypairBtn: document.getElementById('cancelAddKeypairBtn'), + keypairsList: document.getElementById('keypairsList'), + selectedKeypairCard: document.getElementById('selectedKeypairCard'), + messageInput: document.getElementById('messageInput'), + signBtn: document.getElementById('signBtn'), + signatureResult: document.getElementById('signatureResult'), + copyPublicKeyBtn: document.getElementById('copyPublicKeyBtn'), + copySignatureBtn: document.getElementById('copySignatureBtn'), +}; + +let currentKeyspace = null; +let selectedKeypairId = null; + +// Initialize +document.addEventListener('DOMContentLoaded', async function() { + setStatus('Initializing...', false); + + // Event listeners + elements.createKeyspaceBtn.addEventListener('click', createKeyspace); + elements.loginBtn.addEventListener('click', login); + elements.lockBtn.addEventListener('click', lockSession); + elements.toggleAddKeypairBtn.addEventListener('click', toggleAddKeypairForm); + elements.addKeypairBtn.addEventListener('click', addKeypair); + elements.cancelAddKeypairBtn.addEventListener('click', hideAddKeypairForm); + elements.signBtn.addEventListener('click', signMessage); + elements.copyPublicKeyBtn.addEventListener('click', () => { + const publicKey = document.getElementById('selectedPublicKey').textContent; + copyToClipboard(publicKey); + }); + elements.copySignatureBtn.addEventListener('click', () => { + const signature = document.getElementById('signatureValue').textContent; + copyToClipboard(signature); + }); + + // Enable sign button when message is entered + elements.messageInput.addEventListener('input', () => { + elements.signBtn.disabled = !elements.messageInput.value.trim() || !selectedKeypairId; + }); + + // Keyboard shortcuts + document.addEventListener('keydown', (e) => { + // Escape key closes the add keypair form + if (e.key === 'Escape' && !elements.addKeypairCard.classList.contains('hidden')) { + hideAddKeypairForm(); + } + // Enter key in the name input submits the form + if (e.key === 'Enter' && e.target === elements.keyNameInput) { + e.preventDefault(); + if (elements.keyNameInput.value.trim()) { + addKeypair(); + } + } + }); + + // Check for existing session + await checkExistingSession(); +}); + +async function checkExistingSession() { + try { + const response = await sendMessage('getStatus'); + if (response && response.success && response.status && response.session) { + // Session is active + currentKeyspace = response.session.keyspace; + elements.keyspaceInput.value = currentKeyspace; + setStatus(`Connected to ${currentKeyspace}`, true); + showSection('vaultSection'); + await loadKeypairs(); + showToast('Session restored!', 'success'); + } else { + // No active session + setStatus('Ready', true); + showSection('authSection'); + } + } catch (error) { + console.error('Error checking session:', error); + setStatus('Ready', true); + showSection('authSection'); + // Don't show toast for session check errors as it's not user-initiated + } +} + +// Toggle add keypair form +function toggleAddKeypairForm() { + const isHidden = elements.addKeypairCard.classList.contains('hidden'); + if (isHidden) { + showAddKeypairForm(); + } else { + hideAddKeypairForm(); + } +} + +function showAddKeypairForm() { + elements.addKeypairCard.classList.remove('hidden'); + + // Rotate the + icon to × when form is open + const icon = elements.toggleAddKeypairBtn.querySelector('.btn-icon'); + icon.style.transform = 'rotate(45deg)'; + + // Focus on the name input after animation + setTimeout(() => { + elements.keyNameInput.focus(); + }, 300); +} + +function hideAddKeypairForm() { + elements.addKeypairCard.classList.add('hidden'); + + // Rotate the icon back to + + const icon = elements.toggleAddKeypairBtn.querySelector('.btn-icon'); + icon.style.transform = 'rotate(0deg)'; + + // Clear the form + elements.keyNameInput.value = ''; + elements.keyTypeSelect.selectedIndex = 0; +} + +// Clear all vault-related state and UI +function clearVaultState() { + // Clear message input and signature result + elements.messageInput.value = ''; + elements.signatureResult.classList.add('hidden'); + document.getElementById('signatureValue').textContent = ''; + + // Clear selected keypair state + selectedKeypairId = null; + elements.signBtn.disabled = true; + + // Clear selected keypair info (hidden elements) + document.getElementById('selectedName').textContent = '-'; + document.getElementById('selectedType').textContent = '-'; + document.getElementById('selectedPublicKey').textContent = '-'; + + // Hide add keypair form if open + hideAddKeypairForm(); + + // Clear keypairs list + elements.keypairsList.innerHTML = '
Loading keypairs...
'; +} + +async function createKeyspace() { + const keyspace = elements.keyspaceInput.value.trim(); + const password = elements.passwordInput.value.trim(); + + if (!keyspace || !password) { + showToast('Please enter keyspace name and password', 'error'); + return; + } + + setButtonLoading(elements.createKeyspaceBtn, true); + try { + const response = await sendMessage('createKeyspace', { keyspace, password }); + if (response && response.success) { + showToast('Keyspace created successfully!', 'success'); + // Clear any existing state before auto-login + clearVaultState(); + await login(); // Auto-login after creation + } else { + const errorMsg = getResponseError(response, 'create keyspace'); + showToast(errorMsg, 'error'); + } + } catch (error) { + const errorMsg = getErrorMessage(error, 'Failed to create keyspace'); + console.error('Create keyspace error:', error); + showToast(errorMsg, 'error'); + } finally { + setButtonLoading(elements.createKeyspaceBtn, false); + } +} + +async function login() { + const keyspace = elements.keyspaceInput.value.trim(); + const password = elements.passwordInput.value.trim(); + + if (!keyspace || !password) { + showToast('Please enter keyspace name and password', 'error'); + return; + } + + setButtonLoading(elements.loginBtn, true); + try { + const response = await sendMessage('initSession', { keyspace, password }); + if (response && response.success) { + currentKeyspace = keyspace; + setStatus(`Connected to ${keyspace}`, true); + showSection('vaultSection'); + + // Clear any previous vault state before loading new keyspace + clearVaultState(); + await loadKeypairs(); + + showToast('Logged in successfully!', 'success'); + } else { + const errorMsg = getResponseError(response, 'login'); + showToast(errorMsg, 'error'); + } + } catch (error) { + const errorMsg = getErrorMessage(error, 'Failed to login'); + console.error('Login error:', error); + showToast(errorMsg, 'error'); + } finally { + setButtonLoading(elements.loginBtn, false); + } +} + +async function lockSession() { + showLoading(true); + try { + await sendMessage('lockSession'); + currentKeyspace = null; + selectedKeypairId = null; + setStatus('Locked', false); + showSection('authSection'); + + // Clear all form inputs + elements.keyspaceInput.value = ''; + elements.passwordInput.value = ''; + clearVaultState(); + + showToast('Session locked', 'info'); + } catch (error) { + showToast('Error: ' + error.message, 'error'); + } finally { + showLoading(false); + } +} + +async function addKeypair() { + const keyType = elements.keyTypeSelect.value; + const keyName = elements.keyNameInput.value.trim(); + + if (!keyName) { + showToast('Please enter a name for the keypair', 'error'); + return; + } + + // Use button loading instead of full overlay + setButtonLoading(elements.addKeypairBtn, true); + + try { + console.log('Adding keypair:', { keyType, keyName }); + const metadata = JSON.stringify({ name: keyName }); + console.log('Metadata:', metadata); + + const response = await sendMessage('addKeypair', { keyType, metadata }); + console.log('Add keypair response:', response); + + if (response && response.success) { + console.log('Keypair added successfully, clearing input and reloading list...'); + hideAddKeypairForm(); // Hide the form after successful addition + + // Show inline loading in keypairs list while reloading + showInlineLoading(elements.keypairsList, 'Adding keypair...'); + await loadKeypairs(); + + showToast('Keypair added successfully!', 'success'); + } else { + const errorMsg = getResponseError(response, 'add keypair'); + console.error('Failed to add keypair:', response); + showToast(errorMsg, 'error'); + } + } catch (error) { + const errorMsg = getErrorMessage(error, 'Failed to add keypair'); + console.error('Error adding keypair:', error); + showToast(errorMsg, 'error'); + } finally { + setButtonLoading(elements.addKeypairBtn, false); + } +} + +async function loadKeypairs() { + try { + console.log('Loading keypairs...'); + const response = await sendMessage('listKeypairs'); + console.log('Keypairs response:', response); + + if (response && response.success) { + console.log('Keypairs data:', response.keypairs); + console.log('Keypairs data type:', typeof response.keypairs); + renderKeypairs(response.keypairs); + } else { + const errorMsg = getResponseError(response, 'load keypairs'); + console.error('Failed to load keypairs:', response); + const container = elements.keypairsList; + container.innerHTML = '
Failed to load keypairs. Try refreshing.
'; + showToast(errorMsg, 'error'); + } + } catch (error) { + const errorMsg = getErrorMessage(error, 'Failed to load keypairs'); + console.error('Error loading keypairs:', error); + const container = elements.keypairsList; + container.innerHTML = '
Error loading keypairs. Try refreshing.
'; + showToast(errorMsg, 'error'); + } +} + +function renderKeypairs(keypairs) { + console.log('Rendering keypairs:', keypairs); + console.log('Keypairs type:', typeof keypairs); + console.log('Keypairs is array:', Array.isArray(keypairs)); + + const container = elements.keypairsList; + + // Handle different data types that might be returned + let keypairArray = []; + + if (Array.isArray(keypairs)) { + keypairArray = keypairs; + } else if (keypairs && typeof keypairs === 'object') { + // If it's an object, try to extract array from common properties + if (keypairs.keypairs && Array.isArray(keypairs.keypairs)) { + keypairArray = keypairs.keypairs; + } else if (keypairs.data && Array.isArray(keypairs.data)) { + keypairArray = keypairs.data; + } else { + console.log('Keypairs object structure:', Object.keys(keypairs)); + // Try to convert object to array if it has numeric keys + const keys = Object.keys(keypairs); + if (keys.length > 0 && keys.every(key => !isNaN(key))) { + keypairArray = Object.values(keypairs); + } + } + } + + console.log('Final keypair array:', keypairArray); + console.log('Array length:', keypairArray.length); + + if (!keypairArray || keypairArray.length === 0) { + console.log('No keypairs to render'); + container.innerHTML = '
No keypairs found. Add one above.
'; + return; + } + + console.log('Rendering', keypairArray.length, 'keypairs'); + container.innerHTML = keypairArray.map((keypair, index) => { + console.log('Processing keypair:', keypair); + const metadata = typeof keypair.metadata === 'string' + ? JSON.parse(keypair.metadata) + : keypair.metadata; + + return ` +
+
+
${metadata.name || 'Unnamed'}
+
${keypair.key_type}
+
+ +
+ `; + }).join(''); + + // Add event listeners to all select buttons + const selectButtons = container.querySelectorAll('.select-btn'); + selectButtons.forEach(button => { + button.addEventListener('click', (e) => { + e.preventDefault(); // Prevent any default button behavior + e.stopPropagation(); // Stop event bubbling + + const keypairId = e.target.getAttribute('data-keypair-id'); + console.log('Select button clicked for keypair:', keypairId); + selectKeypair(keypairId); + }); + }); +} + +async function selectKeypair(keyId) { + console.log('Selecting keypair:', keyId); + + // Don't show loading overlay for selection - it's too disruptive + try { + // Update visual state immediately for better UX + updateKeypairSelection(keyId); + + await sendMessage('selectKeypair', { keyId }); + selectedKeypairId = keyId; + + // Get keypair details for internal use (but don't show the card) + const metadataResponse = await sendMessage('getCurrentKeypairMetadata'); + const publicKeyResponse = await sendMessage('getCurrentKeypairPublicKey'); + + if (metadataResponse && metadataResponse.success && publicKeyResponse && publicKeyResponse.success) { + const metadata = metadataResponse.metadata; + + // Store the details in hidden elements for internal use + document.getElementById('selectedName').textContent = metadata.name || 'Unnamed'; + document.getElementById('selectedType').textContent = metadata.key_type; + document.getElementById('selectedPublicKey').textContent = publicKeyResponse.publicKey; + + // Enable sign button if message is entered + elements.signBtn.disabled = !elements.messageInput.value.trim(); + + // Show a subtle success message without toast + console.log(`Keypair "${metadata.name}" selected successfully`); + } else { + // Handle metadata or public key fetch failure + const metadataError = getResponseError(metadataResponse, 'get keypair metadata'); + const publicKeyError = getResponseError(publicKeyResponse, 'get public key'); + const errorMsg = metadataResponse && !metadataResponse.success ? metadataError : publicKeyError; + + console.error('Failed to get keypair details:', { metadataResponse, publicKeyResponse }); + updateKeypairSelection(null); + showToast(errorMsg, 'error'); + } + } catch (error) { + const errorMsg = getErrorMessage(error, 'Failed to select keypair'); + console.error('Error selecting keypair:', error); + // Revert visual state if there was an error + updateKeypairSelection(null); + showToast(errorMsg, 'error'); + } +} + +function updateKeypairSelection(selectedId) { + // Remove previous selection styling + const allKeypairs = document.querySelectorAll('.keypair-item'); + allKeypairs.forEach(item => { + item.classList.remove('selected'); + const button = item.querySelector('.select-btn'); + button.textContent = 'Select'; + button.classList.remove('selected'); + }); + + // Add selection styling to the selected keypair (if any) + if (selectedId) { + const selectedKeypair = document.querySelector(`[data-id="${selectedId}"]`); + if (selectedKeypair) { + selectedKeypair.classList.add('selected'); + const button = selectedKeypair.querySelector('.select-btn'); + button.textContent = 'Selected'; + button.classList.add('selected'); + } + } +} + +async function signMessage() { + const messageText = elements.messageInput.value.trim(); + if (!messageText || !selectedKeypairId) { + showToast('Please enter a message and select a keypair', 'error'); + return; + } + + // Use button loading and show inline loading in signature area + setButtonLoading(elements.signBtn, true); + + // Show loading in signature result area + elements.signatureResult.classList.remove('hidden'); + showInlineLoading(elements.signatureResult, 'Signing message...'); + + try { + const messageBytes = stringToUint8Array(messageText); + const response = await sendMessage('sign', { message: messageBytes }); + + if (response && response.success) { + // Restore signature result structure and show signature + elements.signatureResult.innerHTML = ` + +
+ ${response.signature} + +
+ `; + + // Re-attach copy event listener + document.getElementById('copySignatureBtn').addEventListener('click', () => { + copyToClipboard(response.signature); + }); + + showToast('Message signed successfully!', 'success'); + } else { + const errorMsg = getResponseError(response, 'sign message'); + elements.signatureResult.classList.add('hidden'); + showToast(errorMsg, 'error'); + } + } catch (error) { + const errorMsg = getErrorMessage(error, 'Failed to sign message'); + console.error('Sign message error:', error); + elements.signatureResult.classList.add('hidden'); + showToast(errorMsg, 'error'); + } finally { + setButtonLoading(elements.signBtn, false); + } +} + +// selectKeypair is now handled via event listeners, no need for global access \ No newline at end of file diff --git a/crypto_vault_extension/styles/popup.css b/crypto_vault_extension/styles/popup.css new file mode 100644 index 0000000..075db26 --- /dev/null +++ b/crypto_vault_extension/styles/popup.css @@ -0,0 +1,654 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + width: 400px; + min-height: 600px; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background-attachment: fixed; + color: #333; + line-height: 1.6; + margin: 0; + padding: 0; + overflow-x: hidden; +} + +.container { + position: relative; + min-height: 100vh; + display: flex; + flex-direction: column; +} + +/* Header */ +.header { + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(20px); + padding: 20px; + border-bottom: 1px solid rgba(255, 255, 255, 0.3); +} + +.logo { + display: flex; + align-items: center; + margin-bottom: 12px; +} + +.logo-icon { + font-size: 24px; + margin-right: 12px; +} + +.logo h1 { + font-size: 20px; + font-weight: 600; + color: #2d3748; +} + +.status-indicator { + display: flex; + align-items: center; + font-size: 14px; + color: #666; +} + +.status-dot { + width: 8px; + height: 8px; + border-radius: 50%; + background-color: #fbbf24; + margin-right: 8px; + animation: pulse 2s infinite; +} + +.status-indicator.connected .status-dot { + background-color: #10b981; + animation: none; +} + +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +} + +/* Sections */ +.section { + padding: 20px; + flex: 1; +} + +.section:last-child { + padding-bottom: 20px; +} + +.section.hidden { + display: none; +} + +.completely-hidden { + display: none !important; +} + +/* Cards */ +.card { + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(20px); + border-radius: 16px; + padding: 20px; + margin-bottom: 16px; + border: 1px solid rgba(255, 255, 255, 0.3); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); +} + +.card h2, .card h3 { + margin-bottom: 16px; + color: #2d3748; + font-weight: 600; +} + +.card h2 { + font-size: 18px; +} + +.card h3 { + font-size: 16px; +} + +/* Forms */ +.form-group { + margin-bottom: 16px; +} + +.form-row { + display: flex; + gap: 8px; + align-items: end; +} + +label { + display: block; + margin-bottom: 6px; + font-weight: 500; + color: #4a5568; + font-size: 14px; +} + +input, select, textarea { + width: 100%; + padding: 12px 16px; + border: 2px solid rgba(255, 255, 255, 0.3); + border-radius: 12px; + background: rgba(255, 255, 255, 0.8); + font-size: 14px; + transition: all 0.2s ease; +} + +input:focus, select:focus, textarea:focus { + outline: none; + border-color: #667eea; + background: rgba(255, 255, 255, 0.95); + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); +} + +.select { + flex: 1; +} + +/* Buttons */ +.btn { + padding: 12px 24px; + border: none; + border-radius: 12px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + text-decoration: none; + display: inline-flex; + align-items: center; + justify-content: center; +} + +.btn-primary { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; +} + +.btn-primary:hover { + transform: translateY(-1px); + box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3); +} + +.btn-secondary { + background: rgba(255, 255, 255, 0.8); + color: #4a5568; + border: 2px solid rgba(102, 126, 234, 0.2); +} + +.btn-secondary:hover { + background: rgba(255, 255, 255, 0.95); + border-color: rgba(102, 126, 234, 0.4); +} + +.btn-ghost { + background: transparent; + color: #667eea; + border: 1px solid rgba(102, 126, 234, 0.3); +} + +.btn-ghost:hover { + background: rgba(102, 126, 234, 0.1); +} + +.btn-small { + padding: 8px 16px; + font-size: 12px; +} + +.btn:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none !important; +} + +.btn.loading { + position: relative; + color: transparent !important; + pointer-events: none; +} + +.btn.loading::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 16px; + height: 16px; + margin: -8px 0 0 -8px; + border: 2px solid transparent; + border-top: 2px solid currentColor; + border-radius: 50%; + animation: btn-spin 0.8s linear infinite; + color: white; +} + +.btn-secondary.loading::after { + color: #4a5568; +} + +@keyframes btn-spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +/* Inline Loading Components */ +.inline-loading { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + padding: 8px; + color: #666; + font-size: 14px; +} + +.inline-spinner { + width: 16px; + height: 16px; + border: 2px solid rgba(102, 126, 234, 0.2); + border-top: 2px solid #667eea; + border-radius: 50%; + animation: btn-spin 0.8s linear infinite; +} + +.button-group { + display: flex; + gap: 12px; +} + +.btn-copy { + background: transparent; + border: none; + cursor: pointer; + padding: 4px; + border-radius: 6px; + transition: background 0.2s ease; +} + +.btn-copy:hover { + background: rgba(102, 126, 234, 0.1); +} + +/* Vault Header */ +.vault-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; +} + +.vault-header h2 { + color: white; + font-size: 20px; + font-weight: 600; +} + +/* Add Keypair Toggle */ +.add-keypair-toggle { + margin-bottom: 16px; + text-align: center; +} + +.btn-icon { + margin-right: 8px; + font-weight: bold; + transition: transform 0.2s ease; +} + +.add-keypair-form { + transform: translateY(-10px); + opacity: 0; + transition: all 0.3s ease; + max-height: 0; + overflow: hidden; + padding: 0; + margin-bottom: 0; +} + +.add-keypair-form:not(.hidden) { + transform: translateY(0); + opacity: 1; + max-height: 300px; + padding: 20px; + margin-bottom: 16px; +} + +.form-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; + padding-bottom: 12px; + border-bottom: 1px solid rgba(102, 126, 234, 0.1); +} + +.form-header h3 { + margin: 0; + color: #2d3748; +} + +.btn-close { + background: none; + border: none; + font-size: 20px; + color: #666; + cursor: pointer; + padding: 4px 8px; + border-radius: 6px; + transition: all 0.2s ease; + line-height: 1; +} + +.btn-close:hover { + background: rgba(239, 68, 68, 0.1); + color: #ef4444; +} + +.form-content { + animation: slideInUp 0.3s ease-out; +} + +.form-actions { + margin-top: 16px; + display: flex; + justify-content: flex-end; + gap: 8px; +} + +/* Keypairs List */ +.keypairs-list { + max-height: 200px; + overflow-y: auto; + overflow-x: hidden; +} + +.keypair-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px; + border-radius: 8px; + margin-bottom: 8px; + background: rgba(102, 126, 234, 0.05); + border: 1px solid rgba(102, 126, 234, 0.1); + transition: all 0.2s ease; + min-width: 0; /* Allow flex items to shrink */ +} + +.keypair-item:hover { + background: rgba(102, 126, 234, 0.1); + transform: translateX(4px); +} + +.keypair-item.selected { + background: rgba(16, 185, 129, 0.1); + border-color: rgba(16, 185, 129, 0.3); + transform: translateX(4px); +} + +.keypair-item.selected .keypair-name { + color: #065f46; + font-weight: 600; +} + +.keypair-item.selected .select-btn { + background: rgba(16, 185, 129, 0.2); + color: #065f46; + border-color: rgba(16, 185, 129, 0.3); +} + +.keypair-item.selected .select-btn:hover { + background: rgba(16, 185, 129, 0.3); +} + +.keypair-info { + flex: 1; + min-width: 0; /* Allow shrinking */ + margin-right: 12px; +} + +.keypair-name { + font-weight: 500; + color: #2d3748; + word-break: break-word; /* Break long names */ + overflow: hidden; + text-overflow: ellipsis; +} + +.keypair-type { + font-size: 12px; + color: #666; + background: rgba(102, 126, 234, 0.1); + padding: 2px 8px; + border-radius: 12px; + margin-top: 4px; +} + +.empty-state, .loading { + text-align: center; + color: #666; + font-style: italic; + padding: 20px; +} + +/* Selected Keypair Info */ +.keypair-info .info-row { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; + padding: 8px 0; + border-bottom: 1px solid rgba(102, 126, 234, 0.1); +} + +.keypair-info .info-row:last-child { + border-bottom: none; +} + +.keypair-info label { + font-weight: 500; + color: #4a5568; + margin: 0; +} + +.public-key-container, .signature-container { + display: flex; + align-items: center; + gap: 8px; + flex: 1; + margin-left: 12px; +} + +.keypair-info code, .signature-result code { + background: rgba(102, 126, 234, 0.1); + padding: 6px 10px; + border-radius: 6px; + font-size: 11px; + word-break: break-all; + flex: 1; + color: #2d3748; +} + +/* Signature Result */ +.signature-result { + margin-top: 16px; + padding: 16px; + background: rgba(16, 185, 129, 0.1); + border-radius: 12px; + border: 1px solid rgba(16, 185, 129, 0.2); +} + +.signature-result.hidden { + display: none; +} + +.signature-result label { + color: #065f46; + font-weight: 500; + margin-bottom: 8px; +} + +/* Loading Overlay */ +.loading-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(8px); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + z-index: 1000; +} + +.loading-overlay.hidden { + display: none; +} + +.spinner { + width: 40px; + height: 40px; + border: 3px solid rgba(102, 126, 234, 0.3); + border-top: 3px solid #667eea; + border-radius: 50%; + animation: spin 1s linear infinite; + margin-bottom: 16px; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +/* Toast Notifications */ +.toast { + position: fixed; + top: 20px; + left: 20px; + right: 20px; + max-width: 360px; + margin: 0 auto; + padding: 12px 16px; + border-radius: 12px; + font-size: 14px; + font-weight: 500; + text-align: center; + z-index: 1001; + transform: translateY(-100px); + opacity: 0; + transition: all 0.3s ease; + backdrop-filter: blur(20px); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2); +} + +.toast:not(.hidden) { + transform: translateY(0); + opacity: 1; +} + +.toast.success { + background: rgba(16, 185, 129, 0.9); + color: white; + border: 1px solid rgba(16, 185, 129, 1); +} + +.toast.error { + background: rgba(239, 68, 68, 0.9); + color: white; + border: 1px solid rgba(239, 68, 68, 1); +} + +.toast.info { + background: rgba(59, 130, 246, 0.9); + color: white; + border: 1px solid rgba(59, 130, 246, 1); +} + +/* Scrollbar Styles */ +.keypairs-list::-webkit-scrollbar { + width: 6px; +} + +.keypairs-list::-webkit-scrollbar-track { + background: rgba(102, 126, 234, 0.1); + border-radius: 3px; +} + +.keypairs-list::-webkit-scrollbar-thumb { + background: rgba(102, 126, 234, 0.3); + border-radius: 3px; +} + +.keypairs-list::-webkit-scrollbar-thumb:hover { + background: rgba(102, 126, 234, 0.5); +} + +/* Responsive adjustments */ +@media (max-width: 400px) { + body { + width: 350px; + } + + .form-row { + flex-direction: column; + gap: 12px; + } + + .button-group { + flex-direction: column; + } +} + +/* Animation for card entrance */ +.card { + animation: slideInUp 0.3s ease-out; +} + +@keyframes slideInUp { + from { + transform: translateY(20px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +/* Hover effects for better UX */ +.keypair-item { + position: relative; + overflow: hidden; +} + +.keypair-item::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); + transition: left 0.5s; +} + +.keypair-item:hover::before { + left: 100%; +} \ No newline at end of file diff --git a/crypto_vault_extension/wasm/wasm_app.js b/crypto_vault_extension/wasm/wasm_app.js new file mode 100644 index 0000000..5583e35 --- /dev/null +++ b/crypto_vault_extension/wasm/wasm_app.js @@ -0,0 +1,822 @@ +let wasm; + +function addToExternrefTable0(obj) { + const idx = wasm.__externref_table_alloc(); + wasm.__wbindgen_export_2.set(idx, obj); + return idx; +} + +function handleError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + const idx = addToExternrefTable0(e); + wasm.__wbindgen_exn_store(idx); + } +} + +const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } ); + +if (typeof TextDecoder !== 'undefined') { cachedTextDecoder.decode(); }; + +let cachedUint8ArrayMemory0 = null; + +function getUint8ArrayMemory0() { + if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) { + cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8ArrayMemory0; +} + +function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0; + return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len)); +} + +function isLikeNone(x) { + return x === undefined || x === null; +} + +function getArrayU8FromWasm0(ptr, len) { + ptr = ptr >>> 0; + return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len); +} + +let WASM_VECTOR_LEN = 0; + +const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } ); + +const encodeString = (typeof cachedTextEncoder.encodeInto === 'function' + ? function (arg, view) { + return cachedTextEncoder.encodeInto(arg, view); +} + : function (arg, view) { + const buf = cachedTextEncoder.encode(arg); + view.set(buf); + return { + read: arg.length, + written: buf.length + }; +}); + +function passStringToWasm0(arg, malloc, realloc) { + + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length, 1) >>> 0; + getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len, 1) >>> 0; + + const mem = getUint8ArrayMemory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7F) break; + mem[ptr + offset] = code; + } + + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0; + const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); + const ret = encodeString(arg, view); + + offset += ret.written; + ptr = realloc(ptr, len, offset, 1) >>> 0; + } + + WASM_VECTOR_LEN = offset; + return ptr; +} + +let cachedDataViewMemory0 = null; + +function getDataViewMemory0() { + if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) { + cachedDataViewMemory0 = new DataView(wasm.memory.buffer); + } + return cachedDataViewMemory0; +} + +const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') + ? { register: () => {}, unregister: () => {} } + : new FinalizationRegistry(state => { + wasm.__wbindgen_export_5.get(state.dtor)(state.a, state.b) +}); + +function makeMutClosure(arg0, arg1, dtor, f) { + const state = { a: arg0, b: arg1, cnt: 1, dtor }; + const real = (...args) => { + // First up with a closure we increment the internal reference + // count. This ensures that the Rust closure environment won't + // be deallocated while we're invoking it. + state.cnt++; + const a = state.a; + state.a = 0; + try { + return f(a, state.b, ...args); + } finally { + if (--state.cnt === 0) { + wasm.__wbindgen_export_5.get(state.dtor)(a, state.b); + CLOSURE_DTORS.unregister(state); + } else { + state.a = a; + } + } + }; + real.original = state; + CLOSURE_DTORS.register(real, state, state); + return real; +} + +function debugString(val) { + // primitive types + const type = typeof val; + if (type == 'number' || type == 'boolean' || val == null) { + return `${val}`; + } + if (type == 'string') { + return `"${val}"`; + } + if (type == 'symbol') { + const description = val.description; + if (description == null) { + return 'Symbol'; + } else { + return `Symbol(${description})`; + } + } + if (type == 'function') { + const name = val.name; + if (typeof name == 'string' && name.length > 0) { + return `Function(${name})`; + } else { + return 'Function'; + } + } + // objects + if (Array.isArray(val)) { + const length = val.length; + let debug = '['; + if (length > 0) { + debug += debugString(val[0]); + } + for(let i = 1; i < length; i++) { + debug += ', ' + debugString(val[i]); + } + debug += ']'; + return debug; + } + // Test for built-in + const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val)); + let className; + if (builtInMatches && builtInMatches.length > 1) { + className = builtInMatches[1]; + } else { + // Failed to match the standard '[object ClassName]' + return toString.call(val); + } + if (className == 'Object') { + // we're a user defined class or Object + // JSON.stringify avoids problems with cycles, and is generally much + // easier than looping through ownProperties of `val`. + try { + return 'Object(' + JSON.stringify(val) + ')'; + } catch (_) { + return 'Object'; + } + } + // errors + if (val instanceof Error) { + return `${val.name}: ${val.message}\n${val.stack}`; + } + // TODO we could test for more things here, like `Set`s and `Map`s. + return className; +} +/** + * Initialize the scripting environment (must be called before run_rhai) + */ +export function init_rhai_env() { + wasm.init_rhai_env(); +} + +function takeFromExternrefTable0(idx) { + const value = wasm.__wbindgen_export_2.get(idx); + wasm.__externref_table_dealloc(idx); + return value; +} +/** + * Securely run a Rhai script in the extension context (must be called only after user approval) + * @param {string} script + * @returns {any} + */ +export function run_rhai(script) { + const ptr0 = passStringToWasm0(script, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.run_rhai(ptr0, len0); + if (ret[2]) { + throw takeFromExternrefTable0(ret[1]); + } + return takeFromExternrefTable0(ret[0]); +} + +/** + * Create and unlock a new keyspace with the given name and password + * @param {string} keyspace + * @param {string} password + * @returns {Promise} + */ +export function create_keyspace(keyspace, password) { + const ptr0 = passStringToWasm0(keyspace, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passStringToWasm0(password, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + const ret = wasm.create_keyspace(ptr0, len0, ptr1, len1); + return ret; +} + +/** + * Initialize session with keyspace and password + * @param {string} keyspace + * @param {string} password + * @returns {Promise} + */ +export function init_session(keyspace, password) { + const ptr0 = passStringToWasm0(keyspace, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passStringToWasm0(password, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + const ret = wasm.init_session(ptr0, len0, ptr1, len1); + return ret; +} + +/** + * Lock the session (zeroize password and session) + */ +export function lock_session() { + wasm.lock_session(); +} + +/** + * Get metadata of the currently selected keypair + * @returns {any} + */ +export function current_keypair_metadata() { + const ret = wasm.current_keypair_metadata(); + if (ret[2]) { + throw takeFromExternrefTable0(ret[1]); + } + return takeFromExternrefTable0(ret[0]); +} + +/** + * Get public key of the currently selected keypair as Uint8Array + * @returns {any} + */ +export function current_keypair_public_key() { + const ret = wasm.current_keypair_public_key(); + if (ret[2]) { + throw takeFromExternrefTable0(ret[1]); + } + return takeFromExternrefTable0(ret[0]); +} + +/** + * Returns true if a keyspace is currently unlocked + * @returns {boolean} + */ +export function is_unlocked() { + const ret = wasm.is_unlocked(); + return ret !== 0; +} + +/** + * Get all keypairs from the current session + * Returns an array of keypair objects with id, type, and metadata + * Select keypair for the session + * @param {string} key_id + */ +export function select_keypair(key_id) { + const ptr0 = passStringToWasm0(key_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.select_keypair(ptr0, len0); + if (ret[1]) { + throw takeFromExternrefTable0(ret[0]); + } +} + +/** + * List keypairs in the current session's keyspace + * @returns {Promise} + */ +export function list_keypairs() { + const ret = wasm.list_keypairs(); + return ret; +} + +/** + * Add a keypair to the current keyspace + * @param {string | null} [key_type] + * @param {string | null} [metadata] + * @returns {Promise} + */ +export function add_keypair(key_type, metadata) { + var ptr0 = isLikeNone(key_type) ? 0 : passStringToWasm0(key_type, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len0 = WASM_VECTOR_LEN; + var ptr1 = isLikeNone(metadata) ? 0 : passStringToWasm0(metadata, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len1 = WASM_VECTOR_LEN; + const ret = wasm.add_keypair(ptr0, len0, ptr1, len1); + return ret; +} + +function passArray8ToWasm0(arg, malloc) { + const ptr = malloc(arg.length * 1, 1) >>> 0; + getUint8ArrayMemory0().set(arg, ptr / 1); + WASM_VECTOR_LEN = arg.length; + return ptr; +} +/** + * Sign message with current session + * @param {Uint8Array} message + * @returns {Promise} + */ +export function sign(message) { + const ptr0 = passArray8ToWasm0(message, wasm.__wbindgen_malloc); + const len0 = WASM_VECTOR_LEN; + const ret = wasm.sign(ptr0, len0); + return ret; +} + +function __wbg_adapter_32(arg0, arg1, arg2) { + wasm.closure89_externref_shim(arg0, arg1, arg2); +} + +function __wbg_adapter_35(arg0, arg1, arg2) { + wasm.closure133_externref_shim(arg0, arg1, arg2); +} + +function __wbg_adapter_38(arg0, arg1, arg2) { + wasm.closure188_externref_shim(arg0, arg1, arg2); +} + +function __wbg_adapter_135(arg0, arg1, arg2, arg3) { + wasm.closure1847_externref_shim(arg0, arg1, arg2, arg3); +} + +const __wbindgen_enum_IdbTransactionMode = ["readonly", "readwrite", "versionchange", "readwriteflush", "cleanup"]; + +async function __wbg_load(module, imports) { + if (typeof Response === 'function' && module instanceof Response) { + if (typeof WebAssembly.instantiateStreaming === 'function') { + try { + return await WebAssembly.instantiateStreaming(module, imports); + + } catch (e) { + if (module.headers.get('Content-Type') != 'application/wasm') { + console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e); + + } else { + throw e; + } + } + } + + const bytes = await module.arrayBuffer(); + return await WebAssembly.instantiate(bytes, imports); + + } else { + const instance = await WebAssembly.instantiate(module, imports); + + if (instance instanceof WebAssembly.Instance) { + return { instance, module }; + + } else { + return instance; + } + } +} + +function __wbg_get_imports() { + const imports = {}; + imports.wbg = {}; + imports.wbg.__wbg_buffer_609cc3eee51ed158 = function(arg0) { + const ret = arg0.buffer; + return ret; + }; + imports.wbg.__wbg_call_672a4d21634d4a24 = function() { return handleError(function (arg0, arg1) { + const ret = arg0.call(arg1); + return ret; + }, arguments) }; + imports.wbg.__wbg_call_7cccdd69e0791ae2 = function() { return handleError(function (arg0, arg1, arg2) { + const ret = arg0.call(arg1, arg2); + return ret; + }, arguments) }; + imports.wbg.__wbg_createObjectStore_d2f9e1016f4d81b9 = function() { return handleError(function (arg0, arg1, arg2, arg3) { + const ret = arg0.createObjectStore(getStringFromWasm0(arg1, arg2), arg3); + return ret; + }, arguments) }; + imports.wbg.__wbg_crypto_574e78ad8b13b65f = function(arg0) { + const ret = arg0.crypto; + return ret; + }; + imports.wbg.__wbg_error_524f506f44df1645 = function(arg0) { + console.error(arg0); + }; + imports.wbg.__wbg_error_ff4ddaabdfc5dbb3 = function() { return handleError(function (arg0) { + const ret = arg0.error; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }, arguments) }; + imports.wbg.__wbg_getRandomValues_3c9c0d586e575a16 = function() { return handleError(function (arg0, arg1) { + globalThis.crypto.getRandomValues(getArrayU8FromWasm0(arg0, arg1)); + }, arguments) }; + imports.wbg.__wbg_getRandomValues_b8f5dbd5f3995a9e = function() { return handleError(function (arg0, arg1) { + arg0.getRandomValues(arg1); + }, arguments) }; + imports.wbg.__wbg_getTime_46267b1c24877e30 = function(arg0) { + const ret = arg0.getTime(); + return ret; + }; + imports.wbg.__wbg_get_4f73335ab78445db = function(arg0, arg1, arg2) { + const ret = arg1[arg2 >>> 0]; + var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + var len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; + imports.wbg.__wbg_get_67b2ba62fc30de12 = function() { return handleError(function (arg0, arg1) { + const ret = Reflect.get(arg0, arg1); + return ret; + }, arguments) }; + imports.wbg.__wbg_get_8da03f81f6a1111e = function() { return handleError(function (arg0, arg1) { + const ret = arg0.get(arg1); + return ret; + }, arguments) }; + imports.wbg.__wbg_instanceof_IdbDatabase_a3ef009ca00059f9 = function(arg0) { + let result; + try { + result = arg0 instanceof IDBDatabase; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }; + imports.wbg.__wbg_instanceof_IdbFactory_12eaba3366f4302f = function(arg0) { + let result; + try { + result = arg0 instanceof IDBFactory; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }; + imports.wbg.__wbg_instanceof_IdbOpenDbRequest_a3416e156c9db893 = function(arg0) { + let result; + try { + result = arg0 instanceof IDBOpenDBRequest; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }; + imports.wbg.__wbg_instanceof_IdbRequest_4813c3f207666aa4 = function(arg0) { + let result; + try { + result = arg0 instanceof IDBRequest; + } catch (_) { + result = false; + } + const ret = result; + return ret; + }; + imports.wbg.__wbg_length_52b6c4580c5ec934 = function(arg0) { + const ret = arg0.length; + return ret; + }; + imports.wbg.__wbg_msCrypto_a61aeb35a24c1329 = function(arg0) { + const ret = arg0.msCrypto; + return ret; + }; + imports.wbg.__wbg_new0_f788a2397c7ca929 = function() { + const ret = new Date(); + return ret; + }; + imports.wbg.__wbg_new_23a2665fac83c611 = function(arg0, arg1) { + try { + var state0 = {a: arg0, b: arg1}; + var cb0 = (arg0, arg1) => { + const a = state0.a; + state0.a = 0; + try { + return __wbg_adapter_135(a, state0.b, arg0, arg1); + } finally { + state0.a = a; + } + }; + const ret = new Promise(cb0); + return ret; + } finally { + state0.a = state0.b = 0; + } + }; + imports.wbg.__wbg_new_405e22f390576ce2 = function() { + const ret = new Object(); + return ret; + }; + imports.wbg.__wbg_new_78feb108b6472713 = function() { + const ret = new Array(); + return ret; + }; + imports.wbg.__wbg_new_a12002a7f91c75be = function(arg0) { + const ret = new Uint8Array(arg0); + return ret; + }; + imports.wbg.__wbg_newnoargs_105ed471475aaf50 = function(arg0, arg1) { + const ret = new Function(getStringFromWasm0(arg0, arg1)); + return ret; + }; + imports.wbg.__wbg_newwithbyteoffsetandlength_d97e637ebe145a9a = function(arg0, arg1, arg2) { + const ret = new Uint8Array(arg0, arg1 >>> 0, arg2 >>> 0); + return ret; + }; + imports.wbg.__wbg_newwithlength_a381634e90c276d4 = function(arg0) { + const ret = new Uint8Array(arg0 >>> 0); + return ret; + }; + imports.wbg.__wbg_node_905d3e251edff8a2 = function(arg0) { + const ret = arg0.node; + return ret; + }; + imports.wbg.__wbg_now_d18023d54d4e5500 = function(arg0) { + const ret = arg0.now(); + return ret; + }; + imports.wbg.__wbg_objectStoreNames_9bb1ab04a7012aaf = function(arg0) { + const ret = arg0.objectStoreNames; + return ret; + }; + imports.wbg.__wbg_objectStore_21878d46d25b64b6 = function() { return handleError(function (arg0, arg1, arg2) { + const ret = arg0.objectStore(getStringFromWasm0(arg1, arg2)); + return ret; + }, arguments) }; + imports.wbg.__wbg_open_88b1390d99a7c691 = function() { return handleError(function (arg0, arg1, arg2) { + const ret = arg0.open(getStringFromWasm0(arg1, arg2)); + return ret; + }, arguments) }; + imports.wbg.__wbg_open_e0c0b2993eb596e1 = function() { return handleError(function (arg0, arg1, arg2, arg3) { + const ret = arg0.open(getStringFromWasm0(arg1, arg2), arg3 >>> 0); + return ret; + }, arguments) }; + imports.wbg.__wbg_process_dc0fbacc7c1c06f7 = function(arg0) { + const ret = arg0.process; + return ret; + }; + imports.wbg.__wbg_push_737cfc8c1432c2c6 = function(arg0, arg1) { + const ret = arg0.push(arg1); + return ret; + }; + imports.wbg.__wbg_put_066faa31a6a88f5b = function() { return handleError(function (arg0, arg1, arg2) { + const ret = arg0.put(arg1, arg2); + return ret; + }, arguments) }; + imports.wbg.__wbg_put_9ef5363941008835 = function() { return handleError(function (arg0, arg1) { + const ret = arg0.put(arg1); + return ret; + }, arguments) }; + imports.wbg.__wbg_queueMicrotask_97d92b4fcc8a61c5 = function(arg0) { + queueMicrotask(arg0); + }; + imports.wbg.__wbg_queueMicrotask_d3219def82552485 = function(arg0) { + const ret = arg0.queueMicrotask; + return ret; + }; + imports.wbg.__wbg_randomFillSync_ac0988aba3254290 = function() { return handleError(function (arg0, arg1) { + arg0.randomFillSync(arg1); + }, arguments) }; + imports.wbg.__wbg_require_60cc747a6bc5215a = function() { return handleError(function () { + const ret = module.require; + return ret; + }, arguments) }; + imports.wbg.__wbg_resolve_4851785c9c5f573d = function(arg0) { + const ret = Promise.resolve(arg0); + return ret; + }; + imports.wbg.__wbg_result_f29afabdf2c05826 = function() { return handleError(function (arg0) { + const ret = arg0.result; + return ret; + }, arguments) }; + imports.wbg.__wbg_set_65595bdd868b3009 = function(arg0, arg1, arg2) { + arg0.set(arg1, arg2 >>> 0); + }; + imports.wbg.__wbg_setonerror_d7e3056cc6e56085 = function(arg0, arg1) { + arg0.onerror = arg1; + }; + imports.wbg.__wbg_setonsuccess_afa464ee777a396d = function(arg0, arg1) { + arg0.onsuccess = arg1; + }; + imports.wbg.__wbg_setonupgradeneeded_fcf7ce4f2eb0cb5f = function(arg0, arg1) { + arg0.onupgradeneeded = arg1; + }; + imports.wbg.__wbg_static_accessor_GLOBAL_88a902d13a557d07 = function() { + const ret = typeof global === 'undefined' ? null : global; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }; + imports.wbg.__wbg_static_accessor_GLOBAL_THIS_56578be7e9f832b0 = function() { + const ret = typeof globalThis === 'undefined' ? null : globalThis; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }; + imports.wbg.__wbg_static_accessor_SELF_37c5d418e4bf5819 = function() { + const ret = typeof self === 'undefined' ? null : self; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }; + imports.wbg.__wbg_static_accessor_WINDOW_5de37043a91a9c40 = function() { + const ret = typeof window === 'undefined' ? null : window; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }; + imports.wbg.__wbg_subarray_aa9065fa9dc5df96 = function(arg0, arg1, arg2) { + const ret = arg0.subarray(arg1 >>> 0, arg2 >>> 0); + return ret; + }; + imports.wbg.__wbg_target_0a62d9d79a2a1ede = function(arg0) { + const ret = arg0.target; + return isLikeNone(ret) ? 0 : addToExternrefTable0(ret); + }; + imports.wbg.__wbg_then_44b73946d2fb3e7d = function(arg0, arg1) { + const ret = arg0.then(arg1); + return ret; + }; + imports.wbg.__wbg_transaction_d6d07c3c9963c49e = function() { return handleError(function (arg0, arg1, arg2) { + const ret = arg0.transaction(arg1, __wbindgen_enum_IdbTransactionMode[arg2]); + return ret; + }, arguments) }; + imports.wbg.__wbg_versions_c01dfd4722a88165 = function(arg0) { + const ret = arg0.versions; + return ret; + }; + imports.wbg.__wbindgen_cb_drop = function(arg0) { + const obj = arg0.original; + if (obj.cnt-- == 1) { + obj.a = 0; + return true; + } + const ret = false; + return ret; + }; + imports.wbg.__wbindgen_closure_wrapper288 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 90, __wbg_adapter_32); + return ret; + }; + imports.wbg.__wbindgen_closure_wrapper518 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 134, __wbg_adapter_35); + return ret; + }; + imports.wbg.__wbindgen_closure_wrapper776 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 189, __wbg_adapter_38); + return ret; + }; + imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { + const ret = debugString(arg1); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; + imports.wbg.__wbindgen_init_externref_table = function() { + const table = wasm.__wbindgen_export_2; + const offset = table.grow(4); + table.set(0, undefined); + table.set(offset + 0, undefined); + table.set(offset + 1, null); + table.set(offset + 2, true); + table.set(offset + 3, false); + ; + }; + imports.wbg.__wbindgen_is_function = function(arg0) { + const ret = typeof(arg0) === 'function'; + return ret; + }; + imports.wbg.__wbindgen_is_null = function(arg0) { + const ret = arg0 === null; + return ret; + }; + imports.wbg.__wbindgen_is_object = function(arg0) { + const val = arg0; + const ret = typeof(val) === 'object' && val !== null; + return ret; + }; + imports.wbg.__wbindgen_is_string = function(arg0) { + const ret = typeof(arg0) === 'string'; + return ret; + }; + imports.wbg.__wbindgen_is_undefined = function(arg0) { + const ret = arg0 === undefined; + return ret; + }; + imports.wbg.__wbindgen_json_parse = function(arg0, arg1) { + const ret = JSON.parse(getStringFromWasm0(arg0, arg1)); + return ret; + }; + imports.wbg.__wbindgen_json_serialize = function(arg0, arg1) { + const obj = arg1; + const ret = JSON.stringify(obj === undefined ? null : obj); + const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc); + const len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }; + imports.wbg.__wbindgen_memory = function() { + const ret = wasm.memory; + return ret; + }; + imports.wbg.__wbindgen_string_new = function(arg0, arg1) { + const ret = getStringFromWasm0(arg0, arg1); + return ret; + }; + imports.wbg.__wbindgen_throw = function(arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); + }; + + return imports; +} + +function __wbg_init_memory(imports, memory) { + +} + +function __wbg_finalize_init(instance, module) { + wasm = instance.exports; + __wbg_init.__wbindgen_wasm_module = module; + cachedDataViewMemory0 = null; + cachedUint8ArrayMemory0 = null; + + + wasm.__wbindgen_start(); + return wasm; +} + +function initSync(module) { + if (wasm !== undefined) return wasm; + + + if (typeof module !== 'undefined') { + if (Object.getPrototypeOf(module) === Object.prototype) { + ({module} = module) + } else { + console.warn('using deprecated parameters for `initSync()`; pass a single object instead') + } + } + + const imports = __wbg_get_imports(); + + __wbg_init_memory(imports); + + if (!(module instanceof WebAssembly.Module)) { + module = new WebAssembly.Module(module); + } + + const instance = new WebAssembly.Instance(module, imports); + + return __wbg_finalize_init(instance, module); +} + +async function __wbg_init(module_or_path) { + if (wasm !== undefined) return wasm; + + + if (typeof module_or_path !== 'undefined') { + if (Object.getPrototypeOf(module_or_path) === Object.prototype) { + ({module_or_path} = module_or_path) + } else { + console.warn('using deprecated parameters for the initialization function; pass a single object instead') + } + } + + if (typeof module_or_path === 'undefined') { + module_or_path = new URL('wasm_app_bg.wasm', import.meta.url); + } + const imports = __wbg_get_imports(); + + if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) { + module_or_path = fetch(module_or_path); + } + + __wbg_init_memory(imports); + + const { instance, module } = await __wbg_load(await module_or_path, imports); + + return __wbg_finalize_init(instance, module); +} + +export { initSync }; +export default __wbg_init; diff --git a/crypto_vault_extension/wasm/wasm_app_bg.wasm b/crypto_vault_extension/wasm/wasm_app_bg.wasm new file mode 100644 index 0000000..d5b242e Binary files /dev/null and b/crypto_vault_extension/wasm/wasm_app_bg.wasm differ