feat: implement browser extension UI with WebAssembly integration
							
								
								
									
										35
									
								
								extension/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,35 @@ | ||||
| # Modular Vault Browser Extension | ||||
|  | ||||
| A cross-browser (Manifest V3) extension for secure cryptographic operations and Rhai scripting, powered by Rust/WASM. | ||||
|  | ||||
| ## Features | ||||
| - Session/keypair management | ||||
| - Cryptographic signing, encryption, and EVM actions | ||||
| - Secure WASM integration (signing only accessible from extension scripts) | ||||
| - React-based popup UI with dark mode | ||||
| - Future: WebSocket integration for remote scripting | ||||
|  | ||||
| ## Structure | ||||
| - `manifest.json`: Extension manifest (MV3, Chrome/Firefox) | ||||
| - `popup/`: React UI for user interaction | ||||
| - `background/`: Service worker for session, keypair, and WASM logic | ||||
| - `assets/`: Icons and static assets | ||||
|  | ||||
| ## Dev Workflow | ||||
| 1. Build Rust WASM: `wasm-pack build --target web --out-dir ../extension/wasm` | ||||
| 2. Install JS deps: `npm install` (from `extension/`) | ||||
| 3. Build popup: `npm run build` | ||||
| 4. Load `/extension` as an unpacked extension in your browser | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## Security | ||||
| - WASM cryptographic APIs are only accessible from extension scripts (not content scripts or web pages). | ||||
| - All sensitive actions require explicit user approval. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## TODO | ||||
| - Implement background logic for session/keypair | ||||
| - Integrate popup UI with WASM APIs | ||||
| - Add WebSocket support (Phase 2) | ||||
							
								
								
									
										
											BIN
										
									
								
								extension/assets/icon-128.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								extension/assets/icon-16.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 454 B | 
							
								
								
									
										
											BIN
										
									
								
								extension/assets/icon-32.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 712 B | 
							
								
								
									
										
											BIN
										
									
								
								extension/assets/icon-48.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.1 KiB | 
							
								
								
									
										81
									
								
								extension/background/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,81 @@ | ||||
| // Background service worker for Modular Vault Extension | ||||
| // Handles state persistence between popup sessions | ||||
|  | ||||
| console.log('Background service worker started'); | ||||
|  | ||||
| // Store session state locally for quicker access | ||||
| let sessionState = { | ||||
|   currentKeyspace: null, | ||||
|   keypairs: [], | ||||
|   selectedKeypair: null | ||||
| }; | ||||
|  | ||||
| // Initialize state from storage | ||||
| chrome.storage.local.get(['currentKeyspace', 'keypairs', 'selectedKeypair']) | ||||
|   .then(state => { | ||||
|     sessionState = { | ||||
|       currentKeyspace: state.currentKeyspace || null, | ||||
|       keypairs: state.keypairs || [], | ||||
|       selectedKeypair: state.selectedKeypair || null | ||||
|     }; | ||||
|     console.log('Session state loaded from storage:', sessionState); | ||||
|   }) | ||||
|   .catch(error => { | ||||
|     console.error('Failed to load session state:', error); | ||||
|   }); | ||||
|  | ||||
| // Handle messages from the popup | ||||
| chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { | ||||
|   console.log('Background received message:', message.action, message.type || ''); | ||||
|    | ||||
|   // Update session state | ||||
|   if (message.action === 'update_session') { | ||||
|     try { | ||||
|       const { type, data } = message; | ||||
|        | ||||
|       // Update our local state | ||||
|       if (type === 'keyspace') { | ||||
|         sessionState.currentKeyspace = data; | ||||
|       } else if (type === 'keypair_selected') { | ||||
|         sessionState.selectedKeypair = data; | ||||
|       } else if (type === 'keypair_added') { | ||||
|         sessionState.keypairs = [...sessionState.keypairs, data]; | ||||
|       } else if (type === 'keypairs_loaded') { | ||||
|         // Replace the entire keypair list with what came from the vault | ||||
|         console.log('Updating keypairs from vault:', data); | ||||
|         sessionState.keypairs = data; | ||||
|       } else if (type === 'session_locked') { | ||||
|         // When locking, we don't need to maintain keypairs in memory anymore | ||||
|         // since they'll be reloaded from the vault when unlocking | ||||
|         sessionState = { | ||||
|           currentKeyspace: null, | ||||
|           keypairs: [], // Clear keypairs from memory since they're in the vault | ||||
|           selectedKeypair: null | ||||
|         }; | ||||
|       } | ||||
|        | ||||
|       // Persist to storage | ||||
|       chrome.storage.local.set(sessionState) | ||||
|         .then(() => { | ||||
|           console.log('Updated session state in storage:', sessionState); | ||||
|           sendResponse({ success: true }); | ||||
|         }) | ||||
|         .catch(error => { | ||||
|           console.error('Failed to persist session state:', error); | ||||
|           sendResponse({ success: false, error: error.message }); | ||||
|         }); | ||||
|        | ||||
|       return true; // Keep connection open for async response | ||||
|     } catch (error) { | ||||
|       console.error('Error in update_session message handler:', error); | ||||
|       sendResponse({ success: false, error: error.message }); | ||||
|       return true; | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   // Get session state | ||||
|   if (message.action === 'get_session') { | ||||
|     sendResponse(sessionState); | ||||
|     return false; // No async response needed | ||||
|   } | ||||
| }); | ||||
							
								
								
									
										84
									
								
								extension/build.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,84 @@ | ||||
| // Simple build script for browser extension | ||||
| const fs = require('fs'); | ||||
| const path = require('path'); | ||||
|  | ||||
| // Paths | ||||
| const sourceDir = __dirname; | ||||
| const distDir = path.join(sourceDir, 'dist'); | ||||
|  | ||||
| // Make sure the dist directory exists | ||||
| if (!fs.existsSync(distDir)) { | ||||
|   fs.mkdirSync(distDir, { recursive: true }); | ||||
| } | ||||
|  | ||||
| // Helper function to copy a file | ||||
| function copyFile(src, dest) { | ||||
|   // Create destination directory if it doesn't exist | ||||
|   const destDir = path.dirname(dest); | ||||
|   if (!fs.existsSync(destDir)) { | ||||
|     fs.mkdirSync(destDir, { recursive: true }); | ||||
|   } | ||||
|    | ||||
|   // Copy the file | ||||
|   fs.copyFileSync(src, dest); | ||||
|   console.log(`Copied: ${path.relative(sourceDir, src)} -> ${path.relative(sourceDir, dest)}`); | ||||
| } | ||||
|  | ||||
| // Helper function to copy an entire directory | ||||
| function copyDir(src, dest) { | ||||
|   // Create destination directory | ||||
|   if (!fs.existsSync(dest)) { | ||||
|     fs.mkdirSync(dest, { recursive: true }); | ||||
|   } | ||||
|    | ||||
|   // Get list of files | ||||
|   const files = fs.readdirSync(src); | ||||
|    | ||||
|   // Copy each file | ||||
|   for (const file of files) { | ||||
|     const srcPath = path.join(src, file); | ||||
|     const destPath = path.join(dest, file); | ||||
|      | ||||
|     const stat = fs.statSync(srcPath); | ||||
|      | ||||
|     if (stat.isDirectory()) { | ||||
|       // Recursively copy directories | ||||
|       copyDir(srcPath, destPath); | ||||
|     } else { | ||||
|       // Copy file | ||||
|       copyFile(srcPath, destPath); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| // Copy manifest | ||||
| copyFile( | ||||
|   path.join(sourceDir, 'manifest.json'), | ||||
|   path.join(distDir, 'manifest.json') | ||||
| ); | ||||
|  | ||||
| // Copy assets | ||||
| copyDir( | ||||
|   path.join(sourceDir, 'assets'), | ||||
|   path.join(distDir, 'assets') | ||||
| ); | ||||
|  | ||||
| // Copy popup files | ||||
| copyDir( | ||||
|   path.join(sourceDir, 'popup'), | ||||
|   path.join(distDir, 'popup') | ||||
| ); | ||||
|  | ||||
| // Copy background script | ||||
| copyDir( | ||||
|   path.join(sourceDir, 'background'), | ||||
|   path.join(distDir, 'background') | ||||
| ); | ||||
|  | ||||
| // Copy WebAssembly files | ||||
| copyDir( | ||||
|   path.join(sourceDir, 'wasm'), | ||||
|   path.join(distDir, 'wasm') | ||||
| ); | ||||
|  | ||||
| console.log('Build complete! Extension files copied to dist directory.'); | ||||
							
								
								
									
										
											BIN
										
									
								
								extension/dist/assets/icon-128.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								extension/dist/assets/icon-16.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 454 B | 
							
								
								
									
										
											BIN
										
									
								
								extension/dist/assets/icon-32.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 712 B | 
							
								
								
									
										
											BIN
										
									
								
								extension/dist/assets/icon-48.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 1.1 KiB | 
							
								
								
									
										2
									
								
								extension/dist/assets/popup.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,2 @@ | ||||
| (function(){"use strict";var o=document.createElement("style");o.textContent=`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;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 .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:#8ab4f81a}.hidden{display:none}.session-info{margin-top:15px} | ||||
| `,document.head.appendChild(o);const e=""})(); | ||||
							
								
								
									
										81
									
								
								extension/dist/background/index.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,81 @@ | ||||
| // Background service worker for Modular Vault Extension | ||||
| // Handles state persistence between popup sessions | ||||
|  | ||||
| console.log('Background service worker started'); | ||||
|  | ||||
| // Store session state locally for quicker access | ||||
| let sessionState = { | ||||
|   currentKeyspace: null, | ||||
|   keypairs: [], | ||||
|   selectedKeypair: null | ||||
| }; | ||||
|  | ||||
| // Initialize state from storage | ||||
| chrome.storage.local.get(['currentKeyspace', 'keypairs', 'selectedKeypair']) | ||||
|   .then(state => { | ||||
|     sessionState = { | ||||
|       currentKeyspace: state.currentKeyspace || null, | ||||
|       keypairs: state.keypairs || [], | ||||
|       selectedKeypair: state.selectedKeypair || null | ||||
|     }; | ||||
|     console.log('Session state loaded from storage:', sessionState); | ||||
|   }) | ||||
|   .catch(error => { | ||||
|     console.error('Failed to load session state:', error); | ||||
|   }); | ||||
|  | ||||
| // Handle messages from the popup | ||||
| chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { | ||||
|   console.log('Background received message:', message.action, message.type || ''); | ||||
|    | ||||
|   // Update session state | ||||
|   if (message.action === 'update_session') { | ||||
|     try { | ||||
|       const { type, data } = message; | ||||
|        | ||||
|       // Update our local state | ||||
|       if (type === 'keyspace') { | ||||
|         sessionState.currentKeyspace = data; | ||||
|       } else if (type === 'keypair_selected') { | ||||
|         sessionState.selectedKeypair = data; | ||||
|       } else if (type === 'keypair_added') { | ||||
|         sessionState.keypairs = [...sessionState.keypairs, data]; | ||||
|       } else if (type === 'keypairs_loaded') { | ||||
|         // Replace the entire keypair list with what came from the vault | ||||
|         console.log('Updating keypairs from vault:', data); | ||||
|         sessionState.keypairs = data; | ||||
|       } else if (type === 'session_locked') { | ||||
|         // When locking, we don't need to maintain keypairs in memory anymore | ||||
|         // since they'll be reloaded from the vault when unlocking | ||||
|         sessionState = { | ||||
|           currentKeyspace: null, | ||||
|           keypairs: [], // Clear keypairs from memory since they're in the vault | ||||
|           selectedKeypair: null | ||||
|         }; | ||||
|       } | ||||
|        | ||||
|       // Persist to storage | ||||
|       chrome.storage.local.set(sessionState) | ||||
|         .then(() => { | ||||
|           console.log('Updated session state in storage:', sessionState); | ||||
|           sendResponse({ success: true }); | ||||
|         }) | ||||
|         .catch(error => { | ||||
|           console.error('Failed to persist session state:', error); | ||||
|           sendResponse({ success: false, error: error.message }); | ||||
|         }); | ||||
|        | ||||
|       return true; // Keep connection open for async response | ||||
|     } catch (error) { | ||||
|       console.error('Error in update_session message handler:', error); | ||||
|       sendResponse({ success: false, error: error.message }); | ||||
|       return true; | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   // Get session state | ||||
|   if (message.action === 'get_session') { | ||||
|     sendResponse(sessionState); | ||||
|     return false; // No async response needed | ||||
|   } | ||||
| }); | ||||
							
								
								
									
										36
									
								
								extension/dist/manifest.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,36 @@ | ||||
| { | ||||
|   "manifest_version": 3, | ||||
|   "name": "Modular Vault Extension", | ||||
|   "version": "0.1.0", | ||||
|   "description": "Cross-browser modular vault for cryptographic operations and scripting.", | ||||
|   "action": { | ||||
|     "default_popup": "popup/index.html", | ||||
|     "default_icon": { | ||||
|       "16": "assets/icon-16.png", | ||||
|       "32": "assets/icon-32.png", | ||||
|       "48": "assets/icon-48.png", | ||||
|       "128": "assets/icon-128.png" | ||||
|     } | ||||
|   }, | ||||
|   "background": { | ||||
|     "service_worker": "background/index.js", | ||||
|     "type": "module" | ||||
|   }, | ||||
|   "permissions": [ | ||||
|     "storage", | ||||
|     "scripting" | ||||
|   ], | ||||
|   "host_permissions": [], | ||||
|   "icons": { | ||||
|     "16": "assets/icon-16.png", | ||||
|     "32": "assets/icon-32.png", | ||||
|     "48": "assets/icon-48.png", | ||||
|     "128": "assets/icon-128.png" | ||||
|   }, | ||||
|   "web_accessible_resources": [ | ||||
|     { | ||||
|       "resources": ["wasm/*.wasm", "wasm/*.js"], | ||||
|       "matches": ["<all_urls>"] | ||||
|     } | ||||
|   ] | ||||
| } | ||||
							
								
								
									
										13
									
								
								extension/dist/popup/index.html
									
									
									
									
										vendored
									
									
										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> | ||||
							
								
								
									
										117
									
								
								extension/dist/popup/popup.css
									
									
									
									
										vendored
									
									
										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/dist/popup/popup.js
									
									
									
									
										vendored
									
									
										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!'; | ||||
|   }); | ||||
| }); | ||||
							
								
								
									
										765
									
								
								extension/dist/wasm/wasm_app.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,765 @@ | ||||
| import * as __wbg_star0 from 'env'; | ||||
|  | ||||
| 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]); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * 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 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.closure77_externref_shim(arg0, arg1, arg2); | ||||
| } | ||||
|  | ||||
| function __wbg_adapter_35(arg0, arg1, arg2) { | ||||
|     wasm.closure126_externref_shim(arg0, arg1, arg2); | ||||
| } | ||||
|  | ||||
| function __wbg_adapter_38(arg0, arg1, arg2) { | ||||
|     wasm.closure188_externref_shim(arg0, arg1, arg2); | ||||
| } | ||||
|  | ||||
| function __wbg_adapter_123(arg0, arg1, arg2, arg3) { | ||||
|     wasm.closure213_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_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_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_123(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_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_wrapper284 = function(arg0, arg1, arg2) { | ||||
|         const ret = makeMutClosure(arg0, arg1, 78, __wbg_adapter_32); | ||||
|         return ret; | ||||
|     }; | ||||
|     imports.wbg.__wbindgen_closure_wrapper493 = function(arg0, arg1, arg2) { | ||||
|         const ret = makeMutClosure(arg0, arg1, 127, __wbg_adapter_35); | ||||
|         return ret; | ||||
|     }; | ||||
|     imports.wbg.__wbindgen_closure_wrapper762 = 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)); | ||||
|     }; | ||||
|     imports['env'] = __wbg_star0; | ||||
|  | ||||
|     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; | ||||
							
								
								
									
										
											BIN
										
									
								
								extension/dist/wasm/wasm_app_bg.wasm
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										36
									
								
								extension/manifest.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,36 @@ | ||||
| { | ||||
|   "manifest_version": 3, | ||||
|   "name": "Modular Vault Extension", | ||||
|   "version": "0.1.0", | ||||
|   "description": "Cross-browser modular vault for cryptographic operations and scripting.", | ||||
|   "action": { | ||||
|     "default_popup": "popup/index.html", | ||||
|     "default_icon": { | ||||
|       "16": "assets/icon-16.png", | ||||
|       "32": "assets/icon-32.png", | ||||
|       "48": "assets/icon-48.png", | ||||
|       "128": "assets/icon-128.png" | ||||
|     } | ||||
|   }, | ||||
|   "background": { | ||||
|     "service_worker": "background/index.js", | ||||
|     "type": "module" | ||||
|   }, | ||||
|   "permissions": [ | ||||
|     "storage", | ||||
|     "scripting" | ||||
|   ], | ||||
|   "host_permissions": [], | ||||
|   "icons": { | ||||
|     "16": "assets/icon-16.png", | ||||
|     "32": "assets/icon-32.png", | ||||
|     "48": "assets/icon-48.png", | ||||
|     "128": "assets/icon-128.png" | ||||
|   }, | ||||
|   "web_accessible_resources": [ | ||||
|     { | ||||
|       "resources": ["wasm/*.wasm", "wasm/*.js"], | ||||
|       "matches": ["<all_urls>"] | ||||
|     } | ||||
|   ] | ||||
| } | ||||
							
								
								
									
										1474
									
								
								extension/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										21
									
								
								extension/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,21 @@ | ||||
| { | ||||
|   "name": "modular-vault-extension", | ||||
|   "version": "0.1.0", | ||||
|   "description": "Cross-browser modular vault extension with secure WASM integration and React UI.", | ||||
|   "private": true, | ||||
|   "scripts": { | ||||
|     "dev": "vite --mode development", | ||||
|     "build": "vite build", | ||||
|     "build:ext": "node build.js" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@vitejs/plugin-react": "^4.4.1", | ||||
|     "react": "^18.3.1", | ||||
|     "react-dom": "^18.3.1" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "vite": "^4.5.0", | ||||
|     "vite-plugin-top-level-await": "^1.4.0", | ||||
|     "vite-plugin-wasm": "^3.4.1" | ||||
|   } | ||||
| } | ||||
							
								
								
									
										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
									
								
							
							
						
						| @@ -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
									
								
							
							
						
						| @@ -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
									
								
							
							
						
						| @@ -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
									
								
							
							
						
						| @@ -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
									
								
							
							
						
						| @@ -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
									
								
							
							
						
						| @@ -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
									
								
							
							
						
						| @@ -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
									
								
							
							
						
						| @@ -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
									
								
							
							
						
						| @@ -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
									
								
							
							
						
						| @@ -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
									
								
							
							
						
						| @@ -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
									
								
							
							
						
						| @@ -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'); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
							
								
								
									
										102
									
								
								extension/public/background/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,102 @@ | ||||
| // Background service worker for Modular Vault Extension | ||||
| // Handles session, keypair, and WASM logic | ||||
|  | ||||
| // We need to use dynamic imports for service workers in MV3 | ||||
| let wasmModule; | ||||
| let init; | ||||
| let wasm; | ||||
| let wasmReady = false; | ||||
|  | ||||
| // Initialize WASM on startup with dynamic import | ||||
| async function loadWasm() { | ||||
|   try { | ||||
|     // Using importScripts for service worker | ||||
|     const wasmUrl = chrome.runtime.getURL('wasm/wasm_app.js'); | ||||
|     wasmModule = await import(wasmUrl); | ||||
|     init = wasmModule.default; | ||||
|     wasm = wasmModule; | ||||
|      | ||||
|     // Initialize WASM with explicit WASM file path | ||||
|     await init(chrome.runtime.getURL('wasm/wasm_app_bg.wasm')); | ||||
|     wasmReady = true; | ||||
|     console.log('WASM initialized in background'); | ||||
|   } catch (error) { | ||||
|     console.error('Failed to initialize WASM:', error); | ||||
|   } | ||||
| } | ||||
|  | ||||
| // Start loading WASM | ||||
| loadWasm(); | ||||
|  | ||||
| chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => { | ||||
|   if (!wasmReady) { | ||||
|     sendResponse({ error: 'WASM not ready' }); | ||||
|     return true; | ||||
|   } | ||||
|   // Session unlock/create | ||||
|   if (request.action === 'init_session') { | ||||
|     try { | ||||
|       const result = await wasm.init_session(request.keyspace, request.password); | ||||
|       // Persist current session info | ||||
|       await chrome.storage.local.set({ currentKeyspace: request.keyspace }); | ||||
|       sendResponse({ ok: true }); | ||||
|     } catch (e) { | ||||
|       sendResponse({ error: e.message }); | ||||
|     } | ||||
|     return true; | ||||
|   } | ||||
|   // Lock session | ||||
|   if (request.action === 'lock_session') { | ||||
|     try { | ||||
|       wasm.lock_session(); | ||||
|       await chrome.storage.local.set({ currentKeyspace: null }); | ||||
|       sendResponse({ ok: true }); | ||||
|     } catch (e) { | ||||
|       sendResponse({ error: e.message }); | ||||
|     } | ||||
|     return true; | ||||
|   } | ||||
|   // Add keypair | ||||
|   if (request.action === 'add_keypair') { | ||||
|     try { | ||||
|       const keyId = await wasm.add_keypair('Secp256k1', null); | ||||
|       let keypairs = (await chrome.storage.local.get(['keypairs'])).keypairs || []; | ||||
|       keypairs.push({ id: keyId, label: `Secp256k1-${keypairs.length + 1}` }); | ||||
|       await chrome.storage.local.set({ keypairs }); | ||||
|       sendResponse({ keyId }); | ||||
|     } catch (e) { | ||||
|       sendResponse({ error: e.message }); | ||||
|     } | ||||
|     return true; | ||||
|   } | ||||
|   // Select keypair | ||||
|   if (request.action === 'select_keypair') { | ||||
|     try { | ||||
|       await wasm.select_keypair(request.keyId); | ||||
|       await chrome.storage.local.set({ selectedKeypair: request.keyId }); | ||||
|       sendResponse({ ok: true }); | ||||
|     } catch (e) { | ||||
|       sendResponse({ error: e.message }); | ||||
|     } | ||||
|     return true; | ||||
|   } | ||||
|   // Sign | ||||
|   if (request.action === 'sign') { | ||||
|     try { | ||||
|       // Convert plaintext to Uint8Array | ||||
|       const encoder = new TextEncoder(); | ||||
|       const msgBytes = encoder.encode(request.message); | ||||
|       const signature = await wasm.sign(msgBytes); | ||||
|       sendResponse({ signature }); | ||||
|     } catch (e) { | ||||
|       sendResponse({ error: e.message }); | ||||
|     } | ||||
|     return true; | ||||
|   } | ||||
|   // Query status | ||||
|   if (request.action === 'get_status') { | ||||
|     const { currentKeyspace, keypairs, selectedKeypair } = await chrome.storage.local.get(['currentKeyspace', 'keypairs', 'selectedKeypair']); | ||||
|     sendResponse({ currentKeyspace, keypairs: keypairs || [], selectedKeypair }); | ||||
|     return true; | ||||
|   } | ||||
| }); | ||||
							
								
								
									
										765
									
								
								extension/public/wasm/wasm_app.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,765 @@ | ||||
| import * as __wbg_star0 from 'env'; | ||||
|  | ||||
| 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]); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * 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 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.closure77_externref_shim(arg0, arg1, arg2); | ||||
| } | ||||
|  | ||||
| function __wbg_adapter_35(arg0, arg1, arg2) { | ||||
|     wasm.closure126_externref_shim(arg0, arg1, arg2); | ||||
| } | ||||
|  | ||||
| function __wbg_adapter_38(arg0, arg1, arg2) { | ||||
|     wasm.closure188_externref_shim(arg0, arg1, arg2); | ||||
| } | ||||
|  | ||||
| function __wbg_adapter_123(arg0, arg1, arg2, arg3) { | ||||
|     wasm.closure213_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_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_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_123(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_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_wrapper284 = function(arg0, arg1, arg2) { | ||||
|         const ret = makeMutClosure(arg0, arg1, 78, __wbg_adapter_32); | ||||
|         return ret; | ||||
|     }; | ||||
|     imports.wbg.__wbindgen_closure_wrapper493 = function(arg0, arg1, arg2) { | ||||
|         const ret = makeMutClosure(arg0, arg1, 127, __wbg_adapter_35); | ||||
|         return ret; | ||||
|     }; | ||||
|     imports.wbg.__wbindgen_closure_wrapper762 = 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)); | ||||
|     }; | ||||
|     imports['env'] = __wbg_star0; | ||||
|  | ||||
|     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; | ||||
							
								
								
									
										
											BIN
										
									
								
								extension/public/wasm/wasm_app_bg.wasm
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										120
									
								
								extension/vite.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,120 @@ | ||||
| import { defineConfig } from 'vite'; | ||||
| import react from '@vitejs/plugin-react'; | ||||
| import wasm from 'vite-plugin-wasm'; | ||||
| import topLevelAwait from 'vite-plugin-top-level-await'; | ||||
| import { resolve } from 'path'; | ||||
| import fs from 'fs'; | ||||
| import { Plugin } from 'vite'; | ||||
|  | ||||
| // Custom plugin to copy extension files directly to the dist directory | ||||
| const copyExtensionFiles = () => { | ||||
|   return { | ||||
|     name: 'copy-extension-files', | ||||
|     closeBundle() { | ||||
|       // Create the wasm directory in dist if it doesn't exist | ||||
|       const wasmDistDir = resolve(__dirname, 'dist/wasm'); | ||||
|       if (!fs.existsSync(wasmDistDir)) { | ||||
|         fs.mkdirSync(wasmDistDir, { recursive: true }); | ||||
|       } | ||||
|        | ||||
|       // Copy the wasm.js file | ||||
|       const wasmJsSource = resolve(__dirname, 'wasm/wasm_app.js'); | ||||
|       const wasmJsDest = resolve(wasmDistDir, 'wasm_app.js'); | ||||
|       fs.copyFileSync(wasmJsSource, wasmJsDest); | ||||
|        | ||||
|       // Copy the wasm binary file | ||||
|       const wasmBinSource = resolve(__dirname, 'wasm/wasm_app_bg.wasm'); | ||||
|       const wasmBinDest = resolve(wasmDistDir, 'wasm_app_bg.wasm'); | ||||
|       fs.copyFileSync(wasmBinSource, wasmBinDest); | ||||
|  | ||||
|       // Create background directory and copy the background script | ||||
|       const bgDistDir = resolve(__dirname, 'dist/background'); | ||||
|       if (!fs.existsSync(bgDistDir)) { | ||||
|         fs.mkdirSync(bgDistDir, { recursive: true }); | ||||
|       } | ||||
|        | ||||
|       const bgSource = resolve(__dirname, 'background/index.js'); | ||||
|       const bgDest = resolve(bgDistDir, 'index.js'); | ||||
|       fs.copyFileSync(bgSource, bgDest); | ||||
|        | ||||
|       // Create popup directory and copy the popup files | ||||
|       const popupDistDir = resolve(__dirname, 'dist/popup'); | ||||
|       if (!fs.existsSync(popupDistDir)) { | ||||
|         fs.mkdirSync(popupDistDir, { recursive: true }); | ||||
|       } | ||||
|        | ||||
|       // Copy HTML file | ||||
|       const htmlSource = resolve(__dirname, 'popup/index.html'); | ||||
|       const htmlDest = resolve(popupDistDir, 'index.html'); | ||||
|       fs.copyFileSync(htmlSource, htmlDest); | ||||
|        | ||||
|       // Copy JS file | ||||
|       const jsSource = resolve(__dirname, 'popup/popup.js'); | ||||
|       const jsDest = resolve(popupDistDir, 'popup.js'); | ||||
|       fs.copyFileSync(jsSource, jsDest); | ||||
|        | ||||
|       // Copy CSS file | ||||
|       const cssSource = resolve(__dirname, 'popup/popup.css'); | ||||
|       const cssDest = resolve(popupDistDir, 'popup.css'); | ||||
|       fs.copyFileSync(cssSource, cssDest); | ||||
|        | ||||
|       // Also copy the manifest.json file | ||||
|       const manifestSource = resolve(__dirname, 'manifest.json'); | ||||
|       const manifestDest = resolve(__dirname, 'dist/manifest.json'); | ||||
|       fs.copyFileSync(manifestSource, manifestDest); | ||||
|        | ||||
|       // Copy assets directory | ||||
|       const assetsDistDir = resolve(__dirname, 'dist/assets'); | ||||
|       if (!fs.existsSync(assetsDistDir)) { | ||||
|         fs.mkdirSync(assetsDistDir, { recursive: true }); | ||||
|       } | ||||
|        | ||||
|       // Copy icon files | ||||
|       const iconSizes = [16, 32, 48, 128]; | ||||
|       iconSizes.forEach(size => { | ||||
|         const iconSource = resolve(__dirname, `assets/icon-${size}.png`); | ||||
|         const iconDest = resolve(assetsDistDir, `icon-${size}.png`); | ||||
|         if (fs.existsSync(iconSource)) { | ||||
|           fs.copyFileSync(iconSource, iconDest); | ||||
|         } | ||||
|       }); | ||||
|  | ||||
|       console.log('Extension files copied to dist directory'); | ||||
|     } | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| export default defineConfig({ | ||||
|   plugins: [ | ||||
|     react(), | ||||
|     wasm(), | ||||
|     topLevelAwait(), | ||||
|     copyExtensionFiles() | ||||
|   ], | ||||
|   build: { | ||||
|     outDir: 'dist', | ||||
|     emptyOutDir: true, | ||||
|     // Simplify the build output for browser extension | ||||
|     rollupOptions: { | ||||
|       input: { | ||||
|         popup: resolve(__dirname, 'popup/index.html') | ||||
|       }, | ||||
|       output: { | ||||
|         // Use a simpler output format without hash values | ||||
|         entryFileNames: 'assets/[name].js', | ||||
|         chunkFileNames: 'assets/[name]-[hash].js', | ||||
|         assetFileNames: 'assets/[name].[ext]', | ||||
|         // Make sure output is compatible with browser extensions | ||||
|         format: 'iife', | ||||
|         // Don't generate separate code-split chunks | ||||
|         manualChunks: undefined | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   // Provide a simple dev server config | ||||
|   server: { | ||||
|     fs: { | ||||
|       allow: ['../'] | ||||
|     } | ||||
|   } | ||||
| }); | ||||