feat: Implement Sign Request Manager component for handling sign requests in the popup (WIP)

- Added SignRequestManager.js to manage sign requests, including UI states for keyspace lock, mismatch, and approval.
- Implemented methods for loading state, rendering UI, and handling user interactions (approve/reject requests).
- Integrated background message listeners for keyspace unlock events and request updates.
This commit is contained in:
Sameh Abouel-saad 2025-06-04 16:55:15 +03:00
parent a0622629ae
commit 580fd72dce
13 changed files with 2067 additions and 41 deletions

View File

@ -5,6 +5,7 @@ let keepAliveInterval = null;
let sessionTimeoutDuration = 15; // Default 15 seconds let sessionTimeoutDuration = 15; // Default 15 seconds
let sessionTimeoutId = null; // Background timer let sessionTimeoutId = null; // Background timer
let popupPort = null; // Track popup connection let popupPort = null; // Track popup connection
let sigSocketService = null; // SigSocket service instance
// Utility function to convert Uint8Array to hex // Utility function to convert Uint8Array to hex
function toHex(uint8Array) { function toHex(uint8Array) {
@ -138,6 +139,9 @@ async function restoreSession() {
// Import WASM module functions // Import WASM module functions
import init, * as wasmFunctions from './wasm/wasm_app.js'; import init, * as wasmFunctions from './wasm/wasm_app.js';
// Import SigSocket service
import SigSocketService from './background/sigsocket.js';
// Initialize WASM module // Initialize WASM module
async function initVault() { async function initVault() {
try { try {
@ -151,6 +155,9 @@ async function initVault() {
vault = wasmFunctions; vault = wasmFunctions;
isInitialized = true; isInitialized = true;
// Initialize SigSocket service
await initSigSocketService();
// Try to restore previous session // Try to restore previous session
await restoreSession(); await restoreSession();
@ -161,6 +168,17 @@ async function initVault() {
} }
} }
// Initialize SigSocket service
async function initSigSocketService() {
try {
sigSocketService = new SigSocketService();
await sigSocketService.initialize(vault);
console.log('SigSocket service initialized');
} catch (error) {
console.error('Failed to initialize SigSocket service:', error);
}
}
// Consolidated message handlers // Consolidated message handlers
const messageHandlers = { const messageHandlers = {
@ -172,6 +190,16 @@ const messageHandlers = {
initSession: async (request) => { initSession: async (request) => {
await vault.init_session(request.keyspace, request.password); await vault.init_session(request.keyspace, request.password);
await sessionManager.save(request.keyspace); await sessionManager.save(request.keyspace);
// Auto-connect to SigSocket server when session is initialized (only if not already connected)
if (sigSocketService && !sigSocketService.isConnected) {
console.log(`Attempting to connect to SigSocket for keyspace: ${request.keyspace}`);
const connected = await sigSocketService.connectToServer(request.keyspace);
console.log(`SigSocket connection result: ${connected}`);
} else if (sigSocketService && sigSocketService.isConnected) {
console.log('SigSocket already connected, skipping connection attempt');
}
return { success: true }; return { success: true };
}, },
@ -261,6 +289,52 @@ const messageHandlers = {
await chrome.storage.local.set({ sessionTimeout: request.timeout }); await chrome.storage.local.set({ sessionTimeout: request.timeout });
resetSessionTimeout(); // Restart with new duration resetSessionTimeout(); // Restart with new duration
return { success: true }; return { success: true };
},
// SigSocket message handlers
connectSigSocket: async (request) => {
if (!sigSocketService) {
return { success: false, error: 'SigSocket service not available' };
}
const connected = await sigSocketService.connectToServer(request.workspaceId);
return { success: connected };
},
getPendingRequests: () => {
if (!sigSocketService) {
return { success: false, error: 'SigSocket service not available' };
}
return { success: true, requests: sigSocketService.getPendingRequests() };
},
keypaceUnlocked: async () => {
if (sigSocketService) {
await sigSocketService.onKeypaceUnlocked();
}
return { success: true };
},
approveSignRequest: async (request) => {
if (!sigSocketService) {
return { success: false, error: 'SigSocket service not available' };
}
const approved = await sigSocketService.approveSignRequest(request.requestId);
return { success: approved };
},
rejectSignRequest: async (request) => {
if (!sigSocketService) {
return { success: false, error: 'SigSocket service not available' };
}
const rejected = await sigSocketService.rejectSignRequest(request.requestId, request.reason);
return { success: rejected };
},
getSigSocketStatus: () => {
if (!sigSocketService) {
return { success: false, error: 'SigSocket service not available' };
}
return { success: true, status: sigSocketService.getStatus() };
} }
}; };
@ -302,6 +376,11 @@ chrome.runtime.onConnect.addListener((port) => {
// Track popup connection // Track popup connection
popupPort = port; popupPort = port;
// Set popup port in SigSocket service
if (sigSocketService) {
sigSocketService.setPopupPort(port);
}
// If we have an active session, ensure keep-alive is running // If we have an active session, ensure keep-alive is running
if (currentSession) { if (currentSession) {
startKeepAlive(); startKeepAlive();
@ -310,6 +389,9 @@ chrome.runtime.onConnect.addListener((port) => {
port.onDisconnect.addListener(() => { port.onDisconnect.addListener(() => {
// Popup closed, clear reference and stop keep-alive // Popup closed, clear reference and stop keep-alive
popupPort = null; popupPort = null;
if (sigSocketService) {
sigSocketService.setPopupPort(null);
}
stopKeepAlive(); stopKeepAlive();
}); });
} }

View File

@ -0,0 +1,476 @@
/**
* SigSocket Service for Browser Extension
*
* Handles SigSocket client functionality including:
* - Auto-connecting to SigSocket server when workspace is created
* - Managing pending sign requests
* - Handling user approval/rejection flow
* - Validating keyspace matches before showing approval UI
*/
class SigSocketService {
constructor() {
this.connection = null;
this.pendingRequests = new Map(); // requestId -> SignRequestData
this.connectedPublicKey = null;
this.isConnected = false;
this.defaultServerUrl = "ws://localhost:8080/ws";
// Initialize WASM module reference
this.wasmModule = null;
// Reference to popup port for communication
this.popupPort = null;
}
/**
* Initialize the service with WASM module
* @param {Object} wasmModule - The loaded WASM module
*/
async initialize(wasmModule) {
this.wasmModule = wasmModule;
// Load server URL from storage
try {
const result = await chrome.storage.local.get(['sigSocketUrl']);
if (result.sigSocketUrl) {
this.defaultServerUrl = result.sigSocketUrl;
}
} catch (error) {
console.warn('Failed to load SigSocket URL from storage:', error);
}
// Set up global callbacks for WASM
globalThis.onSignRequestReceived = this.handleIncomingRequest.bind(this);
globalThis.onConnectionStateChanged = this.handleConnectionStateChange.bind(this);
}
/**
* Connect to SigSocket server for a workspace
* @param {string} workspaceId - The workspace/keyspace identifier
* @returns {Promise<boolean>} - True if connected successfully
*/
async connectToServer(workspaceId) {
try {
if (!this.wasmModule) {
throw new Error('WASM module not initialized');
}
// Check if already connected to this workspace
if (this.isConnected && this.connection) {
console.log(`Already connected to SigSocket server for workspace: ${workspaceId}`);
return true;
}
// Disconnect any existing connection first
if (this.connection) {
this.disconnect();
}
// Get the workspace default public key
const publicKeyHex = await this.wasmModule.get_workspace_default_public_key(workspaceId);
if (!publicKeyHex) {
throw new Error('No public key found for workspace');
}
console.log(`Connecting to SigSocket server for workspace: ${workspaceId} with key: ${publicKeyHex.substring(0, 16)}...`);
// Create new SigSocket connection
console.log('Creating new SigSocketConnection instance');
this.connection = new this.wasmModule.SigSocketConnection();
console.log('SigSocketConnection instance created');
// Connect to server
await this.connection.connect(this.defaultServerUrl, publicKeyHex);
this.connectedPublicKey = publicKeyHex;
// Clear pending requests if switching to a different workspace
if (this.currentWorkspace && this.currentWorkspace !== workspaceId) {
console.log(`Switching workspace from ${this.currentWorkspace} to ${workspaceId}, clearing pending requests`);
this.pendingRequests.clear();
this.updateBadge();
}
this.currentWorkspace = workspaceId;
this.isConnected = true;
console.log(`Successfully connected to SigSocket server for workspace: ${workspaceId}`);
return true;
} catch (error) {
console.error('Failed to connect to SigSocket server:', error);
this.isConnected = false;
this.connectedPublicKey = null;
this.currentWorkspace = null;
if (this.connection) {
this.connection.disconnect();
this.connection = null;
}
return false;
}
}
/**
* Handle incoming sign request from server
* @param {string} requestId - Unique request identifier
* @param {string} messageBase64 - Message to be signed (base64-encoded)
*/
handleIncomingRequest(requestId, messageBase64) {
console.log(`Received sign request: ${requestId}`);
// Security check: Only accept requests if we have an active connection
if (!this.isConnected || !this.connectedPublicKey || !this.currentWorkspace) {
console.warn(`Rejecting sign request ${requestId}: No active workspace connection`);
return;
}
// Store the request with workspace info
const requestData = {
id: requestId,
message: messageBase64,
timestamp: Date.now(),
status: 'pending',
workspace: this.currentWorkspace,
connectedPublicKey: this.connectedPublicKey
};
this.pendingRequests.set(requestId, requestData);
console.log(`Stored sign request for workspace: ${this.currentWorkspace}`);
// Show notification to user
this.showSignRequestNotification();
// Update extension badge
this.updateBadge();
// Notify popup about new request if it's open and keyspace is unlocked
this.notifyPopupOfNewRequest();
}
/**
* Handle connection state changes
* @param {boolean} connected - True if connected, false if disconnected
*/
handleConnectionStateChange(connected) {
this.isConnected = connected;
console.log(`SigSocket connection state changed: ${connected ? 'connected' : 'disconnected'}`);
if (!connected) {
this.connectedPublicKey = null;
this.currentWorkspace = null;
// Optionally attempt reconnection here
}
}
/**
* Called when keyspace is unlocked - validate and show/hide approval UI
*/
async onKeypaceUnlocked() {
try {
if (!this.wasmModule) {
return;
}
// Only check keyspace match if we have a connection
if (!this.isConnected || !this.connectedPublicKey) {
console.log('No SigSocket connection to validate against');
return;
}
// Get the currently unlocked workspace name
const unlockedWorkspaceName = this.wasmModule.get_current_keyspace_name();
// Get workspace default public key for the UNLOCKED workspace (not connected workspace)
const unlockedWorkspacePublicKey = await this.wasmModule.get_workspace_default_public_key(unlockedWorkspaceName);
// Check if the unlocked workspace matches the connected workspace
const workspaceMatches = unlockedWorkspaceName === this.currentWorkspace;
const publicKeyMatches = unlockedWorkspacePublicKey === this.connectedPublicKey;
const keypaceMatches = workspaceMatches && publicKeyMatches;
console.log(`Keyspace unlock validation:`);
console.log(` Connected workspace: ${this.currentWorkspace}`);
console.log(` Unlocked workspace: ${unlockedWorkspaceName}`);
console.log(` Connected public key: ${this.connectedPublicKey}`);
console.log(` Unlocked public key: ${unlockedWorkspacePublicKey}`);
console.log(` Workspace matches: ${workspaceMatches}`);
console.log(` Public key matches: ${publicKeyMatches}`);
console.log(` Overall match: ${keypaceMatches}`);
// Always get current pending requests (filtered by connected workspace)
const currentPendingRequests = this.getPendingRequests();
// Notify popup about keyspace state
console.log(`Sending KEYSPACE_UNLOCKED message to popup: keypaceMatches=${keypaceMatches}, pendingRequests=${currentPendingRequests.length}`);
if (this.popupPort) {
this.popupPort.postMessage({
type: 'KEYSPACE_UNLOCKED',
keypaceMatches,
pendingRequests: currentPendingRequests
});
console.log('KEYSPACE_UNLOCKED message sent to popup');
} else {
console.log('No popup port available to send KEYSPACE_UNLOCKED message');
}
} catch (error) {
if (error.message && error.message.includes('Workspace not found')) {
console.log(`Keyspace unlock: Different workspace unlocked (connected to: ${this.currentWorkspace})`);
// Send message with no match and empty requests
if (this.popupPort) {
this.popupPort.postMessage({
type: 'KEYSPACE_UNLOCKED',
keypaceMatches: false,
pendingRequests: []
});
}
} else {
console.error('Error handling keyspace unlock:', error);
}
}
}
/**
* Approve a sign request
* @param {string} requestId - Request to approve
* @returns {Promise<boolean>} - True if approved successfully
*/
async approveSignRequest(requestId) {
try {
const request = this.pendingRequests.get(requestId);
if (!request) {
throw new Error('Request not found');
}
// Validate request is for current workspace
if (request.workspace !== this.currentWorkspace) {
throw new Error(`Request is for workspace '${request.workspace}', but current workspace is '${this.currentWorkspace}'`);
}
if (request.connectedPublicKey !== this.connectedPublicKey) {
throw new Error('Request public key does not match current connection');
}
// Validate keyspace is still unlocked and matches
if (!this.wasmModule.is_unlocked()) {
throw new Error('Keyspace is locked');
}
const currentPublicKey = await this.wasmModule.get_workspace_default_public_key(this.currentWorkspace);
if (currentPublicKey !== this.connectedPublicKey) {
throw new Error('Keyspace mismatch');
}
// Decode message from base64
const messageBytes = atob(request.message).split('').map(c => c.charCodeAt(0));
// Sign the message with default keypair (doesn't require selected keypair)
const signatureHex = await this.wasmModule.sign_with_default_keypair(new Uint8Array(messageBytes));
// Send response to server
await this.connection.send_response(requestId, request.message, signatureHex);
// Update request status
request.status = 'approved';
request.signature = signatureHex;
// Remove from pending requests
this.pendingRequests.delete(requestId);
// Update badge
this.updateBadge();
console.log(`Approved sign request: ${requestId}`);
return true;
} catch (error) {
console.error('Failed to approve sign request:', error);
return false;
}
}
/**
* Reject a sign request
* @param {string} requestId - Request to reject
* @param {string} reason - Reason for rejection (optional)
* @returns {Promise<boolean>} - True if rejected successfully
*/
async rejectSignRequest(requestId, reason = 'User rejected') {
try {
const request = this.pendingRequests.get(requestId);
if (!request) {
throw new Error('Request not found');
}
// Send rejection to server
await this.connection.send_rejection(requestId, reason);
// Update request status
request.status = 'rejected';
request.reason = reason;
// Remove from pending requests
this.pendingRequests.delete(requestId);
// Update badge
this.updateBadge();
console.log(`Rejected sign request: ${requestId}, reason: ${reason}`);
return true;
} catch (error) {
console.error('Failed to reject sign request:', error);
return false;
}
}
/**
* Get all pending requests for the current workspace
* @returns {Array} - Array of pending request data for current workspace
*/
getPendingRequests() {
const allRequests = Array.from(this.pendingRequests.values());
// Filter requests to only include those for the current workspace
const filteredRequests = allRequests.filter(request => {
const isCurrentWorkspace = request.workspace === this.currentWorkspace;
const isCurrentPublicKey = request.connectedPublicKey === this.connectedPublicKey;
if (!isCurrentWorkspace || !isCurrentPublicKey) {
console.log(`Filtering out request ${request.id}: workspace=${request.workspace} (current=${this.currentWorkspace}), publicKey match=${isCurrentPublicKey}`);
}
return isCurrentWorkspace && isCurrentPublicKey;
});
console.log(`getPendingRequests: ${allRequests.length} total, ${filteredRequests.length} for current workspace`);
return filteredRequests;
}
/**
* Show notification for new sign request
*/
showSignRequestNotification() {
// Create notification
chrome.notifications.create({
type: 'basic',
iconUrl: 'icons/icon48.png',
title: 'Sign Request',
message: 'New signature request received. Click to review.'
});
}
/**
* Notify popup about new request if popup is open and keyspace is unlocked
*/
async notifyPopupOfNewRequest() {
// Only notify if popup is connected
if (!this.popupPort) {
console.log('No popup port available, skipping new request notification');
return;
}
// Check if we have WASM module and can validate keyspace
if (!this.wasmModule) {
console.log('WASM module not available, skipping new request notification');
return;
}
try {
// Check if keyspace is unlocked
if (!this.wasmModule.is_unlocked()) {
console.log('Keyspace is locked, skipping new request notification');
return;
}
// Get the currently unlocked workspace name
const unlockedWorkspaceName = this.wasmModule.get_current_keyspace_name();
// Get workspace default public key for the UNLOCKED workspace
const unlockedWorkspacePublicKey = await this.wasmModule.get_workspace_default_public_key(unlockedWorkspaceName);
// Check if the unlocked workspace matches the connected workspace
const workspaceMatches = unlockedWorkspaceName === this.currentWorkspace;
const publicKeyMatches = unlockedWorkspacePublicKey === this.connectedPublicKey;
const keypaceMatches = workspaceMatches && publicKeyMatches;
console.log(`New request notification check: keypaceMatches=${keypaceMatches}, workspace=${unlockedWorkspaceName}, connected=${this.currentWorkspace}`);
// Get current pending requests (filtered by connected workspace)
const currentPendingRequests = this.getPendingRequests();
// SECURITY: Only send requests if workspace matches, otherwise send empty array
const requestsToSend = keypaceMatches ? currentPendingRequests : [];
// Send update to popup
this.popupPort.postMessage({
type: 'NEW_SIGN_REQUEST',
keypaceMatches,
pendingRequests: requestsToSend
});
console.log(`Sent NEW_SIGN_REQUEST message to popup: keypaceMatches=${keypaceMatches}, ${requestsToSend.length} requests (${currentPendingRequests.length} total for connected workspace)`);
} catch (error) {
console.log('Error in notifyPopupOfNewRequest:', error);
}
}
/**
* Update extension badge with pending request count for current workspace
*/
updateBadge() {
// Only count requests for the current workspace
const currentWorkspaceRequests = this.getPendingRequests();
const count = currentWorkspaceRequests.length;
const badgeText = count > 0 ? count.toString() : '';
console.log(`Updating badge: ${this.pendingRequests.size} total requests, ${count} for current workspace, badge text: "${badgeText}"`);
chrome.action.setBadgeText({ text: badgeText });
chrome.action.setBadgeBackgroundColor({ color: '#ff6b6b' });
}
/**
* Disconnect from SigSocket server
*/
disconnect() {
if (this.connection) {
this.connection.disconnect();
this.connection = null;
}
this.isConnected = false;
this.connectedPublicKey = null;
this.currentWorkspace = null;
this.pendingRequests.clear();
this.updateBadge();
}
/**
* Get connection status
* @returns {Object} - Connection status information
*/
getStatus() {
return {
isConnected: this.isConnected,
connectedPublicKey: this.connectedPublicKey,
pendingRequestCount: this.getPendingRequests().length,
serverUrl: this.defaultServerUrl
};
}
/**
* Set the popup port for communication
* @param {chrome.runtime.Port} port - The popup port
*/
setPopupPort(port) {
this.popupPort = port;
}
}
// Export for use in background script
export default SigSocketService;

View File

@ -0,0 +1,75 @@
# Mock SigSocket Server Demo
This directory contains a mock SigSocket server for testing the browser extension functionality.
## Setup
1. Install dependencies:
```bash
npm install
```
2. Start the mock server:
```bash
npm start
```
The server will listen on `ws://localhost:8080/ws`
## Usage
### Interactive Commands
Once the server is running, you can use these commands:
- `test` - Send a test sign request to all connected clients
- `status` - Show server status and connected clients
- `quit` - Shutdown the server
### Testing Flow
1. Start the mock server
2. Load the browser extension in Chrome
3. Create a keyspace and keypair in the extension
4. The extension should automatically connect to the server
5. The server will send a test sign request after 3 seconds
6. Use the extension popup to approve or reject the request
7. The server will log the response and send another request after 10 seconds
### Expected Output
When a client connects:
```
New WebSocket connection from: ::1
Received message: 04a8b2c3d4e5f6...
Client registered: client_1234567890_abc123 with public key: 04a8b2c3d4e5f6...
📝 Sending sign request to client_1234567890_abc123: req_1_1234567890
Message: "Test message 1 - 2024-01-01T12:00:00.000Z"
```
When a sign response is received:
```
Received sign response from client_1234567890_abc123: {
id: 'req_1_1234567890',
message: 'VGVzdCBtZXNzYWdlIDEgLSAyMDI0LTAxLTAxVDEyOjAwOjAwLjAwMFo=',
signature: '3045022100...'
}
✅ Sign request req_1_1234567890 completed successfully
Signature: 3045022100...
```
## Protocol
The mock server implements a simplified version of the SigSocket protocol:
1. **Client Introduction**: Client sends hex-encoded public key
2. **Welcome Message**: Server responds with welcome JSON
3. **Sign Requests**: Server sends JSON with `id` and `message` (base64)
4. **Sign Responses**: Client sends JSON with `id`, `message`, and `signature`
## Troubleshooting
- **Connection refused**: Make sure the server is running on port 8080
- **No sign requests**: Check that the extension is properly connected
- **Extension errors**: Check the browser console for JavaScript errors
- **WASM errors**: Ensure the WASM files are properly built and loaded

View File

@ -0,0 +1,232 @@
#!/usr/bin/env node
/**
* Mock SigSocket Server for Testing Browser Extension
*
* This is a simple WebSocket server that simulates the SigSocket protocol
* for testing the browser extension functionality.
*
* Usage:
* node mock_sigsocket_server.js
*
* The server will listen on ws://localhost:8080/ws
*/
const WebSocket = require('ws');
const http = require('http');
class MockSigSocketServer {
constructor(port = 8080) {
this.port = port;
this.clients = new Map(); // clientId -> { ws, publicKey }
this.requestCounter = 0;
this.setupServer();
}
setupServer() {
// Create HTTP server
this.httpServer = http.createServer();
// Create WebSocket server
this.wss = new WebSocket.Server({
server: this.httpServer,
path: '/ws'
});
this.wss.on('connection', (ws, req) => {
console.log('New WebSocket connection from:', req.socket.remoteAddress);
this.handleConnection(ws);
});
this.httpServer.listen(this.port, () => {
console.log(`Mock SigSocket Server listening on ws://localhost:${this.port}/ws`);
console.log('Waiting for browser extension connections...');
});
}
handleConnection(ws) {
let clientId = null;
let publicKey = null;
ws.on('message', (data) => {
try {
const message = data.toString();
console.log('Received message:', message);
// Check if this is a client introduction (hex-encoded public key)
if (!clientId && this.isHexString(message)) {
publicKey = message;
clientId = this.generateClientId();
this.clients.set(clientId, { ws, publicKey });
console.log(`Client registered: ${clientId} with public key: ${publicKey.substring(0, 16)}...`);
// Send welcome message
ws.send(JSON.stringify({
type: 'welcome',
clientId: clientId,
message: 'Connected to Mock SigSocket Server'
}));
// Schedule a test sign request after 3 seconds
setTimeout(() => {
this.sendTestSignRequest(clientId);
}, 3000);
return;
}
// Try to parse as JSON (sign response)
try {
const jsonMessage = JSON.parse(message);
this.handleSignResponse(clientId, jsonMessage);
} catch (e) {
console.log('Received non-JSON message:', message);
}
} catch (error) {
console.error('Error handling message:', error);
}
});
ws.on('close', () => {
if (clientId) {
this.clients.delete(clientId);
console.log(`Client disconnected: ${clientId}`);
}
});
ws.on('error', (error) => {
console.error('WebSocket error:', error);
});
}
handleSignResponse(clientId, response) {
console.log(`Received sign response from ${clientId}:`, response);
if (response.id && response.signature) {
console.log(`✅ Sign request ${response.id} completed successfully`);
console.log(` Signature: ${response.signature.substring(0, 32)}...`);
// Send another test request after 10 seconds
setTimeout(() => {
this.sendTestSignRequest(clientId);
}, 10000);
} else {
console.log('❌ Invalid sign response format');
}
}
sendTestSignRequest(clientId) {
const client = this.clients.get(clientId);
if (!client) {
console.log(`Client ${clientId} not found`);
return;
}
this.requestCounter++;
const requestId = `req_${this.requestCounter}_${Date.now()}`;
const testMessage = `Test message ${this.requestCounter} - ${new Date().toISOString()}`;
const messageBase64 = Buffer.from(testMessage).toString('base64');
const signRequest = {
id: requestId,
message: messageBase64
};
console.log(`📝 Sending sign request to ${clientId}:`, requestId);
console.log(` Message: "${testMessage}"`);
try {
client.ws.send(JSON.stringify(signRequest));
} catch (error) {
console.error(`Failed to send sign request to ${clientId}:`, error);
}
}
isHexString(str) {
return /^[0-9a-fA-F]+$/.test(str) && str.length >= 32; // At least 16 bytes
}
generateClientId() {
return `client_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;
}
// Send a test request to all connected clients
broadcastTestRequest() {
console.log('\n📢 Broadcasting test sign request to all clients...');
for (const [clientId] of this.clients) {
this.sendTestSignRequest(clientId);
}
}
// Get server status
getStatus() {
return {
port: this.port,
connectedClients: this.clients.size,
clients: Array.from(this.clients.keys())
};
}
}
// Create and start the server
const server = new MockSigSocketServer();
// Handle graceful shutdown
process.on('SIGINT', () => {
console.log('\n🛑 Shutting down Mock SigSocket Server...');
server.httpServer.close(() => {
console.log('Server closed');
process.exit(0);
});
});
// Add some interactive commands
process.stdin.setEncoding('utf8');
console.log('\n📋 Available commands:');
console.log(' "test" - Send test sign request to all clients');
console.log(' "status" - Show server status');
console.log(' "quit" - Shutdown server');
console.log(' Type a command and press Enter\n');
process.stdin.on('readable', () => {
const chunk = process.stdin.read();
if (chunk !== null) {
const command = chunk.trim().toLowerCase();
switch (command) {
case 'test':
server.broadcastTestRequest();
break;
case 'status':
const status = server.getStatus();
console.log('\n📊 Server Status:');
console.log(` Port: ${status.port}`);
console.log(` Connected clients: ${status.connectedClients}`);
if (status.clients.length > 0) {
console.log(` Client IDs: ${status.clients.join(', ')}`);
}
console.log('');
break;
case 'quit':
case 'exit':
process.emit('SIGINT');
break;
case '':
break;
default:
console.log(`Unknown command: ${command}`);
break;
}
}
});
// Export for testing
module.exports = MockSigSocketServer;

View File

@ -0,0 +1,21 @@
{
"name": "mock-sigsocket-server",
"version": "1.0.0",
"description": "Mock SigSocket server for testing browser extension",
"main": "mock_sigsocket_server.js",
"scripts": {
"start": "node mock_sigsocket_server.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"dependencies": {
"ws": "^8.14.0"
},
"keywords": [
"websocket",
"sigsocket",
"testing",
"mock"
],
"author": "",
"license": "MIT"
}

View File

@ -6,7 +6,8 @@
"permissions": [ "permissions": [
"storage", "storage",
"activeTab" "activeTab",
"notifications"
], ],
"icons": { "icons": {

View File

@ -27,6 +27,10 @@
<span>seconds</span> <span>seconds</span>
</div> </div>
</div> </div>
<div class="settings-item">
<label for="sigSocketUrlInput">SigSocket Server</label>
<input type="text" id="sigSocketUrlInput" placeholder="ws://localhost:8080/ws" value="ws://localhost:8080/ws">
</div>
</div> </div>
</div> </div>
<button id="themeToggle" class="btn-icon-only" title="Switch to dark mode"> <button id="themeToggle" class="btn-icon-only" title="Switch to dark mode">
@ -189,6 +193,11 @@
</div> </div>
</div> </div>
</div> </div>
<!-- Sign Request Manager -->
<div id="signRequestContainer">
<!-- Sign request manager will be rendered here -->
</div>
</section> </section>
@ -198,6 +207,7 @@
<!-- Enhanced JavaScript modules --> <!-- Enhanced JavaScript modules -->
<script src="js/errorHandler.js"></script> <script src="js/errorHandler.js"></script>
<script src="popup/components/SignRequestManager.js"></script>
<script src="popup.js"></script> <script src="popup.js"></script>
</body> </body>
</html> </html>

View File

@ -129,6 +129,7 @@ const elements = {
settingsToggle: document.getElementById('settingsToggle'), settingsToggle: document.getElementById('settingsToggle'),
settingsDropdown: document.getElementById('settingsDropdown'), settingsDropdown: document.getElementById('settingsDropdown'),
timeoutInput: document.getElementById('timeoutInput'), timeoutInput: document.getElementById('timeoutInput'),
sigSocketUrlInput: document.getElementById('sigSocketUrlInput'),
// Keypair management elements // Keypair management elements
toggleAddKeypairBtn: document.getElementById('toggleAddKeypairBtn'), toggleAddKeypairBtn: document.getElementById('toggleAddKeypairBtn'),
@ -167,6 +168,7 @@ let currentKeyspace = null;
let selectedKeypairId = null; let selectedKeypairId = null;
let backgroundPort = null; let backgroundPort = null;
let sessionTimeoutDuration = 15; // Default 15 seconds let sessionTimeoutDuration = 15; // Default 15 seconds
let signRequestManager = null; // Sign request manager instance
// Session timeout management // Session timeout management
function handleError(error, context, shouldShowToast = true) { function handleError(error, context, shouldShowToast = true) {
@ -198,11 +200,14 @@ function validateInput(value, fieldName, options = {}) {
return true; return true;
} }
async function loadTimeoutSetting() { async function loadTimeoutSetting() {
const result = await chrome.storage.local.get(['sessionTimeout']); const result = await chrome.storage.local.get(['sessionTimeout', 'sigSocketUrl']);
sessionTimeoutDuration = result.sessionTimeout || 15; sessionTimeoutDuration = result.sessionTimeout || 15;
if (elements.timeoutInput) { if (elements.timeoutInput) {
elements.timeoutInput.value = sessionTimeoutDuration; elements.timeoutInput.value = sessionTimeoutDuration;
} }
if (elements.sigSocketUrlInput) {
elements.sigSocketUrlInput.value = result.sigSocketUrl || 'ws://localhost:8080/ws';
}
} }
async function checkSessionTimeout() { async function checkSessionTimeout() {
@ -307,6 +312,39 @@ function connectToBackground() {
// Show timeout notification // Show timeout notification
showToast(message.message, 'info'); showToast(message.message, 'info');
// Update sign request manager
if (signRequestManager) {
signRequestManager.updateState({ isKeypaceUnlocked: false });
}
} else if (message.type === 'KEYSPACE_UNLOCKED') {
// Handle keyspace unlock from SigSocket service
console.log('Popup received KEYSPACE_UNLOCKED message:', message);
if (signRequestManager) {
console.log('Updating SignRequestManager state with keypaceMatches:', message.keypaceMatches);
signRequestManager.updateState({
isKeypaceUnlocked: true,
keypaceMatches: message.keypaceMatches,
pendingRequests: message.pendingRequests
});
console.log('SignRequestManager state updated');
} else {
console.log('No SignRequestManager available to update');
}
} else if (message.type === 'NEW_SIGN_REQUEST') {
// Handle new sign request when keyspace is already unlocked
console.log('Popup received NEW_SIGN_REQUEST message:', message);
if (signRequestManager) {
console.log('Updating SignRequestManager with new request, keypaceMatches:', message.keypaceMatches);
// Only update if we have a valid keyspace match, otherwise requests array will be empty for security
signRequestManager.updateState({
keypaceMatches: message.keypaceMatches,
pendingRequests: message.pendingRequests
});
console.log(`SignRequestManager updated with new request: ${message.pendingRequests.length} requests visible`);
} else {
console.log('No SignRequestManager available to update with new request');
}
} }
}); });
@ -315,6 +353,22 @@ function connectToBackground() {
}); });
} }
// Initialize Sign Request Manager
async function initializeSignRequestManager() {
try {
const container = document.getElementById('signRequestContainer');
if (container && window.SignRequestManager) {
signRequestManager = new window.SignRequestManager();
await signRequestManager.initialize(container);
console.log('Sign Request Manager initialized');
} else {
console.warn('SignRequestManager not available or container not found');
}
} catch (error) {
console.error('Failed to initialize Sign Request Manager:', error);
}
}
// Initialize // Initialize
document.addEventListener('DOMContentLoaded', async function() { document.addEventListener('DOMContentLoaded', async function() {
// Initialize theme first // Initialize theme first
@ -332,6 +386,9 @@ document.addEventListener('DOMContentLoaded', async function() {
// Connect to background script for keep-alive // Connect to background script for keep-alive
connectToBackground(); connectToBackground();
// Initialize Sign Request Manager
await initializeSignRequestManager();
// Consolidated event listeners // Consolidated event listeners
const eventMap = { const eventMap = {
createKeyspaceBtn: createKeyspace, createKeyspaceBtn: createKeyspace,
@ -376,6 +433,18 @@ document.addEventListener('DOMContentLoaded', async function() {
} }
}); });
// SigSocket URL setting event listener
elements.sigSocketUrlInput?.addEventListener('change', async (e) => {
const url = e.target.value.trim();
if (url) {
await chrome.storage.local.set({ sigSocketUrl: url });
// Update the SigSocket service with new URL if it exists
if (signRequestManager && signRequestManager.sigSocketService) {
signRequestManager.sigSocketService.defaultServerUrl = url;
}
}
});
// Activity detection - reset timeout on any interaction // Activity detection - reset timeout on any interaction
document.addEventListener('click', (e) => { document.addEventListener('click', (e) => {
resetSessionTimeout(); resetSessionTimeout();
@ -644,6 +713,12 @@ async function login() {
clearVaultState(); clearVaultState();
await loadKeypairs(); await loadKeypairs();
// Notify sign request manager about keyspace unlock
if (signRequestManager) {
signRequestManager.updateState({ isKeypaceUnlocked: true });
await signRequestManager.refresh();
}
return response; return response;
} else { } else {
throw new Error(getResponseError(response, 'login')); throw new Error(getResponseError(response, 'login'));
@ -673,6 +748,11 @@ async function lockSession() {
elements.passwordInput.value = ''; elements.passwordInput.value = '';
clearVaultState(); clearVaultState();
// Update sign request manager
if (signRequestManager) {
signRequestManager.updateState({ isKeypaceUnlocked: false });
}
showToast('Session locked', 'info'); showToast('Session locked', 'info');
} catch (error) { } catch (error) {
showToast('Error: ' + error.message, 'error'); showToast('Error: ' + error.message, 'error');

View File

@ -0,0 +1,443 @@
/**
* Sign Request Manager Component
*
* Handles the display and management of SigSocket sign requests in the popup.
* Manages different UI states:
* 1. Keyspace locked: Show unlock form
* 2. Wrong keyspace: Show mismatch message
* 3. Correct keyspace: Show approval UI
*/
class SignRequestManager {
constructor() {
this.pendingRequests = [];
this.isKeypaceUnlocked = false;
this.keypaceMatch = false;
this.connectionStatus = { isConnected: false };
this.container = null;
this.initialized = false;
}
/**
* Initialize the component
* @param {HTMLElement} container - Container element to render into
*/
async initialize(container) {
this.container = container;
this.initialized = true;
// Load initial state
await this.loadState();
// Render initial UI
this.render();
// Set up event listeners
this.setupEventListeners();
// Listen for background messages
this.setupBackgroundListener();
}
/**
* Load current state from background script
*/
async loadState() {
try {
// Check if keyspace is unlocked
const unlockedResponse = await this.sendMessage('isUnlocked');
this.isKeypaceUnlocked = unlockedResponse?.unlocked || false;
// Get pending requests
const requestsResponse = await this.sendMessage('getPendingRequests');
this.pendingRequests = requestsResponse?.requests || [];
// Get SigSocket status
const statusResponse = await this.sendMessage('getSigSocketStatus');
this.connectionStatus = statusResponse?.status || { isConnected: false };
// If keyspace is unlocked, notify background to check keyspace match
if (this.isKeypaceUnlocked) {
await this.sendMessage('keypaceUnlocked');
}
} catch (error) {
console.error('Failed to load sign request state:', error);
}
}
/**
* Render the component UI
*/
render() {
if (!this.container) return;
const hasRequests = this.pendingRequests.length > 0;
if (!hasRequests) {
this.renderNoRequests();
return;
}
if (!this.isKeypaceUnlocked) {
this.renderUnlockPrompt();
return;
}
if (!this.keypaceMatch) {
this.renderKeypaceMismatch();
return;
}
this.renderApprovalUI();
}
/**
* Render no requests state
*/
renderNoRequests() {
this.container.innerHTML = `
<div class="sign-request-manager">
<div class="connection-status ${this.connectionStatus.isConnected ? 'connected' : 'disconnected'}">
<span class="status-indicator"></span>
SigSocket: ${this.connectionStatus.isConnected ? 'Connected' : 'Disconnected'}
</div>
<div class="no-requests">
<p>No pending sign requests</p>
</div>
</div>
`;
}
/**
* Render unlock prompt
*/
renderUnlockPrompt() {
const requestCount = this.pendingRequests.length;
this.container.innerHTML = `
<div class="sign-request-manager">
<div class="connection-status ${this.connectionStatus.isConnected ? 'connected' : 'disconnected'}">
<span class="status-indicator"></span>
SigSocket: ${this.connectionStatus.isConnected ? 'Connected' : 'Disconnected'}
</div>
<div class="unlock-prompt">
<h3>🔒 Unlock Keyspace</h3>
<p>Unlock your keyspace to see ${requestCount} pending sign request${requestCount !== 1 ? 's' : ''}.</p>
<p class="hint">Use the login form above to unlock your keyspace.</p>
</div>
</div>
`;
}
/**
* Render keyspace mismatch message
*/
renderKeypaceMismatch() {
this.container.innerHTML = `
<div class="sign-request-manager">
<div class="connection-status ${this.connectionStatus.isConnected ? 'connected' : 'disconnected'}">
<span class="status-indicator"></span>
SigSocket: ${this.connectionStatus.isConnected ? 'Connected' : 'Disconnected'}
</div>
<div class="keyspace-mismatch">
<h3> Wrong Keyspace</h3>
<p>The unlocked keyspace doesn't match the connected SigSocket session.</p>
<p class="hint">Please unlock the correct keyspace to approve sign requests.</p>
</div>
</div>
`;
}
/**
* Render approval UI with pending requests
*/
renderApprovalUI() {
const requestsHtml = this.pendingRequests.map(request => this.renderSignRequestCard(request)).join('');
this.container.innerHTML = `
<div class="sign-request-manager">
<div class="connection-status connected">
<span class="status-indicator"></span>
SigSocket: Connected
</div>
<div class="requests-header">
<h3>📝 Sign Requests (${this.pendingRequests.length})</h3>
</div>
<div class="requests-list">
${requestsHtml}
</div>
</div>
`;
}
/**
* Render individual sign request card
* @param {Object} request - Sign request data
* @returns {string} - HTML string for the request card
*/
renderSignRequestCard(request) {
const timestamp = new Date(request.timestamp).toLocaleTimeString();
const messagePreview = this.getMessagePreview(request.message);
return `
<div class="sign-request-card" data-request-id="${request.id}">
<div class="request-header">
<div class="request-id">Request: ${request.id.substring(0, 8)}...</div>
<div class="request-time">${timestamp}</div>
</div>
<div class="request-message">
<label>Message:</label>
<div class="message-content">
<div class="message-preview">${messagePreview}</div>
<button class="expand-message" data-request-id="${request.id}">
<span class="expand-text">Show Full</span>
</button>
</div>
</div>
<div class="request-actions">
<button class="btn-reject" data-request-id="${request.id}">
Reject
</button>
<button class="btn-approve" data-request-id="${request.id}">
Approve & Sign
</button>
</div>
</div>
`;
}
/**
* Get a preview of the message content
* @param {string} messageBase64 - Base64 encoded message
* @returns {string} - Preview text
*/
getMessagePreview(messageBase64) {
try {
const decoded = atob(messageBase64);
const preview = decoded.length > 50 ? decoded.substring(0, 50) + '...' : decoded;
return preview;
} catch (error) {
return `Base64: ${messageBase64.substring(0, 20)}...`;
}
}
/**
* Set up event listeners
*/
setupEventListeners() {
if (!this.container) return;
// Use event delegation for dynamic content
this.container.addEventListener('click', (e) => {
const target = e.target;
if (target.classList.contains('btn-approve')) {
const requestId = target.getAttribute('data-request-id');
this.approveRequest(requestId);
} else if (target.classList.contains('btn-reject')) {
const requestId = target.getAttribute('data-request-id');
this.rejectRequest(requestId);
} else if (target.classList.contains('expand-message')) {
const requestId = target.getAttribute('data-request-id');
this.toggleMessageExpansion(requestId);
}
});
}
/**
* Set up listener for background script messages
*/
setupBackgroundListener() {
// Listen for keyspace unlock events
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'KEYSPACE_UNLOCKED') {
this.isKeypaceUnlocked = true;
this.keypaceMatch = message.keypaceMatches;
this.pendingRequests = message.pendingRequests || [];
this.render();
}
});
}
/**
* Approve a sign request
* @param {string} requestId - Request ID to approve
*/
async approveRequest(requestId) {
try {
const button = this.container.querySelector(`[data-request-id="${requestId}"].btn-approve`);
if (button) {
button.disabled = true;
button.textContent = 'Signing...';
}
const response = await this.sendMessage('approveSignRequest', { requestId });
if (response?.success) {
// Remove the request from UI
this.pendingRequests = this.pendingRequests.filter(r => r.id !== requestId);
this.render();
// Show success message
this.showToast('Sign request approved successfully!', 'success');
} else {
throw new Error(response?.error || 'Failed to approve request');
}
} catch (error) {
console.error('Failed to approve request:', error);
this.showToast('Failed to approve request: ' + error.message, 'error');
// Re-enable button
const button = this.container.querySelector(`[data-request-id="${requestId}"].btn-approve`);
if (button) {
button.disabled = false;
button.textContent = '✅ Approve & Sign';
}
}
}
/**
* Reject a sign request
* @param {string} requestId - Request ID to reject
*/
async rejectRequest(requestId) {
try {
const button = this.container.querySelector(`[data-request-id="${requestId}"].btn-reject`);
if (button) {
button.disabled = true;
button.textContent = 'Rejecting...';
}
const response = await this.sendMessage('rejectSignRequest', {
requestId,
reason: 'User rejected'
});
if (response?.success) {
// Remove the request from UI
this.pendingRequests = this.pendingRequests.filter(r => r.id !== requestId);
this.render();
// Show success message
this.showToast('Sign request rejected', 'info');
} else {
throw new Error(response?.error || 'Failed to reject request');
}
} catch (error) {
console.error('Failed to reject request:', error);
this.showToast('Failed to reject request: ' + error.message, 'error');
// Re-enable button
const button = this.container.querySelector(`[data-request-id="${requestId}"].btn-reject`);
if (button) {
button.disabled = false;
button.textContent = '❌ Reject';
}
}
}
/**
* Toggle message expansion
* @param {string} requestId - Request ID
*/
toggleMessageExpansion(requestId) {
const request = this.pendingRequests.find(r => r.id === requestId);
if (!request) return;
const card = this.container.querySelector(`[data-request-id="${requestId}"]`);
const messageContent = card.querySelector('.message-content');
const expandButton = card.querySelector('.expand-message');
const isExpanded = messageContent.classList.contains('expanded');
if (isExpanded) {
messageContent.classList.remove('expanded');
messageContent.querySelector('.message-preview').textContent = this.getMessagePreview(request.message);
expandButton.querySelector('.expand-text').textContent = 'Show Full';
} else {
messageContent.classList.add('expanded');
try {
const fullMessage = atob(request.message);
messageContent.querySelector('.message-preview').textContent = fullMessage;
} catch (error) {
messageContent.querySelector('.message-preview').textContent = `Base64: ${request.message}`;
}
expandButton.querySelector('.expand-text').textContent = 'Show Less';
}
}
/**
* Send message to background script
* @param {string} action - Action to perform
* @param {Object} data - Additional data
* @returns {Promise<Object>} - Response from background script
*/
async sendMessage(action, data = {}) {
return new Promise((resolve) => {
chrome.runtime.sendMessage({ action, ...data }, resolve);
});
}
/**
* Show toast notification
* @param {string} message - Message to show
* @param {string} type - Toast type (success, error, info)
*/
showToast(message, type = 'info') {
// Use the existing toast system from popup.js
if (typeof showToast === 'function') {
showToast(message, type);
} else {
console.log(`[${type.toUpperCase()}] ${message}`);
}
}
/**
* Update component state
* @param {Object} newState - New state data
*/
updateState(newState) {
console.log('SignRequestManager.updateState called with:', newState);
console.log('Current state before update:', {
isKeypaceUnlocked: this.isKeypaceUnlocked,
keypaceMatch: this.keypaceMatch,
pendingRequests: this.pendingRequests.length
});
Object.assign(this, newState);
// Fix the property name mismatch
if (newState.keypaceMatches !== undefined) {
this.keypaceMatch = newState.keypaceMatches;
}
console.log('State after update:', {
isKeypaceUnlocked: this.isKeypaceUnlocked,
keypaceMatch: this.keypaceMatch,
pendingRequests: this.pendingRequests.length
});
if (this.initialized) {
console.log('Rendering SignRequestManager with new state');
this.render();
}
}
/**
* Refresh component data
*/
async refresh() {
await this.loadState();
this.render();
}
}
// Export for use in popup
if (typeof module !== 'undefined' && module.exports) {
module.exports = SignRequestManager;
} else {
window.SignRequestManager = SignRequestManager;
}

View File

@ -1070,3 +1070,194 @@ input::placeholder, textarea::placeholder {
width: 20px; width: 20px;
height: 20px; height: 20px;
} }
/* Sign Request Manager Styles */
.sign-request-manager {
margin-top: 16px;
padding: 16px;
background: var(--bg-secondary);
border-radius: 8px;
border: 1px solid var(--border-color);
}
.connection-status {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 12px;
font-size: 12px;
font-weight: 500;
}
.status-indicator {
width: 8px;
height: 8px;
border-radius: 50%;
background: #ef4444;
}
.connection-status.connected .status-indicator {
background: #10b981;
}
.no-requests {
text-align: center;
padding: 20px;
color: var(--text-secondary);
}
.unlock-prompt, .keyspace-mismatch {
text-align: center;
padding: 20px;
}
.unlock-prompt h3, .keyspace-mismatch h3 {
margin: 0 0 12px 0;
font-size: 16px;
color: var(--text-primary);
}
.unlock-prompt p, .keyspace-mismatch p {
margin: 8px 0;
color: var(--text-secondary);
font-size: 14px;
}
.hint {
font-style: italic;
font-size: 12px !important;
color: var(--text-tertiary) !important;
}
.requests-header {
margin-bottom: 12px;
}
.requests-header h3 {
margin: 0;
font-size: 16px;
color: var(--text-primary);
}
.requests-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.sign-request-card {
background: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 6px;
padding: 12px;
}
.request-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.request-id {
font-family: monospace;
font-size: 12px;
color: var(--text-secondary);
font-weight: 500;
}
.request-time {
font-size: 11px;
color: var(--text-tertiary);
}
.request-message {
margin-bottom: 12px;
}
.request-message label {
display: block;
font-size: 12px;
font-weight: 500;
color: var(--text-secondary);
margin-bottom: 4px;
}
.message-content {
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 8px;
position: relative;
}
.message-preview {
font-family: monospace;
font-size: 11px;
color: var(--text-primary);
word-break: break-all;
line-height: 1.4;
max-height: 60px;
overflow: hidden;
}
.message-content.expanded .message-preview {
max-height: none;
}
.expand-message {
position: absolute;
top: 4px;
right: 4px;
background: var(--accent-color);
color: white;
border: none;
border-radius: 3px;
padding: 2px 6px;
font-size: 10px;
cursor: pointer;
transition: background-color 0.2s;
}
.expand-message:hover {
background: var(--accent-hover);
}
.request-actions {
display: flex;
gap: 8px;
justify-content: flex-end;
}
.btn-approve, .btn-reject {
padding: 6px 12px;
border: none;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
}
.btn-approve {
background: #10b981;
color: white;
}
.btn-approve:hover:not(:disabled) {
background: #059669;
}
.btn-reject {
background: #ef4444;
color: white;
}
.btn-reject:hover:not(:disabled) {
background: #dc2626;
}
.btn-approve:disabled, .btn-reject:disabled {
opacity: 0.6;
cursor: not-allowed;
}

View File

@ -0,0 +1,114 @@
# Testing the SigSocket Browser Extension
## Prerequisites
1. **SigSocket Server**: You need a running SigSocket server at `ws://localhost:8080/ws`
2. **Browser**: Chrome or Chromium-based browser with developer mode enabled
## Test Steps
### 1. Load the Extension
1. Open Chrome and go to `chrome://extensions/`
2. Enable "Developer mode" in the top right
3. Click "Load unpacked" and select the `crypto_vault_extension` directory
4. The CryptoVault extension should appear in your extensions list
### 2. Basic Functionality Test
1. Click the CryptoVault extension icon in the toolbar
2. Create a new keyspace:
- Enter a keyspace name (e.g., "test-workspace")
- Enter a password
- Click "Create New"
3. The extension should automatically connect to the SigSocket server
4. Add a keypair:
- Click "Add Keypair"
- Enter a name for the keypair
- Click "Create Keypair"
### 3. SigSocket Integration Test
1. **Check Connection Status**:
- Look for the SigSocket connection status at the bottom of the popup
- It should show "SigSocket: Connected" with a green indicator
2. **Test Sign Request Flow**:
- Send a sign request to the SigSocket server (you'll need to implement this on the server side)
- The extension should show a notification
- The extension badge should show the number of pending requests
- Open the extension popup to see the sign request
3. **Test Approval Flow**:
- If keyspace is locked, you should see "Unlock keyspace to see X pending requests"
- Unlock the keyspace using the login form
- You should see the sign request details
- Click "Approve & Sign" to approve the request
- The request should be signed and sent back to the server
### 4. Settings Test
1. Click the settings gear icon in the extension popup
2. Change the SigSocket server URL if needed
3. Adjust the session timeout if desired
## Expected Behavior
- ✅ Extension loads without errors
- ✅ Can create keyspaces and keypairs
- ✅ SigSocket connection is established automatically
- ✅ Sign requests are received and displayed
- ✅ Approval flow works correctly
- ✅ Settings can be configured
## Troubleshooting
### Common Issues
1. **Extension won't load**: Check the console for JavaScript errors
2. **SigSocket won't connect**: Verify the server is running and the URL is correct
3. **WASM errors**: Check that the WASM files are properly built and copied
4. **Sign requests not appearing**: Check the browser console for callback errors
### Debug Steps
1. Open Chrome DevTools
2. Go to the Extensions tab
3. Find CryptoVault and click "Inspect views: background page"
4. Check the console for any errors
5. Also inspect the popup by right-clicking the extension icon and selecting "Inspect popup"
## Server-Side Testing
To fully test the extension, you'll need a SigSocket server that can:
1. Accept WebSocket connections at `/ws`
2. Handle client introduction messages (hex-encoded public keys)
3. Send sign requests in the format:
```json
{
"id": "unique-request-id",
"message": "base64-encoded-message"
}
```
4. Receive sign responses in the format:
```json
{
"id": "request-id",
"message": "base64-encoded-message",
"signature": "base64-encoded-signature"
}
```
## Next Steps
If basic functionality works:
1. Test with multiple concurrent sign requests
2. Test connection recovery after network issues
3. Test with different keyspace configurations
4. Test the rejection flow
5. Test session timeout behavior

View File

@ -202,6 +202,33 @@ function debugString(val) {
// TODO we could test for more things here, like `Set`s and `Map`s. // TODO we could test for more things here, like `Set`s and `Map`s.
return className; return className;
} }
/**
* Initialize the scripting environment (must be called before run_rhai)
*/
export function init_rhai_env() {
wasm.init_rhai_env();
}
function takeFromExternrefTable0(idx) {
const value = wasm.__wbindgen_export_2.get(idx);
wasm.__externref_table_dealloc(idx);
return value;
}
/**
* Securely run a Rhai script in the extension context (must be called only after user approval)
* @param {string} script
* @returns {any}
*/
export function run_rhai(script) {
const ptr0 = passStringToWasm0(script, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ret = wasm.run_rhai(ptr0, len0);
if (ret[2]) {
throw takeFromExternrefTable0(ret[1]);
}
return takeFromExternrefTable0(ret[0]);
}
/** /**
* Create and unlock a new keyspace with the given name and password * Create and unlock a new keyspace with the given name and password
* @param {string} keyspace * @param {string} keyspace
@ -239,11 +266,6 @@ export function lock_session() {
wasm.lock_session(); wasm.lock_session();
} }
function takeFromExternrefTable0(idx) {
const value = wasm.__wbindgen_export_2.get(idx);
wasm.__externref_table_dealloc(idx);
return value;
}
/** /**
* Get metadata of the currently selected keypair * Get metadata of the currently selected keypair
* @returns {any} * @returns {any}
@ -277,6 +299,42 @@ export function is_unlocked() {
return ret !== 0; return ret !== 0;
} }
/**
* Get the default public key for a workspace (keyspace)
* This returns the public key of the first keypair in the keyspace
* @param {string} workspace_id
* @returns {Promise<any>}
*/
export function get_workspace_default_public_key(workspace_id) {
const ptr0 = passStringToWasm0(workspace_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ret = wasm.get_workspace_default_public_key(ptr0, len0);
return ret;
}
/**
* Get the current unlocked public key as hex string
* @returns {string}
*/
export function get_current_unlocked_public_key() {
let deferred2_0;
let deferred2_1;
try {
const ret = wasm.get_current_unlocked_public_key();
var ptr1 = ret[0];
var len1 = ret[1];
if (ret[3]) {
ptr1 = 0; len1 = 0;
throw takeFromExternrefTable0(ret[2]);
}
deferred2_0 = ptr1;
deferred2_1 = len1;
return getStringFromWasm0(ptr1, len1);
} finally {
wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
}
}
/** /**
* Get all keypairs from the current session * Get all keypairs from the current session
* Returns an array of keypair objects with id, type, and metadata * Returns an array of keypair objects with id, type, and metadata
@ -323,7 +381,7 @@ function passArray8ToWasm0(arg, malloc) {
return ptr; return ptr;
} }
/** /**
* Sign message with current session * Sign message with current session (requires selected keypair)
* @param {Uint8Array} message * @param {Uint8Array} message
* @returns {Promise<any>} * @returns {Promise<any>}
*/ */
@ -334,6 +392,41 @@ export function sign(message) {
return ret; return ret;
} }
/**
* Get the current keyspace name
* @returns {string}
*/
export function get_current_keyspace_name() {
let deferred2_0;
let deferred2_1;
try {
const ret = wasm.get_current_keyspace_name();
var ptr1 = ret[0];
var len1 = ret[1];
if (ret[3]) {
ptr1 = 0; len1 = 0;
throw takeFromExternrefTable0(ret[2]);
}
deferred2_0 = ptr1;
deferred2_1 = len1;
return getStringFromWasm0(ptr1, len1);
} finally {
wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
}
}
/**
* Sign message with default keypair (first keypair in keyspace) without changing session state
* @param {Uint8Array} message
* @returns {Promise<any>}
*/
export function sign_with_default_keypair(message) {
const ptr0 = passArray8ToWasm0(message, wasm.__wbindgen_malloc);
const len0 = WASM_VECTOR_LEN;
const ret = wasm.sign_with_default_keypair(ptr0, len0);
return ret;
}
/** /**
* Verify a signature with the current session's selected keypair * Verify a signature with the current session's selected keypair
* @param {Uint8Array} message * @param {Uint8Array} message
@ -373,46 +466,162 @@ export function decrypt_data(encrypted) {
return ret; return ret;
} }
/** function __wbg_adapter_34(arg0, arg1, arg2) {
* Initialize the scripting environment (must be called before run_rhai) wasm.closure135_externref_shim(arg0, arg1, arg2);
*/
export function init_rhai_env() {
wasm.init_rhai_env();
} }
/** function __wbg_adapter_39(arg0, arg1) {
* Securely run a Rhai script in the extension context (must be called only after user approval) wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__ha4436a3f79fb1a0f(arg0, arg1);
* @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]);
} }
function __wbg_adapter_32(arg0, arg1, arg2) { function __wbg_adapter_44(arg0, arg1, arg2) {
wasm.closure121_externref_shim(arg0, arg1, arg2); wasm.closure199_externref_shim(arg0, arg1, arg2);
} }
function __wbg_adapter_35(arg0, arg1, arg2) { function __wbg_adapter_49(arg0, arg1) {
wasm.closure150_externref_shim(arg0, arg1, arg2); wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hf148c54a4a246cea(arg0, arg1);
} }
function __wbg_adapter_38(arg0, arg1, arg2) { function __wbg_adapter_52(arg0, arg1, arg2) {
wasm.closure227_externref_shim(arg0, arg1, arg2); wasm.closure264_externref_shim(arg0, arg1, arg2);
} }
function __wbg_adapter_138(arg0, arg1, arg2, arg3) { function __wbg_adapter_55(arg0, arg1, arg2) {
wasm.closure1879_externref_shim(arg0, arg1, arg2, arg3); wasm.closure349_externref_shim(arg0, arg1, arg2);
} }
function __wbg_adapter_195(arg0, arg1, arg2, arg3) {
wasm.closure2004_externref_shim(arg0, arg1, arg2, arg3);
}
const __wbindgen_enum_BinaryType = ["blob", "arraybuffer"];
const __wbindgen_enum_IdbTransactionMode = ["readonly", "readwrite", "versionchange", "readwriteflush", "cleanup"]; const __wbindgen_enum_IdbTransactionMode = ["readonly", "readwrite", "versionchange", "readwriteflush", "cleanup"];
const SigSocketConnectionFinalization = (typeof FinalizationRegistry === 'undefined')
? { register: () => {}, unregister: () => {} }
: new FinalizationRegistry(ptr => wasm.__wbg_sigsocketconnection_free(ptr >>> 0, 1));
/**
* WASM-bindgen wrapper for SigSocket client
*
* This provides a clean JavaScript API for the browser extension to:
* - Connect to SigSocket servers
* - Send responses to sign requests
* - Manage connection state
*/
export class SigSocketConnection {
__destroy_into_raw() {
const ptr = this.__wbg_ptr;
this.__wbg_ptr = 0;
SigSocketConnectionFinalization.unregister(this);
return ptr;
}
free() {
const ptr = this.__destroy_into_raw();
wasm.__wbg_sigsocketconnection_free(ptr, 0);
}
/**
* Create a new SigSocket connection
*/
constructor() {
const ret = wasm.sigsocketconnection_new();
this.__wbg_ptr = ret >>> 0;
SigSocketConnectionFinalization.register(this, this.__wbg_ptr, this);
return this;
}
/**
* Connect to a SigSocket server
*
* # Arguments
* * `server_url` - WebSocket server URL (e.g., "ws://localhost:8080/ws")
* * `public_key_hex` - Client's public key as hex string
*
* # Returns
* * `Ok(())` - Successfully connected
* * `Err(error)` - Connection failed
* @param {string} server_url
* @param {string} public_key_hex
* @returns {Promise<void>}
*/
connect(server_url, public_key_hex) {
const ptr0 = passStringToWasm0(server_url, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ptr1 = passStringToWasm0(public_key_hex, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
const ret = wasm.sigsocketconnection_connect(this.__wbg_ptr, ptr0, len0, ptr1, len1);
return ret;
}
/**
* Send a response to a sign request
*
* This should be called by the extension after the user has approved
* a sign request and the message has been signed.
*
* # Arguments
* * `request_id` - ID of the original request
* * `message_base64` - Original message (base64-encoded)
* * `signature_hex` - Signature as hex string
*
* # Returns
* * `Ok(())` - Response sent successfully
* * `Err(error)` - Failed to send response
* @param {string} request_id
* @param {string} message_base64
* @param {string} signature_hex
* @returns {Promise<void>}
*/
send_response(request_id, message_base64, signature_hex) {
const ptr0 = passStringToWasm0(request_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ptr1 = passStringToWasm0(message_base64, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
const ptr2 = passStringToWasm0(signature_hex, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len2 = WASM_VECTOR_LEN;
const ret = wasm.sigsocketconnection_send_response(this.__wbg_ptr, ptr0, len0, ptr1, len1, ptr2, len2);
return ret;
}
/**
* Send a rejection for a sign request
*
* This should be called when the user rejects a sign request.
*
* # Arguments
* * `request_id` - ID of the request to reject
* * `reason` - Reason for rejection (optional)
*
* # Returns
* * `Ok(())` - Rejection sent successfully
* * `Err(error)` - Failed to send rejection
* @param {string} request_id
* @param {string} reason
* @returns {Promise<void>}
*/
send_rejection(request_id, reason) {
const ptr0 = passStringToWasm0(request_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ptr1 = passStringToWasm0(reason, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN;
const ret = wasm.sigsocketconnection_send_rejection(this.__wbg_ptr, ptr0, len0, ptr1, len1);
return ret;
}
/**
* Disconnect from the SigSocket server
*/
disconnect() {
wasm.sigsocketconnection_disconnect(this.__wbg_ptr);
}
/**
* Check if connected to the server
* @returns {boolean}
*/
is_connected() {
const ret = wasm.sigsocketconnection_is_connected(this.__wbg_ptr);
return ret !== 0;
}
}
async function __wbg_load(module, imports) { async function __wbg_load(module, imports) {
if (typeof Response === 'function' && module instanceof Response) { if (typeof Response === 'function' && module instanceof Response) {
if (typeof WebAssembly.instantiateStreaming === 'function') { if (typeof WebAssembly.instantiateStreaming === 'function') {
@ -467,6 +676,10 @@ function __wbg_get_imports() {
const ret = arg0.crypto; const ret = arg0.crypto;
return ret; return ret;
}; };
imports.wbg.__wbg_data_432d9c3df2630942 = function(arg0) {
const ret = arg0.data;
return ret;
};
imports.wbg.__wbg_error_524f506f44df1645 = function(arg0) { imports.wbg.__wbg_error_524f506f44df1645 = function(arg0) {
console.error(arg0); console.error(arg0);
}; };
@ -539,10 +752,23 @@ function __wbg_get_imports() {
const ret = result; const ret = result;
return ret; return ret;
}; };
imports.wbg.__wbg_instanceof_Window_def73ea0955fc569 = function(arg0) {
let result;
try {
result = arg0 instanceof Window;
} catch (_) {
result = false;
}
const ret = result;
return ret;
};
imports.wbg.__wbg_length_52b6c4580c5ec934 = function(arg0) { imports.wbg.__wbg_length_52b6c4580c5ec934 = function(arg0) {
const ret = arg0.length; const ret = arg0.length;
return ret; return ret;
}; };
imports.wbg.__wbg_log_c222819a41e063d3 = function(arg0) {
console.log(arg0);
};
imports.wbg.__wbg_msCrypto_a61aeb35a24c1329 = function(arg0) { imports.wbg.__wbg_msCrypto_a61aeb35a24c1329 = function(arg0) {
const ret = arg0.msCrypto; const ret = arg0.msCrypto;
return ret; return ret;
@ -558,7 +784,7 @@ function __wbg_get_imports() {
const a = state0.a; const a = state0.a;
state0.a = 0; state0.a = 0;
try { try {
return __wbg_adapter_138(a, state0.b, arg0, arg1); return __wbg_adapter_195(a, state0.b, arg0, arg1);
} finally { } finally {
state0.a = a; state0.a = a;
} }
@ -577,6 +803,10 @@ function __wbg_get_imports() {
const ret = new Array(); const ret = new Array();
return ret; return ret;
}; };
imports.wbg.__wbg_new_92c54fc74574ef55 = function() { return handleError(function (arg0, arg1) {
const ret = new WebSocket(getStringFromWasm0(arg0, arg1));
return ret;
}, arguments) };
imports.wbg.__wbg_new_a12002a7f91c75be = function(arg0) { imports.wbg.__wbg_new_a12002a7f91c75be = function(arg0) {
const ret = new Uint8Array(arg0); const ret = new Uint8Array(arg0);
return ret; return ret;
@ -609,6 +839,12 @@ function __wbg_get_imports() {
const ret = arg0.objectStore(getStringFromWasm0(arg1, arg2)); const ret = arg0.objectStore(getStringFromWasm0(arg1, arg2));
return ret; return ret;
}, arguments) }; }, arguments) };
imports.wbg.__wbg_onConnectionStateChanged_b0dc098522afadba = function(arg0) {
onConnectionStateChanged(arg0 !== 0);
};
imports.wbg.__wbg_onSignRequestReceived_93232ba7a0919705 = function(arg0, arg1, arg2, arg3) {
onSignRequestReceived(getStringFromWasm0(arg0, arg1), getStringFromWasm0(arg2, arg3));
};
imports.wbg.__wbg_open_88b1390d99a7c691 = function() { return handleError(function (arg0, arg1, arg2) { imports.wbg.__wbg_open_88b1390d99a7c691 = function() { return handleError(function (arg0, arg1, arg2) {
const ret = arg0.open(getStringFromWasm0(arg1, arg2)); const ret = arg0.open(getStringFromWasm0(arg1, arg2));
return ret; return ret;
@ -643,6 +879,10 @@ function __wbg_get_imports() {
imports.wbg.__wbg_randomFillSync_ac0988aba3254290 = function() { return handleError(function (arg0, arg1) { imports.wbg.__wbg_randomFillSync_ac0988aba3254290 = function() { return handleError(function (arg0, arg1) {
arg0.randomFillSync(arg1); arg0.randomFillSync(arg1);
}, arguments) }; }, arguments) };
imports.wbg.__wbg_readyState_7ef6e63c349899ed = function(arg0) {
const ret = arg0.readyState;
return ret;
};
imports.wbg.__wbg_require_60cc747a6bc5215a = function() { return handleError(function () { imports.wbg.__wbg_require_60cc747a6bc5215a = function() { return handleError(function () {
const ret = module.require; const ret = module.require;
return ret; return ret;
@ -655,12 +895,34 @@ function __wbg_get_imports() {
const ret = arg0.result; const ret = arg0.result;
return ret; return ret;
}, arguments) }; }, arguments) };
imports.wbg.__wbg_send_0293179ba074ffb4 = function() { return handleError(function (arg0, arg1, arg2) {
arg0.send(getStringFromWasm0(arg1, arg2));
}, arguments) };
imports.wbg.__wbg_setTimeout_f2fe5af8e3debeb3 = function() { return handleError(function (arg0, arg1, arg2) {
const ret = arg0.setTimeout(arg1, arg2);
return ret;
}, arguments) };
imports.wbg.__wbg_set_65595bdd868b3009 = function(arg0, arg1, arg2) { imports.wbg.__wbg_set_65595bdd868b3009 = function(arg0, arg1, arg2) {
arg0.set(arg1, arg2 >>> 0); arg0.set(arg1, arg2 >>> 0);
}; };
imports.wbg.__wbg_setbinaryType_92fa1ffd873b327c = function(arg0, arg1) {
arg0.binaryType = __wbindgen_enum_BinaryType[arg1];
};
imports.wbg.__wbg_setonclose_14fc475a49d488fc = function(arg0, arg1) {
arg0.onclose = arg1;
};
imports.wbg.__wbg_setonerror_8639efe354b947cd = function(arg0, arg1) {
arg0.onerror = arg1;
};
imports.wbg.__wbg_setonerror_d7e3056cc6e56085 = function(arg0, arg1) { imports.wbg.__wbg_setonerror_d7e3056cc6e56085 = function(arg0, arg1) {
arg0.onerror = arg1; arg0.onerror = arg1;
}; };
imports.wbg.__wbg_setonmessage_6eccab530a8fb4c7 = function(arg0, arg1) {
arg0.onmessage = arg1;
};
imports.wbg.__wbg_setonopen_2da654e1f39745d5 = function(arg0, arg1) {
arg0.onopen = arg1;
};
imports.wbg.__wbg_setonsuccess_afa464ee777a396d = function(arg0, arg1) { imports.wbg.__wbg_setonsuccess_afa464ee777a396d = function(arg0, arg1) {
arg0.onsuccess = arg1; arg0.onsuccess = arg1;
}; };
@ -695,6 +957,10 @@ function __wbg_get_imports() {
const ret = arg0.then(arg1); const ret = arg0.then(arg1);
return ret; return ret;
}; };
imports.wbg.__wbg_then_48b406749878a531 = function(arg0, arg1, arg2) {
const ret = arg0.then(arg1, arg2);
return ret;
};
imports.wbg.__wbg_transaction_d6d07c3c9963c49e = function() { return handleError(function (arg0, arg1, arg2) { imports.wbg.__wbg_transaction_d6d07c3c9963c49e = function() { return handleError(function (arg0, arg1, arg2) {
const ret = arg0.transaction(arg1, __wbindgen_enum_IdbTransactionMode[arg2]); const ret = arg0.transaction(arg1, __wbindgen_enum_IdbTransactionMode[arg2]);
return ret; return ret;
@ -703,6 +969,9 @@ function __wbg_get_imports() {
const ret = arg0.versions; const ret = arg0.versions;
return ret; return ret;
}; };
imports.wbg.__wbg_warn_4ca3906c248c47c4 = function(arg0) {
console.warn(arg0);
};
imports.wbg.__wbindgen_cb_drop = function(arg0) { imports.wbg.__wbindgen_cb_drop = function(arg0) {
const obj = arg0.original; const obj = arg0.original;
if (obj.cnt-- == 1) { if (obj.cnt-- == 1) {
@ -712,16 +981,40 @@ function __wbg_get_imports() {
const ret = false; const ret = false;
return ret; return ret;
}; };
imports.wbg.__wbindgen_closure_wrapper378 = function(arg0, arg1, arg2) { imports.wbg.__wbindgen_closure_wrapper1181 = function(arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 122, __wbg_adapter_32); const ret = makeMutClosure(arg0, arg1, 350, __wbg_adapter_55);
return ret; return ret;
}; };
imports.wbg.__wbindgen_closure_wrapper549 = function(arg0, arg1, arg2) { imports.wbg.__wbindgen_closure_wrapper335 = function(arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 151, __wbg_adapter_35); const ret = makeMutClosure(arg0, arg1, 133, __wbg_adapter_34);
return ret; return ret;
}; };
imports.wbg.__wbindgen_closure_wrapper857 = function(arg0, arg1, arg2) { imports.wbg.__wbindgen_closure_wrapper336 = function(arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 228, __wbg_adapter_38); const ret = makeMutClosure(arg0, arg1, 133, __wbg_adapter_34);
return ret;
};
imports.wbg.__wbindgen_closure_wrapper337 = function(arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 133, __wbg_adapter_39);
return ret;
};
imports.wbg.__wbindgen_closure_wrapper340 = function(arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 133, __wbg_adapter_34);
return ret;
};
imports.wbg.__wbindgen_closure_wrapper657 = function(arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 200, __wbg_adapter_44);
return ret;
};
imports.wbg.__wbindgen_closure_wrapper658 = function(arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 200, __wbg_adapter_44);
return ret;
};
imports.wbg.__wbindgen_closure_wrapper661 = function(arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 200, __wbg_adapter_49);
return ret;
};
imports.wbg.__wbindgen_closure_wrapper876 = function(arg0, arg1, arg2) {
const ret = makeMutClosure(arg0, arg1, 265, __wbg_adapter_52);
return ret; return ret;
}; };
imports.wbg.__wbindgen_debug_string = function(arg0, arg1) { imports.wbg.__wbindgen_debug_string = function(arg0, arg1) {
@ -778,6 +1071,14 @@ function __wbg_get_imports() {
const ret = wasm.memory; const ret = wasm.memory;
return ret; return ret;
}; };
imports.wbg.__wbindgen_string_get = function(arg0, arg1) {
const obj = arg1;
const ret = typeof(obj) === 'string' ? obj : undefined;
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.__wbindgen_string_new = function(arg0, arg1) { imports.wbg.__wbindgen_string_new = function(arg0, arg1) {
const ret = getStringFromWasm0(arg0, arg1); const ret = getStringFromWasm0(arg0, arg1);
return ret; return ret;