sal-modular/crypto_vault_extension/popup.js

953 lines
31 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Enhanced toast system
function showToast(message, type = 'info') {
// Remove any existing toast
const existingToast = document.querySelector('.toast-notification');
if (existingToast) {
existingToast.remove();
}
// Create new toast element
const toast = document.createElement('div');
toast.className = `toast-notification toast-${type}`;
// Add icon based on type
const icon = getToastIcon(type);
toast.innerHTML = `
<div class="toast-icon">${icon}</div>
<div class="toast-content">
<div class="toast-message">${message}</div>
</div>
<button class="toast-close" onclick="this.parentElement.remove()">×</button>
`;
// Add to document
document.body.appendChild(toast);
// Trigger entrance animation
setTimeout(() => toast.classList.add('toast-show'), 10);
// Auto-remove after 4 seconds
setTimeout(() => {
if (toast.parentElement) {
toast.classList.add('toast-hide');
setTimeout(() => toast.remove(), 300);
}
}, 4000);
}
function getToastIcon(type) {
switch (type) {
case 'success':
return `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="20,6 9,17 4,12"></polyline>
</svg>`;
case 'error':
return `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<line x1="15" y1="9" x2="9" y2="15"></line>
<line x1="9" y1="9" x2="15" y2="15"></line>
</svg>`;
case 'info':
default:
return `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="16" x2="12" y2="12"></line>
<line x1="12" y1="8" x2="12.01" y2="8"></line>
</svg>`;
}
}
function showLoading(show = true) {
const overlay = document.getElementById('loadingOverlay');
overlay.classList.toggle('hidden', !show);
}
// Enhanced loading states for buttons
function setButtonLoading(button, loading = true) {
if (loading) {
button.dataset.originalText = button.textContent;
button.classList.add('loading');
button.disabled = true;
} else {
button.classList.remove('loading');
button.disabled = false;
if (button.dataset.originalText) {
button.textContent = button.dataset.originalText;
}
}
}
// 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);
// Show/hide lock button - only show when session is unlocked
const lockBtn = document.getElementById('lockBtn');
if (lockBtn) {
// Only show lock button when connected AND status indicates unlocked session
const isUnlocked = isConnected && text.toLowerCase().startsWith('connected to');
lockBtn.classList.toggle('hidden', !isUnlocked);
}
}
// 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) {
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'),
themeToggle: document.getElementById('themeToggle'),
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'),
// Sign tab
messageInput: document.getElementById('messageInput'),
signBtn: document.getElementById('signBtn'),
signatureResult: document.getElementById('signatureResult'),
copySignatureBtn: document.getElementById('copySignatureBtn'),
// Encrypt tab
encryptMessageInput: document.getElementById('encryptMessageInput'),
encryptBtn: document.getElementById('encryptBtn'),
encryptResult: document.getElementById('encryptResult'),
// Decrypt tab
encryptedMessageInput: document.getElementById('encryptedMessageInput'),
decryptBtn: document.getElementById('decryptBtn'),
decryptResult: document.getElementById('decryptResult'),
// Verify tab
verifyMessageInput: document.getElementById('verifyMessageInput'),
signatureToVerifyInput: document.getElementById('signatureToVerifyInput'),
verifyBtn: document.getElementById('verifyBtn'),
verifyResult: document.getElementById('verifyResult'),
};
let currentKeyspace = null;
let selectedKeypairId = null;
let backgroundPort = null;
// Theme management
function initializeTheme() {
const savedTheme = localStorage.getItem('cryptovault-theme') || 'light';
document.documentElement.setAttribute('data-theme', savedTheme);
updateThemeIcon(savedTheme);
}
function toggleTheme() {
const currentTheme = document.documentElement.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', newTheme);
localStorage.setItem('cryptovault-theme', newTheme);
updateThemeIcon(newTheme);
}
function updateThemeIcon(theme) {
const themeToggle = elements.themeToggle;
if (!themeToggle) return;
if (theme === 'dark') {
themeToggle.innerHTML = '☀️';
themeToggle.title = 'Switch to light mode';
} else {
// Dark crescent moon SVG for better visibility
themeToggle.innerHTML = `
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor" stroke="none">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" fill="#333"/>
</svg>
`;
themeToggle.title = 'Switch to dark mode';
}
}
// Establish connection to background script for keep-alive
function connectToBackground() {
try {
backgroundPort = chrome.runtime.connect({ name: 'popup' });
backgroundPort.onDisconnect.addListener(() => {
backgroundPort = null;
});
} catch (error) {
// Silently handle connection errors
}
}
// Initialize
document.addEventListener('DOMContentLoaded', async function() {
// Initialize theme first
initializeTheme();
// Ensure lock button starts hidden
const lockBtn = document.getElementById('lockBtn');
if (lockBtn) {
lockBtn.classList.add('hidden');
}
setStatus('Initializing...', false);
// Connect to background script for keep-alive
connectToBackground();
// Event listeners (with null checks)
if (elements.createKeyspaceBtn) {
elements.createKeyspaceBtn.addEventListener('click', createKeyspace);
}
if (elements.loginBtn) {
elements.loginBtn.addEventListener('click', login);
}
if (elements.lockBtn) {
elements.lockBtn.addEventListener('click', lockSession);
}
if (elements.themeToggle) {
elements.themeToggle.addEventListener('click', toggleTheme);
}
if (elements.toggleAddKeypairBtn) {
elements.toggleAddKeypairBtn.addEventListener('click', toggleAddKeypairForm);
}
if (elements.addKeypairBtn) {
elements.addKeypairBtn.addEventListener('click', addKeypair);
}
if (elements.cancelAddKeypairBtn) {
elements.cancelAddKeypairBtn.addEventListener('click', hideAddKeypairForm);
}
// Crypto operation buttons (with null checks)
if (elements.signBtn) {
elements.signBtn.addEventListener('click', signMessage);
}
if (elements.encryptBtn) {
elements.encryptBtn.addEventListener('click', encryptMessage);
}
if (elements.decryptBtn) {
elements.decryptBtn.addEventListener('click', decryptMessage);
}
if (elements.verifyBtn) {
elements.verifyBtn.addEventListener('click', verifySignature);
}
// Tab functionality
initializeTabs();
// Copy button event listeners (with null checks)
if (elements.copySignatureBtn) {
elements.copySignatureBtn.addEventListener('click', () => {
const signature = document.getElementById('signatureValue');
if (signature) {
copyToClipboard(signature.textContent);
}
});
}
// Enable sign button when message is entered (with null checks)
if (elements.messageInput && elements.signBtn) {
elements.messageInput.addEventListener('input', () => {
elements.signBtn.disabled = !elements.messageInput.value.trim() || !selectedKeypairId;
});
}
// Basic keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && elements.addKeypairCard && !elements.addKeypairCard.classList.contains('hidden')) {
hideAddKeypairForm();
}
if (e.key === 'Enter' && e.target === elements.keyNameInput && elements.keyNameInput.value.trim()) {
e.preventDefault();
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();
} else {
// No active session
setStatus('Ready', false);
showSection('authSection');
}
} catch (error) {
setStatus('Ready', false);
showSection('authSection');
}
}
// 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');
elements.keyNameInput.focus();
}
function hideAddKeypairForm() {
elements.addKeypairCard.classList.add('hidden');
// Clear the form
elements.keyNameInput.value = '';
elements.keyTypeSelect.selectedIndex = 0;
}
// Tab functionality
function initializeTabs() {
const tabButtons = document.querySelectorAll('.tab-btn');
const tabContents = document.querySelectorAll('.tab-content');
tabButtons.forEach(button => {
button.addEventListener('click', () => {
const targetTab = button.getAttribute('data-tab');
// Remove active class from all tabs and contents
tabButtons.forEach(btn => btn.classList.remove('active'));
tabContents.forEach(content => content.classList.remove('active'));
// Add active class to clicked tab and corresponding content
button.classList.add('active');
document.getElementById(`${targetTab}-tab`).classList.add('active');
// Scroll the selected tab into view
scrollTabIntoView(button);
// Clear results when switching tabs
clearTabResults();
// Update button states
updateButtonStates();
});
});
// Initialize input validation
initializeInputValidation();
}
function scrollTabIntoView(selectedTab) {
// Simple scroll into view
if (selectedTab) {
selectedTab.scrollIntoView({ behavior: 'smooth', inline: 'center' });
}
}
function clearTabResults() {
// Hide all result sections (with null checks)
if (elements.signatureResult) {
elements.signatureResult.classList.add('hidden');
elements.signatureResult.innerHTML = '';
}
if (elements.encryptResult) {
elements.encryptResult.classList.add('hidden');
elements.encryptResult.innerHTML = '';
}
if (elements.decryptResult) {
elements.decryptResult.classList.add('hidden');
elements.decryptResult.innerHTML = '';
}
if (elements.verifyResult) {
elements.verifyResult.classList.add('hidden');
elements.verifyResult.innerHTML = '';
}
}
function initializeInputValidation() {
// Sign tab validation (with null checks)
if (elements.messageInput) {
elements.messageInput.addEventListener('input', updateButtonStates);
}
// Encrypt tab validation (with null checks)
if (elements.encryptMessageInput) {
elements.encryptMessageInput.addEventListener('input', updateButtonStates);
}
// Decrypt tab validation (with null checks)
if (elements.encryptedMessageInput) {
elements.encryptedMessageInput.addEventListener('input', updateButtonStates);
}
// Verify tab validation (with null checks)
if (elements.verifyMessageInput) {
elements.verifyMessageInput.addEventListener('input', updateButtonStates);
}
if (elements.signatureToVerifyInput) {
elements.signatureToVerifyInput.addEventListener('input', updateButtonStates);
}
}
function updateButtonStates() {
// Sign button (with null checks)
if (elements.signBtn && elements.messageInput) {
elements.signBtn.disabled = !elements.messageInput.value.trim() || !selectedKeypairId;
}
// Encrypt button (with null checks) - only needs message and keyspace session
if (elements.encryptBtn && elements.encryptMessageInput) {
elements.encryptBtn.disabled = !elements.encryptMessageInput.value.trim() || !currentKeyspace;
}
// Decrypt button (with null checks) - only needs encrypted message and keyspace session
if (elements.decryptBtn && elements.encryptedMessageInput) {
elements.decryptBtn.disabled = !elements.encryptedMessageInput.value.trim() || !currentKeyspace;
}
// Verify button (with null checks) - only needs message and signature
if (elements.verifyBtn && elements.verifyMessageInput && elements.signatureToVerifyInput) {
elements.verifyBtn.disabled = !elements.verifyMessageInput.value.trim() ||
!elements.signatureToVerifyInput.value.trim() ||
!selectedKeypairId;
}
}
// Clear all vault-related state and UI
function clearVaultState() {
// Clear all crypto operation inputs (with null checks)
if (elements.messageInput) elements.messageInput.value = '';
if (elements.encryptMessageInput) elements.encryptMessageInput.value = '';
if (elements.encryptedMessageInput) elements.encryptedMessageInput.value = '';
if (elements.verifyMessageInput) elements.verifyMessageInput.value = '';
if (elements.signatureToVerifyInput) elements.signatureToVerifyInput.value = '';
// Clear all result sections
clearTabResults();
// Clear signature value with null check
const signatureValue = document.getElementById('signatureValue');
if (signatureValue) signatureValue.textContent = '';
// Clear selected keypair state
selectedKeypairId = null;
updateButtonStates();
// Clear selected keypair info (hidden elements) with null checks
const selectedName = document.getElementById('selectedName');
const selectedType = document.getElementById('selectedType');
const selectedPublicKey = document.getElementById('selectedPublicKey');
if (selectedName) selectedName.textContent = '-';
if (selectedType) selectedType.textContent = '-';
if (selectedPublicKey) selectedPublicKey.textContent = '-';
// Hide add keypair form if open
hideAddKeypairForm();
// Clear keypairs list
if (elements.keypairsList) {
elements.keypairsList.innerHTML = '<div class="loading">Loading keypairs...</div>';
}
}
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;
}
try {
await executeOperation(
async () => {
const response = await sendMessage('createKeyspace', { keyspace, password });
if (response && response.success) {
// Clear any existing state before auto-login
clearVaultState();
await login(); // Auto-login after creation
return response;
} else {
throw new Error(getResponseError(response, 'create keyspace'));
}
},
{
loadingElement: elements.createKeyspaceBtn,
successMessage: 'Keyspace created successfully!',
maxRetries: 1
}
);
} catch (error) {
console.error('Create keyspace error:', error);
}
}
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;
}
try {
await executeOperation(
async () => {
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();
return response;
} else {
throw new Error(getResponseError(response, 'login'));
}
},
{
loadingElement: elements.loginBtn,
successMessage: 'Logged in successfully!',
maxRetries: 2
}
);
} catch (error) {
console.error('Login error:', error);
}
}
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;
}
try {
await executeOperation(
async () => {
const metadata = JSON.stringify({ name: keyName });
const response = await sendMessage('addKeypair', { keyType, metadata });
if (response?.success) {
hideAddKeypairForm();
await loadKeypairs();
return response;
} else {
throw new Error(getResponseError(response, 'add keypair'));
}
},
{
loadingElement: elements.addKeypairBtn,
successMessage: 'Keypair added successfully!'
}
);
} catch (error) {
// Error already handled by executeOperation
}
}
async function loadKeypairs() {
try {
const response = await sendMessage('listKeypairs');
if (response && response.success) {
renderKeypairs(response.keypairs);
} else {
const errorMsg = getResponseError(response, 'load keypairs');
const container = elements.keypairsList;
container.innerHTML = '<div class="empty-state">Failed to load keypairs. Try refreshing.</div>';
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 = '<div class="empty-state">Error loading keypairs. Try refreshing.</div>';
showToast(errorMsg, 'error');
}
}
function renderKeypairs(keypairs) {
const container = elements.keypairsList;
// Simple array handling
const keypairArray = Array.isArray(keypairs) ? keypairs : [];
if (keypairArray.length === 0) {
container.innerHTML = '<div class="empty-state">No keypairs found. Add one above.</div>';
return;
}
container.innerHTML = keypairArray.map((keypair) => {
const metadata = typeof keypair.metadata === 'string'
? JSON.parse(keypair.metadata)
: keypair.metadata;
return `
<div class="keypair-item" data-id="${keypair.id}">
<div class="keypair-info">
<div class="keypair-name">${metadata.name || 'Unnamed'}</div>
<div class="keypair-type">${keypair.key_type}</div>
</div>
<button class="btn btn-small select-btn" data-keypair-id="${keypair.id}">
Select
</button>
</div>
`;
}).join('');
// Add event listeners to all select buttons
container.querySelectorAll('.select-btn').forEach(button => {
button.addEventListener('click', (e) => {
const keypairId = e.target.getAttribute('data-keypair-id');
selectKeypair(keypairId);
});
});
}
async function selectKeypair(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
updateButtonStates();
} 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;
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;
}
try {
await executeOperation(
async () => {
const messageBytes = stringToUint8Array(messageText);
const response = await sendMessage('sign', { message: messageBytes });
if (response?.success) {
elements.signatureResult.classList.remove('hidden');
elements.signatureResult.innerHTML = `
<label>Signature:</label>
<div class="signature-container">
<code id="signatureValue">${response.signature}</code>
<button id="copySignatureBtn" class="btn-copy" title="Copy">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
</button>
</div>
`;
document.getElementById('copySignatureBtn').addEventListener('click', () => {
copyToClipboard(response.signature);
});
return response;
} else {
throw new Error(getResponseError(response, 'sign message'));
}
},
{
loadingElement: elements.signBtn,
successMessage: 'Message signed successfully!'
}
);
} catch (error) {
elements.signatureResult.classList.add('hidden');
}
}
async function encryptMessage() {
const messageText = elements.encryptMessageInput.value.trim();
if (!messageText || !currentKeyspace) {
showToast('Please enter a message and ensure you are connected to a keyspace', 'error');
return;
}
try {
await executeOperation(
async () => {
const response = await sendMessage('encrypt', { message: messageText });
if (response?.success) {
elements.encryptResult.classList.remove('hidden');
elements.encryptResult.innerHTML = `
<label>Encrypted Message:</label>
<div class="signature-container">
<code id="encryptedValue">${response.encryptedMessage}</code>
<button id="copyEncryptedBtn" class="btn-copy" title="Copy">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
</button>
</div>
`;
document.getElementById('copyEncryptedBtn').addEventListener('click', () => {
copyToClipboard(response.encryptedMessage);
});
return response;
} else {
throw new Error(getResponseError(response, 'encrypt message'));
}
},
{
loadingElement: elements.encryptBtn,
successMessage: 'Message encrypted successfully!'
}
);
} catch (error) {
elements.encryptResult.classList.add('hidden');
}
}
async function decryptMessage() {
const encryptedText = elements.encryptedMessageInput.value.trim();
if (!encryptedText || !currentKeyspace) {
showToast('Please enter encrypted message and ensure you are connected to a keyspace', 'error');
return;
}
try {
await executeOperation(
async () => {
const response = await sendMessage('decrypt', { encryptedMessage: encryptedText });
if (response?.success) {
elements.decryptResult.classList.remove('hidden');
elements.decryptResult.innerHTML = `
<label>Decrypted Message:</label>
<div class="signature-container">
<code id="decryptedValue">${response.decryptedMessage}</code>
<button id="copyDecryptedBtn" class="btn-copy" title="Copy">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
</button>
</div>
`;
document.getElementById('copyDecryptedBtn').addEventListener('click', () => {
copyToClipboard(response.decryptedMessage);
});
return response;
} else {
throw new Error(getResponseError(response, 'decrypt message'));
}
},
{
loadingElement: elements.decryptBtn,
successMessage: 'Message decrypted successfully!'
}
);
} catch (error) {
elements.decryptResult.classList.add('hidden');
}
}
async function verifySignature() {
const messageText = elements.verifyMessageInput.value.trim();
const signature = elements.signatureToVerifyInput.value.trim();
if (!messageText || !signature || !selectedKeypairId) {
showToast('Please enter message, signature, and select a keypair', 'error');
return;
}
try {
await executeOperation(
async () => {
const messageBytes = stringToUint8Array(messageText);
const response = await sendMessage('verify', { message: messageBytes, signature });
if (response?.success) {
const isValid = response.isValid;
const icon = isValid ? '✅' : '❌';
const text = isValid ? 'Signature is valid' : 'Signature is invalid';
elements.verifyResult.classList.remove('hidden');
elements.verifyResult.innerHTML = `
<div class="verification-status ${isValid ? 'valid' : 'invalid'}">
<span>${icon}</span>
<span>${text}</span>
</div>
`;
return response;
} else {
throw new Error(getResponseError(response, 'verify signature'));
}
},
{
loadingElement: elements.verifyBtn,
successMessage: null // No success message for verification
}
);
} catch (error) {
elements.verifyResult.classList.add('hidden');
}
}
// selectKeypair is now handled via event listeners, no need for global access