648 lines
21 KiB
JavaScript
648 lines
21 KiB
JavaScript
// 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 = `
|
||
<div class="inline-loading">
|
||
<div class="inline-spinner"></div>
|
||
<span>${message}</span>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// 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 = '<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;
|
||
}
|
||
|
||
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 = '<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) {
|
||
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 = '<div class="empty-state">No keypairs found. Add one above.</div>';
|
||
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 `
|
||
<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}" data-index="${index}">
|
||
Select
|
||
</button>
|
||
</div>
|
||
`;
|
||
}).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 = `
|
||
<label>Signature:</label>
|
||
<div class="signature-container">
|
||
<code id="signatureValue">${response.signature}</code>
|
||
<button id="copySignatureBtn" class="btn-copy" title="Copy to clipboard">📋</button>
|
||
</div>
|
||
`;
|
||
|
||
// 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
|