// Consolidated toast system
function showToast(message, type = 'info') {
document.querySelector('.toast-notification')?.remove();
const icons = {
success: '',
error: '',
info: ''
};
const toast = Object.assign(document.createElement('div'), {
className: `toast-notification toast-${type}`,
innerHTML: `
`
});
document.body.appendChild(toast);
setTimeout(() => toast.classList.add('toast-show'), 10);
setTimeout(() => {
if (toast.parentElement) {
toast.classList.add('toast-hide');
setTimeout(() => toast.remove(), 300);
}
}, 4000);
}
// 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) {
const statusText = document.getElementById('statusText');
const statusSection = document.getElementById('vaultStatus');
const lockBtn = document.getElementById('lockBtn');
if (isConnected && text) {
// Show keyspace name and status section
statusText.textContent = text;
statusSection.classList.remove('hidden');
if (lockBtn) {
lockBtn.classList.remove('hidden');
}
}
}
// 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 = {
// Authentication elements
keyspaceInput: document.getElementById('keyspaceInput'),
passwordInput: document.getElementById('passwordInput'),
createKeyspaceBtn: document.getElementById('createKeyspaceBtn'),
loginBtn: document.getElementById('loginBtn'),
// Header elements
lockBtn: document.getElementById('lockBtn'),
themeToggle: document.getElementById('themeToggle'),
settingsToggle: document.getElementById('settingsToggle'),
settingsDropdown: document.getElementById('settingsDropdown'),
timeoutInput: document.getElementById('timeoutInput'),
// Keypair management elements
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'),
// Crypto operation elements - Sign tab
messageInput: document.getElementById('messageInput'),
signBtn: document.getElementById('signBtn'),
signatureResult: document.getElementById('signatureResult'),
copySignatureBtn: document.getElementById('copySignatureBtn'),
// Crypto operation elements - Encrypt tab
encryptMessageInput: document.getElementById('encryptMessageInput'),
encryptBtn: document.getElementById('encryptBtn'),
encryptResult: document.getElementById('encryptResult'),
// Crypto operation elements - Decrypt tab
encryptedMessageInput: document.getElementById('encryptedMessageInput'),
decryptBtn: document.getElementById('decryptBtn'),
decryptResult: document.getElementById('decryptResult'),
// Crypto operation elements - Verify tab
verifyMessageInput: document.getElementById('verifyMessageInput'),
signatureToVerifyInput: document.getElementById('signatureToVerifyInput'),
verifyBtn: document.getElementById('verifyBtn'),
verifyResult: document.getElementById('verifyResult'),
};
// Global state variables
let currentKeyspace = null;
let selectedKeypairId = null;
let backgroundPort = null;
let sessionTimeoutDuration = 15; // Default 15 seconds
// Session timeout management
function handleError(error, context, shouldShowToast = true) {
const errorMessage = error?.message || 'An unexpected error occurred';
if (shouldShowToast) {
showToast(`${context}: ${errorMessage}`, 'error');
}
}
function validateInput(value, fieldName, options = {}) {
const { minLength = 1, maxLength = 1000, required = true } = options;
if (required && (!value || !value.trim())) {
showToast(`${fieldName} is required`, 'error');
return false;
}
if (value && value.length < minLength) {
showToast(`${fieldName} must be at least ${minLength} characters`, 'error');
return false;
}
if (value && value.length > maxLength) {
showToast(`${fieldName} must be less than ${maxLength} characters`, 'error');
return false;
}
return true;
}
async function loadTimeoutSetting() {
const result = await chrome.storage.local.get(['sessionTimeout']);
sessionTimeoutDuration = result.sessionTimeout || 15;
if (elements.timeoutInput) {
elements.timeoutInput.value = sessionTimeoutDuration;
}
}
async function checkSessionTimeout() {
const result = await chrome.storage.local.get(['sessionTimedOut']);
if (result.sessionTimedOut) {
// Clear the flag
await chrome.storage.local.remove(['sessionTimedOut']);
// Show timeout notification
showToast('Session timed out due to inactivity', 'info');
}
}
async function saveTimeoutSetting(timeout) {
sessionTimeoutDuration = timeout;
await sendMessage('updateTimeout', { timeout });
}
async function resetSessionTimeout() {
if (currentKeyspace) {
await sendMessage('resetTimeout');
}
}
// 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);
}
// Settings dropdown management
function toggleSettingsDropdown() {
const dropdown = elements.settingsDropdown;
if (dropdown) {
dropdown.classList.toggle('hidden');
}
}
function closeSettingsDropdown() {
const dropdown = elements.settingsDropdown;
if (dropdown) {
dropdown.classList.add('hidden');
}
}
function updateThemeIcon(theme) {
const themeToggle = elements.themeToggle;
if (!themeToggle) return;
if (theme === 'dark') {
// Bright sun SVG for dark theme
themeToggle.innerHTML = `
`;
themeToggle.title = 'Switch to light mode';
} else {
// Dark crescent moon SVG for light theme
themeToggle.innerHTML = `
`;
themeToggle.title = 'Switch to dark mode';
}
}
// Establish connection to background script for keep-alive
function connectToBackground() {
backgroundPort = chrome.runtime.connect({ name: 'popup' });
// Listen for messages from background script
backgroundPort.onMessage.addListener((message) => {
if (message.type === 'sessionTimeout') {
// Update UI state to reflect locked session
currentKeyspace = null;
selectedKeypairId = null;
setStatus('', false);
showSection('authSection');
clearVaultState();
// Clear form inputs
if (elements.keyspaceInput) elements.keyspaceInput.value = '';
if (elements.passwordInput) elements.passwordInput.value = '';
// Show timeout notification
showToast(message.message, 'info');
}
});
backgroundPort.onDisconnect.addListener(() => {
backgroundPort = null;
});
}
// Initialize
document.addEventListener('DOMContentLoaded', async function() {
// Initialize theme first
initializeTheme();
// Load timeout setting
await loadTimeoutSetting();
// Ensure lock button starts hidden
const lockBtn = document.getElementById('lockBtn');
if (lockBtn) {
lockBtn.classList.add('hidden');
}
// Connect to background script for keep-alive
connectToBackground();
// Consolidated event listeners
const eventMap = {
createKeyspaceBtn: createKeyspace,
loginBtn: login,
lockBtn: lockSession,
themeToggle: toggleTheme,
settingsToggle: toggleSettingsDropdown,
toggleAddKeypairBtn: toggleAddKeypairForm,
addKeypairBtn: addKeypair,
cancelAddKeypairBtn: hideAddKeypairForm,
signBtn: signMessage,
encryptBtn: encryptMessage,
decryptBtn: decryptMessage,
verifyBtn: verifySignature
};
Object.entries(eventMap).forEach(([elementKey, handler]) => {
elements[elementKey]?.addEventListener('click', handler);
});
// Tab functionality
initializeTabs();
// Additional event listeners
elements.copySignatureBtn?.addEventListener('click', () => {
copyToClipboard(document.getElementById('signatureValue')?.textContent);
});
elements.messageInput?.addEventListener('input', () => {
if (elements.signBtn) {
elements.signBtn.disabled = !elements.messageInput.value.trim() || !selectedKeypairId;
}
});
// Timeout setting event listener
elements.timeoutInput?.addEventListener('change', async (e) => {
const timeout = parseInt(e.target.value);
if (timeout >= 3 && timeout <= 300) {
await saveTimeoutSetting(timeout);
} else {
e.target.value = sessionTimeoutDuration; // Reset to current value if invalid
}
});
// Activity detection - reset timeout on any interaction
document.addEventListener('click', (e) => {
resetSessionTimeout();
// Close settings dropdown if clicking outside
if (!elements.settingsToggle?.contains(e.target) &&
!elements.settingsDropdown?.contains(e.target)) {
closeSettingsDropdown();
}
});
document.addEventListener('keydown', resetSessionTimeout);
document.addEventListener('input', resetSessionTimeout);
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && !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(currentKeyspace, true);
showSection('vaultSection');
await loadKeypairs();
} else {
// No active session
setStatus('', false);
showSection('authSection');
}
} catch (error) {
setStatus('', 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 tabContainer = document.querySelector('.operation-tabs');
if (tabContainer) {
// Use event delegation for better performance
tabContainer.addEventListener('click', (e) => {
if (e.target.classList.contains('tab-btn')) {
handleTabSwitch(e.target);
}
});
}
// Initialize input validation
initializeInputValidation();
}
function handleTabSwitch(clickedTab) {
const targetTab = clickedTab.getAttribute('data-tab');
const tabButtons = document.querySelectorAll('.tab-btn');
const tabContents = document.querySelectorAll('.tab-content');
// 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
clickedTab.classList.add('active');
const targetContent = document.getElementById(`${targetTab}-tab`);
if (targetContent) {
targetContent.classList.add('active');
}
// Clear results when switching tabs
clearTabResults();
// Update button states
updateButtonStates();
}
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();
// Hide add keypair form if open
hideAddKeypairForm();
// Clear keypairs list
if (elements.keypairsList) {
elements.keypairsList.innerHTML = 'Loading keypairs...
';
}
}
// Validation utilities
const validateAuth = () => {
const keyspace = elements.keyspaceInput.value.trim();
const password = elements.passwordInput.value.trim();
if (!validateInput(keyspace, 'Keyspace name', { minLength: 1, maxLength: 100 })) {
return null;
}
if (!validateInput(password, 'Password', { minLength: 1, maxLength: 1000 })) {
return null;
}
return { keyspace, password };
};
async function createKeyspace() {
const auth = validateAuth();
if (!auth) return;
try {
await executeOperation(
async () => {
const response = await sendMessage('createKeyspace', auth);
if (response?.success) {
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) {
handleError(error, 'Create keyspace');
}
}
async function login() {
const auth = validateAuth();
if (!auth) return;
try {
await executeOperation(
async () => {
const response = await sendMessage('initSession', auth);
if (response?.success) {
currentKeyspace = auth.keyspace;
setStatus(auth.keyspace, true);
showSection('vaultSection');
clearVaultState();
await loadKeypairs();
return response;
} else {
throw new Error(getResponseError(response, 'login'));
}
},
{
loadingElement: elements.loginBtn,
successMessage: 'Logged in successfully!',
maxRetries: 2
}
);
} catch (error) {
handleError(error, 'Create keyspace');
}
}
async function lockSession() {
try {
await sendMessage('lockSession');
currentKeyspace = null;
selectedKeypairId = null;
setStatus('', 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');
}
}
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;
}
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!'
}
);
}
async function loadKeypairs() {
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 = 'Failed to load keypairs. Try refreshing.
';
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 = 'No keypairs found. Add one above.
';
return;
}
container.innerHTML = keypairArray.map((keypair) => {
const metadata = typeof keypair.metadata === 'string'
? JSON.parse(keypair.metadata)
: keypair.metadata;
return `
`;
}).join('');
// Add event listeners to all keypair cards
container.querySelectorAll('.keypair-item').forEach(card => {
card.addEventListener('click', (e) => {
const keypairId = e.currentTarget.getAttribute('data-keypair-id');
selectKeypair(keypairId);
});
// Add keyboard support
card.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
const keypairId = e.currentTarget.getAttribute('data-keypair-id');
selectKeypair(keypairId);
}
});
});
// Restore selection state if there was a previously selected keypair
if (selectedKeypairId) {
updateKeypairSelection(selectedKeypairId);
}
}
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) {
// 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');
// Revert visual state if there was an error
updateKeypairSelection(null);
showToast(errorMsg, 'error');
}
}
function updateKeypairSelection(selectedId) {
// Remove previous selection styling from all keypair items
const allKeypairs = document.querySelectorAll('.keypair-item');
allKeypairs.forEach(item => {
item.classList.remove('selected');
});
// Add selection styling to the selected keypair (if any)
if (selectedId) {
const selectedKeypair = document.querySelector(`[data-keypair-id="${selectedId}"]`);
if (selectedKeypair) {
selectedKeypair.classList.add('selected');
}
}
}
// Shared templates
const copyIcon = ``;
const createResultContainer = (label, value, btnId) => `
${value}
`;
// Unified crypto operation handler
async function performCryptoOperation(config) {
const { input, validation, action, resultElement, button, successMsg, resultProcessor } = config;
if (!validation()) {
showToast(config.errorMsg, 'error');
return;
}
await executeOperation(
async () => {
const response = await sendMessage(action, input());
if (response?.success) {
resultElement.classList.remove('hidden');
resultElement.innerHTML = resultProcessor(response);
// Add copy button listener if result has copy button
const copyBtn = resultElement.querySelector('.btn-copy');
if (copyBtn && config.copyValue) {
copyBtn.addEventListener('click', () => copyToClipboard(config.copyValue(response)));
}
return response;
} else {
throw new Error(getResponseError(response, action));
}
},
{ loadingElement: button, successMessage: successMsg }
);
}
// Crypto operation functions using shared templates
const signMessage = () => performCryptoOperation({
validation: () => elements.messageInput.value.trim() && selectedKeypairId,
errorMsg: 'Please enter a message and select a keypair',
action: 'sign',
input: () => ({ message: stringToUint8Array(elements.messageInput.value.trim()) }),
resultElement: elements.signatureResult,
button: elements.signBtn,
successMsg: 'Message signed successfully!',
copyValue: (response) => response.signature,
resultProcessor: (response) => createResultContainer('Signature', response.signature, 'copySignatureBtn')
});
const encryptMessage = () => performCryptoOperation({
validation: () => elements.encryptMessageInput.value.trim() && currentKeyspace,
errorMsg: 'Please enter a message and ensure you are connected to a keyspace',
action: 'encrypt',
input: () => ({ message: elements.encryptMessageInput.value.trim() }),
resultElement: elements.encryptResult,
button: elements.encryptBtn,
successMsg: 'Message encrypted successfully!',
copyValue: (response) => response.encryptedMessage,
resultProcessor: (response) => createResultContainer('Encrypted Message', response.encryptedMessage, 'copyEncryptedBtn')
});
const decryptMessage = () => performCryptoOperation({
validation: () => elements.encryptedMessageInput.value.trim() && currentKeyspace,
errorMsg: 'Please enter encrypted message and ensure you are connected to a keyspace',
action: 'decrypt',
input: () => ({ encryptedMessage: elements.encryptedMessageInput.value.trim() }),
resultElement: elements.decryptResult,
button: elements.decryptBtn,
successMsg: 'Message decrypted successfully!',
copyValue: (response) => response.decryptedMessage,
resultProcessor: (response) => createResultContainer('Decrypted Message', response.decryptedMessage, 'copyDecryptedBtn')
});
const verifySignature = () => performCryptoOperation({
validation: () => elements.verifyMessageInput.value.trim() && elements.signatureToVerifyInput.value.trim() && selectedKeypairId,
errorMsg: 'Please enter message, signature, and select a keypair',
action: 'verify',
input: () => ({
message: stringToUint8Array(elements.verifyMessageInput.value.trim()),
signature: elements.signatureToVerifyInput.value.trim()
}),
resultElement: elements.verifyResult,
button: elements.verifyBtn,
successMsg: null,
resultProcessor: (response) => {
const isValid = response.isValid;
const icon = isValid
? ``
: ``;
const text = isValid ? 'Signature is valid' : 'Signature is invalid';
return `
${icon}
${text}
`;
}
});