v2
This commit is contained in:
@@ -1,31 +1,35 @@
|
||||
/**
|
||||
* 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
|
||||
* 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() {
|
||||
this.connection = null;
|
||||
this.pendingRequests = new Map(); // requestId -> SignRequestData
|
||||
this.connectedPublicKey = null;
|
||||
// Connection state
|
||||
this.isConnected = false;
|
||||
this.currentWorkspace = null;
|
||||
this.connectedPublicKey = null;
|
||||
|
||||
// Configuration
|
||||
this.defaultServerUrl = "ws://localhost:8080/ws";
|
||||
|
||||
// Initialize WASM module reference
|
||||
// WASM module reference
|
||||
this.wasmModule = null;
|
||||
|
||||
// Reference to popup port for communication
|
||||
// UI communication
|
||||
this.popupPort = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the service with WASM module
|
||||
* @param {Object} wasmModule - The loaded WASM module
|
||||
* @param {Object} wasmModule - The loaded WASM module with SigSocketManager
|
||||
*/
|
||||
async initialize(wasmModule) {
|
||||
this.wasmModule = wasmModule;
|
||||
@@ -40,427 +44,333 @@ class SigSocketService {
|
||||
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);
|
||||
console.log('🔌 SigSocket service initialized with WASM APIs');
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to SigSocket server for a workspace
|
||||
* 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) {
|
||||
throw new Error('WASM module not initialized');
|
||||
if (!this.wasmModule?.SigSocketManager) {
|
||||
throw new Error('WASM SigSocketManager not available');
|
||||
}
|
||||
|
||||
// Check if already connected to this workspace
|
||||
if (this.isConnected && this.connection) {
|
||||
console.log(`Already connected to SigSocket server for workspace: ${workspaceId}`);
|
||||
return true;
|
||||
}
|
||||
console.log(`🔗 Requesting SigSocket connection for workspace: ${workspaceId}`);
|
||||
|
||||
// Disconnect any existing connection first
|
||||
if (this.connection) {
|
||||
this.disconnect();
|
||||
}
|
||||
// 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)
|
||||
);
|
||||
|
||||
// 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');
|
||||
}
|
||||
// Parse connection info
|
||||
const info = JSON.parse(connectionInfo);
|
||||
this.currentWorkspace = info.workspace;
|
||||
this.connectedPublicKey = info.public_key;
|
||||
this.isConnected = info.is_connected;
|
||||
|
||||
console.log(`Connecting to SigSocket server for workspace: ${workspaceId} with key: ${publicKeyHex.substring(0, 16)}...`);
|
||||
console.log(`✅ SigSocket connection result:`, {
|
||||
workspace: this.currentWorkspace,
|
||||
publicKey: this.connectedPublicKey?.substring(0, 16) + '...',
|
||||
connected: this.isConnected
|
||||
});
|
||||
|
||||
// Create new SigSocket connection
|
||||
console.log('Creating new SigSocketConnection instance');
|
||||
this.connection = new this.wasmModule.SigSocketConnection();
|
||||
console.log('SigSocketConnection instance created');
|
||||
// Update badge to show current state
|
||||
this.updateBadge();
|
||||
|
||||
// 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;
|
||||
return this.isConnected;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to connect to SigSocket server:', error);
|
||||
console.error('❌ SigSocket connection failed:', error);
|
||||
this.isConnected = false;
|
||||
this.connectedPublicKey = null;
|
||||
this.currentWorkspace = null;
|
||||
if (this.connection) {
|
||||
this.connection.disconnect();
|
||||
this.connection = null;
|
||||
}
|
||||
this.connectedPublicKey = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle incoming sign request from server
|
||||
* @param {string} requestId - Unique request identifier
|
||||
* @param {string} messageBase64 - Message to be signed (base64-encoded)
|
||||
* Handle events from the WASM SigSocket client
|
||||
* This is called automatically when requests arrive
|
||||
* @param {Object} event - Event from WASM layer
|
||||
*/
|
||||
handleIncomingRequest(requestId, messageBase64) {
|
||||
console.log(`Received sign request: ${requestId}`);
|
||||
handleSigSocketEvent(event) {
|
||||
console.log('📨 Received SigSocket event:', event);
|
||||
|
||||
// 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;
|
||||
}
|
||||
if (event.type === 'sign_request') {
|
||||
console.log(`🔐 New sign request: ${event.request_id}`);
|
||||
|
||||
// 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
|
||||
// The request is automatically stored by WASM
|
||||
// We just handle UI updates
|
||||
this.showSignRequestNotification();
|
||||
this.updateBadge();
|
||||
this.notifyPopupOfNewRequest();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Approve a sign request using WASM APIs
|
||||
* @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');
|
||||
if (!this.wasmModule?.SigSocketManager) {
|
||||
throw new Error('WASM SigSocketManager not available');
|
||||
}
|
||||
|
||||
// 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}'`);
|
||||
}
|
||||
console.log(`✅ Approving request: ${requestId}`);
|
||||
|
||||
if (request.connectedPublicKey !== this.connectedPublicKey) {
|
||||
throw new Error('Request public key does not match current connection');
|
||||
}
|
||||
// WASM handles all validation, signing, and server communication
|
||||
await this.wasmModule.SigSocketManager.approve_request(requestId);
|
||||
|
||||
// Validate keyspace is still unlocked and matches
|
||||
if (!this.wasmModule.is_unlocked()) {
|
||||
throw new Error('Keyspace is locked');
|
||||
}
|
||||
console.log(`🎉 Request approved successfully: ${requestId}`);
|
||||
|
||||
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
|
||||
// Update UI
|
||||
this.updateBadge();
|
||||
|
||||
console.log(`Approved sign request: ${requestId}`);
|
||||
this.notifyPopupOfRequestUpdate();
|
||||
|
||||
return true;
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to approve sign request:', error);
|
||||
console.error(`❌ Failed to approve request ${requestId}:`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reject a sign request
|
||||
* Reject a sign request using WASM APIs
|
||||
* @param {string} requestId - Request to reject
|
||||
* @param {string} reason - Reason for rejection (optional)
|
||||
* @param {string} reason - Reason for rejection
|
||||
* @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');
|
||||
if (!this.wasmModule?.SigSocketManager) {
|
||||
throw new Error('WASM SigSocketManager not available');
|
||||
}
|
||||
|
||||
// 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
|
||||
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();
|
||||
|
||||
console.log(`Rejected sign request: ${requestId}, reason: ${reason}`);
|
||||
this.notifyPopupOfRequestUpdate();
|
||||
|
||||
return true;
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to reject sign request:', error);
|
||||
console.error(`❌ Failed to reject request ${requestId}:`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all pending requests for the current workspace
|
||||
* @returns {Array} - Array of pending request data for current workspace
|
||||
* Get pending requests from WASM (filtered by current workspace)
|
||||
* @returns {Promise<Array>} - Array of pending requests for current workspace
|
||||
*/
|
||||
getPendingRequests() {
|
||||
const allRequests = Array.from(this.pendingRequests.values());
|
||||
async getPendingRequests() {
|
||||
return this.getFilteredRequests();
|
||||
}
|
||||
|
||||
// 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}`);
|
||||
/**
|
||||
* Get filtered requests from WASM (workspace-aware)
|
||||
* @returns {Promise<Array>} - Array of filtered requests
|
||||
*/
|
||||
async getFilteredRequests() {
|
||||
try {
|
||||
if (!this.wasmModule?.SigSocketManager) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return isCurrentWorkspace && isCurrentPublicKey;
|
||||
});
|
||||
const requestsJson = await this.wasmModule.SigSocketManager.get_filtered_requests();
|
||||
const requests = JSON.parse(requestsJson);
|
||||
|
||||
console.log(`getPendingRequests: ${allRequests.length} total, ${filteredRequests.length} for current workspace`);
|
||||
return filteredRequests;
|
||||
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() {
|
||||
// Create notification
|
||||
chrome.notifications.create({
|
||||
type: 'basic',
|
||||
iconUrl: 'icons/icon48.png',
|
||||
title: 'Sign Request',
|
||||
message: 'New signature request received. Click to review.'
|
||||
});
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify popup about new request if popup is open and keyspace is unlocked
|
||||
* 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() {
|
||||
// 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');
|
||||
console.log('No popup connected, skipping notification');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Check if keyspace is unlocked
|
||||
if (!this.wasmModule.is_unlocked()) {
|
||||
console.log('Keyspace is locked, skipping new request notification');
|
||||
return;
|
||||
}
|
||||
const requests = await this.getPendingRequests();
|
||||
const canApprove = requests.length > 0 ? await this.canApproveRequest(requests[0].id) : false;
|
||||
|
||||
// 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
|
||||
canApprove,
|
||||
pendingRequests: requests
|
||||
});
|
||||
|
||||
console.log(`Sent NEW_SIGN_REQUEST message to popup: keypaceMatches=${keypaceMatches}, ${requestsToSend.length} requests (${currentPendingRequests.length} total for connected workspace)`);
|
||||
console.log(`📤 Notified popup: ${requests.length} requests, canApprove: ${canApprove}`);
|
||||
|
||||
} catch (error) {
|
||||
console.log('Error in notifyPopupOfNewRequest:', error);
|
||||
console.error('Failed to notify popup:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update extension badge with pending request count for current workspace
|
||||
* Notify popup about request updates
|
||||
*/
|
||||
updateBadge() {
|
||||
// Only count requests for the current workspace
|
||||
const currentWorkspaceRequests = this.getPendingRequests();
|
||||
const count = currentWorkspaceRequests.length;
|
||||
const badgeText = count > 0 ? count.toString() : '';
|
||||
async notifyPopupOfRequestUpdate() {
|
||||
if (!this.popupPort) return;
|
||||
|
||||
console.log(`Updating badge: ${this.pendingRequests.size} total requests, ${count} for current workspace, badge text: "${badgeText}"`);
|
||||
try {
|
||||
const requests = await this.getPendingRequests();
|
||||
|
||||
chrome.action.setBadgeText({ text: badgeText });
|
||||
chrome.action.setBadgeBackgroundColor({ color: '#ff6b6b' });
|
||||
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
|
||||
*/
|
||||
disconnect() {
|
||||
if (this.connection) {
|
||||
this.connection.disconnect();
|
||||
this.connection = null;
|
||||
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);
|
||||
}
|
||||
|
||||
this.isConnected = false;
|
||||
this.connectedPublicKey = null;
|
||||
this.currentWorkspace = null;
|
||||
this.pendingRequests.clear();
|
||||
this.updateBadge();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection status
|
||||
* @returns {Object} - Connection status information
|
||||
* Get connection status from WASM
|
||||
* @returns {Promise<Object>} - Connection status information
|
||||
*/
|
||||
getStatus() {
|
||||
return {
|
||||
isConnected: this.isConnected,
|
||||
connectedPublicKey: this.connectedPublicKey,
|
||||
pendingRequestCount: this.getPendingRequests().length,
|
||||
serverUrl: this.defaultServerUrl
|
||||
};
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -469,8 +379,32 @@ class SigSocketService {
|
||||
*/
|
||||
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;
|
||||
export default SigSocketService;
|
Reference in New Issue
Block a user