// Key operations module for the 4DSTAR Bundle Manager
// Handles all key management operations (list, generate, add, remove, sync)
const { ipcRenderer } = require('electron');
// Dependencies (injected by renderer-refactored.js)
let stateManager, domManager, uiComponents;
// Initialize dependencies
function initializeDependencies(deps) {
stateManager = deps.stateManager;
domManager = deps.domManager;
uiComponents = deps.uiComponents;
console.log('[KEY_OPERATIONS] Dependencies initialized');
}
// === KEY LISTING ===
async function loadKeys() {
try {
domManager.showSpinner();
const result = await ipcRenderer.invoke('list-keys');
domManager.hideSpinner();
if (result.success) {
stateManager.setKeysState(result);
displayKeys(result);
return result;
} else {
domManager.showModal('Error', `Failed to load keys: ${result.error}`);
return null;
}
} catch (error) {
domManager.hideSpinner();
domManager.showModal('Error', `Failed to load keys: ${error.message}`);
return null;
}
}
function displayKeys(keysData) {
const keysContainer = document.getElementById('keys-list-container');
if (!keysContainer) return;
if (keysData.total_count === 0) {
keysContainer.innerHTML = `
🔑
No Keys Found
No trusted keys are currently installed. Generate or add keys to get started.
`;
return;
}
// Update the main header with count and add action buttons
const mainHeader = document.querySelector('#keys-list-view .keys-header h3');
if (mainHeader) {
mainHeader.textContent = `Trusted Keys (${keysData.total_count})`;
}
// Add action buttons to the header if they don't exist
let keysHeader = document.querySelector('#keys-list-view .keys-header');
if (keysHeader && !keysHeader.querySelector('.keys-actions')) {
const actionsDiv = document.createElement('div');
actionsDiv.className = 'keys-actions';
actionsDiv.innerHTML = `
🔄 Refresh
🔄 Sync Remotes
`;
keysHeader.appendChild(actionsDiv);
}
let html = '';
for (const [sourceName, keys] of Object.entries(keysData.keys)) {
html += `
Name
Fingerprint
Size
Actions
`;
for (const key of keys) {
const shortFingerprint = key.fingerprint.substring(0, 16) + '...';
html += `
${key.name}
${shortFingerprint}
${key.size_bytes} bytes
Remove
`;
}
html += `
`;
}
keysContainer.innerHTML = html;
// Add event listeners for dynamically generated content
setupKeyListEventListeners();
}
function setupKeyListEventListeners() {
// Refresh keys button
const refreshBtn = document.getElementById('refresh-keys-btn');
if (refreshBtn) {
refreshBtn.addEventListener('click', loadKeys);
}
// Sync remotes button
const syncBtn = document.getElementById('sync-remotes-btn');
if (syncBtn) {
syncBtn.addEventListener('click', handleSyncRemotes);
}
// Remove key buttons - now handled exclusively here (removed from event-handlers.js)
const removeButtons = document.querySelectorAll('.remove-key-btn');
removeButtons.forEach(btn => {
btn.addEventListener('click', (e) => {
const fingerprint = e.target.dataset.keyFingerprint;
const keyName = e.target.dataset.keyName;
handleRemoveKey(fingerprint, keyName);
});
});
}
// === KEY GENERATION ===
async function handleGenerateKey() {
const keyName = document.getElementById('generate-key-name')?.value || 'author_key';
const keyType = document.getElementById('generate-key-type')?.value || 'ed25519';
const outputDir = document.getElementById('generate-output-dir')?.value || '.';
if (!keyName.trim()) {
domManager.showModal('Error', 'Please enter a key name.');
return;
}
try {
domManager.showSpinner();
const result = await ipcRenderer.invoke('generate-key', {
keyName: keyName.trim(),
keyType: keyType,
outputDir: outputDir
});
domManager.hideSpinner();
if (result.success) {
domManager.showModal('Success', `
Key Generated Successfully!
Key Type: ${result.key_type.toUpperCase()}
Fingerprint: ${result.fingerprint}
Private Key: ${result.private_key_path}
Public Key: ${result.public_key_path}
⚠️ Important: Keep your private key secure and never share it!
`);
// Clear form
document.getElementById('generate-key-name').value = '';
document.getElementById('generate-output-dir').value = '.';
} else {
domManager.showModal('Error', `Failed to generate key: ${result.error}`);
}
} catch (error) {
domManager.hideSpinner();
domManager.showModal('Error', `Failed to generate key: ${error.message}`);
}
}
// === KEY ADDITION ===
async function handleAddKey() {
try {
// Use IPC-based file dialog instead of @electron/remote
const keyPath = await ipcRenderer.invoke('select-key-file');
if (!keyPath) {
return; // User canceled dialog
}
domManager.showSpinner();
const addResult = await ipcRenderer.invoke('add-key', keyPath);
domManager.hideSpinner();
if (addResult.success) {
if (addResult.already_existed) {
domManager.showModal('Info', `Key '${addResult.key_name}' already exists in trust store.`);
} else {
domManager.showModal('Success', `
${addResult.fingerprint}
Copy
`);
}
// Refresh keys list
await loadKeys();
} else {
domManager.showModal('Error', `Failed to add key: ${addResult.error}`);
}
} catch (error) {
domManager.hideSpinner();
domManager.showModal('Error', `Failed to add key: ${error.message}`);
}
}
// === KEY REMOVAL ===
async function handleRemoveKey(fingerprint, keyName) {
const confirmed = await uiComponents.showConfirmDialog(
'Remove Key',
`Are you sure you want to remove the key "${keyName}"?\n\nThis action cannot be undone.`
);
if (!confirmed) return;
try {
domManager.showSpinner();
const result = await ipcRenderer.invoke('remove-key', fingerprint);
domManager.hideSpinner();
if (result.success) {
domManager.showModal('Success', `Removed ${result.removed_count} key(s) successfully.`);
// Refresh keys list
await loadKeys();
} else {
domManager.showModal('Error', `Failed to remove key: ${result.error}`);
}
} catch (error) {
domManager.hideSpinner();
domManager.showModal('Error', `Failed to remove key: ${error.message}`);
}
}
// === REMOTE SYNC ===
async function handleSyncRemotes() {
try {
domManager.showSpinner();
const result = await ipcRenderer.invoke('sync-remotes');
domManager.hideSpinner();
if (result.success) {
const successCount = result.synced_remotes.filter(r => r.status === 'success').length;
const failedCount = result.synced_remotes.filter(r => r.status === 'failed').length;
let message = `
Remote Sync Completed
✅ Successful: ${successCount}
❌ Failed: ${failedCount}
📦 Total keys synced: ${result.total_keys_synced}
`;
if (result.removed_remotes.length > 0) {
message += `Removed failing remotes: ${result.removed_remotes.join(', ')}
`;
}
domManager.showModal('Sync Results', message);
// Refresh keys list
await loadKeys();
} else {
domManager.showModal('Error', `Failed to sync remotes: ${result.error}`);
}
} catch (error) {
domManager.hideSpinner();
domManager.showModal('Error', `Failed to sync remotes: ${error.message}`);
}
}
// === REMOTE MANAGEMENT ===
async function loadRemoteSources() {
try {
const result = await ipcRenderer.invoke('get-remote-sources');
if (result.success) {
displayRemoteSources(result.remotes);
return result.remotes;
} else {
domManager.showModal('Error', `Failed to load remote sources: ${result.error}`);
return [];
}
} catch (error) {
domManager.showModal('Error', `Failed to load remote sources: ${error.message}`);
return [];
}
}
function displayRemoteSources(remotes) {
const remotesContainer = document.getElementById('remotes-list-container');
if (!remotesContainer) return;
if (remotes.length === 0) {
remotesContainer.innerHTML = `
🌐
No Remote Sources
No remote key sources are configured. Add remote repositories to sync keys automatically.
`;
return;
}
let html = `
Status
Name
URL
Keys
Actions
`;
for (const remote of remotes) {
const status = remote.exists ? '✅' : '❌';
const statusText = remote.exists ? 'Synced' : 'Not synced';
html += `
${status}
${remote.name}
${remote.url}
${remote.keys_count}
Remove
`;
}
html += `
`;
remotesContainer.innerHTML = html;
// Add event listeners for remove buttons
const removeButtons = document.querySelectorAll('.remove-remote-btn');
removeButtons.forEach(btn => {
btn.addEventListener('click', (e) => {
const remoteName = e.target.dataset.remoteName;
handleRemoveRemoteSource(remoteName);
});
});
}
async function handleAddRemoteSource() {
const name = document.getElementById('remote-name')?.value;
const url = document.getElementById('remote-url')?.value;
if (!name || !url) {
domManager.showModal('Error', 'Please enter both name and URL for the remote source.');
return;
}
try {
domManager.showSpinner();
const result = await ipcRenderer.invoke('add-remote-source', { name: name.trim(), url: url.trim() });
domManager.hideSpinner();
if (result.success) {
domManager.showModal('Success', `Remote source '${result.name}' added successfully.`);
// Clear form
document.getElementById('remote-name').value = '';
document.getElementById('remote-url').value = '';
// Refresh remotes list
await loadRemoteSources();
} else {
domManager.showModal('Error', `Failed to add remote source: ${result.error}`);
}
} catch (error) {
domManager.hideSpinner();
domManager.showModal('Error', `Failed to add remote source: ${error.message}`);
}
}
async function handleRemoveRemoteSource(remoteName) {
const confirmed = await uiComponents.showConfirmDialog(
'Remove Remote Source',
`Are you sure you want to remove the remote source "${remoteName}"?\n\nThis will also remove all keys synced from this source.`
);
if (!confirmed) return;
try {
domManager.showSpinner();
const result = await ipcRenderer.invoke('remove-remote-source', remoteName);
domManager.hideSpinner();
if (result.success) {
domManager.showModal('Success', `Remote source '${result.name}' removed successfully.`);
// Refresh remotes list and keys list
await loadRemoteSources();
await loadKeys();
} else {
domManager.showModal('Error', `Failed to remove remote source: ${result.error}`);
}
} catch (error) {
domManager.hideSpinner();
domManager.showModal('Error', `Failed to remove remote source: ${error.message}`);
}
}
// Export functions
module.exports = {
initializeDependencies,
loadKeys,
loadTrustedKeys: loadKeys, // Alias for compatibility
handleGenerateKey,
handleAddKey,
handleRemoveKey,
handleSyncRemotes,
loadRemoteSources,
handleAddRemoteSource,
handleRemoveRemoteSource
};