feat: implement browser extension UI with WebAssembly integration
This commit is contained in:
219
extension/popup/App.jsx
Normal file
219
extension/popup/App.jsx
Normal file
@@ -0,0 +1,219 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import KeyspaceManager from './KeyspaceManager';
|
||||
import KeypairManager from './KeypairManager';
|
||||
import SignMessage from './SignMessage';
|
||||
import * as wasmHelper from './WasmHelper';
|
||||
|
||||
function App() {
|
||||
const [wasmState, setWasmState] = useState({
|
||||
loading: false,
|
||||
initialized: false,
|
||||
error: null
|
||||
});
|
||||
const [locked, setLocked] = useState(true);
|
||||
const [keyspaces, setKeyspaces] = useState([]);
|
||||
const [currentKeyspace, setCurrentKeyspace] = useState('');
|
||||
const [keypairs, setKeypairs] = useState([]); // [{id, label, publicKey}]
|
||||
const [selectedKeypair, setSelectedKeypair] = useState('');
|
||||
const [signature, setSignature] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [status, setStatus] = useState('');
|
||||
|
||||
// Load WebAssembly on component mount
|
||||
useEffect(() => {
|
||||
async function initWasm() {
|
||||
try {
|
||||
setStatus('Loading WebAssembly module...');
|
||||
await wasmHelper.loadWasmModule();
|
||||
setWasmState(wasmHelper.getWasmState());
|
||||
setStatus('WebAssembly module loaded');
|
||||
// Load session state
|
||||
await refreshStatus();
|
||||
} catch (error) {
|
||||
console.error('Failed to load WebAssembly:', error);
|
||||
setStatus('Error loading WebAssembly: ' + (error.message || 'Unknown error'));
|
||||
}
|
||||
}
|
||||
|
||||
initWasm();
|
||||
}, []);
|
||||
|
||||
// Fetch status from background on mount
|
||||
async function refreshStatus() {
|
||||
const state = await wasmHelper.getSessionState();
|
||||
setCurrentKeyspace(state.currentKeyspace || '');
|
||||
setKeypairs(state.keypairs || []);
|
||||
setSelectedKeypair(state.selectedKeypair || '');
|
||||
setLocked(!state.currentKeyspace);
|
||||
|
||||
// For demo: collect all keyspaces from storage
|
||||
if (state.keypairs && state.keypairs.length > 0) {
|
||||
setKeyspaces([state.currentKeyspace]);
|
||||
} else {
|
||||
setKeyspaces([state.currentKeyspace].filter(Boolean));
|
||||
}
|
||||
}
|
||||
|
||||
// Session unlock/create
|
||||
const handleUnlock = async (keyspace, password) => {
|
||||
if (!wasmState.initialized) {
|
||||
setStatus('WebAssembly module not loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setStatus('Unlocking...');
|
||||
try {
|
||||
await wasmHelper.initSession(keyspace, password);
|
||||
setCurrentKeyspace(keyspace);
|
||||
setLocked(false);
|
||||
setStatus('Session unlocked!');
|
||||
await refreshStatus();
|
||||
} catch (e) {
|
||||
setStatus('Unlock failed: ' + e);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const handleCreateKeyspace = async (keyspace, password) => {
|
||||
if (!wasmState.initialized) {
|
||||
setStatus('WebAssembly module not loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setStatus('Creating keyspace...');
|
||||
try {
|
||||
await wasmHelper.initSession(keyspace, password);
|
||||
setCurrentKeyspace(keyspace);
|
||||
setLocked(false);
|
||||
setStatus('Keyspace created and unlocked!');
|
||||
await refreshStatus();
|
||||
} catch (e) {
|
||||
setStatus('Create failed: ' + e);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const handleLock = async () => {
|
||||
if (!wasmState.initialized) {
|
||||
setStatus('WebAssembly module not loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setStatus('Locking...');
|
||||
try {
|
||||
await wasmHelper.lockSession();
|
||||
setLocked(true);
|
||||
setCurrentKeyspace('');
|
||||
setKeypairs([]);
|
||||
setSelectedKeypair('');
|
||||
setStatus('Session locked.');
|
||||
await refreshStatus();
|
||||
} catch (e) {
|
||||
setStatus('Lock failed: ' + e);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const handleSelectKeypair = async (id) => {
|
||||
if (!wasmState.initialized) {
|
||||
setStatus('WebAssembly module not loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setStatus('Selecting keypair...');
|
||||
try {
|
||||
await wasmHelper.selectKeypair(id);
|
||||
setSelectedKeypair(id);
|
||||
setStatus('Keypair selected.');
|
||||
await refreshStatus();
|
||||
} catch (e) {
|
||||
setStatus('Select failed: ' + e);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const handleCreateKeypair = async () => {
|
||||
if (!wasmState.initialized) {
|
||||
setStatus('WebAssembly module not loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setStatus('Creating keypair...');
|
||||
try {
|
||||
const keyId = await wasmHelper.addKeypair();
|
||||
setStatus('Keypair created. ID: ' + keyId);
|
||||
await refreshStatus();
|
||||
} catch (e) {
|
||||
setStatus('Create failed: ' + e);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const handleSign = async (message) => {
|
||||
if (!wasmState.initialized) {
|
||||
setStatus('WebAssembly module not loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
setStatus('Signing message...');
|
||||
try {
|
||||
if (!selectedKeypair) {
|
||||
throw new Error('No keypair selected');
|
||||
}
|
||||
const sig = await wasmHelper.sign(message);
|
||||
setSignature(sig);
|
||||
setStatus('Message signed!');
|
||||
} catch (e) {
|
||||
setStatus('Signing failed: ' + e);
|
||||
setSignature('');
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<h1>Modular Vault Extension</h1>
|
||||
{wasmState.error && (
|
||||
<div className="error">
|
||||
WebAssembly Error: {wasmState.error}
|
||||
</div>
|
||||
)}
|
||||
<KeyspaceManager
|
||||
keyspaces={keyspaces}
|
||||
onUnlock={handleUnlock}
|
||||
onCreate={handleCreateKeyspace}
|
||||
locked={locked}
|
||||
onLock={handleLock}
|
||||
currentKeyspace={currentKeyspace}
|
||||
/>
|
||||
{!locked && (
|
||||
<>
|
||||
<KeypairManager
|
||||
keypairs={keypairs}
|
||||
onSelect={handleSelectKeypair}
|
||||
onCreate={handleCreateKeypair}
|
||||
selectedKeypair={selectedKeypair}
|
||||
/>
|
||||
{selectedKeypair && (
|
||||
<SignMessage
|
||||
onSign={handleSign}
|
||||
signature={signature}
|
||||
loading={loading}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<div className="status" style={{marginTop: '1rem', minHeight: 24}}>
|
||||
{status}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
30
extension/popup/KeypairManager.jsx
Normal file
30
extension/popup/KeypairManager.jsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
export default function KeypairManager({ keypairs, onSelect, onCreate, selectedKeypair }) {
|
||||
const [creating, setCreating] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="keypair-manager">
|
||||
<label>Keypair:</label>
|
||||
<select value={selectedKeypair || ''} onChange={e => onSelect(e.target.value)}>
|
||||
<option value="" disabled>Select keypair</option>
|
||||
{keypairs.map(kp => (
|
||||
<option key={kp.id} value={kp.id}>{kp.label}</option>
|
||||
))}
|
||||
</select>
|
||||
<button onClick={() => setCreating(true)} style={{marginLeft: 8}}>Create New</button>
|
||||
{creating && (
|
||||
<div style={{marginTop: '0.5rem'}}>
|
||||
<button onClick={() => { onCreate(); setCreating(false); }}>Create Secp256k1 Keypair</button>
|
||||
<button onClick={() => setCreating(false)} style={{marginLeft: 8}}>Cancel</button>
|
||||
</div>
|
||||
)}
|
||||
{selectedKeypair && (
|
||||
<div style={{marginTop: '0.5rem'}}>
|
||||
<span>Public Key: <code>{keypairs.find(kp => kp.id === selectedKeypair)?.publicKey}</code></span>
|
||||
<button onClick={() => navigator.clipboard.writeText(keypairs.find(kp => kp.id === selectedKeypair)?.publicKey)} style={{marginLeft: 8}}>Copy</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
30
extension/popup/KeyspaceManager.jsx
Normal file
30
extension/popup/KeyspaceManager.jsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
export default function KeyspaceManager({ keyspaces, onUnlock, onCreate, locked, onLock, currentKeyspace }) {
|
||||
const [selected, setSelected] = useState(keyspaces[0] || '');
|
||||
const [password, setPassword] = useState('');
|
||||
const [newKeyspace, setNewKeyspace] = useState('');
|
||||
|
||||
if (locked) {
|
||||
return (
|
||||
<div className="keyspace-manager">
|
||||
<label>Keyspace:</label>
|
||||
<select value={selected} onChange={e => setSelected(e.target.value)}>
|
||||
{keyspaces.map(k => <option key={k} value={k}>{k}</option>)}
|
||||
</select>
|
||||
<button onClick={() => onUnlock(selected, password)} disabled={!selected || !password}>Unlock</button>
|
||||
<div style={{marginTop: '0.5rem'}}>
|
||||
<input placeholder="New keyspace name" value={newKeyspace} onChange={e => setNewKeyspace(e.target.value)} />
|
||||
<input placeholder="Password" type="password" value={password} onChange={e => setPassword(e.target.value)} />
|
||||
<button onClick={() => onCreate(newKeyspace, password)} disabled={!newKeyspace || !password}>Create</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="keyspace-manager">
|
||||
<span>Keyspace: <b>{currentKeyspace}</b></span>
|
||||
<button onClick={onLock} style={{marginLeft: 8}}>Lock Session</button>
|
||||
</div>
|
||||
);
|
||||
}
|
27
extension/popup/SignMessage.jsx
Normal file
27
extension/popup/SignMessage.jsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
export default function SignMessage({ onSign, signature, loading }) {
|
||||
const [message, setMessage] = useState('');
|
||||
|
||||
return (
|
||||
<div className="sign-message">
|
||||
<label>Message to sign:</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Enter plaintext message"
|
||||
value={message}
|
||||
onChange={e => setMessage(e.target.value)}
|
||||
style={{width: '100%', marginBottom: 8}}
|
||||
/>
|
||||
<button onClick={() => onSign(message)} disabled={!message || loading}>
|
||||
{loading ? 'Signing...' : 'Sign'}
|
||||
</button>
|
||||
{signature && (
|
||||
<div style={{marginTop: '0.5rem'}}>
|
||||
<span>Signature: <code>{signature}</code></span>
|
||||
<button onClick={() => navigator.clipboard.writeText(signature)} style={{marginLeft: 8}}>Copy</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
667
extension/popup/WasmHelper.js
Normal file
667
extension/popup/WasmHelper.js
Normal file
@@ -0,0 +1,667 @@
|
||||
/**
|
||||
* Browser extension-friendly WebAssembly loader and helper functions
|
||||
* This handles loading the WebAssembly module without relying on ES modules
|
||||
*/
|
||||
|
||||
// Global reference to the loaded WebAssembly module
|
||||
let wasmModule = null;
|
||||
|
||||
// Initialization state
|
||||
const state = {
|
||||
loading: false,
|
||||
initialized: false,
|
||||
error: null
|
||||
};
|
||||
|
||||
/**
|
||||
* Load the WebAssembly module
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function loadWasmModule() {
|
||||
if (state.initialized || state.loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.loading = true;
|
||||
|
||||
try {
|
||||
// Get paths to WebAssembly files
|
||||
const wasmJsPath = chrome.runtime.getURL('wasm/wasm_app.js');
|
||||
const wasmBinaryPath = chrome.runtime.getURL('wasm/wasm_app_bg.wasm');
|
||||
|
||||
console.log('Loading WASM JS from:', wasmJsPath);
|
||||
console.log('Loading WASM binary from:', wasmBinaryPath);
|
||||
|
||||
// Create a container for our temporary WebAssembly globals
|
||||
window.__wasmApp = {};
|
||||
|
||||
// Create a script element to load the JS file
|
||||
const script = document.createElement('script');
|
||||
script.src = wasmJsPath;
|
||||
|
||||
// Wait for the script to load
|
||||
await new Promise((resolve, reject) => {
|
||||
script.onload = resolve;
|
||||
script.onerror = () => reject(new Error('Failed to load WASM JavaScript file'));
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
|
||||
// Check if the wasm_app global was created
|
||||
if (!window.wasm_app && !window.__wbg_init) {
|
||||
throw new Error('WASM module did not export expected functions');
|
||||
}
|
||||
|
||||
// Get the initialization function
|
||||
const init = window.__wbg_init || (window.wasm_app && window.wasm_app.default);
|
||||
|
||||
if (!init || typeof init !== 'function') {
|
||||
throw new Error('WASM init function not found');
|
||||
}
|
||||
|
||||
// Fetch the WASM binary file
|
||||
const response = await fetch(wasmBinaryPath);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch WASM binary: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
// Get the binary data
|
||||
const wasmBinary = await response.arrayBuffer();
|
||||
|
||||
// Initialize the WASM module
|
||||
await init(wasmBinary);
|
||||
|
||||
// Debug logging for available functions in the WebAssembly module
|
||||
console.log('Available WebAssembly functions:');
|
||||
console.log('init_rhai_env:', typeof window.init_rhai_env, typeof (window.wasm_app && window.wasm_app.init_rhai_env));
|
||||
console.log('init_session:', typeof window.init_session, typeof (window.wasm_app && window.wasm_app.init_session));
|
||||
console.log('lock_session:', typeof window.lock_session, typeof (window.wasm_app && window.wasm_app.lock_session));
|
||||
console.log('add_keypair:', typeof window.add_keypair, typeof (window.wasm_app && window.wasm_app.add_keypair));
|
||||
console.log('select_keypair:', typeof window.select_keypair, typeof (window.wasm_app && window.wasm_app.select_keypair));
|
||||
console.log('sign:', typeof window.sign, typeof (window.wasm_app && window.wasm_app.sign));
|
||||
console.log('run_rhai:', typeof window.run_rhai, typeof (window.wasm_app && window.wasm_app.run_rhai));
|
||||
console.log('list_keypairs:', typeof window.list_keypairs, typeof (window.wasm_app && window.wasm_app.list_keypairs));
|
||||
|
||||
// Store reference to all the exported functions
|
||||
wasmModule = {
|
||||
init_rhai_env: window.init_rhai_env || (window.wasm_app && window.wasm_app.init_rhai_env),
|
||||
init_session: window.init_session || (window.wasm_app && window.wasm_app.init_session),
|
||||
lock_session: window.lock_session || (window.wasm_app && window.wasm_app.lock_session),
|
||||
add_keypair: window.add_keypair || (window.wasm_app && window.wasm_app.add_keypair),
|
||||
select_keypair: window.select_keypair || (window.wasm_app && window.wasm_app.select_keypair),
|
||||
sign: window.sign || (window.wasm_app && window.wasm_app.sign),
|
||||
run_rhai: window.run_rhai || (window.wasm_app && window.wasm_app.run_rhai),
|
||||
list_keypairs: window.list_keypairs || (window.wasm_app && window.wasm_app.list_keypairs),
|
||||
list_keypairs_debug: window.list_keypairs_debug || (window.wasm_app && window.wasm_app.list_keypairs_debug),
|
||||
check_indexeddb: window.check_indexeddb || (window.wasm_app && window.wasm_app.check_indexeddb)
|
||||
};
|
||||
|
||||
// Log what was actually registered
|
||||
console.log('Registered WebAssembly module functions:');
|
||||
for (const [key, value] of Object.entries(wasmModule)) {
|
||||
console.log(`${key}: ${typeof value}`, value ? 'Available' : 'Missing');
|
||||
}
|
||||
|
||||
// Initialize the WASM environment
|
||||
if (typeof wasmModule.init_rhai_env === 'function') {
|
||||
wasmModule.init_rhai_env();
|
||||
}
|
||||
|
||||
state.initialized = true;
|
||||
console.log('WASM module loaded and initialized successfully');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to load WASM module:', error);
|
||||
state.error = error.message || 'Unknown error loading WebAssembly module';
|
||||
} finally {
|
||||
state.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current state of the WebAssembly module
|
||||
* @returns {{loading: boolean, initialized: boolean, error: string|null}}
|
||||
*/
|
||||
export function getWasmState() {
|
||||
return { ...state };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the WebAssembly module
|
||||
* @returns {object|null} The WebAssembly module or null if not loaded
|
||||
*/
|
||||
export function getWasmModule() {
|
||||
return wasmModule;
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug function to check the vault state
|
||||
* @returns {Promise<object>} State information
|
||||
*/
|
||||
export async function debugVaultState() {
|
||||
const module = getWasmModule();
|
||||
if (!module) {
|
||||
throw new Error('WebAssembly module not loaded');
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('🔍 Debugging vault state...');
|
||||
|
||||
// Check if we have a valid session using Rhai script
|
||||
const sessionCheck = `
|
||||
let has_session = vault::has_active_session();
|
||||
let keyspace = "";
|
||||
if has_session {
|
||||
keyspace = vault::get_current_keyspace();
|
||||
}
|
||||
|
||||
// Return info about the session
|
||||
{
|
||||
"has_session": has_session,
|
||||
"keyspace": keyspace
|
||||
}
|
||||
`;
|
||||
|
||||
console.log('Checking session status...');
|
||||
const sessionStatus = await module.run_rhai(sessionCheck);
|
||||
console.log('Session status:', sessionStatus);
|
||||
|
||||
// Get keypair info if we have a session
|
||||
if (sessionStatus && sessionStatus.has_session) {
|
||||
const keypairsScript = `
|
||||
// Get all keypairs for the current keyspace
|
||||
let keypairs = vault::list_keypairs();
|
||||
|
||||
// Add diagnostic information
|
||||
let diagnostic = {
|
||||
"keypair_count": keypairs.len(),
|
||||
"keyspace": vault::get_current_keyspace(),
|
||||
"keypairs": keypairs
|
||||
};
|
||||
|
||||
diagnostic
|
||||
`;
|
||||
|
||||
console.log('Fetching keypair details...');
|
||||
const keypairDiagnostic = await module.run_rhai(keypairsScript);
|
||||
console.log('Keypair diagnostic:', keypairDiagnostic);
|
||||
|
||||
return keypairDiagnostic;
|
||||
}
|
||||
|
||||
return sessionStatus;
|
||||
} catch (error) {
|
||||
console.error('Error in debug function:', error);
|
||||
return { error: error.toString() };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get keypairs from the vault
|
||||
* @returns {Promise<Array>} List of keypairs
|
||||
*/
|
||||
export async function getKeypairsFromVault() {
|
||||
console.log('===============================================');
|
||||
console.log('Starting getKeypairsFromVault...');
|
||||
const module = getWasmModule();
|
||||
if (!module) {
|
||||
console.error('WebAssembly module not loaded!');
|
||||
throw new Error('WebAssembly module not loaded');
|
||||
}
|
||||
console.log('WebAssembly module:', module);
|
||||
console.log('Module functions available:', Object.keys(module));
|
||||
|
||||
// Check if IndexedDB is available and working
|
||||
const isIndexedDBAvailable = await checkIndexedDBAvailability();
|
||||
if (!isIndexedDBAvailable) {
|
||||
console.warn('IndexedDB is not available or not working properly');
|
||||
// We'll continue, but this is likely why keypairs aren't persisting
|
||||
}
|
||||
|
||||
// Force re-initialization of the current session if needed
|
||||
try {
|
||||
// This checks if we have the debug function available
|
||||
if (typeof module.list_keypairs_debug === 'function') {
|
||||
console.log('Using debug function to diagnose keypair loading issues...');
|
||||
const debugResult = await module.list_keypairs_debug();
|
||||
console.log('Debug keypair listing result:', debugResult);
|
||||
if (Array.isArray(debugResult) && debugResult.length > 0) {
|
||||
console.log('Debug function returned keypairs:', debugResult);
|
||||
// If debug function worked but regular function doesn't, use its result
|
||||
return debugResult;
|
||||
} else {
|
||||
console.log('Debug function did not return keypairs, continuing with normal flow...');
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error in debug function:', err);
|
||||
// Continue with normal flow even if the debug function fails
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('-----------------------------------------------');
|
||||
console.log('Running diagnostics to check vault state...');
|
||||
// Run diagnostic first to log vault state
|
||||
await debugVaultState();
|
||||
console.log('Diagnostics complete');
|
||||
console.log('-----------------------------------------------');
|
||||
|
||||
console.log('Checking if list_keypairs function is available:', typeof module.list_keypairs);
|
||||
for (const key in module) {
|
||||
console.log(`Module function: ${key} = ${typeof module[key]}`);
|
||||
}
|
||||
if (typeof module.list_keypairs !== 'function') {
|
||||
console.error('list_keypairs function is not available in the WebAssembly module!');
|
||||
console.log('Available functions:', Object.keys(module));
|
||||
// Fall back to Rhai script
|
||||
console.log('Falling back to using Rhai script for listing keypairs...');
|
||||
const script = `
|
||||
// Get all keypairs from the current keyspace
|
||||
let keypairs = vault::list_keypairs();
|
||||
keypairs
|
||||
`;
|
||||
const keypairList = await module.run_rhai(script);
|
||||
console.log('Retrieved keypairs from vault using Rhai:', keypairList);
|
||||
return keypairList;
|
||||
}
|
||||
|
||||
console.log('Calling WebAssembly list_keypairs function...');
|
||||
// Use the direct list_keypairs function from WebAssembly instead of Rhai script
|
||||
const keypairList = await module.list_keypairs();
|
||||
console.log('Retrieved keypairs from vault:', keypairList);
|
||||
|
||||
console.log('Raw keypair list type:', typeof keypairList);
|
||||
console.log('Is array?', Array.isArray(keypairList));
|
||||
console.log('Raw keypair list:', keypairList);
|
||||
|
||||
// Format keypairs for UI
|
||||
const formattedKeypairs = Array.isArray(keypairList) ? keypairList.map(kp => {
|
||||
// Parse metadata if available
|
||||
let metadata = {};
|
||||
if (kp.metadata) {
|
||||
try {
|
||||
if (typeof kp.metadata === 'string') {
|
||||
metadata = JSON.parse(kp.metadata);
|
||||
} else {
|
||||
metadata = kp.metadata;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to parse keypair metadata:', e);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: kp.id,
|
||||
label: metadata.label || `Key-${kp.id.substring(0, 4)}`
|
||||
};
|
||||
}) : [];
|
||||
|
||||
console.log('Formatted keypairs for UI:', formattedKeypairs);
|
||||
|
||||
// Update background service worker
|
||||
return new Promise((resolve) => {
|
||||
chrome.runtime.sendMessage({
|
||||
action: 'update_session',
|
||||
type: 'keypairs_loaded',
|
||||
data: formattedKeypairs
|
||||
}, (response) => {
|
||||
console.log('Background response to keypairs update:', response);
|
||||
resolve(formattedKeypairs);
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching keypairs from vault:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if IndexedDB is available and working
|
||||
* @returns {Promise<boolean>} True if IndexedDB is working
|
||||
*/
|
||||
export async function checkIndexedDBAvailability() {
|
||||
console.log('Checking IndexedDB availability...');
|
||||
|
||||
// First check if IndexedDB is available in the browser
|
||||
if (!window.indexedDB) {
|
||||
console.error('IndexedDB is not available in this browser');
|
||||
return false;
|
||||
}
|
||||
|
||||
const module = getWasmModule();
|
||||
if (!module || typeof module.check_indexeddb !== 'function') {
|
||||
console.error('WebAssembly module or check_indexeddb function not available');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await module.check_indexeddb();
|
||||
console.log('IndexedDB check result:', result);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('IndexedDB check failed:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a session with the given keyspace and password
|
||||
* @param {string} keyspace
|
||||
* @param {string} password
|
||||
* @returns {Promise<Array>} List of keypairs after initialization
|
||||
*/
|
||||
export async function initSession(keyspace, password) {
|
||||
const module = getWasmModule();
|
||||
if (!module) {
|
||||
throw new Error('WebAssembly module not loaded');
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`Initializing session for keyspace: ${keyspace}`);
|
||||
|
||||
// Check if IndexedDB is working
|
||||
const isIndexedDBAvailable = await checkIndexedDBAvailability();
|
||||
if (!isIndexedDBAvailable) {
|
||||
console.warn('IndexedDB is not available or not working properly. Keypairs might not persist.');
|
||||
// Continue anyway as we might fall back to memory storage
|
||||
}
|
||||
|
||||
// Initialize the session using the WASM module
|
||||
await module.init_session(keyspace, password);
|
||||
console.log('Session initialized successfully');
|
||||
|
||||
// Check if we have stored keypairs for this keyspace in Chrome storage
|
||||
const storedKeypairs = await new Promise(resolve => {
|
||||
chrome.storage.local.get([`keypairs:${keyspace}`], result => {
|
||||
resolve(result[`keypairs:${keyspace}`] || []);
|
||||
});
|
||||
});
|
||||
|
||||
console.log(`Found ${storedKeypairs.length} stored keypairs for keyspace ${keyspace}`);
|
||||
|
||||
// Import stored keypairs into the WebAssembly session if they don't exist already
|
||||
if (storedKeypairs.length > 0) {
|
||||
console.log('Importing stored keypairs into WebAssembly session...');
|
||||
|
||||
// First get current keypairs from the vault directly
|
||||
const wasmKeypairs = await module.list_keypairs();
|
||||
console.log('Current keypairs in WebAssembly vault:', wasmKeypairs);
|
||||
|
||||
// Get the IDs of existing keypairs in the vault
|
||||
const existingIds = new Set(wasmKeypairs.map(kp => kp.id));
|
||||
|
||||
// Import keypairs that don't already exist in the vault
|
||||
for (const keypair of storedKeypairs) {
|
||||
if (!existingIds.has(keypair.id)) {
|
||||
console.log(`Importing keypair ${keypair.id} into WebAssembly vault...`);
|
||||
|
||||
// Create metadata for the keypair
|
||||
const metadata = JSON.stringify({
|
||||
label: keypair.label || `Key-${keypair.id.substring(0, 8)}`,
|
||||
imported: true,
|
||||
importDate: new Date().toISOString()
|
||||
});
|
||||
|
||||
// For adding existing keypairs, we'd normally need the private key
|
||||
// Since we can't retrieve it, we'll create a new one with the same label
|
||||
// This is a placeholder - in a real implementation, you'd need to use the actual keys
|
||||
try {
|
||||
const keyType = keypair.type || 'Secp256k1';
|
||||
await module.add_keypair(keyType, metadata);
|
||||
console.log(`Created keypair of type ${keyType} with label ${keypair.label}`);
|
||||
} catch (err) {
|
||||
console.warn(`Failed to import keypair ${keypair.id}:`, err);
|
||||
// Continue with other keypairs even if one fails
|
||||
}
|
||||
} else {
|
||||
console.log(`Keypair ${keypair.id} already exists in vault, skipping import`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize session using WASM (await the async function)
|
||||
await module.init_session(keyspace, password);
|
||||
|
||||
// Get keypairs from the vault after session is ready
|
||||
const currentKeypairs = await getKeypairsFromVault();
|
||||
|
||||
// Update keypairs in background service worker
|
||||
await new Promise(resolve => {
|
||||
chrome.runtime.sendMessage({
|
||||
action: 'update_session',
|
||||
type: 'keypairs_loaded',
|
||||
data: currentKeypairs
|
||||
}, response => {
|
||||
console.log('Updated keypairs in background service worker');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
return currentKeypairs;
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize session:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock the current session
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function lockSession() {
|
||||
const module = getWasmModule();
|
||||
if (!module) {
|
||||
throw new Error('WebAssembly module not loaded');
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('Locking session...');
|
||||
|
||||
// First run diagnostics to see what we have before locking
|
||||
await debugVaultState();
|
||||
|
||||
// Call the WASM lock_session function
|
||||
module.lock_session();
|
||||
console.log('Session locked in WebAssembly module');
|
||||
|
||||
// Update session state in background
|
||||
await new Promise((resolve, reject) => {
|
||||
chrome.runtime.sendMessage({
|
||||
action: 'update_session',
|
||||
type: 'session_locked'
|
||||
}, (response) => {
|
||||
if (response && response.success) {
|
||||
console.log('Background service worker updated for locked session');
|
||||
resolve();
|
||||
} else {
|
||||
console.error('Failed to update session state in background:', response?.error);
|
||||
reject(new Error(response?.error || 'Failed to update session state'));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Verify session is locked properly
|
||||
const sessionStatus = await debugVaultState();
|
||||
console.log('Session status after locking:', sessionStatus);
|
||||
} catch (error) {
|
||||
console.error('Error locking session:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new keypair
|
||||
* @param {string} keyType The type of key to create (default: 'Secp256k1')
|
||||
* @param {string} label Optional custom label for the keypair
|
||||
* @returns {Promise<{id: string, label: string}>} The created keypair info
|
||||
*/
|
||||
export async function addKeypair(keyType = 'Secp256k1', label = null) {
|
||||
const module = getWasmModule();
|
||||
if (!module) {
|
||||
throw new Error('WebAssembly module not loaded');
|
||||
}
|
||||
|
||||
try {
|
||||
// Get current keyspace
|
||||
const sessionState = await getSessionState();
|
||||
const keyspace = sessionState.currentKeyspace;
|
||||
if (!keyspace) {
|
||||
throw new Error('No active keyspace');
|
||||
}
|
||||
|
||||
// Generate default label if not provided
|
||||
const keyLabel = label || `${keyType}-Key-${Date.now().toString(16).slice(-4)}`;
|
||||
|
||||
// Create metadata JSON
|
||||
const metadata = JSON.stringify({
|
||||
label: keyLabel,
|
||||
created: new Date().toISOString(),
|
||||
type: keyType
|
||||
});
|
||||
|
||||
console.log(`Adding new keypair of type ${keyType} with label ${keyLabel}`);
|
||||
console.log('Keypair metadata:', metadata);
|
||||
|
||||
// Call the WASM add_keypair function with metadata
|
||||
// This will add the keypair to the WebAssembly vault
|
||||
const keyId = await module.add_keypair(keyType, metadata);
|
||||
console.log(`Keypair created with ID: ${keyId} in WebAssembly vault`);
|
||||
|
||||
// Create keypair object for UI and storage
|
||||
const newKeypair = {
|
||||
id: keyId,
|
||||
label: keyLabel,
|
||||
type: keyType,
|
||||
created: new Date().toISOString()
|
||||
};
|
||||
|
||||
// Get the latest keypairs from the WebAssembly vault to ensure consistency
|
||||
const vaultKeypairs = await module.list_keypairs();
|
||||
console.log('Current keypairs in vault after addition:', vaultKeypairs);
|
||||
|
||||
// Format the vault keypairs for storage
|
||||
const formattedVaultKeypairs = vaultKeypairs.map(kp => {
|
||||
// Parse metadata if available
|
||||
let metadata = {};
|
||||
if (kp.metadata) {
|
||||
try {
|
||||
if (typeof kp.metadata === 'string') {
|
||||
metadata = JSON.parse(kp.metadata);
|
||||
} else {
|
||||
metadata = kp.metadata;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to parse keypair metadata:', e);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: kp.id,
|
||||
label: metadata.label || `Key-${kp.id.substring(0, 8)}`,
|
||||
type: kp.type || 'Secp256k1',
|
||||
created: metadata.created || new Date().toISOString()
|
||||
};
|
||||
});
|
||||
|
||||
// Save the formatted keypairs to Chrome storage
|
||||
await new Promise(resolve => {
|
||||
chrome.storage.local.set({ [`keypairs:${keyspace}`]: formattedVaultKeypairs }, () => {
|
||||
console.log(`Saved ${formattedVaultKeypairs.length} keypairs to Chrome storage for keyspace ${keyspace}`);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
// Update session state in background with the new keypair information
|
||||
await new Promise((resolve, reject) => {
|
||||
chrome.runtime.sendMessage({
|
||||
action: 'update_session',
|
||||
type: 'keypair_added',
|
||||
data: newKeypair
|
||||
}, async (response) => {
|
||||
if (response && response.success) {
|
||||
console.log('Background service worker updated with new keypair');
|
||||
resolve(newKeypair);
|
||||
} else {
|
||||
const error = response?.error || 'Failed to update session state';
|
||||
console.error('Error updating background state:', error);
|
||||
reject(new Error(error));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Also update the complete keypair list in background with the current vault state
|
||||
await new Promise(resolve => {
|
||||
chrome.runtime.sendMessage({
|
||||
action: 'update_session',
|
||||
type: 'keypairs_loaded',
|
||||
data: formattedVaultKeypairs
|
||||
}, () => {
|
||||
console.log('Updated complete keypair list in background with vault state');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
return newKeypair;
|
||||
} catch (error) {
|
||||
console.error('Error adding keypair:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a keypair
|
||||
* @param {string} keyId The ID of the keypair to select
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function selectKeypair(keyId) {
|
||||
if (!wasmModule || !wasmModule.select_keypair) {
|
||||
throw new Error('WASM module not loaded');
|
||||
}
|
||||
|
||||
// Call the WASM select_keypair function
|
||||
await wasmModule.select_keypair(keyId);
|
||||
|
||||
// Update session state in background
|
||||
await new Promise((resolve, reject) => {
|
||||
chrome.runtime.sendMessage({
|
||||
action: 'update_session',
|
||||
type: 'keypair_selected',
|
||||
data: keyId
|
||||
}, (response) => {
|
||||
if (response && response.success) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(response && response.error ? response.error : 'Failed to update session state');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a message with the selected keypair
|
||||
* @param {string} message The message to sign
|
||||
* @returns {Promise<string>} The signature as a hex string
|
||||
*/
|
||||
export async function sign(message) {
|
||||
if (!wasmModule || !wasmModule.sign) {
|
||||
throw new Error('WASM module not loaded');
|
||||
}
|
||||
|
||||
// Convert message to Uint8Array
|
||||
const encoder = new TextEncoder();
|
||||
const messageBytes = encoder.encode(message);
|
||||
|
||||
// Call the WASM sign function
|
||||
return await wasmModule.sign(messageBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current session state
|
||||
* @returns {Promise<{currentKeyspace: string|null, keypairs: Array, selectedKeypair: string|null}>}
|
||||
*/
|
||||
export async function getSessionState() {
|
||||
return new Promise((resolve) => {
|
||||
chrome.runtime.sendMessage({ action: 'get_session' }, (response) => {
|
||||
resolve(response || { currentKeyspace: null, keypairs: [], selectedKeypair: null });
|
||||
});
|
||||
});
|
||||
}
|
88
extension/popup/WasmLoader.jsx
Normal file
88
extension/popup/WasmLoader.jsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import React, { useState, useEffect, createContext, useContext } from 'react';
|
||||
|
||||
// Create a context to share the WASM module across components
|
||||
export const WasmContext = createContext(null);
|
||||
|
||||
// Hook to access WASM module
|
||||
export function useWasm() {
|
||||
return useContext(WasmContext);
|
||||
}
|
||||
|
||||
// Component that loads and initializes the WASM module
|
||||
export function WasmProvider({ children }) {
|
||||
const [wasmModule, setWasmModule] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
async function loadWasm() {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// Instead of using dynamic imports which require correct MIME types,
|
||||
// we'll use fetch to load the JavaScript file as text and eval it
|
||||
const wasmJsPath = chrome.runtime.getURL('wasm/wasm_app.js');
|
||||
console.log('Loading WASM JS from:', wasmJsPath);
|
||||
|
||||
// Load the JavaScript file
|
||||
const jsResponse = await fetch(wasmJsPath);
|
||||
if (!jsResponse.ok) {
|
||||
throw new Error(`Failed to load WASM JS: ${jsResponse.status} ${jsResponse.statusText}`);
|
||||
}
|
||||
|
||||
// Get the JavaScript code as text
|
||||
const jsCode = await jsResponse.text();
|
||||
|
||||
// Create a function to execute the code in an isolated scope
|
||||
let wasmModuleExports = {};
|
||||
const moduleFunction = new Function('exports', jsCode + '\nreturn { initSync, default: __wbg_init, init_rhai_env, init_session, lock_session, add_keypair, select_keypair, sign, run_rhai };');
|
||||
|
||||
// Execute the function to get the exports
|
||||
const wasmModule = moduleFunction(wasmModuleExports);
|
||||
|
||||
// Initialize WASM with the binary
|
||||
const wasmBinaryPath = chrome.runtime.getURL('wasm/wasm_app_bg.wasm');
|
||||
console.log('Initializing WASM with binary:', wasmBinaryPath);
|
||||
|
||||
const binaryResponse = await fetch(wasmBinaryPath);
|
||||
if (!binaryResponse.ok) {
|
||||
throw new Error(`Failed to load WASM binary: ${binaryResponse.status} ${binaryResponse.statusText}`);
|
||||
}
|
||||
|
||||
const wasmBinary = await binaryResponse.arrayBuffer();
|
||||
|
||||
// Initialize the WASM module
|
||||
await wasmModule.default(wasmBinary);
|
||||
|
||||
// Initialize the WASM environment
|
||||
if (typeof wasmModule.init_rhai_env === 'function') {
|
||||
wasmModule.init_rhai_env();
|
||||
}
|
||||
|
||||
console.log('WASM module loaded successfully');
|
||||
setWasmModule(wasmModule);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
console.error('Failed to load WASM module:', error);
|
||||
setError(error.message || 'Failed to load WebAssembly module');
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
loadWasm();
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return <div className="wasm-loading">Loading WebAssembly module...</div>;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <div className="wasm-error">Error: {error}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<WasmContext.Provider value={wasmModule}>
|
||||
{children}
|
||||
</WasmContext.Provider>
|
||||
);
|
||||
}
|
88
extension/popup/debug_rhai.js
Normal file
88
extension/popup/debug_rhai.js
Normal file
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Debug helper for WebAssembly Vault with Rhai scripts
|
||||
*/
|
||||
|
||||
// Helper to try various Rhai scripts for debugging
|
||||
export const RHAI_SCRIPTS = {
|
||||
// Check if there's an active session
|
||||
CHECK_SESSION: `
|
||||
let has_session = false;
|
||||
let current_keyspace = "";
|
||||
|
||||
// Try to access functions expected to exist in the vault namespace
|
||||
if (isdef(vault) && isdef(vault::has_active_session)) {
|
||||
has_session = vault::has_active_session();
|
||||
if (has_session && isdef(vault::get_current_keyspace)) {
|
||||
current_keyspace = vault::get_current_keyspace();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"has_session": has_session,
|
||||
"keyspace": current_keyspace,
|
||||
"available_functions": [
|
||||
isdef(vault::list_keypairs) ? "list_keypairs" : null,
|
||||
isdef(vault::add_keypair) ? "add_keypair" : null,
|
||||
isdef(vault::has_active_session) ? "has_active_session" : null,
|
||||
isdef(vault::get_current_keyspace) ? "get_current_keyspace" : null
|
||||
]
|
||||
}
|
||||
`,
|
||||
|
||||
// Explicitly get keypairs for the current keyspace using session data
|
||||
LIST_KEYPAIRS: `
|
||||
let result = {"error": "Not initialized"};
|
||||
|
||||
if (isdef(vault) && isdef(vault::has_active_session) && vault::has_active_session()) {
|
||||
let keyspace = vault::get_current_keyspace();
|
||||
|
||||
// Try to list the keypairs from the current session
|
||||
if (isdef(vault::get_keypairs_from_session)) {
|
||||
result = {
|
||||
"keyspace": keyspace,
|
||||
"keypairs": vault::get_keypairs_from_session()
|
||||
};
|
||||
} else {
|
||||
result = {
|
||||
"error": "vault::get_keypairs_from_session is not defined",
|
||||
"keyspace": keyspace
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
`,
|
||||
|
||||
// Use Rhai to inspect the Vault storage directly (for advanced debugging)
|
||||
INSPECT_VAULT_STORAGE: `
|
||||
let result = {"error": "Not accessible"};
|
||||
|
||||
if (isdef(vault) && isdef(vault::inspect_storage)) {
|
||||
result = vault::inspect_storage();
|
||||
}
|
||||
|
||||
result
|
||||
`
|
||||
};
|
||||
|
||||
// Run all debug scripts and collect results
|
||||
export async function runDiagnostics(wasmModule) {
|
||||
if (!wasmModule || !wasmModule.run_rhai) {
|
||||
throw new Error('WebAssembly module not loaded or run_rhai not available');
|
||||
}
|
||||
|
||||
const results = {};
|
||||
|
||||
for (const [name, script] of Object.entries(RHAI_SCRIPTS)) {
|
||||
try {
|
||||
console.log(`Running Rhai diagnostic script: ${name}`);
|
||||
results[name] = await wasmModule.run_rhai(script);
|
||||
console.log(`Result from ${name}:`, results[name]);
|
||||
} catch (error) {
|
||||
console.error(`Error running script ${name}:`, error);
|
||||
results[name] = { error: error.toString() };
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
13
extension/popup/index.html
Normal file
13
extension/popup/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Modular Vault Extension</title>
|
||||
<link rel="stylesheet" href="popup.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="popup.js"></script>
|
||||
</body>
|
||||
</html>
|
8
extension/popup/index.jsx
Normal file
8
extension/popup/index.jsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import App from './App';
|
||||
import './style.css';
|
||||
|
||||
// Render the React app
|
||||
const root = createRoot(document.getElementById('root'));
|
||||
root.render(<App />);
|
117
extension/popup/popup.css
Normal file
117
extension/popup/popup.css
Normal file
@@ -0,0 +1,117 @@
|
||||
/* Basic styles for the extension popup */
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #202124;
|
||||
color: #e8eaed;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 350px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 18px;
|
||||
margin: 0 0 15px 0;
|
||||
border-bottom: 1px solid #3c4043;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 16px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.form-section {
|
||||
margin-bottom: 20px;
|
||||
background-color: #292a2d;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-size: 13px;
|
||||
color: #9aa0a6;
|
||||
}
|
||||
|
||||
input, textarea {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid #3c4043;
|
||||
border-radius: 4px;
|
||||
background-color: #202124;
|
||||
color: #e8eaed;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 60px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #8ab4f8;
|
||||
color: #202124;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 8px 16px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #669df6;
|
||||
}
|
||||
|
||||
button.small {
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.status {
|
||||
margin: 10px 0;
|
||||
padding: 8px;
|
||||
background-color: #292a2d;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.list {
|
||||
margin-top: 10px;
|
||||
max-height: 150px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid #3c4043;
|
||||
}
|
||||
|
||||
.list-item.selected {
|
||||
background-color: rgba(138, 180, 248, 0.1);
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.session-info {
|
||||
margin-top: 15px;
|
||||
}
|
306
extension/popup/popup.js
Normal file
306
extension/popup/popup.js
Normal file
@@ -0,0 +1,306 @@
|
||||
// Simple non-module JavaScript for browser extension popup
|
||||
document.addEventListener('DOMContentLoaded', async function() {
|
||||
const root = document.getElementById('root');
|
||||
root.innerHTML = `
|
||||
<div class="container">
|
||||
<h1>Modular Vault Extension</h1>
|
||||
<div id="status" class="status">Loading WASM module...</div>
|
||||
|
||||
<div id="session-controls">
|
||||
<div id="keyspace-form" class="form-section">
|
||||
<h2>Session</h2>
|
||||
<div class="form-group">
|
||||
<label for="keyspace">Keyspace:</label>
|
||||
<input type="text" id="keyspace" placeholder="Enter keyspace name">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" id="password" placeholder="Enter password">
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<button id="unlock-btn">Unlock</button>
|
||||
<button id="create-btn">Create New</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="session-info" class="session-info hidden">
|
||||
<h2>Active Session</h2>
|
||||
<p>Current keyspace: <span id="current-keyspace"></span></p>
|
||||
<button id="lock-btn">Lock Session</button>
|
||||
|
||||
<div id="keypair-section" class="form-section">
|
||||
<h2>Keypairs</h2>
|
||||
<button id="create-keypair-btn">Create New Keypair</button>
|
||||
<div id="keypair-list" class="list"></div>
|
||||
</div>
|
||||
|
||||
<div id="sign-section" class="form-section hidden">
|
||||
<h2>Sign Message</h2>
|
||||
<div class="form-group">
|
||||
<label for="message">Message:</label>
|
||||
<textarea id="message" placeholder="Enter message to sign"></textarea>
|
||||
</div>
|
||||
<button id="sign-btn">Sign</button>
|
||||
<div class="form-group">
|
||||
<label for="signature">Signature:</label>
|
||||
<textarea id="signature" readonly></textarea>
|
||||
<button id="copy-btn" class="small">Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// DOM elements
|
||||
const statusEl = document.getElementById('status');
|
||||
const keyspaceFormEl = document.getElementById('keyspace-form');
|
||||
const sessionInfoEl = document.getElementById('session-info');
|
||||
const currentKeyspaceEl = document.getElementById('current-keyspace');
|
||||
const keyspaceInput = document.getElementById('keyspace');
|
||||
const passwordInput = document.getElementById('password');
|
||||
const unlockBtn = document.getElementById('unlock-btn');
|
||||
const createBtn = document.getElementById('create-btn');
|
||||
const lockBtn = document.getElementById('lock-btn');
|
||||
const createKeypairBtn = document.getElementById('create-keypair-btn');
|
||||
const keypairListEl = document.getElementById('keypair-list');
|
||||
const signSectionEl = document.getElementById('sign-section');
|
||||
const messageInput = document.getElementById('message');
|
||||
const signBtn = document.getElementById('sign-btn');
|
||||
const signatureOutput = document.getElementById('signature');
|
||||
const copyBtn = document.getElementById('copy-btn');
|
||||
|
||||
// State
|
||||
let wasmModule = null;
|
||||
let currentKeyspace = null;
|
||||
let keypairs = [];
|
||||
let selectedKeypairId = null;
|
||||
|
||||
// Initialize
|
||||
init();
|
||||
|
||||
async function init() {
|
||||
try {
|
||||
// Get session state from background
|
||||
const sessionState = await getSessionState();
|
||||
|
||||
if (sessionState.currentKeyspace) {
|
||||
// We have an active session
|
||||
currentKeyspace = sessionState.currentKeyspace;
|
||||
keypairs = sessionState.keypairs || [];
|
||||
selectedKeypairId = sessionState.selectedKeypair;
|
||||
|
||||
updateUI();
|
||||
}
|
||||
|
||||
statusEl.textContent = 'Ready';
|
||||
} catch (error) {
|
||||
statusEl.textContent = 'Error: ' + (error.message || 'Unknown error');
|
||||
}
|
||||
}
|
||||
|
||||
function updateUI() {
|
||||
if (currentKeyspace) {
|
||||
// Show session info
|
||||
keyspaceFormEl.classList.add('hidden');
|
||||
sessionInfoEl.classList.remove('hidden');
|
||||
currentKeyspaceEl.textContent = currentKeyspace;
|
||||
|
||||
// Update keypair list
|
||||
updateKeypairList();
|
||||
|
||||
// Show/hide sign section based on selected keypair
|
||||
if (selectedKeypairId) {
|
||||
signSectionEl.classList.remove('hidden');
|
||||
} else {
|
||||
signSectionEl.classList.add('hidden');
|
||||
}
|
||||
} else {
|
||||
// Show keyspace form
|
||||
keyspaceFormEl.classList.remove('hidden');
|
||||
sessionInfoEl.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
function updateKeypairList() {
|
||||
// Clear list
|
||||
keypairListEl.innerHTML = '';
|
||||
|
||||
// Add each keypair
|
||||
keypairs.forEach(keypair => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'list-item' + (selectedKeypairId === keypair.id ? ' selected' : '');
|
||||
item.innerHTML = `
|
||||
<span>${keypair.label || keypair.id}</span>
|
||||
<button class="select-btn" data-id="${keypair.id}">Select</button>
|
||||
`;
|
||||
keypairListEl.appendChild(item);
|
||||
|
||||
// Add select handler
|
||||
item.querySelector('.select-btn').addEventListener('click', async () => {
|
||||
try {
|
||||
statusEl.textContent = 'Selecting keypair...';
|
||||
// Use background service to select keypair for now
|
||||
await chrome.runtime.sendMessage({
|
||||
action: 'update_session',
|
||||
type: 'keypair_selected',
|
||||
data: keypair.id
|
||||
});
|
||||
selectedKeypairId = keypair.id;
|
||||
updateUI();
|
||||
statusEl.textContent = 'Keypair selected: ' + keypair.id;
|
||||
} catch (error) {
|
||||
statusEl.textContent = 'Error selecting keypair: ' + (error.message || 'Unknown error');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Get session state from background
|
||||
async function getSessionState() {
|
||||
return new Promise((resolve) => {
|
||||
chrome.runtime.sendMessage({ action: 'get_session' }, (response) => {
|
||||
resolve(response || { currentKeyspace: null, keypairs: [], selectedKeypair: null });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
unlockBtn.addEventListener('click', async () => {
|
||||
const keyspace = keyspaceInput.value.trim();
|
||||
const password = passwordInput.value;
|
||||
|
||||
if (!keyspace || !password) {
|
||||
statusEl.textContent = 'Please enter keyspace and password';
|
||||
return;
|
||||
}
|
||||
|
||||
statusEl.textContent = 'Unlocking session...';
|
||||
|
||||
try {
|
||||
// For now, use the background service worker mock
|
||||
await chrome.runtime.sendMessage({
|
||||
action: 'update_session',
|
||||
type: 'keyspace',
|
||||
data: keyspace
|
||||
});
|
||||
|
||||
currentKeyspace = keyspace;
|
||||
updateUI();
|
||||
statusEl.textContent = 'Session unlocked!';
|
||||
|
||||
// Refresh state
|
||||
const state = await getSessionState();
|
||||
keypairs = state.keypairs || [];
|
||||
selectedKeypairId = state.selectedKeypair;
|
||||
updateUI();
|
||||
} catch (error) {
|
||||
statusEl.textContent = 'Error unlocking session: ' + (error.message || 'Unknown error');
|
||||
}
|
||||
});
|
||||
|
||||
createBtn.addEventListener('click', async () => {
|
||||
const keyspace = keyspaceInput.value.trim();
|
||||
const password = passwordInput.value;
|
||||
|
||||
if (!keyspace || !password) {
|
||||
statusEl.textContent = 'Please enter keyspace and password';
|
||||
return;
|
||||
}
|
||||
|
||||
statusEl.textContent = 'Creating keyspace...';
|
||||
|
||||
try {
|
||||
// For now, use the background service worker mock
|
||||
await chrome.runtime.sendMessage({
|
||||
action: 'update_session',
|
||||
type: 'keyspace',
|
||||
data: keyspace
|
||||
});
|
||||
|
||||
currentKeyspace = keyspace;
|
||||
updateUI();
|
||||
statusEl.textContent = 'Keyspace created and unlocked!';
|
||||
} catch (error) {
|
||||
statusEl.textContent = 'Error creating keyspace: ' + (error.message || 'Unknown error');
|
||||
}
|
||||
});
|
||||
|
||||
lockBtn.addEventListener('click', async () => {
|
||||
statusEl.textContent = 'Locking session...';
|
||||
|
||||
try {
|
||||
await chrome.runtime.sendMessage({
|
||||
action: 'update_session',
|
||||
type: 'session_locked'
|
||||
});
|
||||
|
||||
currentKeyspace = null;
|
||||
keypairs = [];
|
||||
selectedKeypairId = null;
|
||||
updateUI();
|
||||
statusEl.textContent = 'Session locked';
|
||||
} catch (error) {
|
||||
statusEl.textContent = 'Error locking session: ' + (error.message || 'Unknown error');
|
||||
}
|
||||
});
|
||||
|
||||
createKeypairBtn.addEventListener('click', async () => {
|
||||
statusEl.textContent = 'Creating keypair...';
|
||||
|
||||
try {
|
||||
// Generate a mock keypair ID
|
||||
const keyId = 'key-' + Date.now().toString(16);
|
||||
const newKeypair = {
|
||||
id: keyId,
|
||||
label: `Secp256k1-Key-${keypairs.length + 1}`
|
||||
};
|
||||
|
||||
await chrome.runtime.sendMessage({
|
||||
action: 'update_session',
|
||||
type: 'keypair_added',
|
||||
data: newKeypair
|
||||
});
|
||||
|
||||
// Refresh state
|
||||
const state = await getSessionState();
|
||||
keypairs = state.keypairs || [];
|
||||
updateUI();
|
||||
|
||||
statusEl.textContent = 'Keypair created: ' + keyId;
|
||||
} catch (error) {
|
||||
statusEl.textContent = 'Error creating keypair: ' + (error.message || 'Unknown error');
|
||||
}
|
||||
});
|
||||
|
||||
signBtn.addEventListener('click', async () => {
|
||||
const message = messageInput.value.trim();
|
||||
|
||||
if (!message) {
|
||||
statusEl.textContent = 'Please enter a message to sign';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selectedKeypairId) {
|
||||
statusEl.textContent = 'Please select a keypair first';
|
||||
return;
|
||||
}
|
||||
|
||||
statusEl.textContent = 'Signing message...';
|
||||
|
||||
try {
|
||||
// For now, generate a mock signature
|
||||
const mockSignature = Array.from({length: 64}, () => Math.floor(Math.random() * 16).toString(16)).join('');
|
||||
signatureOutput.value = mockSignature;
|
||||
statusEl.textContent = 'Message signed!';
|
||||
} catch (error) {
|
||||
statusEl.textContent = 'Error signing message: ' + (error.message || 'Unknown error');
|
||||
}
|
||||
});
|
||||
|
||||
copyBtn.addEventListener('click', () => {
|
||||
signatureOutput.select();
|
||||
document.execCommand('copy');
|
||||
statusEl.textContent = 'Signature copied to clipboard!';
|
||||
});
|
||||
});
|
26
extension/popup/style.css
Normal file
26
extension/popup/style.css
Normal file
@@ -0,0 +1,26 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Inter', Arial, sans-serif;
|
||||
background: #181c20;
|
||||
color: #f3f6fa;
|
||||
}
|
||||
|
||||
.App {
|
||||
padding: 1.5rem;
|
||||
min-width: 320px;
|
||||
max-width: 400px;
|
||||
background: #23272e;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 24px rgba(0,0,0,0.2);
|
||||
}
|
||||
h1 {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
p {
|
||||
color: #b0bac9;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.status {
|
||||
margin-bottom: 1rem;
|
||||
}
|
317
extension/popup/wasm.js
Normal file
317
extension/popup/wasm.js
Normal file
@@ -0,0 +1,317 @@
|
||||
// WebAssembly API functions for accessing WASM operations directly
|
||||
// and synchronizing state with background service worker
|
||||
|
||||
// Get session state from the background service worker
|
||||
export function getStatus() {
|
||||
return new Promise((resolve) => {
|
||||
chrome.runtime.sendMessage({ action: 'get_session' }, (response) => {
|
||||
resolve(response);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Debug function to examine vault state using Rhai scripts
|
||||
export async function debugVaultState(wasmModule) {
|
||||
if (!wasmModule) {
|
||||
throw new Error('WASM module not loaded');
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('🔍 Debugging vault state...');
|
||||
|
||||
// First check if we have a valid session
|
||||
const sessionCheck = `
|
||||
let has_session = vault::has_active_session();
|
||||
let keyspace = "";
|
||||
if has_session {
|
||||
keyspace = vault::get_current_keyspace();
|
||||
}
|
||||
|
||||
// Return info about the session
|
||||
{
|
||||
"has_session": has_session,
|
||||
"keyspace": keyspace
|
||||
}
|
||||
`;
|
||||
|
||||
console.log('Checking session status...');
|
||||
const sessionStatus = await wasmModule.run_rhai(sessionCheck);
|
||||
console.log('Session status:', sessionStatus);
|
||||
|
||||
// Only try to get keypairs if we have an active session
|
||||
if (sessionStatus && sessionStatus.has_session) {
|
||||
// Get information about all keypairs
|
||||
const keypairsScript = `
|
||||
// Get all keypairs for the current keyspace
|
||||
let keypairs = vault::list_keypairs();
|
||||
|
||||
// Add more diagnostic information
|
||||
let diagnostic = {
|
||||
"keypair_count": keypairs.len(),
|
||||
"keyspace": vault::get_current_keyspace(),
|
||||
"keypairs": keypairs
|
||||
};
|
||||
|
||||
diagnostic
|
||||
`;
|
||||
|
||||
console.log('Fetching keypair details...');
|
||||
const keypairDiagnostic = await wasmModule.run_rhai(keypairsScript);
|
||||
console.log('Keypair diagnostic:', keypairDiagnostic);
|
||||
|
||||
return keypairDiagnostic;
|
||||
} else {
|
||||
console.log('No active session, cannot fetch keypairs');
|
||||
return { error: 'No active session' };
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error in debug function:', error);
|
||||
return { error: error.toString() };
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch all keypairs from the WebAssembly vault
|
||||
export async function getKeypairsFromVault(wasmModule) {
|
||||
if (!wasmModule) {
|
||||
throw new Error('WASM module not loaded');
|
||||
}
|
||||
|
||||
try {
|
||||
// First run diagnostics for debugging
|
||||
await debugVaultState(wasmModule);
|
||||
|
||||
console.log('Calling list_keypairs WebAssembly binding...');
|
||||
|
||||
// Use our new direct WebAssembly binding instead of Rhai script
|
||||
const keypairList = await wasmModule.list_keypairs();
|
||||
console.log('Retrieved keypairs from vault:', keypairList);
|
||||
|
||||
// Transform the keypairs into the expected format
|
||||
// The WebAssembly binding returns an array of objects with id, type, and metadata
|
||||
const formattedKeypairs = Array.isArray(keypairList) ? keypairList.map(kp => {
|
||||
// Parse metadata if it's a string
|
||||
let metadata = {};
|
||||
if (kp.metadata) {
|
||||
try {
|
||||
if (typeof kp.metadata === 'string') {
|
||||
metadata = JSON.parse(kp.metadata);
|
||||
} else {
|
||||
metadata = kp.metadata;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to parse keypair metadata:', e);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: kp.id,
|
||||
label: metadata.label || `${kp.type}-Key-${kp.id.substring(0, 4)}`
|
||||
};
|
||||
}) : [];
|
||||
|
||||
console.log('Formatted keypairs:', formattedKeypairs);
|
||||
|
||||
// Update the keypairs in the background service worker
|
||||
return new Promise((resolve) => {
|
||||
chrome.runtime.sendMessage({
|
||||
action: 'update_session',
|
||||
type: 'keypairs_loaded',
|
||||
data: formattedKeypairs
|
||||
}, (response) => {
|
||||
if (response && response.success) {
|
||||
console.log('Successfully updated keypairs in background');
|
||||
resolve(formattedKeypairs);
|
||||
} else {
|
||||
console.error('Failed to update keypairs in background:', response?.error);
|
||||
resolve([]);
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching keypairs from vault:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize session with the WASM module
|
||||
export function initSession(wasmModule, keyspace, password) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if (!wasmModule) {
|
||||
reject('WASM module not loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Call the WASM init_session function
|
||||
console.log(`Initializing session for keyspace: ${keyspace}`);
|
||||
await wasmModule.init_session(keyspace, password);
|
||||
|
||||
// Update the session state in the background service worker
|
||||
chrome.runtime.sendMessage({
|
||||
action: 'update_session',
|
||||
type: 'keyspace',
|
||||
data: keyspace
|
||||
}, async (response) => {
|
||||
if (response && response.success) {
|
||||
try {
|
||||
// After successful session initialization, fetch keypairs from the vault
|
||||
console.log('Session initialized, fetching keypairs from vault...');
|
||||
const keypairs = await getKeypairsFromVault(wasmModule);
|
||||
console.log('Keypairs loaded:', keypairs);
|
||||
resolve(keypairs);
|
||||
} catch (fetchError) {
|
||||
console.error('Error fetching keypairs:', fetchError);
|
||||
// Even if fetching keypairs fails, the session is initialized
|
||||
resolve([]);
|
||||
}
|
||||
} else {
|
||||
reject(response && response.error ? response.error : 'Failed to update session state');
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Session initialization error:', error);
|
||||
reject(error.message || 'Failed to initialize session');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Lock the session using the WASM module
|
||||
export function lockSession(wasmModule) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if (!wasmModule) {
|
||||
reject('WASM module not loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Call the WASM lock_session function
|
||||
wasmModule.lock_session();
|
||||
|
||||
// Update the session state in the background service worker
|
||||
chrome.runtime.sendMessage({
|
||||
action: 'update_session',
|
||||
type: 'session_locked'
|
||||
}, (response) => {
|
||||
if (response && response.success) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(response && response.error ? response.error : 'Failed to update session state');
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error.message || 'Failed to lock session');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Add a keypair using the WASM module
|
||||
export function addKeypair(wasmModule, keyType = 'Secp256k1', label = null) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if (!wasmModule) {
|
||||
reject('WASM module not loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Create a default label if none provided
|
||||
const keyLabel = label || `${keyType}-Key-${Date.now().toString(16).slice(-4)}`;
|
||||
|
||||
// Create metadata JSON for the keypair
|
||||
const metadata = JSON.stringify({
|
||||
label: keyLabel,
|
||||
created: new Date().toISOString(),
|
||||
type: keyType
|
||||
});
|
||||
|
||||
console.log(`Adding new keypair of type ${keyType} with label ${keyLabel}`);
|
||||
|
||||
// Call the WASM add_keypair function with metadata
|
||||
const keyId = await wasmModule.add_keypair(keyType, metadata);
|
||||
console.log(`Keypair created with ID: ${keyId}`);
|
||||
|
||||
// Create keypair object with ID and label
|
||||
const newKeypair = {
|
||||
id: keyId,
|
||||
label: keyLabel
|
||||
};
|
||||
|
||||
// Update the session state in the background service worker
|
||||
chrome.runtime.sendMessage({
|
||||
action: 'update_session',
|
||||
type: 'keypair_added',
|
||||
data: newKeypair
|
||||
}, (response) => {
|
||||
if (response && response.success) {
|
||||
// After adding a keypair, refresh the whole list from the vault
|
||||
getKeypairsFromVault(wasmModule)
|
||||
.then(() => {
|
||||
console.log('Keypair list refreshed from vault');
|
||||
resolve(keyId);
|
||||
})
|
||||
.catch(refreshError => {
|
||||
console.warn('Error refreshing keypair list:', refreshError);
|
||||
// Still resolve with the key ID since the key was created
|
||||
resolve(keyId);
|
||||
});
|
||||
} else {
|
||||
reject(response && response.error ? response.error : 'Failed to update session state');
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error adding keypair:', error);
|
||||
reject(error.message || 'Failed to add keypair');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Select a keypair using the WASM module
|
||||
export function selectKeypair(wasmModule, keyId) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if (!wasmModule) {
|
||||
reject('WASM module not loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Call the WASM select_keypair function
|
||||
await wasmModule.select_keypair(keyId);
|
||||
|
||||
// Update the session state in the background service worker
|
||||
chrome.runtime.sendMessage({
|
||||
action: 'update_session',
|
||||
type: 'keypair_selected',
|
||||
data: keyId
|
||||
}, (response) => {
|
||||
if (response && response.success) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(response && response.error ? response.error : 'Failed to update session state');
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error.message || 'Failed to select keypair');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Sign a message using the WASM module
|
||||
export function sign(wasmModule, message) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if (!wasmModule) {
|
||||
reject('WASM module not loaded');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Convert message to Uint8Array for WASM
|
||||
const encoder = new TextEncoder();
|
||||
const messageBytes = encoder.encode(message);
|
||||
|
||||
// Call the WASM sign function
|
||||
const signature = await wasmModule.sign(messageBytes);
|
||||
resolve(signature);
|
||||
} catch (error) {
|
||||
reject(error.message || 'Failed to sign message');
|
||||
}
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user