Add extension

This commit is contained in:
zaelgohary 2025-05-27 17:15:53 +03:00
parent beba294054
commit 5bc205b2f7
12 changed files with 2622 additions and 0 deletions

View File

@ -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();
});

View File

@ -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
}, '*');
}
});
})();

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 712 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -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": ["<all_urls>"],
"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": ["<all_urls>"]
}
],
"content_security_policy": {
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'; style-src 'self' 'unsafe-inline';"
}
}

View File

@ -0,0 +1,138 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="styles/popup.css">
</head>
<body>
<div class="container">
<header class="header">
<div class="logo">
<div class="logo-icon">🔐</div>
<h1>CryptoVault</h1>
</div>
<div class="status-indicator" id="statusIndicator">
<div class="status-dot"></div>
<span id="statusText">Initializing...</span>
</div>
</header>
<!-- Create/Login Section -->
<section class="section" id="authSection">
<div class="card">
<h2>Access Your Vault</h2>
<div class="form-group">
<label for="keyspaceInput">Keyspace Name</label>
<input type="text" id="keyspaceInput" placeholder="Enter keyspace name">
</div>
<div class="form-group">
<label for="passwordInput">Password</label>
<input type="password" id="passwordInput" placeholder="Enter password">
</div>
<div class="button-group">
<button id="createKeyspaceBtn" class="btn btn-secondary">Create New</button>
<button id="loginBtn" class="btn btn-primary">Unlock</button>
</div>
</div>
</section>
<!-- Main Vault Section -->
<section class="section hidden" id="vaultSection">
<div class="vault-header">
<h2>Your Keypairs</h2>
<button id="lockBtn" class="btn btn-ghost">🔒 Lock</button>
</div>
<!-- Add Keypair Toggle Button -->
<div class="add-keypair-toggle">
<button id="toggleAddKeypairBtn" class="btn btn-primary">
<span class="btn-icon">+</span>
Add Keypair
</button>
</div>
<!-- Add Keypair Form (Hidden by default) -->
<div class="card add-keypair-form hidden" id="addKeypairCard">
<div class="form-header">
<h3>Add New Keypair</h3>
<button id="cancelAddKeypairBtn" class="btn-close" title="Close">×</button>
</div>
<div class="form-content">
<div class="form-group">
<label for="keyTypeSelect">Key Type</label>
<select id="keyTypeSelect" class="select">
<option value="Secp256k1">Secp256k1</option>
<option value="Ed25519">Ed25519</option>
</select>
</div>
<div class="form-group">
<label for="keyNameInput">Keypair Name</label>
<input type="text" id="keyNameInput" placeholder="Enter a name for your keypair">
</div>
<div class="form-actions">
<button id="addKeypairBtn" class="btn btn-primary">Create Keypair</button>
</div>
</div>
</div>
<!-- Keypairs List -->
<div class="card">
<h3>Keypairs</h3>
<div id="keypairsList" class="keypairs-list">
<div class="loading">Loading keypairs...</div>
</div>
</div>
<!-- Selected Keypair Info - Hidden from user -->
<div class="card hidden completely-hidden" id="selectedKeypairCard">
<h3>Selected Keypair</h3>
<div class="keypair-info">
<div class="info-row">
<label>Name:</label>
<span id="selectedName">-</span>
</div>
<div class="info-row">
<label>Type:</label>
<span id="selectedType">-</span>
</div>
<div class="info-row">
<label>Public Key:</label>
<div class="public-key-container">
<code id="selectedPublicKey">-</code>
<button id="copyPublicKeyBtn" class="btn-copy" title="Copy to clipboard">📋</button>
</div>
</div>
</div>
</div>
<!-- Sign Message -->
<div class="card">
<h3>Sign Message</h3>
<div class="form-group">
<label for="messageInput">Message (hex or text)</label>
<textarea id="messageInput" placeholder="Enter message to sign" rows="3"></textarea>
</div>
<button id="signBtn" class="btn btn-primary" disabled>Sign Message</button>
<div id="signatureResult" class="signature-result hidden">
<label>Signature:</label>
<div class="signature-container">
<code id="signatureValue"></code>
<button id="copySignatureBtn" class="btn-copy" title="Copy to clipboard">📋</button>
</div>
</div>
</div>
</section>
<!-- Loading Overlay -->
<div class="loading-overlay hidden" id="loadingOverlay">
<div class="spinner"></div>
<p>Processing...</p>
</div>
<!-- Toast Notifications -->
<div id="toast" class="toast hidden"></div>
</div>
<script src="popup.js"></script>
</body>
</html>

View File

@ -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 = `
<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

View File

@ -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%;
}

View File

@ -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<void>}
*/
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<void>}
*/
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<any>}
*/
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<any>}
*/
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<any>}
*/
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;

Binary file not shown.