feat: Implement SigSocket request queuing and approval system, Enhance Settings UI
This commit is contained in:
parent
c641d0ae2e
commit
4f3f98a954
@ -29,7 +29,21 @@ function startSessionTimeout() {
|
||||
if (vault && currentSession) {
|
||||
// Lock the session
|
||||
vault.lock_session();
|
||||
|
||||
// Keep the session info for SigSocket connection but mark it as timed out
|
||||
const keyspace = currentSession.keyspace;
|
||||
await sessionManager.clear();
|
||||
|
||||
// Maintain SigSocket connection for the locked keyspace to receive pending requests
|
||||
if (sigSocketService && keyspace) {
|
||||
try {
|
||||
// Keep SigSocket connected to receive requests even when locked
|
||||
console.log(`🔒 Session timed out but maintaining SigSocket connection for: ${keyspace}`);
|
||||
} catch (error) {
|
||||
console.warn('Failed to maintain SigSocket connection after timeout:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Notify popup if it's open
|
||||
if (popupPort) {
|
||||
popupPort.postMessage({
|
||||
@ -130,12 +144,48 @@ async function restoreSession() {
|
||||
if (isUnlocked) {
|
||||
// Restart keep-alive for restored session
|
||||
startKeepAlive();
|
||||
|
||||
// Connect to SigSocket for the restored session
|
||||
if (sigSocketService) {
|
||||
try {
|
||||
const connected = await sigSocketService.connectToServer(session.keyspace);
|
||||
if (connected) {
|
||||
console.log(`🔗 SigSocket reconnected for restored workspace: ${session.keyspace}`);
|
||||
}
|
||||
} catch (error) {
|
||||
// Don't show as warning if it's just "no workspace" - this is expected on fresh start
|
||||
if (error.message && error.message.includes('Workspace not found')) {
|
||||
console.log(`ℹ️ SigSocket connection skipped for restored session: No workspace available yet`);
|
||||
} else {
|
||||
console.warn('Failed to reconnect SigSocket for restored session:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return session;
|
||||
} else {
|
||||
await sessionManager.clear();
|
||||
// Session exists but is locked - still try to connect SigSocket to receive pending requests
|
||||
if (sigSocketService && session.keyspace) {
|
||||
try {
|
||||
const connected = await sigSocketService.connectToServer(session.keyspace);
|
||||
if (connected) {
|
||||
console.log(`🔗 SigSocket connected for locked workspace: ${session.keyspace} (will queue requests)`);
|
||||
}
|
||||
} catch (error) {
|
||||
// Don't show as warning if it's just "no workspace" - this is expected on fresh start
|
||||
if (error.message && error.message.includes('Workspace not found')) {
|
||||
console.log(`ℹ️ SigSocket connection skipped for locked session: No workspace available yet`);
|
||||
} else {
|
||||
console.warn('Failed to connect SigSocket for locked session:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Don't clear the session - keep it for SigSocket connection
|
||||
// await sessionManager.clear();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return session; // Return session even if locked, so we know which keyspace to use
|
||||
}
|
||||
|
||||
// Import WASM module functions and SigSocket service
|
||||
@ -187,14 +237,31 @@ const messageHandlers = {
|
||||
// Smart auto-connect to SigSocket when session is initialized
|
||||
if (sigSocketService) {
|
||||
try {
|
||||
console.log(`🔗 Initializing SigSocket connection for workspace: ${request.keyspace}`);
|
||||
|
||||
// This will reuse existing connection if same workspace, or switch if different
|
||||
const connected = await sigSocketService.connectToServer(request.keyspace);
|
||||
if (connected) {
|
||||
console.log(`🔗 SigSocket ready for workspace: ${request.keyspace}`);
|
||||
console.log(`✅ SigSocket ready for workspace: ${request.keyspace}`);
|
||||
} else {
|
||||
console.warn(`⚠️ SigSocket connection failed for workspace: ${request.keyspace}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to auto-connect to SigSocket:', error);
|
||||
|
||||
// If connection fails, try once more after a short delay
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
console.log(`🔄 Retrying SigSocket connection for workspace: ${request.keyspace}`);
|
||||
await sigSocketService.connectToServer(request.keyspace);
|
||||
} catch (retryError) {
|
||||
console.warn('SigSocket retry connection also failed:', retryError);
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// Notify SigSocket service that keyspace is now unlocked
|
||||
await sigSocketService.onKeypaceUnlocked();
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
@ -288,6 +355,19 @@ const messageHandlers = {
|
||||
return { success: true };
|
||||
},
|
||||
|
||||
updateSigSocketUrl: async (request) => {
|
||||
if (sigSocketService) {
|
||||
// Update the server URL in the SigSocket service
|
||||
sigSocketService.defaultServerUrl = request.serverUrl;
|
||||
|
||||
// Save to storage (already done in popup, but ensure consistency)
|
||||
await chrome.storage.local.set({ sigSocketUrl: request.serverUrl });
|
||||
|
||||
console.log(`🔗 SigSocket server URL updated to: ${request.serverUrl}`);
|
||||
}
|
||||
return { success: true };
|
||||
},
|
||||
|
||||
// SigSocket handlers
|
||||
connectSigSocket: async (request) => {
|
||||
if (!sigSocketService) {
|
||||
@ -313,6 +393,15 @@ const messageHandlers = {
|
||||
return { success: true, status };
|
||||
},
|
||||
|
||||
getSigSocketStatusWithTest: async () => {
|
||||
if (!sigSocketService) {
|
||||
return { success: false, error: 'SigSocket service not initialized' };
|
||||
}
|
||||
// Use the enhanced connection testing method
|
||||
const status = await sigSocketService.getStatusWithConnectionTest();
|
||||
return { success: true, status };
|
||||
},
|
||||
|
||||
getPendingSignRequests: async () => {
|
||||
if (!sigSocketService) {
|
||||
return { success: false, error: 'SigSocket service not initialized' };
|
||||
@ -393,6 +482,25 @@ chrome.runtime.onConnect.addListener((port) => {
|
||||
startKeepAlive();
|
||||
}
|
||||
|
||||
// Handle messages from popup
|
||||
port.onMessage.addListener(async (message) => {
|
||||
if (message.type === 'REQUEST_IMMEDIATE_STATUS') {
|
||||
// Immediately send current SigSocket status to popup
|
||||
if (sigSocketService) {
|
||||
try {
|
||||
const status = await sigSocketService.getStatus();
|
||||
port.postMessage({
|
||||
type: 'CONNECTION_STATUS_CHANGED',
|
||||
status: status
|
||||
});
|
||||
console.log('📡 Sent immediate status to popup:', status.isConnected ? 'Connected' : 'Disconnected');
|
||||
} catch (error) {
|
||||
console.warn('Failed to send immediate status:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
port.onDisconnect.addListener(() => {
|
||||
// Popup closed, clear reference and stop keep-alive
|
||||
popupPort = null;
|
||||
@ -405,3 +513,158 @@ chrome.runtime.onConnect.addListener((port) => {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Handle notification clicks to open extension (notifications are now clickable without buttons)
|
||||
chrome.notifications.onClicked.addListener(async (notificationId) => {
|
||||
console.log(`🔔 Notification clicked: ${notificationId}`);
|
||||
|
||||
// Check if this is a SigSocket notification
|
||||
if (notificationId.startsWith('sigsocket-request-')) {
|
||||
console.log('🔔 SigSocket notification clicked, opening extension...');
|
||||
try {
|
||||
await openExtensionPopup();
|
||||
// Clear the notification after successfully opening
|
||||
chrome.notifications.clear(notificationId);
|
||||
console.log('✅ Notification cleared after opening extension');
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to handle notification click:', error);
|
||||
}
|
||||
} else {
|
||||
console.log('🔔 Non-SigSocket notification clicked, ignoring');
|
||||
}
|
||||
});
|
||||
|
||||
// Note: Notification button handler removed - notifications are now clickable without buttons
|
||||
|
||||
// Function to open extension popup with best UX
|
||||
async function openExtensionPopup() {
|
||||
try {
|
||||
console.log('🔔 Opening extension popup from notification...');
|
||||
|
||||
// First, check if there's already a popup window open
|
||||
const windows = await chrome.windows.getAll({ populate: true });
|
||||
const existingPopup = windows.find(window =>
|
||||
window.type === 'popup' &&
|
||||
window.tabs?.some(tab => tab.url?.includes('popup.html'))
|
||||
);
|
||||
|
||||
if (existingPopup) {
|
||||
// Focus existing popup and send focus message
|
||||
await chrome.windows.update(existingPopup.id, { focused: true });
|
||||
console.log('✅ Focused existing popup window');
|
||||
|
||||
// Send message to focus on SigSocket section
|
||||
if (popupPort) {
|
||||
popupPort.postMessage({
|
||||
type: 'FOCUS_SIGSOCKET',
|
||||
fromNotification: true
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Best UX: Try to use the normal popup experience
|
||||
// The action API gives the same popup as clicking the extension icon
|
||||
try {
|
||||
if (chrome.action && chrome.action.openPopup) {
|
||||
await chrome.action.openPopup();
|
||||
console.log('✅ Extension popup opened via action API (best UX - normal popup)');
|
||||
|
||||
// Send focus message after popup opens
|
||||
setTimeout(() => {
|
||||
if (popupPort) {
|
||||
popupPort.postMessage({
|
||||
type: 'FOCUS_SIGSOCKET',
|
||||
fromNotification: true
|
||||
});
|
||||
}
|
||||
}, 200);
|
||||
|
||||
return;
|
||||
}
|
||||
} catch (actionError) {
|
||||
// The action API fails when there's no active browser window
|
||||
// This is common when all browser windows are closed but extension is still running
|
||||
console.log('⚠️ Action API failed (likely no active window):', actionError.message);
|
||||
|
||||
// Check if we have any normal browser windows
|
||||
const allWindows = await chrome.windows.getAll();
|
||||
const normalWindows = allWindows.filter(w => w.type === 'normal');
|
||||
|
||||
if (normalWindows.length > 0) {
|
||||
// We have browser windows, try to focus one and retry action API
|
||||
try {
|
||||
const targetWindow = normalWindows.find(w => w.focused) || normalWindows[0];
|
||||
await chrome.windows.update(targetWindow.id, { focused: true });
|
||||
|
||||
// Small delay and retry
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await chrome.action.openPopup();
|
||||
console.log('✅ Extension popup opened via action API after focusing window');
|
||||
|
||||
setTimeout(() => {
|
||||
if (popupPort) {
|
||||
popupPort.postMessage({
|
||||
type: 'FOCUS_SIGSOCKET',
|
||||
fromNotification: true
|
||||
});
|
||||
}
|
||||
}, 200);
|
||||
|
||||
return;
|
||||
} catch (retryError) {
|
||||
console.log('⚠️ Action API retry also failed:', retryError.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If action API fails completely, we need to create a window
|
||||
// But let's make it as close to the normal popup experience as possible
|
||||
console.log('⚠️ Creating popup window as fallback (action API unavailable)');
|
||||
|
||||
const popupUrl = chrome.runtime.getURL('popup.html?from=notification');
|
||||
|
||||
// Position the popup where the extension icon would normally show its popup
|
||||
// Try to position it in the top-right area like a normal extension popup
|
||||
let left = screen.width - 420; // 400px width + 20px margin
|
||||
let top = 80; // Below browser toolbar area
|
||||
|
||||
try {
|
||||
// If we have a browser window, position relative to it
|
||||
const allWindows = await chrome.windows.getAll();
|
||||
const normalWindows = allWindows.filter(w => w.type === 'normal');
|
||||
|
||||
if (normalWindows.length > 0) {
|
||||
const referenceWindow = normalWindows[0];
|
||||
left = (referenceWindow.left || 0) + (referenceWindow.width || 800) - 420;
|
||||
top = (referenceWindow.top || 0) + 80;
|
||||
}
|
||||
} catch (positionError) {
|
||||
console.log('⚠️ Could not get window position, using screen-based positioning');
|
||||
}
|
||||
|
||||
const newWindow = await chrome.windows.create({
|
||||
url: popupUrl,
|
||||
type: 'popup',
|
||||
width: 400,
|
||||
height: 600,
|
||||
left: Math.max(0, left),
|
||||
top: Math.max(0, top),
|
||||
focused: true
|
||||
});
|
||||
|
||||
console.log(`✅ Extension popup window created: ${newWindow.id}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to open extension popup:', error);
|
||||
|
||||
// Final fallback: open in new tab (least ideal but still functional)
|
||||
try {
|
||||
const popupUrl = chrome.runtime.getURL('popup.html?from=notification');
|
||||
await chrome.tabs.create({ url: popupUrl, active: true });
|
||||
console.log('✅ Opened extension in new tab as final fallback');
|
||||
} catch (tabError) {
|
||||
console.error('❌ All popup opening methods failed:', tabError);
|
||||
}
|
||||
}
|
||||
}
|
@ -25,6 +25,10 @@ class SigSocketService {
|
||||
|
||||
// UI communication
|
||||
this.popupPort = null;
|
||||
|
||||
// Status monitoring
|
||||
this.statusMonitorInterval = null;
|
||||
this.lastKnownConnectionState = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -44,72 +48,221 @@ class SigSocketService {
|
||||
console.warn('Failed to load SigSocket URL from storage:', error);
|
||||
}
|
||||
|
||||
// Restore any persisted pending requests
|
||||
await this.restorePendingRequests();
|
||||
|
||||
console.log('🔌 SigSocket service initialized with WASM APIs');
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore pending requests from persistent storage
|
||||
* Only restore requests that match the current workspace
|
||||
*/
|
||||
async restorePendingRequests() {
|
||||
try {
|
||||
const result = await chrome.storage.local.get(['sigSocketPendingRequests']);
|
||||
if (result.sigSocketPendingRequests && Array.isArray(result.sigSocketPendingRequests)) {
|
||||
console.log(`🔄 Found ${result.sigSocketPendingRequests.length} stored requests`);
|
||||
|
||||
// Filter requests for current workspace only
|
||||
const currentWorkspaceRequests = result.sigSocketPendingRequests.filter(request =>
|
||||
request.target_public_key === this.connectedPublicKey
|
||||
);
|
||||
|
||||
console.log(`🔄 Restoring ${currentWorkspaceRequests.length} requests for current workspace`);
|
||||
|
||||
// Add each workspace-specific request back to WASM storage
|
||||
for (const request of currentWorkspaceRequests) {
|
||||
try {
|
||||
await this.wasmModule.SigSocketManager.add_pending_request(JSON.stringify(request.request || request));
|
||||
console.log(`✅ Restored request: ${request.id || request.request?.id}`);
|
||||
} catch (error) {
|
||||
console.warn(`Failed to restore request ${request.id || request.request?.id}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// Update badge after restoration
|
||||
this.updateBadge();
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to restore pending requests:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist pending requests to storage with workspace isolation
|
||||
*/
|
||||
async persistPendingRequests() {
|
||||
try {
|
||||
const requests = await this.getFilteredRequests();
|
||||
|
||||
// Get existing storage to merge with other workspaces
|
||||
const result = await chrome.storage.local.get(['sigSocketPendingRequests']);
|
||||
const existingRequests = result.sigSocketPendingRequests || [];
|
||||
|
||||
// Remove old requests for current workspace
|
||||
const otherWorkspaceRequests = existingRequests.filter(request =>
|
||||
request.target_public_key !== this.connectedPublicKey
|
||||
);
|
||||
|
||||
// Combine with current workspace requests
|
||||
const allRequests = [...otherWorkspaceRequests, ...requests];
|
||||
|
||||
await chrome.storage.local.set({ sigSocketPendingRequests: allRequests });
|
||||
console.log(`💾 Persisted ${requests.length} requests for current workspace (${allRequests.length} total)`);
|
||||
} catch (error) {
|
||||
console.warn('Failed to persist pending requests:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to SigSocket server using WASM APIs
|
||||
* WASM handles all connection logic (reuse, switching, etc.)
|
||||
* @param {string} workspaceId - The workspace/keyspace identifier
|
||||
* @param {number} retryCount - Number of retry attempts (default: 3)
|
||||
* @returns {Promise<boolean>} - True if connected successfully
|
||||
*/
|
||||
async connectToServer(workspaceId) {
|
||||
try {
|
||||
if (!this.wasmModule?.SigSocketManager) {
|
||||
throw new Error('WASM SigSocketManager not available');
|
||||
async connectToServer(workspaceId, retryCount = 3) {
|
||||
for (let attempt = 1; attempt <= retryCount; attempt++) {
|
||||
try {
|
||||
if (!this.wasmModule?.SigSocketManager) {
|
||||
throw new Error('WASM SigSocketManager not available');
|
||||
}
|
||||
|
||||
console.log(`🔗 Requesting SigSocket connection for workspace: ${workspaceId} (attempt ${attempt}/${retryCount})`);
|
||||
|
||||
// Clean workspace switching
|
||||
if (this.currentWorkspace && this.currentWorkspace !== workspaceId) {
|
||||
console.log(`🔄 Clean workspace switch: ${this.currentWorkspace} -> ${workspaceId}`);
|
||||
await this.cleanWorkspaceSwitch(workspaceId);
|
||||
// Small delay to ensure clean state transition
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
}
|
||||
|
||||
// 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 = workspaceId; // Use the parameter we passed, not WASM response
|
||||
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,
|
||||
serverUrl: this.defaultServerUrl
|
||||
});
|
||||
|
||||
// Validate that we have a public key if connected
|
||||
if (this.isConnected && !this.connectedPublicKey) {
|
||||
console.warn('⚠️ Connected but no public key received - this may cause request issues');
|
||||
}
|
||||
|
||||
// Update badge to show current state
|
||||
this.updateBadge();
|
||||
|
||||
if (this.isConnected) {
|
||||
// Clean flow: Connect -> Restore workspace requests -> Update UI
|
||||
console.log(`🔗 Connected to workspace: ${workspaceId}, restoring pending requests...`);
|
||||
|
||||
// 1. Restore requests for this specific workspace
|
||||
await this.restorePendingRequests();
|
||||
|
||||
// 2. Update badge with current count
|
||||
this.updateBadge();
|
||||
|
||||
console.log(`✅ Workspace ${workspaceId} ready with restored requests`);
|
||||
return true;
|
||||
}
|
||||
|
||||
// If not connected but no error, try again
|
||||
if (attempt < retryCount) {
|
||||
console.log(`⏳ Connection not established, retrying in 1 second...`);
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
// Check if this is an expected "no workspace" error during startup
|
||||
const isExpectedStartupError = error.message &&
|
||||
(error.message.includes('Workspace not found') ||
|
||||
error.message.includes('no keypairs available'));
|
||||
|
||||
if (isExpectedStartupError && attempt === 1) {
|
||||
console.log(`⏳ SigSocket connection attempt ${attempt}: No active workspace (expected after extension reload)`);
|
||||
}
|
||||
|
||||
// Check if this is a public key related error
|
||||
if (error.message && error.message.includes('public key')) {
|
||||
console.error(`🔑 Public key error detected: ${error.message}`);
|
||||
// For public key errors, don't retry immediately - might need workspace change
|
||||
if (attempt === 1) {
|
||||
console.log(`🔄 Public key error on first attempt, trying to disconnect and reconnect...`);
|
||||
await this.disconnect();
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
}
|
||||
|
||||
if (attempt < retryCount) {
|
||||
if (!isExpectedStartupError) {
|
||||
console.log(`⏳ Retrying connection in 2 seconds...`);
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
} else {
|
||||
// Final attempt failed
|
||||
this.isConnected = false;
|
||||
this.currentWorkspace = null;
|
||||
this.connectedPublicKey = null;
|
||||
|
||||
if (isExpectedStartupError) {
|
||||
console.log(`ℹ️ SigSocket connection failed: No active workspace. Will connect when user logs in.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
async handleSigSocketEvent(event) {
|
||||
console.log('📨 Received SigSocket event:', event);
|
||||
|
||||
if (event.type === 'sign_request') {
|
||||
console.log(`🔐 New sign request: ${event.request_id}`);
|
||||
console.log(`🔐 New sign request: ${event.request_id} for workspace: ${this.currentWorkspace}`);
|
||||
|
||||
// The request is automatically stored by WASM
|
||||
// We just handle UI updates
|
||||
this.showSignRequestNotification();
|
||||
this.updateBadge();
|
||||
this.notifyPopupOfNewRequest();
|
||||
// Clean flow: Request arrives -> Store -> Persist -> Update UI
|
||||
try {
|
||||
// 1. Request is automatically stored in WASM (already done by WASM layer)
|
||||
|
||||
// 2. Persist to storage with workspace isolation
|
||||
await this.persistPendingRequests();
|
||||
|
||||
// 3. Update badge count
|
||||
this.updateBadge();
|
||||
|
||||
// 4. Show notification
|
||||
this.showSignRequestNotification();
|
||||
|
||||
// 5. Notify popup if connected
|
||||
this.notifyPopupOfNewRequest();
|
||||
|
||||
console.log(`✅ Request ${event.request_id} processed and stored for workspace: ${this.currentWorkspace}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ Failed to process request ${event.request_id}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,6 +277,19 @@ class SigSocketService {
|
||||
throw new Error('WASM SigSocketManager not available');
|
||||
}
|
||||
|
||||
// Check if we're connected before attempting approval
|
||||
if (!this.isConnected) {
|
||||
console.warn(`⚠️ Not connected to SigSocket server, cannot approve request: ${requestId}`);
|
||||
throw new Error('Not connected to SigSocket server');
|
||||
}
|
||||
|
||||
// Verify we can approve this request
|
||||
const canApprove = await this.canApproveRequest(requestId);
|
||||
if (!canApprove) {
|
||||
console.warn(`⚠️ Cannot approve request ${requestId} - keyspace may be locked or request not found`);
|
||||
throw new Error('Cannot approve request - keyspace may be locked or request not found');
|
||||
}
|
||||
|
||||
console.log(`✅ Approving request: ${requestId}`);
|
||||
|
||||
// WASM handles all validation, signing, and server communication
|
||||
@ -131,14 +297,37 @@ class SigSocketService {
|
||||
|
||||
console.log(`🎉 Request approved successfully: ${requestId}`);
|
||||
|
||||
// Update UI
|
||||
// Clean flow: Approve -> Remove from storage -> Update UI
|
||||
// 1. Remove from persistent storage (WASM already removed it)
|
||||
await this.persistPendingRequests();
|
||||
|
||||
// 2. Update badge count
|
||||
this.updateBadge();
|
||||
|
||||
// 3. Notify popup of updated state
|
||||
this.notifyPopupOfRequestUpdate();
|
||||
|
||||
console.log(`✅ Request ${requestId} approved and cleaned up`);
|
||||
return true;
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ Failed to approve request ${requestId}:`, error);
|
||||
|
||||
// Check if this is a connection-related error
|
||||
if (error.message && (error.message.includes('Connection not found') || error.message.includes('public key'))) {
|
||||
console.error(`🔑 Connection/public key error during approval. Current state:`, {
|
||||
connected: this.isConnected,
|
||||
workspace: this.currentWorkspace,
|
||||
publicKey: this.connectedPublicKey?.substring(0, 16) + '...'
|
||||
});
|
||||
|
||||
// Try to reconnect for next time
|
||||
if (this.currentWorkspace) {
|
||||
console.log(`🔄 Attempting to reconnect to workspace: ${this.currentWorkspace}`);
|
||||
setTimeout(() => this.connectToServer(this.currentWorkspace), 1000);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -162,10 +351,17 @@ class SigSocketService {
|
||||
|
||||
console.log(`✅ Request rejected successfully: ${requestId}`);
|
||||
|
||||
// Update UI
|
||||
// Clean flow: Reject -> Remove from storage -> Update UI
|
||||
// 1. Remove from persistent storage (WASM already removed it)
|
||||
await this.persistPendingRequests();
|
||||
|
||||
// 2. Update badge count
|
||||
this.updateBadge();
|
||||
|
||||
// 3. Notify popup of updated state
|
||||
this.notifyPopupOfRequestUpdate();
|
||||
|
||||
console.log(`✅ Request ${requestId} rejected and cleaned up`);
|
||||
return true;
|
||||
|
||||
} catch (error) {
|
||||
@ -224,18 +420,54 @@ class SigSocketService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Show notification for new sign request
|
||||
* Show clickable notification for new sign request
|
||||
* Call this AFTER the request has been stored and persisted
|
||||
*/
|
||||
showSignRequestNotification() {
|
||||
async showSignRequestNotification() {
|
||||
try {
|
||||
if (chrome.notifications && chrome.notifications.create) {
|
||||
chrome.notifications.create({
|
||||
// Small delay to ensure request is fully stored
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
console.log(`📢 Preparing notification for new signature request`);
|
||||
|
||||
// Check if keyspace is currently unlocked to customize message
|
||||
let message = 'New signature request received. Click to review and approve.';
|
||||
let title = 'SigSocket Sign Request';
|
||||
|
||||
// Try to determine if keyspace is locked
|
||||
try {
|
||||
const requests = await this.getPendingRequests();
|
||||
const canApprove = requests.length > 0 ? await this.canApproveRequest(requests[0].id) : false;
|
||||
if (!canApprove) {
|
||||
message = 'New signature request received. Click to unlock keyspace and approve.';
|
||||
title = 'SigSocket Request';
|
||||
}
|
||||
} catch (error) {
|
||||
// If we can't check, use generic message
|
||||
message = 'New signature request received. Click to open extension.';
|
||||
}
|
||||
|
||||
// Create clickable notification with unique ID
|
||||
const notificationId = `sigsocket-request-${Date.now()}`;
|
||||
|
||||
const notificationOptions = {
|
||||
type: 'basic',
|
||||
iconUrl: 'icons/icon48.png',
|
||||
title: 'SigSocket Sign Request',
|
||||
message: 'New signature request received. Click to review.'
|
||||
title: title,
|
||||
message: message,
|
||||
requireInteraction: true // Keep notification visible until user interacts
|
||||
};
|
||||
|
||||
console.log(`📢 Creating notification: ${notificationId}`, notificationOptions);
|
||||
|
||||
chrome.notifications.create(notificationId, notificationOptions, (createdId) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.error('❌ Failed to create notification:', chrome.runtime.lastError);
|
||||
} else {
|
||||
console.log(`✅ Notification created successfully: ${createdId}`);
|
||||
}
|
||||
});
|
||||
console.log('📢 Notification shown for sign request');
|
||||
} else {
|
||||
console.log('📢 Notifications not available, skipping notification');
|
||||
}
|
||||
@ -322,6 +554,10 @@ class SigSocketService {
|
||||
this.isConnected = false;
|
||||
this.currentWorkspace = null;
|
||||
this.connectedPublicKey = null;
|
||||
this.lastKnownConnectionState = false;
|
||||
|
||||
// Stop status monitoring
|
||||
this.stopStatusMonitoring();
|
||||
|
||||
this.updateBadge();
|
||||
|
||||
@ -333,7 +569,50 @@ class SigSocketService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection status from WASM
|
||||
* Clear persisted pending requests from storage
|
||||
*/
|
||||
async clearPersistedRequests() {
|
||||
try {
|
||||
await chrome.storage.local.remove(['sigSocketPendingRequests']);
|
||||
console.log('🗑️ Cleared persisted pending requests from storage');
|
||||
} catch (error) {
|
||||
console.warn('Failed to clear persisted requests:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean workspace switch - clear current workspace requests only
|
||||
*/
|
||||
async cleanWorkspaceSwitch(newWorkspace) {
|
||||
try {
|
||||
console.log(`🔄 Clean workspace switch: ${this.currentWorkspace} -> ${newWorkspace}`);
|
||||
|
||||
// 1. Persist current workspace requests before switching
|
||||
if (this.currentWorkspace && this.isConnected) {
|
||||
await this.persistPendingRequests();
|
||||
console.log(`💾 Saved requests for workspace: ${this.currentWorkspace}`);
|
||||
}
|
||||
|
||||
// 2. Clear WASM state (will be restored for new workspace)
|
||||
if (this.wasmModule?.SigSocketManager) {
|
||||
await this.wasmModule.SigSocketManager.clear_pending_requests();
|
||||
console.log('🧹 Cleared WASM request state');
|
||||
}
|
||||
|
||||
// 3. Reset local state
|
||||
this.currentWorkspace = null;
|
||||
this.connectedPublicKey = null;
|
||||
this.isConnected = false;
|
||||
|
||||
console.log('✅ Workspace switch cleanup completed');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to clean workspace switch:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection status with real connection verification
|
||||
* @returns {Promise<Object>} - Connection status information
|
||||
*/
|
||||
async getStatus() {
|
||||
@ -348,21 +627,63 @@ class SigSocketService {
|
||||
};
|
||||
}
|
||||
|
||||
// Let WASM provide the authoritative status
|
||||
// Get WASM status first
|
||||
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,
|
||||
// Verify connection by trying to get requests (this will fail if not connected)
|
||||
let actuallyConnected = false;
|
||||
let requests = [];
|
||||
|
||||
try {
|
||||
requests = await this.getPendingRequests();
|
||||
// If we can get requests and WASM says connected, we're probably connected
|
||||
actuallyConnected = status.is_connected && Array.isArray(requests);
|
||||
} catch (error) {
|
||||
// If getting requests fails, we're definitely not connected
|
||||
console.warn('Connection verification failed:', error);
|
||||
actuallyConnected = false;
|
||||
}
|
||||
|
||||
// Update our internal state
|
||||
this.isConnected = actuallyConnected;
|
||||
|
||||
if (status.connected_public_key && actuallyConnected) {
|
||||
this.connectedPublicKey = status.connected_public_key;
|
||||
} else {
|
||||
this.connectedPublicKey = null;
|
||||
}
|
||||
|
||||
// If we're disconnected, clear our workspace
|
||||
if (!actuallyConnected) {
|
||||
this.currentWorkspace = null;
|
||||
}
|
||||
|
||||
const statusResult = {
|
||||
isConnected: actuallyConnected,
|
||||
workspace: this.currentWorkspace,
|
||||
publicKey: status.connected_public_key,
|
||||
pendingRequestCount: requests.length,
|
||||
serverUrl: this.defaultServerUrl
|
||||
serverUrl: this.defaultServerUrl,
|
||||
// Clean flow status indicators
|
||||
cleanFlowReady: actuallyConnected && this.currentWorkspace && status.connected_public_key
|
||||
};
|
||||
|
||||
console.log('📊 Clean flow status:', {
|
||||
connected: statusResult.isConnected,
|
||||
workspace: statusResult.workspace,
|
||||
requestCount: statusResult.pendingRequestCount,
|
||||
flowReady: statusResult.cleanFlowReady
|
||||
});
|
||||
|
||||
return statusResult;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to get status:', error);
|
||||
// Clear state on error
|
||||
this.isConnected = false;
|
||||
this.currentWorkspace = null;
|
||||
this.connectedPublicKey = null;
|
||||
return {
|
||||
isConnected: false,
|
||||
workspace: null,
|
||||
@ -375,33 +696,178 @@ class SigSocketService {
|
||||
|
||||
/**
|
||||
* Set the popup port for communication
|
||||
* @param {chrome.runtime.Port} port - The popup port
|
||||
* @param {chrome.runtime.Port|null} port - The popup port or null to disconnect
|
||||
*/
|
||||
setPopupPort(port) {
|
||||
this.popupPort = port;
|
||||
console.log('📱 Popup connected to SigSocket service');
|
||||
|
||||
if (port) {
|
||||
console.log('📱 Popup connected to SigSocket service');
|
||||
|
||||
// Immediately check connection status when popup opens
|
||||
this.checkConnectionStatusNow();
|
||||
|
||||
// Start monitoring connection status when popup connects
|
||||
this.startStatusMonitoring();
|
||||
} else {
|
||||
console.log('📱 Popup disconnected from SigSocket service');
|
||||
// Stop monitoring when popup disconnects
|
||||
this.stopStatusMonitoring();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when keyspace is unlocked - notify popup of current state
|
||||
* Immediately check and update connection status
|
||||
*/
|
||||
async checkConnectionStatusNow() {
|
||||
try {
|
||||
// Force a fresh connection check
|
||||
const currentStatus = await this.getStatusWithConnectionTest();
|
||||
this.lastKnownConnectionState = currentStatus.isConnected;
|
||||
|
||||
// Notify popup of current status
|
||||
this.notifyPopupOfStatusChange(currentStatus);
|
||||
|
||||
console.log(`🔍 Immediate status check: ${currentStatus.isConnected ? 'Connected' : 'Disconnected'}`);
|
||||
} catch (error) {
|
||||
console.warn('Failed to check connection status immediately:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status with additional connection testing
|
||||
*/
|
||||
async getStatusWithConnectionTest() {
|
||||
const status = await this.getStatus();
|
||||
|
||||
// If WASM claims we're connected, do an additional verification
|
||||
if (status.isConnected) {
|
||||
try {
|
||||
// Try to get connection status again - if this fails, we're not really connected
|
||||
const verifyJson = await this.wasmModule.SigSocketManager.get_connection_status();
|
||||
const verifyStatus = JSON.parse(verifyJson);
|
||||
|
||||
if (!verifyStatus.is_connected) {
|
||||
console.log('🔍 Connection verification failed - marking as disconnected');
|
||||
status.isConnected = false;
|
||||
this.isConnected = false;
|
||||
this.currentWorkspace = null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('🔍 Connection test failed - marking as disconnected:', error.message);
|
||||
status.isConnected = false;
|
||||
this.isConnected = false;
|
||||
this.currentWorkspace = null;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start periodic status monitoring to detect connection changes
|
||||
*/
|
||||
startStatusMonitoring() {
|
||||
// Clear any existing monitoring
|
||||
if (this.statusMonitorInterval) {
|
||||
clearInterval(this.statusMonitorInterval);
|
||||
}
|
||||
|
||||
// Check status every 2 seconds when popup is open (more responsive)
|
||||
this.statusMonitorInterval = setInterval(async () => {
|
||||
if (this.popupPort) {
|
||||
try {
|
||||
const currentStatus = await this.getStatusWithConnectionTest();
|
||||
|
||||
// Check if connection status changed
|
||||
if (currentStatus.isConnected !== this.lastKnownConnectionState) {
|
||||
console.log(`🔄 Connection state changed: ${this.lastKnownConnectionState} -> ${currentStatus.isConnected}`);
|
||||
this.lastKnownConnectionState = currentStatus.isConnected;
|
||||
|
||||
// Notify popup of status change
|
||||
this.notifyPopupOfStatusChange(currentStatus);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Status monitoring error:', error);
|
||||
// On error, assume disconnected
|
||||
if (this.lastKnownConnectionState !== false) {
|
||||
console.log('🔄 Status monitoring error - marking as disconnected');
|
||||
this.lastKnownConnectionState = false;
|
||||
this.notifyPopupOfStatusChange({
|
||||
isConnected: false,
|
||||
workspace: null,
|
||||
publicKey: null,
|
||||
pendingRequestCount: 0,
|
||||
serverUrl: this.defaultServerUrl
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Stop monitoring when popup is closed
|
||||
this.stopStatusMonitoring();
|
||||
}
|
||||
}, 2000); // 2 seconds for better responsiveness
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop status monitoring
|
||||
*/
|
||||
stopStatusMonitoring() {
|
||||
if (this.statusMonitorInterval) {
|
||||
clearInterval(this.statusMonitorInterval);
|
||||
this.statusMonitorInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify popup of connection status change
|
||||
* @param {Object} status - Current connection status
|
||||
*/
|
||||
notifyPopupOfStatusChange(status) {
|
||||
if (this.popupPort) {
|
||||
this.popupPort.postMessage({
|
||||
type: 'CONNECTION_STATUS_CHANGED',
|
||||
status: status
|
||||
});
|
||||
console.log(`📡 Notified popup of connection status change: ${status.isConnected ? 'Connected' : 'Disconnected'}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when keyspace is unlocked - clean approach to show pending requests
|
||||
*/
|
||||
async onKeypaceUnlocked() {
|
||||
if (!this.popupPort) return;
|
||||
|
||||
try {
|
||||
console.log('🔓 Keyspace unlocked - preparing to show pending requests');
|
||||
|
||||
// 1. Restore any persisted requests for this workspace
|
||||
await this.restorePendingRequests();
|
||||
|
||||
// 2. Get current requests (includes restored + any new ones)
|
||||
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
|
||||
});
|
||||
// 3. Check if we can approve requests (keyspace should be unlocked now)
|
||||
const canApprove = requests.length > 0 ? await this.canApproveRequest(requests[0].id) : true;
|
||||
|
||||
console.log(`🔓 Keyspace unlocked notification sent: ${requests.length} requests, canApprove: ${canApprove}`);
|
||||
// 4. Update badge with current count
|
||||
this.updateBadge();
|
||||
|
||||
// 5. Notify popup if connected
|
||||
if (this.popupPort) {
|
||||
this.popupPort.postMessage({
|
||||
type: 'KEYSPACE_UNLOCKED',
|
||||
canApprove,
|
||||
pendingRequests: requests
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`🔓 Keyspace unlocked: ${requests.length} requests ready, canApprove: ${canApprove}`);
|
||||
|
||||
return requests;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to handle keyspace unlock:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,28 +7,17 @@
|
||||
<body>
|
||||
<div class="container">
|
||||
<header class="header">
|
||||
<div class="logo">
|
||||
<div class="logo clickable-header" id="headerTitle">
|
||||
<div class="logo-icon">🔐</div>
|
||||
<h1>CryptoVault</h1>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<div class="settings-container">
|
||||
<button id="settingsToggle" class="btn-icon-only" title="Settings">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="3"></circle>
|
||||
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="settings-dropdown hidden" id="settingsDropdown">
|
||||
<div class="settings-item">
|
||||
<label for="timeoutInput">Session Timeout</label>
|
||||
<div class="timeout-input-group">
|
||||
<input type="number" id="timeoutInput" min="3" max="300" value="15">
|
||||
<span>seconds</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button id="settingsBtn" class="btn-icon-only" title="Settings">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="3"></circle>
|
||||
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button id="themeToggle" class="btn-icon-only" title="Switch to dark mode">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
|
||||
@ -84,6 +73,14 @@
|
||||
</div>
|
||||
|
||||
<div class="requests-container" id="requestsContainer">
|
||||
<div class="loading-requests hidden" id="loadingRequestsMessage">
|
||||
<div class="loading-state">
|
||||
<div class="loading-spinner"></div>
|
||||
<p>Loading requests...</p>
|
||||
<small>Fetching pending signature requests</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="no-requests" id="noRequestsMessage">
|
||||
<div class="empty-state">
|
||||
<div class="empty-icon">📝</div>
|
||||
@ -106,14 +103,6 @@
|
||||
</svg>
|
||||
Refresh
|
||||
</button>
|
||||
<button id="sigSocketStatusBtn" class="btn btn-ghost btn-small">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<line x1="12" y1="6" x2="12" y2="12"></line>
|
||||
<line x1="16" y1="16" x2="12" y2="12"></line>
|
||||
</svg>
|
||||
Status
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -235,8 +224,41 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Settings Section -->
|
||||
<section class="section hidden" id="settingsSection">
|
||||
<div class="settings-header">
|
||||
<h2>Settings</h2>
|
||||
</div>
|
||||
|
||||
<!-- Session Settings -->
|
||||
<div class="card">
|
||||
<h3>Session Settings</h3>
|
||||
<div class="settings-item">
|
||||
<label for="timeoutInput">Session Timeout</label>
|
||||
<div class="timeout-input-group">
|
||||
<input type="number" id="timeoutInput" min="3" max="300" value="15">
|
||||
<span>seconds</span>
|
||||
</div>
|
||||
<small class="settings-help">Automatically lock session after inactivity</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SigSocket Settings -->
|
||||
<div class="card">
|
||||
<h3>SigSocket Settings</h3>
|
||||
<div class="settings-item">
|
||||
<label for="serverUrlInput">Server URL</label>
|
||||
<div class="server-input-group">
|
||||
<input type="text" id="serverUrlInput" placeholder="ws://localhost:8080/ws" value="ws://localhost:8080/ws">
|
||||
<button id="saveServerUrlBtn" class="btn btn-small btn-primary">Save</button>
|
||||
</div>
|
||||
<small class="settings-help">WebSocket URL for SigSocket server (ws:// or wss://)</small>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
|
@ -32,6 +32,11 @@ function showToast(message, type = 'info') {
|
||||
|
||||
// Enhanced loading states for buttons
|
||||
function setButtonLoading(button, loading = true) {
|
||||
// Handle null/undefined button gracefully
|
||||
if (!button) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
button.dataset.originalText = button.textContent;
|
||||
button.classList.add('loading');
|
||||
@ -126,9 +131,18 @@ const elements = {
|
||||
// Header elements
|
||||
lockBtn: document.getElementById('lockBtn'),
|
||||
themeToggle: document.getElementById('themeToggle'),
|
||||
settingsToggle: document.getElementById('settingsToggle'),
|
||||
settingsDropdown: document.getElementById('settingsDropdown'),
|
||||
settingsBtn: document.getElementById('settingsBtn'),
|
||||
headerTitle: document.getElementById('headerTitle'),
|
||||
|
||||
// Section elements
|
||||
authSection: document.getElementById('authSection'),
|
||||
vaultSection: document.getElementById('vaultSection'),
|
||||
settingsSection: document.getElementById('settingsSection'),
|
||||
|
||||
// Settings page elements
|
||||
timeoutInput: document.getElementById('timeoutInput'),
|
||||
serverUrlInput: document.getElementById('serverUrlInput'),
|
||||
saveServerUrlBtn: document.getElementById('saveServerUrlBtn'),
|
||||
|
||||
// Keypair management elements
|
||||
toggleAddKeypairBtn: document.getElementById('toggleAddKeypairBtn'),
|
||||
@ -219,6 +233,53 @@ async function saveTimeoutSetting(timeout) {
|
||||
await sendMessage('updateTimeout', { timeout });
|
||||
}
|
||||
|
||||
// Server URL settings
|
||||
async function loadServerUrlSetting() {
|
||||
try {
|
||||
const result = await chrome.storage.local.get(['sigSocketUrl']);
|
||||
const serverUrl = result.sigSocketUrl || 'ws://localhost:8080/ws';
|
||||
if (elements.serverUrlInput) {
|
||||
elements.serverUrlInput.value = serverUrl;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to load server URL setting:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveServerUrlSetting() {
|
||||
try {
|
||||
const serverUrl = elements.serverUrlInput?.value?.trim();
|
||||
if (!serverUrl) {
|
||||
showToast('Please enter a valid server URL', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Basic URL validation
|
||||
if (!serverUrl.startsWith('ws://') && !serverUrl.startsWith('wss://')) {
|
||||
showToast('Server URL must start with ws:// or wss://', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Save to storage
|
||||
await chrome.storage.local.set({ sigSocketUrl: serverUrl });
|
||||
|
||||
// Notify background script to update server URL
|
||||
const response = await sendMessage('updateSigSocketUrl', { serverUrl });
|
||||
|
||||
if (response?.success) {
|
||||
showToast('Server URL saved successfully', 'success');
|
||||
|
||||
// Refresh connection status
|
||||
await loadSigSocketState();
|
||||
} else {
|
||||
showToast('Failed to update server URL', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to save server URL:', error);
|
||||
showToast('Failed to save server URL', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function resetSessionTimeout() {
|
||||
if (currentKeyspace) {
|
||||
await sendMessage('resetTimeout');
|
||||
@ -241,11 +302,63 @@ function toggleTheme() {
|
||||
updateThemeIcon(newTheme);
|
||||
}
|
||||
|
||||
// Settings dropdown management
|
||||
function toggleSettingsDropdown() {
|
||||
const dropdown = elements.settingsDropdown;
|
||||
if (dropdown) {
|
||||
dropdown.classList.toggle('hidden');
|
||||
// Settings page navigation
|
||||
async function showSettingsPage() {
|
||||
// Hide all sections
|
||||
document.querySelectorAll('.section').forEach(section => {
|
||||
section.classList.add('hidden');
|
||||
});
|
||||
|
||||
// Show settings section
|
||||
elements.settingsSection?.classList.remove('hidden');
|
||||
|
||||
// Ensure we have current status before updating settings display
|
||||
await loadSigSocketState();
|
||||
}
|
||||
|
||||
async function hideSettingsPage() {
|
||||
// Hide settings section
|
||||
elements.settingsSection?.classList.add('hidden');
|
||||
|
||||
// Check current session state to determine what to show
|
||||
try {
|
||||
const response = await sendMessage('getStatus');
|
||||
|
||||
if (response && response.success && response.status && response.session) {
|
||||
// Active session exists - show vault section
|
||||
currentKeyspace = response.session.keyspace;
|
||||
if (elements.keyspaceInput) {
|
||||
elements.keyspaceInput.value = currentKeyspace;
|
||||
}
|
||||
setStatus(currentKeyspace, true);
|
||||
elements.vaultSection?.classList.remove('hidden');
|
||||
updateSettingsVisibility(); // Update settings visibility
|
||||
|
||||
// Load vault content
|
||||
await loadKeypairs();
|
||||
|
||||
// Use retry mechanism for existing sessions that might have stale connections
|
||||
await loadSigSocketStateWithRetry();
|
||||
} else {
|
||||
// No active session - show auth section
|
||||
currentKeyspace = null;
|
||||
setStatus('', false);
|
||||
elements.authSection?.classList.remove('hidden');
|
||||
updateSettingsVisibility(); // Update settings visibility
|
||||
|
||||
// For no session, use regular loading
|
||||
await loadSigSocketState();
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to check session state:', error);
|
||||
// Fallback to auth section on error
|
||||
currentKeyspace = null;
|
||||
setStatus('', false);
|
||||
elements.authSection?.classList.remove('hidden');
|
||||
updateSettingsVisibility(); // Update settings visibility
|
||||
|
||||
// Still try to load SigSocket state
|
||||
await loadSigSocketState();
|
||||
}
|
||||
}
|
||||
|
||||
@ -287,6 +400,19 @@ function updateThemeIcon(theme) {
|
||||
}
|
||||
}
|
||||
|
||||
// Update settings button visibility based on keyspace state
|
||||
function updateSettingsVisibility() {
|
||||
if (elements.settingsBtn) {
|
||||
if (currentKeyspace) {
|
||||
// Show settings when keyspace is unlocked
|
||||
elements.settingsBtn.style.display = '';
|
||||
} else {
|
||||
// Hide settings when keyspace is locked
|
||||
elements.settingsBtn.style.display = 'none';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Establish connection to background script for keep-alive
|
||||
function connectToBackground() {
|
||||
backgroundPort = chrome.runtime.connect({ name: 'popup' });
|
||||
@ -299,6 +425,7 @@ function connectToBackground() {
|
||||
selectedKeypairId = null;
|
||||
setStatus('', false);
|
||||
showSection('authSection');
|
||||
updateSettingsVisibility(); // Update settings visibility
|
||||
clearVaultState();
|
||||
|
||||
// Clear form inputs
|
||||
@ -313,6 +440,13 @@ function connectToBackground() {
|
||||
backgroundPort.onDisconnect.addListener(() => {
|
||||
backgroundPort = null;
|
||||
});
|
||||
|
||||
// Immediately request status update when popup connects
|
||||
setTimeout(() => {
|
||||
if (backgroundPort) {
|
||||
backgroundPort.postMessage({ type: 'REQUEST_IMMEDIATE_STATUS' });
|
||||
}
|
||||
}, 50); // Small delay to ensure connection is established
|
||||
}
|
||||
|
||||
// Initialize
|
||||
@ -323,6 +457,9 @@ document.addEventListener('DOMContentLoaded', async function() {
|
||||
// Load timeout setting
|
||||
await loadTimeoutSetting();
|
||||
|
||||
// Load server URL setting
|
||||
await loadServerUrlSetting();
|
||||
|
||||
// Ensure lock button starts hidden
|
||||
const lockBtn = document.getElementById('lockBtn');
|
||||
if (lockBtn) {
|
||||
@ -338,7 +475,9 @@ document.addEventListener('DOMContentLoaded', async function() {
|
||||
loginBtn: login,
|
||||
lockBtn: lockSession,
|
||||
themeToggle: toggleTheme,
|
||||
settingsToggle: toggleSettingsDropdown,
|
||||
settingsBtn: showSettingsPage,
|
||||
headerTitle: hideSettingsPage,
|
||||
saveServerUrlBtn: saveServerUrlSetting,
|
||||
toggleAddKeypairBtn: toggleAddKeypairForm,
|
||||
addKeypairBtn: addKeypair,
|
||||
cancelAddKeypairBtn: hideAddKeypairForm,
|
||||
@ -349,7 +488,10 @@ document.addEventListener('DOMContentLoaded', async function() {
|
||||
};
|
||||
|
||||
Object.entries(eventMap).forEach(([elementKey, handler]) => {
|
||||
elements[elementKey]?.addEventListener('click', handler);
|
||||
const element = elements[elementKey];
|
||||
if (element) {
|
||||
element.addEventListener('click', handler);
|
||||
}
|
||||
});
|
||||
|
||||
// Tab functionality
|
||||
@ -400,10 +542,66 @@ document.addEventListener('DOMContentLoaded', async function() {
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize SigSocket UI elements after DOM is ready
|
||||
sigSocketElements = {
|
||||
connectionStatus: document.getElementById('connectionStatus'),
|
||||
connectionDot: document.getElementById('connectionDot'),
|
||||
connectionText: document.getElementById('connectionText'),
|
||||
requestsContainer: document.getElementById('requestsContainer'),
|
||||
loadingRequestsMessage: document.getElementById('loadingRequestsMessage'),
|
||||
noRequestsMessage: document.getElementById('noRequestsMessage'),
|
||||
requestsList: document.getElementById('requestsList'),
|
||||
refreshRequestsBtn: document.getElementById('refreshRequestsBtn')
|
||||
};
|
||||
|
||||
// Add SigSocket button listeners
|
||||
sigSocketElements.refreshRequestsBtn?.addEventListener('click', refreshSigSocketRequests);
|
||||
|
||||
// Check if opened via notification (focus on SigSocket section)
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const fromNotification = urlParams.get('from') === 'notification';
|
||||
|
||||
// Check for existing session
|
||||
await checkExistingSession();
|
||||
|
||||
// If opened from notification, focus on SigSocket section and show requests
|
||||
if (fromNotification) {
|
||||
console.log('🔔 Opened from notification, focusing on SigSocket section');
|
||||
focusOnSigSocketSection();
|
||||
}
|
||||
|
||||
// Try to load any cached SigSocket state immediately for better UX
|
||||
await loadCachedSigSocketState();
|
||||
});
|
||||
|
||||
// Focus on SigSocket section when opened from notification
|
||||
function focusOnSigSocketSection() {
|
||||
try {
|
||||
// Switch to SigSocket tab if not already active
|
||||
const sigSocketTab = document.querySelector('[data-tab="sigsocket"]');
|
||||
if (sigSocketTab && !sigSocketTab.classList.contains('active')) {
|
||||
sigSocketTab.click();
|
||||
}
|
||||
|
||||
// Scroll to requests container
|
||||
if (sigSocketElements.requestsContainer) {
|
||||
sigSocketElements.requestsContainer.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
}
|
||||
|
||||
// Show a helpful toast
|
||||
showToast('New signature request received! Review pending requests below.', 'info');
|
||||
|
||||
// Refresh requests to ensure latest state
|
||||
setTimeout(() => refreshSigSocketRequests(), 500);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to focus on SigSocket section:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkExistingSession() {
|
||||
try {
|
||||
const response = await sendMessage('getStatus');
|
||||
@ -413,15 +611,31 @@ async function checkExistingSession() {
|
||||
elements.keyspaceInput.value = currentKeyspace;
|
||||
setStatus(currentKeyspace, true);
|
||||
showSection('vaultSection');
|
||||
updateSettingsVisibility(); // Update settings visibility
|
||||
await loadKeypairs();
|
||||
|
||||
// Use retry mechanism for existing sessions to handle stale connections
|
||||
await loadSigSocketStateWithRetry();
|
||||
} else {
|
||||
// No active session
|
||||
currentKeyspace = null;
|
||||
setStatus('', false);
|
||||
showSection('authSection');
|
||||
updateSettingsVisibility(); // Update settings visibility
|
||||
|
||||
// For no session, use regular loading (no retry needed)
|
||||
await loadSigSocketState();
|
||||
}
|
||||
} catch (error) {
|
||||
setStatus('', false);
|
||||
showSection('authSection');
|
||||
|
||||
// Still try to load SigSocket state even on error
|
||||
try {
|
||||
await loadSigSocketState();
|
||||
} catch (sigSocketError) {
|
||||
console.warn('Failed to load SigSocket state:', sigSocketError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -641,9 +855,23 @@ async function login() {
|
||||
currentKeyspace = auth.keyspace;
|
||||
setStatus(auth.keyspace, true);
|
||||
showSection('vaultSection');
|
||||
updateSettingsVisibility(); // Update settings visibility
|
||||
clearVaultState();
|
||||
await loadKeypairs();
|
||||
|
||||
// Clean flow: Login -> Connect -> Restore -> Display
|
||||
console.log('🔓 Login successful, applying clean flow...');
|
||||
|
||||
// 1. Wait for SigSocket to connect and restore requests
|
||||
await loadSigSocketStateWithRetry();
|
||||
|
||||
// 2. Show loading state while fetching
|
||||
showRequestsLoading();
|
||||
|
||||
// 3. Refresh requests to get the clean, restored state
|
||||
await refreshSigSocketRequests();
|
||||
|
||||
console.log('✅ Login clean flow completed');
|
||||
return response;
|
||||
} else {
|
||||
throw new Error(getResponseError(response, 'login'));
|
||||
@ -667,6 +895,7 @@ async function lockSession() {
|
||||
selectedKeypairId = null;
|
||||
setStatus('', false);
|
||||
showSection('authSection');
|
||||
updateSettingsVisibility(); // Update settings visibility
|
||||
|
||||
// Clear all form inputs
|
||||
elements.keyspaceInput.value = '';
|
||||
@ -936,28 +1165,8 @@ const verifySignature = () => performCryptoOperation({
|
||||
// SigSocket functionality
|
||||
let sigSocketRequests = [];
|
||||
let sigSocketStatus = { isConnected: false, workspace: null };
|
||||
|
||||
// Initialize SigSocket UI elements
|
||||
const sigSocketElements = {
|
||||
connectionStatus: document.getElementById('connectionStatus'),
|
||||
connectionDot: document.getElementById('connectionDot'),
|
||||
connectionText: document.getElementById('connectionText'),
|
||||
requestsContainer: document.getElementById('requestsContainer'),
|
||||
noRequestsMessage: document.getElementById('noRequestsMessage'),
|
||||
requestsList: document.getElementById('requestsList'),
|
||||
refreshRequestsBtn: document.getElementById('refreshRequestsBtn'),
|
||||
sigSocketStatusBtn: document.getElementById('sigSocketStatusBtn')
|
||||
};
|
||||
|
||||
// Add SigSocket event listeners
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Add SigSocket button listeners
|
||||
sigSocketElements.refreshRequestsBtn?.addEventListener('click', refreshSigSocketRequests);
|
||||
sigSocketElements.sigSocketStatusBtn?.addEventListener('click', showSigSocketStatus);
|
||||
|
||||
// Load initial SigSocket state
|
||||
loadSigSocketState();
|
||||
});
|
||||
let sigSocketElements = {}; // Will be initialized in DOMContentLoaded
|
||||
let isInitialLoad = true; // Track if this is the first load
|
||||
|
||||
// Listen for messages from background script about SigSocket events
|
||||
if (backgroundPort) {
|
||||
@ -968,6 +1177,12 @@ if (backgroundPort) {
|
||||
updateRequestsList(message.pendingRequests);
|
||||
} else if (message.type === 'KEYSPACE_UNLOCKED') {
|
||||
handleKeypaceUnlocked(message);
|
||||
} else if (message.type === 'CONNECTION_STATUS_CHANGED') {
|
||||
handleConnectionStatusChanged(message);
|
||||
} else if (message.type === 'FOCUS_SIGSOCKET') {
|
||||
// Handle focus request from notification click
|
||||
console.log('🔔 Received focus request from notification');
|
||||
focusOnSigSocketSection();
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -975,19 +1190,117 @@ if (backgroundPort) {
|
||||
// Load SigSocket state when popup opens
|
||||
async function loadSigSocketState() {
|
||||
try {
|
||||
// Get SigSocket status
|
||||
const statusResponse = await sendMessage('getSigSocketStatus');
|
||||
if (statusResponse?.success) {
|
||||
updateConnectionStatus(statusResponse.status);
|
||||
console.log('🔄 Loading SigSocket state...');
|
||||
|
||||
// Show loading state for requests
|
||||
showRequestsLoading();
|
||||
|
||||
// Show loading state for connection status on initial load
|
||||
if (isInitialLoad) {
|
||||
showConnectionLoading();
|
||||
}
|
||||
|
||||
// Get pending requests
|
||||
// Force a fresh connection status check with enhanced testing
|
||||
const statusResponse = await sendMessage('getSigSocketStatusWithTest');
|
||||
if (statusResponse?.success) {
|
||||
console.log('✅ Got SigSocket status:', statusResponse.status);
|
||||
updateConnectionStatus(statusResponse.status);
|
||||
} else {
|
||||
console.warn('Enhanced status check failed, trying fallback...');
|
||||
// Fallback to regular status check
|
||||
const fallbackResponse = await sendMessage('getSigSocketStatus');
|
||||
if (fallbackResponse?.success) {
|
||||
console.log('✅ Got fallback SigSocket status:', fallbackResponse.status);
|
||||
updateConnectionStatus(fallbackResponse.status);
|
||||
} else {
|
||||
// If both fail, show disconnected but don't show error on initial load
|
||||
updateConnectionStatus({
|
||||
isConnected: false,
|
||||
workspace: null,
|
||||
publicKey: null,
|
||||
pendingRequestCount: 0,
|
||||
serverUrl: 'ws://localhost:8080/ws'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Get pending requests - this now works even when keyspace is locked
|
||||
console.log('📋 Fetching pending requests...');
|
||||
const requestsResponse = await sendMessage('getPendingSignRequests');
|
||||
if (requestsResponse?.success) {
|
||||
console.log(`📋 Retrieved ${requestsResponse.requests?.length || 0} pending requests:`, requestsResponse.requests);
|
||||
updateRequestsList(requestsResponse.requests);
|
||||
} else {
|
||||
console.warn('Failed to get pending requests:', requestsResponse);
|
||||
updateRequestsList([]);
|
||||
}
|
||||
|
||||
// Mark initial load as complete
|
||||
isInitialLoad = false;
|
||||
|
||||
} catch (error) {
|
||||
console.warn('Failed to load SigSocket state:', error);
|
||||
|
||||
// Hide loading state and show error state
|
||||
hideRequestsLoading();
|
||||
|
||||
// Set disconnected state on error (but don't show error toast on initial load)
|
||||
updateConnectionStatus({
|
||||
isConnected: false,
|
||||
workspace: null,
|
||||
publicKey: null,
|
||||
pendingRequestCount: 0,
|
||||
serverUrl: 'ws://localhost:8080/ws'
|
||||
});
|
||||
|
||||
// Still try to show any cached requests
|
||||
updateRequestsList([]);
|
||||
|
||||
// Mark initial load as complete
|
||||
isInitialLoad = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Load cached SigSocket state for immediate display
|
||||
async function loadCachedSigSocketState() {
|
||||
try {
|
||||
// Try to get any cached requests from storage for immediate display
|
||||
const cachedData = await chrome.storage.local.get(['sigSocketPendingRequests']);
|
||||
if (cachedData.sigSocketPendingRequests && Array.isArray(cachedData.sigSocketPendingRequests)) {
|
||||
console.log('📋 Loading cached requests for immediate display');
|
||||
updateRequestsList(cachedData.sigSocketPendingRequests);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to load cached SigSocket state:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Load SigSocket state with simple retry for session initialization timing
|
||||
async function loadSigSocketStateWithRetry() {
|
||||
// First try immediately (might already be connected)
|
||||
await loadSigSocketState();
|
||||
|
||||
// If still showing disconnected after initial load, try again after a short delay
|
||||
if (!sigSocketStatus.isConnected) {
|
||||
console.log('🔄 Initial load showed disconnected, retrying after delay...');
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
await loadSigSocketState();
|
||||
}
|
||||
}
|
||||
|
||||
// Show loading state for connection status
|
||||
function showConnectionLoading() {
|
||||
if (sigSocketElements.connectionDot && sigSocketElements.connectionText) {
|
||||
sigSocketElements.connectionDot.classList.remove('connected');
|
||||
sigSocketElements.connectionDot.classList.add('loading');
|
||||
sigSocketElements.connectionText.textContent = 'Checking...';
|
||||
}
|
||||
}
|
||||
|
||||
// Hide loading state for connection status
|
||||
function hideConnectionLoading() {
|
||||
if (sigSocketElements.connectionDot) {
|
||||
sigSocketElements.connectionDot.classList.remove('loading');
|
||||
}
|
||||
}
|
||||
|
||||
@ -995,15 +1308,42 @@ async function loadSigSocketState() {
|
||||
function updateConnectionStatus(status) {
|
||||
sigSocketStatus = status;
|
||||
|
||||
// Hide loading state
|
||||
hideConnectionLoading();
|
||||
|
||||
if (sigSocketElements.connectionDot && sigSocketElements.connectionText) {
|
||||
if (status.isConnected) {
|
||||
sigSocketElements.connectionDot.classList.add('connected');
|
||||
sigSocketElements.connectionText.textContent = `Connected (${status.workspace || 'Unknown'})`;
|
||||
sigSocketElements.connectionText.textContent = 'Connected';
|
||||
} else {
|
||||
sigSocketElements.connectionDot.classList.remove('connected');
|
||||
sigSocketElements.connectionText.textContent = 'Disconnected';
|
||||
}
|
||||
}
|
||||
|
||||
// Log connection details for debugging
|
||||
console.log('🔗 Connection status updated:', {
|
||||
connected: status.isConnected,
|
||||
workspace: status.workspace,
|
||||
publicKey: status.publicKey?.substring(0, 16) + '...',
|
||||
serverUrl: status.serverUrl
|
||||
});
|
||||
}
|
||||
|
||||
// Show loading state for requests
|
||||
function showRequestsLoading() {
|
||||
if (!sigSocketElements.requestsContainer) return;
|
||||
|
||||
sigSocketElements.loadingRequestsMessage?.classList.remove('hidden');
|
||||
sigSocketElements.noRequestsMessage?.classList.add('hidden');
|
||||
sigSocketElements.requestsList?.classList.add('hidden');
|
||||
}
|
||||
|
||||
// Hide loading state for requests
|
||||
function hideRequestsLoading() {
|
||||
if (!sigSocketElements.requestsContainer) return;
|
||||
|
||||
sigSocketElements.loadingRequestsMessage?.classList.add('hidden');
|
||||
}
|
||||
|
||||
// Update requests list display
|
||||
@ -1012,6 +1352,9 @@ function updateRequestsList(requests) {
|
||||
|
||||
if (!sigSocketElements.requestsContainer) return;
|
||||
|
||||
// Hide loading state
|
||||
hideRequestsLoading();
|
||||
|
||||
if (sigSocketRequests.length === 0) {
|
||||
sigSocketElements.noRequestsMessage?.classList.remove('hidden');
|
||||
sigSocketElements.requestsList?.classList.add('hidden');
|
||||
@ -1036,8 +1379,42 @@ function createRequestItem(request) {
|
||||
const shortId = request.id.substring(0, 8) + '...';
|
||||
const decodedMessage = request.message ? atob(request.message) : 'No message';
|
||||
|
||||
// Check if keyspace is currently unlocked
|
||||
const isKeypaceUnlocked = currentKeyspace !== null;
|
||||
|
||||
// Create different UI based on keyspace lock status
|
||||
let actionsHtml;
|
||||
let statusIndicator = '';
|
||||
|
||||
if (isKeypaceUnlocked) {
|
||||
// Normal approve/reject buttons when unlocked
|
||||
actionsHtml = `
|
||||
<div class="request-actions">
|
||||
<button class="btn-approve" data-request-id="${request.id}">
|
||||
✓ Approve
|
||||
</button>
|
||||
<button class="btn-reject" data-request-id="${request.id}">
|
||||
✗ Reject
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
// Show pending status and unlock message when locked
|
||||
statusIndicator = '<div class="request-status pending">⏳ Pending - Unlock keyspace to approve/reject</div>';
|
||||
actionsHtml = `
|
||||
<div class="request-actions locked">
|
||||
<button class="btn-approve" data-request-id="${request.id}" disabled title="Unlock keyspace to approve">
|
||||
✓ Approve
|
||||
</button>
|
||||
<button class="btn-reject" data-request-id="${request.id}" disabled title="Unlock keyspace to reject">
|
||||
✗ Reject
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="request-item" data-request-id="${request.id}">
|
||||
<div class="request-item ${isKeypaceUnlocked ? '' : 'locked'}" data-request-id="${request.id}">
|
||||
<div class="request-header">
|
||||
<div class="request-id" title="${request.id}">${shortId}</div>
|
||||
<div class="request-time">${requestTime}</div>
|
||||
@ -1047,14 +1424,8 @@ function createRequestItem(request) {
|
||||
${decodedMessage.length > 100 ? decodedMessage.substring(0, 100) + '...' : decodedMessage}
|
||||
</div>
|
||||
|
||||
<div class="request-actions">
|
||||
<button class="btn-approve" data-request-id="${request.id}">
|
||||
✓ Approve
|
||||
</button>
|
||||
<button class="btn-reject" data-request-id="${request.id}">
|
||||
✗ Reject
|
||||
</button>
|
||||
</div>
|
||||
${statusIndicator}
|
||||
${actionsHtml}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@ -1091,15 +1462,61 @@ function handleNewSignRequest(message) {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle keyspace unlocked event
|
||||
// Handle keyspace unlocked event - Clean flow implementation
|
||||
function handleKeypaceUnlocked(message) {
|
||||
// Update requests list
|
||||
if (message.pendingRequests) {
|
||||
updateRequestsList(message.pendingRequests);
|
||||
}
|
||||
console.log('🔓 Keyspace unlocked - applying clean flow for request display');
|
||||
|
||||
// Update button states based on whether requests can be approved
|
||||
updateRequestButtonStates(message.canApprove);
|
||||
// Clean flow: Unlock -> Show loading -> Display requests -> Update UI
|
||||
try {
|
||||
// 1. Show loading state immediately
|
||||
showRequestsLoading();
|
||||
|
||||
// 2. Update requests list with restored + current requests
|
||||
if (message.pendingRequests && Array.isArray(message.pendingRequests)) {
|
||||
console.log(`📋 Displaying ${message.pendingRequests.length} restored requests`);
|
||||
updateRequestsList(message.pendingRequests);
|
||||
|
||||
// 3. Update button states (should be enabled now)
|
||||
updateRequestButtonStates(message.canApprove !== false);
|
||||
|
||||
// 4. Show appropriate notification
|
||||
const count = message.pendingRequests.length;
|
||||
if (count > 0) {
|
||||
showToast(`Keyspace unlocked! ${count} pending request${count > 1 ? 's' : ''} ready for review.`, 'info');
|
||||
} else {
|
||||
showToast('Keyspace unlocked! No pending requests.', 'success');
|
||||
}
|
||||
} else {
|
||||
// 5. If no requests in message, fetch fresh from server
|
||||
console.log('📋 No requests in unlock message, fetching from server...');
|
||||
setTimeout(() => refreshSigSocketRequests(), 100);
|
||||
}
|
||||
|
||||
console.log('✅ Keyspace unlock flow completed');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error in keyspace unlock flow:', error);
|
||||
hideRequestsLoading();
|
||||
showToast('Error loading requests after unlock', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Handle connection status change event
|
||||
function handleConnectionStatusChanged(message) {
|
||||
console.log('🔄 Connection status changed:', message.status);
|
||||
|
||||
// Store previous state for comparison
|
||||
const previousState = sigSocketStatus ? sigSocketStatus.isConnected : null;
|
||||
|
||||
// Update the connection status display
|
||||
updateConnectionStatus(message.status);
|
||||
|
||||
// Only show toast for actual changes, not initial status, and not during initial load
|
||||
if (!isInitialLoad && previousState !== null && previousState !== message.status.isConnected) {
|
||||
const statusText = message.status.isConnected ? 'Connected' : 'Disconnected';
|
||||
const toastType = message.status.isConnected ? 'success' : 'warning';
|
||||
showToast(`SigSocket ${statusText}`, toastType);
|
||||
}
|
||||
}
|
||||
|
||||
// Show workspace mismatch warning
|
||||
@ -1133,30 +1550,46 @@ function updateRequestButtonStates(canApprove) {
|
||||
|
||||
// Approve a sign request
|
||||
async function approveSignRequest(requestId) {
|
||||
let button = null;
|
||||
try {
|
||||
const button = document.querySelector(`[data-request-id="${requestId}"].btn-approve`);
|
||||
button = document.querySelector(`[data-request-id="${requestId}"].btn-approve`);
|
||||
setButtonLoading(button, true);
|
||||
|
||||
const response = await sendMessage('approveSignRequest', { requestId });
|
||||
|
||||
if (response?.success) {
|
||||
showToast('Request approved and signed!', 'success');
|
||||
showRequestsLoading();
|
||||
await refreshSigSocketRequests();
|
||||
} else {
|
||||
throw new Error(getResponseError(response, 'approve request'));
|
||||
const errorMsg = getResponseError(response, 'approve request');
|
||||
|
||||
// Check for specific connection errors
|
||||
if (errorMsg.includes('Connection not found') || errorMsg.includes('public key')) {
|
||||
showToast('Connection error: Please check SigSocket connection and try again', 'error');
|
||||
// Trigger a connection status refresh
|
||||
await loadSigSocketState();
|
||||
} else if (errorMsg.includes('keyspace') || errorMsg.includes('locked')) {
|
||||
showToast('Keyspace is locked. Please unlock to approve requests.', 'error');
|
||||
} else {
|
||||
throw new Error(errorMsg);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error approving request:', error);
|
||||
showToast(`Failed to approve request: ${error.message}`, 'error');
|
||||
} finally {
|
||||
const button = document.querySelector(`[data-request-id="${requestId}"].btn-approve`);
|
||||
setButtonLoading(button, false);
|
||||
// Re-query button in case DOM was updated during the operation
|
||||
const finalButton = document.querySelector(`[data-request-id="${requestId}"].btn-approve`);
|
||||
setButtonLoading(finalButton, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Reject a sign request
|
||||
async function rejectSignRequest(requestId) {
|
||||
let button = null;
|
||||
try {
|
||||
const button = document.querySelector(`[data-request-id="${requestId}"].btn-reject`);
|
||||
button = document.querySelector(`[data-request-id="${requestId}"].btn-reject`);
|
||||
setButtonLoading(button, true);
|
||||
|
||||
const response = await sendMessage('rejectSignRequest', {
|
||||
@ -1166,6 +1599,7 @@ async function rejectSignRequest(requestId) {
|
||||
|
||||
if (response?.success) {
|
||||
showToast('Request rejected', 'info');
|
||||
showRequestsLoading();
|
||||
await refreshSigSocketRequests();
|
||||
} else {
|
||||
throw new Error(getResponseError(response, 'reject request'));
|
||||
@ -1173,8 +1607,9 @@ async function rejectSignRequest(requestId) {
|
||||
} catch (error) {
|
||||
showToast(`Failed to reject request: ${error.message}`, 'error');
|
||||
} finally {
|
||||
const button = document.querySelector(`[data-request-id="${requestId}"].btn-reject`);
|
||||
setButtonLoading(button, false);
|
||||
// Re-query button in case DOM was updated during the operation
|
||||
const finalButton = document.querySelector(`[data-request-id="${requestId}"].btn-reject`);
|
||||
setButtonLoading(finalButton, false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1182,42 +1617,35 @@ async function rejectSignRequest(requestId) {
|
||||
async function refreshSigSocketRequests() {
|
||||
try {
|
||||
setButtonLoading(sigSocketElements.refreshRequestsBtn, true);
|
||||
showRequestsLoading();
|
||||
|
||||
console.log('🔄 Refreshing SigSocket requests...');
|
||||
const response = await sendMessage('getPendingSignRequests');
|
||||
|
||||
if (response?.success) {
|
||||
console.log(`📋 Retrieved ${response.requests?.length || 0} pending requests`);
|
||||
updateRequestsList(response.requests);
|
||||
showToast('Requests refreshed', 'success');
|
||||
|
||||
const count = response.requests?.length || 0;
|
||||
if (count > 0) {
|
||||
showToast(`${count} pending request${count > 1 ? 's' : ''} found`, 'success');
|
||||
} else {
|
||||
showToast('No pending requests', 'info');
|
||||
}
|
||||
} else {
|
||||
console.error('Failed to get pending requests:', response);
|
||||
hideRequestsLoading();
|
||||
throw new Error(getResponseError(response, 'refresh requests'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error refreshing requests:', error);
|
||||
hideRequestsLoading();
|
||||
showToast(`Failed to refresh requests: ${error.message}`, 'error');
|
||||
} finally {
|
||||
setButtonLoading(sigSocketElements.refreshRequestsBtn, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Show SigSocket status
|
||||
async function showSigSocketStatus() {
|
||||
try {
|
||||
const response = await sendMessage('getSigSocketStatus');
|
||||
if (response?.success) {
|
||||
const status = response.status;
|
||||
const statusText = `
|
||||
SigSocket Status:
|
||||
• Connected: ${status.isConnected ? 'Yes' : 'No'}
|
||||
• Workspace: ${status.workspace || 'None'}
|
||||
• Public Key: ${status.publicKey ? status.publicKey.substring(0, 16) + '...' : 'None'}
|
||||
• Pending Requests: ${status.pendingRequestCount || 0}
|
||||
• Server URL: ${status.serverUrl}
|
||||
`.trim();
|
||||
|
||||
showToast(statusText, 'info');
|
||||
updateConnectionStatus(status);
|
||||
} else {
|
||||
throw new Error(getResponseError(response, 'get status'));
|
||||
}
|
||||
} catch (error) {
|
||||
showToast(`Failed to get status: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -188,6 +188,15 @@ body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.clickable-header {
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.clickable-header:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -261,6 +270,75 @@ body {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.server-input-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.server-input-group input {
|
||||
flex: 1;
|
||||
padding: var(--spacing-xs) var(--spacing-sm);
|
||||
font-size: 14px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-input);
|
||||
color: var(--text-primary);
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
}
|
||||
|
||||
.server-input-group input:focus {
|
||||
outline: none;
|
||||
border-color: var(--border-focus);
|
||||
box-shadow: 0 0 0 2px hsla(var(--primary-hue), var(--primary-saturation), 55%, 0.15);
|
||||
}
|
||||
|
||||
.settings-help {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
margin-top: var(--spacing-xs);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Settings page styles */
|
||||
|
||||
.settings-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-md);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.settings-header h2 {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.about-info {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.about-info p {
|
||||
margin: 0 0 var(--spacing-xs) 0;
|
||||
font-size: 14px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.about-info strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.version-info {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.btn-icon-only {
|
||||
background: var(--bg-button-ghost);
|
||||
border: none;
|
||||
@ -456,6 +534,17 @@ input::placeholder, textarea::placeholder {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Button icon spacing */
|
||||
.btn svg {
|
||||
margin-right: var(--spacing-xs);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.btn svg:last-child {
|
||||
margin-right: 0;
|
||||
margin-left: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
@ -1073,18 +1162,19 @@ input::placeholder, textarea::placeholder {
|
||||
|
||||
/* SigSocket Requests Styles */
|
||||
.sigsocket-section {
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.section-header h3 {
|
||||
margin: 0;
|
||||
color: var(--text-primary);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
@ -1092,7 +1182,7 @@ input::placeholder, textarea::placeholder {
|
||||
.connection-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
gap: var(--spacing-xs);
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
@ -1109,16 +1199,62 @@ input::placeholder, textarea::placeholder {
|
||||
background: var(--accent-success);
|
||||
}
|
||||
|
||||
.status-dot.loading {
|
||||
background: var(--accent-warning);
|
||||
animation: pulse-dot 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse-dot {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
opacity: 0.6;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
}
|
||||
|
||||
.requests-container {
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
padding: var(--spacing-xl);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.loading-state {
|
||||
text-align: center;
|
||||
padding: var(--spacing-xl);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 2px solid var(--border-color);
|
||||
border-top: 2px solid var(--primary-color);
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto var(--spacing-sm) auto;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-state p {
|
||||
margin: var(--spacing-sm) 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.loading-state small {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 24px;
|
||||
margin-bottom: 8px;
|
||||
@ -1228,10 +1364,42 @@ input::placeholder, textarea::placeholder {
|
||||
|
||||
.sigsocket-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
gap: var(--spacing-sm);
|
||||
margin-top: var(--spacing-md);
|
||||
}
|
||||
|
||||
/* Ensure refresh button follows design system */
|
||||
#refreshRequestsBtn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
/* Request item locked state styles */
|
||||
.request-item.locked {
|
||||
opacity: 0.8;
|
||||
border-left: 3px solid var(--warning-color, #ffa500);
|
||||
}
|
||||
|
||||
.request-status.pending {
|
||||
background: var(--warning-bg, #fff3cd);
|
||||
color: var(--warning-text, #856404);
|
||||
padding: var(--spacing-xs) var(--spacing-sm);
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
margin: var(--spacing-xs) 0;
|
||||
border: 1px solid var(--warning-border, #ffeaa7);
|
||||
}
|
||||
|
||||
.request-actions.locked button {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.request-actions.locked button:hover {
|
||||
background: var(--button-bg) !important;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
.workspace-mismatch {
|
||||
|
@ -467,31 +467,31 @@ export function run_rhai(script) {
|
||||
}
|
||||
|
||||
function __wbg_adapter_34(arg0, arg1, arg2) {
|
||||
wasm.closure174_externref_shim(arg0, arg1, arg2);
|
||||
wasm.closure203_externref_shim(arg0, arg1, arg2);
|
||||
}
|
||||
|
||||
function __wbg_adapter_39(arg0, arg1) {
|
||||
wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__ha4436a3f79fb1a0f(arg0, arg1);
|
||||
wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hd79bf9f6d48e92f7(arg0, arg1);
|
||||
}
|
||||
|
||||
function __wbg_adapter_44(arg0, arg1, arg2) {
|
||||
wasm.closure237_externref_shim(arg0, arg1, arg2);
|
||||
wasm.closure239_externref_shim(arg0, arg1, arg2);
|
||||
}
|
||||
|
||||
function __wbg_adapter_49(arg0, arg1) {
|
||||
wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hf148c54a4a246cea(arg0, arg1);
|
||||
wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hf103de07b8856532(arg0, arg1);
|
||||
}
|
||||
|
||||
function __wbg_adapter_52(arg0, arg1, arg2) {
|
||||
wasm.closure308_externref_shim(arg0, arg1, arg2);
|
||||
wasm.closure319_externref_shim(arg0, arg1, arg2);
|
||||
}
|
||||
|
||||
function __wbg_adapter_55(arg0, arg1, arg2) {
|
||||
wasm.closure392_externref_shim(arg0, arg1, arg2);
|
||||
wasm.closure395_externref_shim(arg0, arg1, arg2);
|
||||
}
|
||||
|
||||
function __wbg_adapter_207(arg0, arg1, arg2, arg3) {
|
||||
wasm.closure2046_externref_shim(arg0, arg1, arg2, arg3);
|
||||
wasm.closure2042_externref_shim(arg0, arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
const __wbindgen_enum_BinaryType = ["blob", "arraybuffer"];
|
||||
@ -1217,40 +1217,40 @@ function __wbg_get_imports() {
|
||||
const ret = false;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper1015 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 309, __wbg_adapter_52);
|
||||
imports.wbg.__wbindgen_closure_wrapper1036 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 320, __wbg_adapter_52);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper1320 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 393, __wbg_adapter_55);
|
||||
imports.wbg.__wbindgen_closure_wrapper1329 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 396, __wbg_adapter_55);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper423 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 172, __wbg_adapter_34);
|
||||
imports.wbg.__wbindgen_closure_wrapper624 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 201, __wbg_adapter_34);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper424 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 172, __wbg_adapter_34);
|
||||
imports.wbg.__wbindgen_closure_wrapper625 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 201, __wbg_adapter_34);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper425 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 172, __wbg_adapter_39);
|
||||
imports.wbg.__wbindgen_closure_wrapper626 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 201, __wbg_adapter_39);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper428 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 172, __wbg_adapter_34);
|
||||
imports.wbg.__wbindgen_closure_wrapper630 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 201, __wbg_adapter_34);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper765 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 240, __wbg_adapter_44);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper766 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 238, __wbg_adapter_44);
|
||||
const ret = makeMutClosure(arg0, arg1, 240, __wbg_adapter_44);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper767 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 238, __wbg_adapter_44);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper770 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 238, __wbg_adapter_49);
|
||||
imports.wbg.__wbindgen_closure_wrapper768 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 240, __wbg_adapter_49);
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbindgen_debug_string = function(arg0, arg1) {
|
||||
|
Binary file not shown.
Loading…
Reference in New Issue
Block a user