refactor: migrate extension to React and update build configuration
This commit is contained in:
		| @@ -1,3 +1,5 @@ | ||||
| import init, * as wasmModuleImport from '@wasm/wasm_app.js'; | ||||
|  | ||||
| /** | ||||
|  * Browser extension-friendly WebAssembly loader and helper functions | ||||
|  * This handles loading the WebAssembly module without relying on ES modules | ||||
| @@ -21,55 +23,11 @@ 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); | ||||
|      | ||||
|     await init(); | ||||
|     window.wasm_app = wasmModuleImport; | ||||
|  | ||||
|     // 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)); | ||||
| @@ -94,21 +52,19 @@ export async function loadWasmModule() { | ||||
|       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'; | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
|   </head> | ||||
|   <body> | ||||
|     <div id="root"></div> | ||||
|     <script src="popup.js"></script> | ||||
|  | ||||
|     <script type="module" src="./main.jsx"></script> | ||||
|   </body> | ||||
| </html> | ||||
|   | ||||
							
								
								
									
										8
									
								
								extension/popup/main.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								extension/popup/main.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 />); | ||||
| @@ -1,306 +0,0 @@ | ||||
| // 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!'; | ||||
|   }); | ||||
| }); | ||||
		Reference in New Issue
	
	Block a user