refactor: migrate extension to React and update build configuration

This commit is contained in:
Sameh Abouel-saad 2025-05-22 16:11:04 +03:00
parent 1f2d7e3fec
commit 1e52c572d2
15 changed files with 109 additions and 683 deletions

View File

@ -30,4 +30,3 @@ build-extension-all: build-wasm-app
cp wasm_app/pkg/wasm_app.js extension/public/wasm/wasm_app.js cp wasm_app/pkg/wasm_app.js extension/public/wasm/wasm_app.js
cp wasm_app/pkg/wasm_app_bg.wasm extension/public/wasm/wasm_app_bg.wasm cp wasm_app/pkg/wasm_app_bg.wasm extension/public/wasm/wasm_app_bg.wasm
cd extension && npm run build cd extension && npm run build

View File

@ -29,7 +29,7 @@ web-sys = { version = "0.3", features = ["console"] }
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.3", features = ["wasm_js"] } getrandom = { version = "0.3", features = ["wasm_js"] }
getrandom_02 = { package = "getrandom", version = "0.2.16", features = ["js"] } getrandom_02 = { package = "getrandom", version = "0.2.16", features = ["js"] }
wasm-bindgen = "0.2" wasm-bindgen = { version = "0.2.92", features = ["serde-serialize"] }
js-sys = "0.3" js-sys = "0.3"
# console_error_panic_hook = "0.1" # console_error_panic_hook = "0.1"
gloo-net = { version = "0.5", features = ["http"] } gloo-net = { version = "0.5", features = ["http"] }

File diff suppressed because one or more lines are too long

BIN
extension/dist/assets/wasm_app_bg.wasm vendored Normal file

Binary file not shown.

View File

@ -4,10 +4,12 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Modular Vault Extension</title> <title>Modular Vault Extension</title>
<link rel="stylesheet" href="popup.css">
<script type="module" crossorigin src="/assets/popup.js"></script>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
<script src="popup.js"></script>
</body> </body>
</html> </html>

View File

@ -1,306 +0,0 @@
// Simple non-module JavaScript for browser extension popup
document.addEventListener('DOMContentLoaded', async function() {
const root = document.getElementById('root');
root.innerHTML = `
<div class="container">
<h1>Modular Vault Extension</h1>
<div id="status" class="status">Loading WASM module...</div>
<div id="session-controls">
<div id="keyspace-form" class="form-section">
<h2>Session</h2>
<div class="form-group">
<label for="keyspace">Keyspace:</label>
<input type="text" id="keyspace" placeholder="Enter keyspace name">
</div>
<div class="form-group">
<label for="password">Password:</label>
<input type="password" id="password" placeholder="Enter password">
</div>
<div class="button-group">
<button id="unlock-btn">Unlock</button>
<button id="create-btn">Create New</button>
</div>
</div>
<div id="session-info" class="session-info hidden">
<h2>Active Session</h2>
<p>Current keyspace: <span id="current-keyspace"></span></p>
<button id="lock-btn">Lock Session</button>
<div id="keypair-section" class="form-section">
<h2>Keypairs</h2>
<button id="create-keypair-btn">Create New Keypair</button>
<div id="keypair-list" class="list"></div>
</div>
<div id="sign-section" class="form-section hidden">
<h2>Sign Message</h2>
<div class="form-group">
<label for="message">Message:</label>
<textarea id="message" placeholder="Enter message to sign"></textarea>
</div>
<button id="sign-btn">Sign</button>
<div class="form-group">
<label for="signature">Signature:</label>
<textarea id="signature" readonly></textarea>
<button id="copy-btn" class="small">Copy</button>
</div>
</div>
</div>
</div>
</div>
`;
// DOM elements
const statusEl = document.getElementById('status');
const keyspaceFormEl = document.getElementById('keyspace-form');
const sessionInfoEl = document.getElementById('session-info');
const currentKeyspaceEl = document.getElementById('current-keyspace');
const keyspaceInput = document.getElementById('keyspace');
const passwordInput = document.getElementById('password');
const unlockBtn = document.getElementById('unlock-btn');
const createBtn = document.getElementById('create-btn');
const lockBtn = document.getElementById('lock-btn');
const createKeypairBtn = document.getElementById('create-keypair-btn');
const keypairListEl = document.getElementById('keypair-list');
const signSectionEl = document.getElementById('sign-section');
const messageInput = document.getElementById('message');
const signBtn = document.getElementById('sign-btn');
const signatureOutput = document.getElementById('signature');
const copyBtn = document.getElementById('copy-btn');
// State
let wasmModule = null;
let currentKeyspace = null;
let keypairs = [];
let selectedKeypairId = null;
// Initialize
init();
async function init() {
try {
// Get session state from background
const sessionState = await getSessionState();
if (sessionState.currentKeyspace) {
// We have an active session
currentKeyspace = sessionState.currentKeyspace;
keypairs = sessionState.keypairs || [];
selectedKeypairId = sessionState.selectedKeypair;
updateUI();
}
statusEl.textContent = 'Ready';
} catch (error) {
statusEl.textContent = 'Error: ' + (error.message || 'Unknown error');
}
}
function updateUI() {
if (currentKeyspace) {
// Show session info
keyspaceFormEl.classList.add('hidden');
sessionInfoEl.classList.remove('hidden');
currentKeyspaceEl.textContent = currentKeyspace;
// Update keypair list
updateKeypairList();
// Show/hide sign section based on selected keypair
if (selectedKeypairId) {
signSectionEl.classList.remove('hidden');
} else {
signSectionEl.classList.add('hidden');
}
} else {
// Show keyspace form
keyspaceFormEl.classList.remove('hidden');
sessionInfoEl.classList.add('hidden');
}
}
function updateKeypairList() {
// Clear list
keypairListEl.innerHTML = '';
// Add each keypair
keypairs.forEach(keypair => {
const item = document.createElement('div');
item.className = 'list-item' + (selectedKeypairId === keypair.id ? ' selected' : '');
item.innerHTML = `
<span>${keypair.label || keypair.id}</span>
<button class="select-btn" data-id="${keypair.id}">Select</button>
`;
keypairListEl.appendChild(item);
// Add select handler
item.querySelector('.select-btn').addEventListener('click', async () => {
try {
statusEl.textContent = 'Selecting keypair...';
// Use background service to select keypair for now
await chrome.runtime.sendMessage({
action: 'update_session',
type: 'keypair_selected',
data: keypair.id
});
selectedKeypairId = keypair.id;
updateUI();
statusEl.textContent = 'Keypair selected: ' + keypair.id;
} catch (error) {
statusEl.textContent = 'Error selecting keypair: ' + (error.message || 'Unknown error');
}
});
});
}
// Get session state from background
async function getSessionState() {
return new Promise((resolve) => {
chrome.runtime.sendMessage({ action: 'get_session' }, (response) => {
resolve(response || { currentKeyspace: null, keypairs: [], selectedKeypair: null });
});
});
}
// Event handlers
unlockBtn.addEventListener('click', async () => {
const keyspace = keyspaceInput.value.trim();
const password = passwordInput.value;
if (!keyspace || !password) {
statusEl.textContent = 'Please enter keyspace and password';
return;
}
statusEl.textContent = 'Unlocking session...';
try {
// For now, use the background service worker mock
await chrome.runtime.sendMessage({
action: 'update_session',
type: 'keyspace',
data: keyspace
});
currentKeyspace = keyspace;
updateUI();
statusEl.textContent = 'Session unlocked!';
// Refresh state
const state = await getSessionState();
keypairs = state.keypairs || [];
selectedKeypairId = state.selectedKeypair;
updateUI();
} catch (error) {
statusEl.textContent = 'Error unlocking session: ' + (error.message || 'Unknown error');
}
});
createBtn.addEventListener('click', async () => {
const keyspace = keyspaceInput.value.trim();
const password = passwordInput.value;
if (!keyspace || !password) {
statusEl.textContent = 'Please enter keyspace and password';
return;
}
statusEl.textContent = 'Creating keyspace...';
try {
// For now, use the background service worker mock
await chrome.runtime.sendMessage({
action: 'update_session',
type: 'keyspace',
data: keyspace
});
currentKeyspace = keyspace;
updateUI();
statusEl.textContent = 'Keyspace created and unlocked!';
} catch (error) {
statusEl.textContent = 'Error creating keyspace: ' + (error.message || 'Unknown error');
}
});
lockBtn.addEventListener('click', async () => {
statusEl.textContent = 'Locking session...';
try {
await chrome.runtime.sendMessage({
action: 'update_session',
type: 'session_locked'
});
currentKeyspace = null;
keypairs = [];
selectedKeypairId = null;
updateUI();
statusEl.textContent = 'Session locked';
} catch (error) {
statusEl.textContent = 'Error locking session: ' + (error.message || 'Unknown error');
}
});
createKeypairBtn.addEventListener('click', async () => {
statusEl.textContent = 'Creating keypair...';
try {
// Generate a mock keypair ID
const keyId = 'key-' + Date.now().toString(16);
const newKeypair = {
id: keyId,
label: `Secp256k1-Key-${keypairs.length + 1}`
};
await chrome.runtime.sendMessage({
action: 'update_session',
type: 'keypair_added',
data: newKeypair
});
// Refresh state
const state = await getSessionState();
keypairs = state.keypairs || [];
updateUI();
statusEl.textContent = 'Keypair created: ' + keyId;
} catch (error) {
statusEl.textContent = 'Error creating keypair: ' + (error.message || 'Unknown error');
}
});
signBtn.addEventListener('click', async () => {
const message = messageInput.value.trim();
if (!message) {
statusEl.textContent = 'Please enter a message to sign';
return;
}
if (!selectedKeypairId) {
statusEl.textContent = 'Please select a keypair first';
return;
}
statusEl.textContent = 'Signing message...';
try {
// For now, generate a mock signature
const mockSignature = Array.from({length: 64}, () => Math.floor(Math.random() * 16).toString(16)).join('');
signatureOutput.value = mockSignature;
statusEl.textContent = 'Message signed!';
} catch (error) {
statusEl.textContent = 'Error signing message: ' + (error.message || 'Unknown error');
}
});
copyBtn.addEventListener('click', () => {
signatureOutput.select();
document.execCommand('copy');
statusEl.textContent = 'Signature copied to clipboard!';
});
});

Binary file not shown.

View File

@ -1,3 +1,5 @@
import init, * as wasmModuleImport from '@wasm/wasm_app.js';
/** /**
* Browser extension-friendly WebAssembly loader and helper functions * Browser extension-friendly WebAssembly loader and helper functions
* This handles loading the WebAssembly module without relying on ES modules * This handles loading the WebAssembly module without relying on ES modules
@ -21,54 +23,10 @@ export async function loadWasmModule() {
if (state.initialized || state.loading) { if (state.initialized || state.loading) {
return; return;
} }
state.loading = true; state.loading = true;
try { try {
// Get paths to WebAssembly files await init();
const wasmJsPath = chrome.runtime.getURL('wasm/wasm_app.js'); window.wasm_app = wasmModuleImport;
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 // Debug logging for available functions in the WebAssembly module
console.log('Available WebAssembly functions:'); console.log('Available WebAssembly functions:');
@ -105,10 +63,8 @@ export async function loadWasmModule() {
if (typeof wasmModule.init_rhai_env === 'function') { if (typeof wasmModule.init_rhai_env === 'function') {
wasmModule.init_rhai_env(); wasmModule.init_rhai_env();
} }
state.initialized = true; state.initialized = true;
console.log('WASM module loaded and initialized successfully'); console.log('WASM module loaded and initialized successfully');
} catch (error) { } catch (error) {
console.error('Failed to load WASM module:', error); console.error('Failed to load WASM module:', error);
state.error = error.message || 'Unknown error loading WebAssembly module'; state.error = error.message || 'Unknown error loading WebAssembly module';

View File

@ -8,6 +8,7 @@
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
<script src="popup.js"></script>
<script type="module" src="./main.jsx"></script>
</body> </body>
</html> </html>

8
extension/popup/main.jsx Normal file
View 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 />);

View File

@ -1,306 +0,0 @@
// Simple non-module JavaScript for browser extension popup
document.addEventListener('DOMContentLoaded', async function() {
const root = document.getElementById('root');
root.innerHTML = `
<div class="container">
<h1>Modular Vault Extension</h1>
<div id="status" class="status">Loading WASM module...</div>
<div id="session-controls">
<div id="keyspace-form" class="form-section">
<h2>Session</h2>
<div class="form-group">
<label for="keyspace">Keyspace:</label>
<input type="text" id="keyspace" placeholder="Enter keyspace name">
</div>
<div class="form-group">
<label for="password">Password:</label>
<input type="password" id="password" placeholder="Enter password">
</div>
<div class="button-group">
<button id="unlock-btn">Unlock</button>
<button id="create-btn">Create New</button>
</div>
</div>
<div id="session-info" class="session-info hidden">
<h2>Active Session</h2>
<p>Current keyspace: <span id="current-keyspace"></span></p>
<button id="lock-btn">Lock Session</button>
<div id="keypair-section" class="form-section">
<h2>Keypairs</h2>
<button id="create-keypair-btn">Create New Keypair</button>
<div id="keypair-list" class="list"></div>
</div>
<div id="sign-section" class="form-section hidden">
<h2>Sign Message</h2>
<div class="form-group">
<label for="message">Message:</label>
<textarea id="message" placeholder="Enter message to sign"></textarea>
</div>
<button id="sign-btn">Sign</button>
<div class="form-group">
<label for="signature">Signature:</label>
<textarea id="signature" readonly></textarea>
<button id="copy-btn" class="small">Copy</button>
</div>
</div>
</div>
</div>
</div>
`;
// DOM elements
const statusEl = document.getElementById('status');
const keyspaceFormEl = document.getElementById('keyspace-form');
const sessionInfoEl = document.getElementById('session-info');
const currentKeyspaceEl = document.getElementById('current-keyspace');
const keyspaceInput = document.getElementById('keyspace');
const passwordInput = document.getElementById('password');
const unlockBtn = document.getElementById('unlock-btn');
const createBtn = document.getElementById('create-btn');
const lockBtn = document.getElementById('lock-btn');
const createKeypairBtn = document.getElementById('create-keypair-btn');
const keypairListEl = document.getElementById('keypair-list');
const signSectionEl = document.getElementById('sign-section');
const messageInput = document.getElementById('message');
const signBtn = document.getElementById('sign-btn');
const signatureOutput = document.getElementById('signature');
const copyBtn = document.getElementById('copy-btn');
// State
let wasmModule = null;
let currentKeyspace = null;
let keypairs = [];
let selectedKeypairId = null;
// Initialize
init();
async function init() {
try {
// Get session state from background
const sessionState = await getSessionState();
if (sessionState.currentKeyspace) {
// We have an active session
currentKeyspace = sessionState.currentKeyspace;
keypairs = sessionState.keypairs || [];
selectedKeypairId = sessionState.selectedKeypair;
updateUI();
}
statusEl.textContent = 'Ready';
} catch (error) {
statusEl.textContent = 'Error: ' + (error.message || 'Unknown error');
}
}
function updateUI() {
if (currentKeyspace) {
// Show session info
keyspaceFormEl.classList.add('hidden');
sessionInfoEl.classList.remove('hidden');
currentKeyspaceEl.textContent = currentKeyspace;
// Update keypair list
updateKeypairList();
// Show/hide sign section based on selected keypair
if (selectedKeypairId) {
signSectionEl.classList.remove('hidden');
} else {
signSectionEl.classList.add('hidden');
}
} else {
// Show keyspace form
keyspaceFormEl.classList.remove('hidden');
sessionInfoEl.classList.add('hidden');
}
}
function updateKeypairList() {
// Clear list
keypairListEl.innerHTML = '';
// Add each keypair
keypairs.forEach(keypair => {
const item = document.createElement('div');
item.className = 'list-item' + (selectedKeypairId === keypair.id ? ' selected' : '');
item.innerHTML = `
<span>${keypair.label || keypair.id}</span>
<button class="select-btn" data-id="${keypair.id}">Select</button>
`;
keypairListEl.appendChild(item);
// Add select handler
item.querySelector('.select-btn').addEventListener('click', async () => {
try {
statusEl.textContent = 'Selecting keypair...';
// Use background service to select keypair for now
await chrome.runtime.sendMessage({
action: 'update_session',
type: 'keypair_selected',
data: keypair.id
});
selectedKeypairId = keypair.id;
updateUI();
statusEl.textContent = 'Keypair selected: ' + keypair.id;
} catch (error) {
statusEl.textContent = 'Error selecting keypair: ' + (error.message || 'Unknown error');
}
});
});
}
// Get session state from background
async function getSessionState() {
return new Promise((resolve) => {
chrome.runtime.sendMessage({ action: 'get_session' }, (response) => {
resolve(response || { currentKeyspace: null, keypairs: [], selectedKeypair: null });
});
});
}
// Event handlers
unlockBtn.addEventListener('click', async () => {
const keyspace = keyspaceInput.value.trim();
const password = passwordInput.value;
if (!keyspace || !password) {
statusEl.textContent = 'Please enter keyspace and password';
return;
}
statusEl.textContent = 'Unlocking session...';
try {
// For now, use the background service worker mock
await chrome.runtime.sendMessage({
action: 'update_session',
type: 'keyspace',
data: keyspace
});
currentKeyspace = keyspace;
updateUI();
statusEl.textContent = 'Session unlocked!';
// Refresh state
const state = await getSessionState();
keypairs = state.keypairs || [];
selectedKeypairId = state.selectedKeypair;
updateUI();
} catch (error) {
statusEl.textContent = 'Error unlocking session: ' + (error.message || 'Unknown error');
}
});
createBtn.addEventListener('click', async () => {
const keyspace = keyspaceInput.value.trim();
const password = passwordInput.value;
if (!keyspace || !password) {
statusEl.textContent = 'Please enter keyspace and password';
return;
}
statusEl.textContent = 'Creating keyspace...';
try {
// For now, use the background service worker mock
await chrome.runtime.sendMessage({
action: 'update_session',
type: 'keyspace',
data: keyspace
});
currentKeyspace = keyspace;
updateUI();
statusEl.textContent = 'Keyspace created and unlocked!';
} catch (error) {
statusEl.textContent = 'Error creating keyspace: ' + (error.message || 'Unknown error');
}
});
lockBtn.addEventListener('click', async () => {
statusEl.textContent = 'Locking session...';
try {
await chrome.runtime.sendMessage({
action: 'update_session',
type: 'session_locked'
});
currentKeyspace = null;
keypairs = [];
selectedKeypairId = null;
updateUI();
statusEl.textContent = 'Session locked';
} catch (error) {
statusEl.textContent = 'Error locking session: ' + (error.message || 'Unknown error');
}
});
createKeypairBtn.addEventListener('click', async () => {
statusEl.textContent = 'Creating keypair...';
try {
// Generate a mock keypair ID
const keyId = 'key-' + Date.now().toString(16);
const newKeypair = {
id: keyId,
label: `Secp256k1-Key-${keypairs.length + 1}`
};
await chrome.runtime.sendMessage({
action: 'update_session',
type: 'keypair_added',
data: newKeypair
});
// Refresh state
const state = await getSessionState();
keypairs = state.keypairs || [];
updateUI();
statusEl.textContent = 'Keypair created: ' + keyId;
} catch (error) {
statusEl.textContent = 'Error creating keypair: ' + (error.message || 'Unknown error');
}
});
signBtn.addEventListener('click', async () => {
const message = messageInput.value.trim();
if (!message) {
statusEl.textContent = 'Please enter a message to sign';
return;
}
if (!selectedKeypairId) {
statusEl.textContent = 'Please select a keypair first';
return;
}
statusEl.textContent = 'Signing message...';
try {
// For now, generate a mock signature
const mockSignature = Array.from({length: 64}, () => Math.floor(Math.random() * 16).toString(16)).join('');
signatureOutput.value = mockSignature;
statusEl.textContent = 'Message signed!';
} catch (error) {
statusEl.textContent = 'Error signing message: ' + (error.message || 'Unknown error');
}
});
copyBtn.addEventListener('click', () => {
signatureOutput.select();
document.execCommand('copy');
statusEl.textContent = 'Signature copied to clipboard!';
});
});

View File

@ -22,8 +22,8 @@ const copyExtensionFiles = () => {
const wasmJsDest = resolve(wasmDistDir, 'wasm_app.js'); const wasmJsDest = resolve(wasmDistDir, 'wasm_app.js');
fs.copyFileSync(wasmJsSource, wasmJsDest); fs.copyFileSync(wasmJsSource, wasmJsDest);
// Copy the wasm binary file // Copy the wasm binary file from the pkg output
const wasmBinSource = resolve(__dirname, 'wasm/wasm_app_bg.wasm'); const wasmBinSource = resolve(__dirname, '../wasm_app/pkg/wasm_app_bg.wasm');
const wasmBinDest = resolve(wasmDistDir, 'wasm_app_bg.wasm'); const wasmBinDest = resolve(wasmDistDir, 'wasm_app_bg.wasm');
fs.copyFileSync(wasmBinSource, wasmBinDest); fs.copyFileSync(wasmBinSource, wasmBinDest);
@ -43,15 +43,7 @@ const copyExtensionFiles = () => {
fs.mkdirSync(popupDistDir, { recursive: true }); 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 // Copy CSS file
const cssSource = resolve(__dirname, 'popup/popup.css'); const cssSource = resolve(__dirname, 'popup/popup.css');
@ -79,12 +71,22 @@ const copyExtensionFiles = () => {
} }
}); });
console.log('Extension files copied to dist directory'); console.log('Extension files copied to dist directory');
} }
}; };
}; };
import path from 'path';
export default defineConfig({ export default defineConfig({
resolve: {
alias: {
'@wasm': path.resolve(__dirname, '../wasm_app/pkg')
}
},
plugins: [ plugins: [
react(), react(),
wasm(), wasm(),

View File

@ -11,7 +11,7 @@ tokio = { version = "1.37", features = ["rt", "macros"] }
async-trait = "0.1" async-trait = "0.1"
getrandom = { version = "0.3", features = ["wasm_js"] } getrandom = { version = "0.3", features = ["wasm_js"] }
wasm-bindgen = { version = "0.2", features = ["serde-serialize"] } wasm-bindgen = { version = "0.2.92", features = ["serde-serialize"] }
wasm-bindgen-futures = "0.4" wasm-bindgen-futures = "0.4"
thiserror = "1" thiserror = "1"
@ -27,6 +27,7 @@ getrandom = { version = "0.3", features = ["wasm_js"] }
getrandom_02 = { package = "getrandom", version = "0.2.16", features = ["js"] } getrandom_02 = { package = "getrandom", version = "0.2.16", features = ["js"] }
idb = { version = "0.6" } idb = { version = "0.6" }
wasm-bindgen-test = "0.3" wasm-bindgen-test = "0.3"
wasm-bindgen = { version = "0.2.92", features = ["serde-serialize"] }
[features] [features]
default = [] default = []

View File

@ -34,6 +34,7 @@ rhai = "1.21.0"
[dev-dependencies] [dev-dependencies]
wasm-bindgen = { version = "0.2.92", features = ["serde-serialize"] }
wasm-bindgen-test = "0.3" wasm-bindgen-test = "0.3"
# console_error_panic_hook = "0.1" # console_error_panic_hook = "0.1"