let settingsLoaded = false; function durationSettingMs(value) { const text = String(value || "").trim().toLowerCase(); if (!text || text === "never") { return 0; } const amount = Number.parseFloat(text); if (!Number.isFinite(amount) || amount <= 0) { return 0; } if (text.includes("second")) { return amount * 1000; } if (text.includes("minute")) { return amount * 60 * 1000; } return 0; } function resetAutoLockTimer() { if (autoLockTimer) { clearTimeout(autoLockTimer); autoLockTimer = null; } if (!walletLoaded) { return; } const timeoutMs = durationSettingMs(guiRuntimeSettings.auto_lock_timeout); if (!timeoutMs) { return; } autoLockTimer = setTimeout(async () => { if (!walletLoaded) { return; } try { await window.__TAURI__?.core?.invoke?.("lock_wallet"); } catch (_) { // Local UI lock is still the important user-facing behavior. } lockWalletSession(); }, timeoutMs); } function applyRuntimeGuiSettings(settings) { guiRuntimeSettings = { ...guiRuntimeSettings, auto_lock_timeout: settings?.auto_lock_timeout || guiRuntimeSettings.auto_lock_timeout, clipboard_clear_timeout: settings?.clipboard_clear_timeout || guiRuntimeSettings.clipboard_clear_timeout, require_key_before_signing: settings?.require_key_before_signing !== false }; resetAutoLockTimer(); } async function loadRuntimeGuiSettings() { const invoke = window.__TAURI__?.core?.invoke; if (!invoke) { return; } try { applyRuntimeGuiSettings(await invoke("read_gui_settings")); } catch (_) { resetAutoLockTimer(); } } function resetAutoLockOnActivity() { if (walletLoaded) { resetAutoLockTimer(); } } function scheduleClipboardClear(copiedText) { if (clipboardClearTimer) { clearTimeout(clipboardClearTimer); clipboardClearTimer = null; } lastClipboardText = copiedText || ""; const timeoutMs = durationSettingMs(guiRuntimeSettings.clipboard_clear_timeout); if (!timeoutMs || !lastClipboardText || !navigator.clipboard) { return; } clipboardClearTimer = setTimeout(async () => { lastClipboardText = ""; try { await navigator.clipboard.writeText(""); } catch (_) { // Clipboard writes can fail outside a user gesture; never prompt or block. } }, timeoutMs); } async function clearWalletClipboard() { if (clipboardClearTimer) { clearTimeout(clipboardClearTimer); clipboardClearTimer = null; } lastClipboardText = ""; const invoke = window.__TAURI__?.core?.invoke; if (invoke) { try { await invoke("clear_system_clipboard"); return; } catch (_) { // Fall back to the Web Clipboard API when the native bridge is unavailable. } } if (!navigator.clipboard) { return; } try { await navigator.clipboard.writeText(""); } catch (_) {} } async function writeClipboardText(text) { if (!text || !navigator.clipboard) { return false; } await navigator.clipboard.writeText(text); scheduleClipboardClear(text); return true; } function setSettingsMessage(message, mode = "") { if (!settingsMessage) { return; } settingsMessage.textContent = message; settingsMessage.classList.toggle("error", mode === "error"); settingsMessage.classList.toggle("success", mode === "success"); } function settingsFieldMap() { return { wallet_path: settingsWalletPath, default_node: settingsDefaultNode, unbroadcast_transactions_path: settingsUnbroadcastPath, address_book_path: settingsAddressBookPath, ipfs_gateway: settingsIpfsGateway, auto_lock_timeout: settingsAutoLockTimeout, clipboard_clear_timeout: settingsClipboardTimeout }; } function applyGuiSettings(settings) { applyRuntimeGuiSettings(settings); const fields = settingsFieldMap(); Object.entries(fields).forEach(([key, input]) => { if (input) { input.value = settings?.[key] || ""; } }); if (settingsSavedNodes) { settingsSavedNodes.value = Array.isArray(settings?.saved_nodes) ? settings.saved_nodes.join("\n") : ""; } if (settingsRequireKey) { settingsRequireKey.checked = settings?.require_key_before_signing !== false; } } function collectGuiSettings() { return { wallet_path: settingsWalletPath?.value?.trim() || "", unbroadcast_transactions_path: settingsUnbroadcastPath?.value?.trim() || "", address_book_path: settingsAddressBookPath?.value?.trim() || "", default_node: settingsDefaultNode?.value?.trim() || "", saved_nodes: (settingsSavedNodes?.value || "") .split(/\r?\n/) .map((node) => node.trim()) .filter(Boolean), ipfs_gateway: settingsIpfsGateway?.value?.trim() || "", auto_load_trusted_nft_images: true, auto_lock_timeout: settingsAutoLockTimeout?.value || "15 minutes", clipboard_clear_timeout: settingsClipboardTimeout?.value || "60 seconds", require_key_before_signing: Boolean(settingsRequireKey?.checked), settings_path: "" }; } async function loadSettingsPage({ force = false } = {}) { const invoke = window.__TAURI__?.core?.invoke; if (!invoke || (settingsLoaded && !force)) { return; } try { const settings = await invoke("read_gui_settings"); applyGuiSettings(settings); settingsLoaded = true; setSettingsMessage(""); } catch (error) { setSettingsMessage(String(error), "error"); } } async function saveSettingsPage() { const invoke = window.__TAURI__?.core?.invoke; if (!invoke) { setSettingsMessage("Settings can only be saved from the desktop app.", "error"); return; } if (settingsSaveButton) { settingsSaveButton.disabled = true; } setSettingsMessage("Saving settings..."); try { await invoke("save_gui_settings", { settings: collectGuiSettings() }); settingsLoaded = false; await loadSettingsPage({ force: true }); await populateBroadcastNodeList(); setSettingsMessage("Settings saved.", "success"); } catch (error) { setSettingsMessage(String(error), "error"); } finally { if (settingsSaveButton) { settingsSaveButton.disabled = false; } } } async function browseSettingsPath(button) { const invoke = window.__TAURI__?.core?.invoke; if (!invoke) { return; } const key = button.dataset.settingsBrowse; const input = settingsFieldMap()[key]; if (!input) { return; } try { const selected = await invoke("select_settings_path", { kind: button.dataset.settingsBrowseKind || "folder", currentPath: input.value || "" }); if (selected) { input.value = selected; } } catch (error) { setSettingsMessage(String(error), "error"); } } function setNodeTestStatus(message, mode = "") { if (!settingsNodeTestStatus) { return; } settingsNodeTestStatus.textContent = message || ""; settingsNodeTestStatus.classList.toggle("hidden", !message); settingsNodeTestStatus.classList.toggle("error", mode === "error"); settingsNodeTestStatus.classList.toggle("success", mode === "success"); } async function testSelectedSettingsNode() { const invoke = window.__TAURI__?.core?.invoke; if (!invoke) { setNodeTestStatus("Node testing is only available in the desktop app.", "error"); return; } const broadcastNode = settingsDefaultNode?.value?.trim() || activeBroadcastNode(); if (!broadcastNode) { setNodeTestStatus("Select a node before testing.", "error"); return; } settingsTestNodeButton && (settingsTestNodeButton.disabled = true); setNodeTestStatus("Testing node..."); try { const timestamp = await invoke("test_broadcast_node_time", { broadcastNode }); setNodeTestStatus(`Active - node time ${formatNetworkTime(timestamp)}`, "success"); } catch (error) { setNodeTestStatus(String(error), "error"); } finally { settingsTestNodeButton && (settingsTestNodeButton.disabled = false); } } function setCacheClearMessage(message, mode = "") { if (!cacheClearMessage) { return; } cacheClearMessage.textContent = message || ""; cacheClearMessage.classList.toggle("error", mode === "error"); cacheClearMessage.classList.toggle("success", mode === "success"); } function setCacheClearModalOpen(open) { cacheClearModal?.classList.toggle("hidden", !open); if (open) { setCacheClearMessage(""); } } async function clearSelectedWalletCache(kind, button) { const invoke = window.__TAURI__?.core?.invoke; if (!invoke || !walletLoaded) { setCacheClearMessage("Load a wallet before clearing local cache.", "error"); return; } button && (button.disabled = true); setCacheClearMessage("Clearing cache..."); try { await invoke("clear_wallet_cache", { cacheKind: kind }); if (kind === "history") { clearHistoryRows(); resetLatestDashboardTransactionsMemory(); if (typeof resetHistoryPage === "function") { resetHistoryPage(); } } else if (kind === "nft" || kind === "nft_details") { if (typeof resetNftsPage === "function") { resetNftsPage(); } if (currentView === "nfts" && typeof prepareNftsPage === "function") { prepareNftsPage({ force: true }); } } else if (kind === "address_book") { if (typeof resetAddressBookPage === "function") { resetAddressBookPage(); } if (currentView === "addressbook" && typeof prepareAddressBookPage === "function") { prepareAddressBookPage(); } } setCacheClearMessage("Cache cleared.", "success"); } catch (error) { setCacheClearMessage(String(error), "error"); } finally { button && (button.disabled = false); } } function setBackupKeyMessage(message, mode = "") { if (!backupKeyMessage) { return; } backupKeyMessage.textContent = message; backupKeyMessage.classList.toggle("error", mode === "error"); backupKeyMessage.classList.toggle("success", mode === "success"); } function closeBackupKeyModal(value = null) { if (backupKeyModal) { backupKeyModal.classList.add("hidden"); } if (backupKeyInput) { backupKeyInput.value = ""; } setBackupKeyMessage(""); if (pendingBackupKeyRequest) { pendingBackupKeyRequest.resolve(value); pendingBackupKeyRequest = null; } } function requestWalletBackupKey(copy, title = "Wallet Backup") { if (!backupKeyModal) { return Promise.resolve(null); } if (pendingBackupKeyRequest) { closeBackupKeyModal(null); } backupKeyTitle && (backupKeyTitle.textContent = title); backupKeyCopy && (backupKeyCopy.textContent = copy || "Enter the wallet encryption key."); backupKeyInput && (backupKeyInput.value = ""); setBackupKeyMessage(""); backupKeyModal.classList.remove("hidden"); setTimeout(() => backupKeyInput?.focus(), 0); return new Promise((resolve) => { pendingBackupKeyRequest = { resolve }; }); } async function requestSigningWalletKey() { if (guiRuntimeSettings.require_key_before_signing === false) { return null; } return requestWalletBackupKey( "Enter the wallet encryption key to sign this transaction.", "Sign Transaction" ); } function htmlEscape(value) { return String(value ?? "") .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function printWalletBackup(data) { const printFrame = document.createElement("iframe"); printFrame.setAttribute("title", "Contractless Wallet Backup Print"); printFrame.style.position = "fixed"; printFrame.style.right = "0"; printFrame.style.bottom = "0"; printFrame.style.width = "0"; printFrame.style.height = "0"; printFrame.style.border = "0"; printFrame.style.opacity = "0"; printFrame.style.pointerEvents = "none"; document.body.appendChild(printFrame); const printDocument = printFrame.contentWindow?.document; if (!printDocument) { printFrame.remove(); setSettingsMessage("Unable to prepare print document.", "error"); return; } printDocument.open(); printDocument.write(`
Keep this page private. Anyone with the private key and encryption key can control this wallet.
${htmlEscape(data.wallet_name)}${htmlEscape(data.private_key)}${htmlEscape(data.encryption_key)}