sal-modular/crypto_vault_extension/background/sigsocket.js
Sameh Abouel-saad 6f42e5ab8d v2
2025-06-06 05:31:03 +03:00

410 lines
13 KiB
JavaScript

/**
* SigSocket Service - Clean Implementation with New WASM APIs
*
* This service provides a clean interface for SigSocket functionality using
* the new WASM-based APIs that handle all WebSocket management, request storage,
* and security validation internally.
*
* Architecture:
* - WASM handles: WebSocket connection, message parsing, request storage, security
* - Extension handles: UI notifications, badge updates, user interactions
*/
class SigSocketService {
constructor() {
// Connection state
this.isConnected = false;
this.currentWorkspace = null;
this.connectedPublicKey = null;
// Configuration
this.defaultServerUrl = "ws://localhost:8080/ws";
// WASM module reference
this.wasmModule = null;
// UI communication
this.popupPort = null;
}
/**
* Initialize the service with WASM module
* @param {Object} wasmModule - The loaded WASM module with SigSocketManager
*/
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);
}
console.log('🔌 SigSocket service initialized with WASM APIs');
}
/**
* Connect to SigSocket server using WASM APIs
* WASM handles all connection logic (reuse, switching, etc.)
* @param {string} workspaceId - The workspace/keyspace identifier
* @returns {Promise<boolean>} - True if connected successfully
*/
async connectToServer(workspaceId) {
try {
if (!this.wasmModule?.SigSocketManager) {
throw new Error('WASM SigSocketManager not available');
}
console.log(`🔗 Requesting SigSocket connection for workspace: ${workspaceId}`);
// Let WASM handle all connection logic (reuse, switching, etc.)
const connectionInfo = await this.wasmModule.SigSocketManager.connect_workspace_with_events(
workspaceId,
this.defaultServerUrl,
(event) => this.handleSigSocketEvent(event)
);
// Parse connection info
const info = JSON.parse(connectionInfo);
this.currentWorkspace = info.workspace;
this.connectedPublicKey = info.public_key;
this.isConnected = info.is_connected;
console.log(`✅ SigSocket connection result:`, {
workspace: this.currentWorkspace,
publicKey: this.connectedPublicKey?.substring(0, 16) + '...',
connected: this.isConnected
});
// Update badge to show current state
this.updateBadge();
return this.isConnected;
} catch (error) {
console.error('❌ SigSocket connection failed:', error);
this.isConnected = false;
this.currentWorkspace = null;
this.connectedPublicKey = null;
return false;
}
}
/**
* Handle events from the WASM SigSocket client
* This is called automatically when requests arrive
* @param {Object} event - Event from WASM layer
*/
handleSigSocketEvent(event) {
console.log('📨 Received SigSocket event:', event);
if (event.type === 'sign_request') {
console.log(`🔐 New sign request: ${event.request_id}`);
// The request is automatically stored by WASM
// We just handle UI updates
this.showSignRequestNotification();
this.updateBadge();
this.notifyPopupOfNewRequest();
}
}
/**
* Approve a sign request using WASM APIs
* @param {string} requestId - Request to approve
* @returns {Promise<boolean>} - True if approved successfully
*/
async approveSignRequest(requestId) {
try {
if (!this.wasmModule?.SigSocketManager) {
throw new Error('WASM SigSocketManager not available');
}
console.log(`✅ Approving request: ${requestId}`);
// WASM handles all validation, signing, and server communication
await this.wasmModule.SigSocketManager.approve_request(requestId);
console.log(`🎉 Request approved successfully: ${requestId}`);
// Update UI
this.updateBadge();
this.notifyPopupOfRequestUpdate();
return true;
} catch (error) {
console.error(`❌ Failed to approve request ${requestId}:`, error);
return false;
}
}
/**
* Reject a sign request using WASM APIs
* @param {string} requestId - Request to reject
* @param {string} reason - Reason for rejection
* @returns {Promise<boolean>} - True if rejected successfully
*/
async rejectSignRequest(requestId, reason = 'User rejected') {
try {
if (!this.wasmModule?.SigSocketManager) {
throw new Error('WASM SigSocketManager not available');
}
console.log(`❌ Rejecting request: ${requestId}, reason: ${reason}`);
// WASM handles rejection and server communication
await this.wasmModule.SigSocketManager.reject_request(requestId, reason);
console.log(`✅ Request rejected successfully: ${requestId}`);
// Update UI
this.updateBadge();
this.notifyPopupOfRequestUpdate();
return true;
} catch (error) {
console.error(`❌ Failed to reject request ${requestId}:`, error);
return false;
}
}
/**
* Get pending requests from WASM (filtered by current workspace)
* @returns {Promise<Array>} - Array of pending requests for current workspace
*/
async getPendingRequests() {
return this.getFilteredRequests();
}
/**
* Get filtered requests from WASM (workspace-aware)
* @returns {Promise<Array>} - Array of filtered requests
*/
async getFilteredRequests() {
try {
if (!this.wasmModule?.SigSocketManager) {
return [];
}
const requestsJson = await this.wasmModule.SigSocketManager.get_filtered_requests();
const requests = JSON.parse(requestsJson);
console.log(`📋 Retrieved ${requests.length} filtered requests for current workspace`);
return requests;
} catch (error) {
console.error('Failed to get filtered requests:', error);
return [];
}
}
/**
* Check if a request can be approved (keyspace validation)
* @param {string} requestId - Request ID to check
* @returns {Promise<boolean>} - True if can be approved
*/
async canApproveRequest(requestId) {
try {
if (!this.wasmModule?.SigSocketManager) {
return false;
}
return await this.wasmModule.SigSocketManager.can_approve_request(requestId);
} catch (error) {
console.error('Failed to check request approval status:', error);
return false;
}
}
/**
* Show notification for new sign request
*/
showSignRequestNotification() {
try {
if (chrome.notifications && chrome.notifications.create) {
chrome.notifications.create({
type: 'basic',
iconUrl: 'icons/icon48.png',
title: 'SigSocket Sign Request',
message: 'New signature request received. Click to review.'
});
console.log('📢 Notification shown for sign request');
} else {
console.log('📢 Notifications not available, skipping notification');
}
} catch (error) {
console.warn('Failed to show notification:', error);
}
}
/**
* Update extension badge with pending request count
*/
async updateBadge() {
try {
const requests = await this.getPendingRequests();
const count = requests.length;
const badgeText = count > 0 ? count.toString() : '';
console.log(`🔢 Updating badge: ${count} pending requests`);
chrome.action.setBadgeText({ text: badgeText });
chrome.action.setBadgeBackgroundColor({ color: '#ff6b6b' });
} catch (error) {
console.error('Failed to update badge:', error);
}
}
/**
* Notify popup about new request
*/
async notifyPopupOfNewRequest() {
if (!this.popupPort) {
console.log('No popup connected, skipping notification');
return;
}
try {
const requests = await this.getPendingRequests();
const canApprove = requests.length > 0 ? await this.canApproveRequest(requests[0].id) : false;
this.popupPort.postMessage({
type: 'NEW_SIGN_REQUEST',
canApprove,
pendingRequests: requests
});
console.log(`📤 Notified popup: ${requests.length} requests, canApprove: ${canApprove}`);
} catch (error) {
console.error('Failed to notify popup:', error);
}
}
/**
* Notify popup about request updates
*/
async notifyPopupOfRequestUpdate() {
if (!this.popupPort) return;
try {
const requests = await this.getPendingRequests();
this.popupPort.postMessage({
type: 'REQUESTS_UPDATED',
pendingRequests: requests
});
} catch (error) {
console.error('Failed to notify popup of update:', error);
}
}
/**
* Disconnect from SigSocket server
* WASM handles all disconnection logic
*/
async disconnect() {
try {
if (this.wasmModule?.SigSocketManager) {
await this.wasmModule.SigSocketManager.disconnect();
}
// Clear local state
this.isConnected = false;
this.currentWorkspace = null;
this.connectedPublicKey = null;
this.updateBadge();
console.log('🔌 SigSocket disconnection requested');
} catch (error) {
console.error('Failed to disconnect:', error);
}
}
/**
* Get connection status from WASM
* @returns {Promise<Object>} - Connection status information
*/
async getStatus() {
try {
if (!this.wasmModule?.SigSocketManager) {
return {
isConnected: false,
workspace: null,
publicKey: null,
pendingRequestCount: 0,
serverUrl: this.defaultServerUrl
};
}
// Let WASM provide the authoritative status
const statusJson = await this.wasmModule.SigSocketManager.get_connection_status();
const status = JSON.parse(statusJson);
const requests = await this.getPendingRequests();
return {
isConnected: status.is_connected,
workspace: status.workspace,
publicKey: status.public_key,
pendingRequestCount: requests.length,
serverUrl: this.defaultServerUrl
};
} catch (error) {
console.error('Failed to get status:', error);
return {
isConnected: false,
workspace: null,
publicKey: null,
pendingRequestCount: 0,
serverUrl: this.defaultServerUrl
};
}
}
/**
* Set the popup port for communication
* @param {chrome.runtime.Port} port - The popup port
*/
setPopupPort(port) {
this.popupPort = port;
console.log('📱 Popup connected to SigSocket service');
}
/**
* Called when keyspace is unlocked - notify popup of current state
*/
async onKeypaceUnlocked() {
if (!this.popupPort) return;
try {
const requests = await this.getPendingRequests();
const canApprove = requests.length > 0 ? await this.canApproveRequest(requests[0].id) : false;
this.popupPort.postMessage({
type: 'KEYSPACE_UNLOCKED',
canApprove,
pendingRequests: requests
});
console.log(`🔓 Keyspace unlocked notification sent: ${requests.length} requests, canApprove: ${canApprove}`);
} catch (error) {
console.error('Failed to handle keyspace unlock:', error);
}
}
}
// Export for use in background script
export default SigSocketService;