mirror of
https://github.com/basicswap/basicswap.git
synced 2026-05-25 05:53:09 +02:00
902 lines
39 KiB
JavaScript
902 lines
39 KiB
JavaScript
|
|
(function() {
|
|
'use strict';
|
|
|
|
const SettingsPage = {
|
|
confirmCallback: null,
|
|
triggerElement: null,
|
|
|
|
originalConnectionTypes: {},
|
|
|
|
init: function() {
|
|
this.setupTabs();
|
|
this.setupCoinHeaders();
|
|
this.setupConfirmModal();
|
|
this.setupNotificationSettings();
|
|
this.setupMigrationIndicator();
|
|
this.setupServerDiscovery();
|
|
},
|
|
|
|
setupTabs: function() {
|
|
const tabButtons = document.querySelectorAll('.tab-button');
|
|
const tabContents = document.querySelectorAll('.tab-content');
|
|
|
|
const switchTab = (targetTab) => {
|
|
tabButtons.forEach(btn => {
|
|
if (btn.dataset.tab === targetTab) {
|
|
btn.className = 'tab-button border-b-2 border-blue-500 text-blue-600 dark:text-blue-400 py-4 px-1 text-sm font-medium focus:outline-none focus:ring-0';
|
|
} else {
|
|
btn.className = 'tab-button border-b-2 border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:border-gray-300 dark:hover:border-gray-600 py-4 px-1 text-sm font-medium focus:outline-none focus:ring-0';
|
|
}
|
|
});
|
|
|
|
tabContents.forEach(content => {
|
|
if (content.id === targetTab) {
|
|
content.classList.remove('hidden');
|
|
} else {
|
|
content.classList.add('hidden');
|
|
}
|
|
});
|
|
};
|
|
|
|
tabButtons.forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
switchTab(btn.dataset.tab);
|
|
});
|
|
});
|
|
},
|
|
|
|
setupCoinHeaders: function() {
|
|
const coinHeaders = document.querySelectorAll('.coin-header');
|
|
coinHeaders.forEach(header => {
|
|
header.addEventListener('click', function() {
|
|
const coinName = this.dataset.coin;
|
|
const details = document.getElementById(`details-${coinName}`);
|
|
const arrow = this.querySelector('.toggle-arrow');
|
|
|
|
if (details.classList.contains('hidden')) {
|
|
details.classList.remove('hidden');
|
|
arrow.style.transform = 'rotate(180deg)';
|
|
} else {
|
|
details.classList.add('hidden');
|
|
arrow.style.transform = 'rotate(0deg)';
|
|
}
|
|
});
|
|
});
|
|
},
|
|
|
|
pendingModeSwitch: null,
|
|
|
|
setupMigrationIndicator: function() {
|
|
const connectionTypeSelects = document.querySelectorAll('select[name^="connection_type_"]');
|
|
connectionTypeSelects.forEach(select => {
|
|
const originalValue = select.dataset.originalValue || select.value;
|
|
this.originalConnectionTypes[select.name] = originalValue;
|
|
|
|
select.addEventListener('change', (e) => {
|
|
const coinName = select.name.replace('connection_type_', '');
|
|
const electrumSection = document.getElementById(`electrum-section-${coinName}`);
|
|
const fundTransferSection = document.getElementById(`fund-transfer-section-${coinName}`);
|
|
const originalValue = this.originalConnectionTypes[select.name];
|
|
|
|
if (e.target.value === 'electrum') {
|
|
if (electrumSection) {
|
|
electrumSection.classList.remove('hidden');
|
|
|
|
const clearnetTextarea = document.getElementById(`electrum_clearnet_${coinName}`);
|
|
const onionTextarea = document.getElementById(`electrum_onion_${coinName}`);
|
|
|
|
if (clearnetTextarea && !clearnetTextarea.value.trim()) {
|
|
clearnetTextarea.value = electrumSection.dataset.defaultClearnet || '';
|
|
}
|
|
if (onionTextarea && !onionTextarea.value.trim()) {
|
|
onionTextarea.value = electrumSection.dataset.defaultOnion || '';
|
|
}
|
|
}
|
|
if (fundTransferSection) {
|
|
fundTransferSection.classList.add('hidden');
|
|
}
|
|
} else {
|
|
if (electrumSection) {
|
|
electrumSection.classList.add('hidden');
|
|
}
|
|
if (fundTransferSection && originalValue === 'electrum') {
|
|
fundTransferSection.classList.remove('hidden');
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
this.setupWalletModeModal();
|
|
|
|
const coinsForm = document.getElementById('coins-form');
|
|
if (coinsForm) {
|
|
coinsForm.addEventListener('submit', (e) => {
|
|
const submitter = e.submitter;
|
|
if (!submitter || !submitter.name.startsWith('apply_')) return;
|
|
|
|
const coinName = submitter.name.replace('apply_', '');
|
|
const select = document.querySelector(`select[name="connection_type_${coinName}"]`);
|
|
if (!select) return;
|
|
|
|
const original = this.originalConnectionTypes[select.name];
|
|
const current = select.value;
|
|
|
|
if (original && current && original !== current) {
|
|
e.preventDefault();
|
|
const direction = (original === 'rpc' && current === 'electrum') ? 'lite' : 'rpc';
|
|
this.showWalletModeConfirmation(coinName, direction, submitter);
|
|
}
|
|
});
|
|
}
|
|
},
|
|
|
|
setupWalletModeModal: function() {
|
|
const confirmBtn = document.getElementById('walletModeConfirm');
|
|
const cancelBtn = document.getElementById('walletModeCancel');
|
|
|
|
if (confirmBtn) {
|
|
confirmBtn.addEventListener('click', () => {
|
|
this.hideWalletModeModal();
|
|
if (this.pendingModeSwitch) {
|
|
const { coinName, direction, submitter } = this.pendingModeSwitch;
|
|
this.showMigrationModal(coinName.toUpperCase(), direction);
|
|
const form = submitter.form;
|
|
const hiddenInput = document.createElement('input');
|
|
hiddenInput.type = 'hidden';
|
|
hiddenInput.name = submitter.name;
|
|
hiddenInput.value = submitter.value;
|
|
form.appendChild(hiddenInput);
|
|
|
|
let transferValue = null;
|
|
const transferRadio = document.querySelector('input[name="transfer_choice"]:checked');
|
|
const transferHidden = document.querySelector('input[name="transfer_choice"][type="hidden"]');
|
|
if (transferRadio) {
|
|
transferValue = transferRadio.value;
|
|
} else if (transferHidden) {
|
|
transferValue = transferHidden.value;
|
|
}
|
|
if (transferValue) {
|
|
const transferInput = document.createElement('input');
|
|
transferInput.type = 'hidden';
|
|
transferInput.name = `auto_transfer_now_${coinName}`;
|
|
transferInput.value = transferValue === 'auto' ? 'true' : 'false';
|
|
form.appendChild(transferInput);
|
|
}
|
|
|
|
form.submit();
|
|
}
|
|
});
|
|
}
|
|
|
|
if (cancelBtn) {
|
|
cancelBtn.addEventListener('click', () => {
|
|
this.hideWalletModeModal();
|
|
if (this.pendingModeSwitch) {
|
|
const { coinName } = this.pendingModeSwitch;
|
|
const select = document.querySelector(`select[name="connection_type_${coinName}"]`);
|
|
if (select) {
|
|
select.value = this.originalConnectionTypes[select.name];
|
|
}
|
|
}
|
|
this.pendingModeSwitch = null;
|
|
});
|
|
}
|
|
},
|
|
|
|
updateConfirmButtonState: function() {
|
|
const confirmBtn = document.getElementById('walletModeConfirm');
|
|
const checkbox = document.getElementById('walletModeKeyConfirmCheckbox');
|
|
if (confirmBtn && checkbox) {
|
|
if (checkbox.checked) {
|
|
confirmBtn.disabled = false;
|
|
confirmBtn.classList.remove('opacity-50', 'cursor-not-allowed');
|
|
} else {
|
|
confirmBtn.disabled = true;
|
|
confirmBtn.classList.add('opacity-50', 'cursor-not-allowed');
|
|
}
|
|
}
|
|
},
|
|
|
|
showWalletModeConfirmation: async function(coinName, direction, submitter) {
|
|
const modal = document.getElementById('walletModeModal');
|
|
const title = document.getElementById('walletModeTitle');
|
|
const message = document.getElementById('walletModeMessage');
|
|
const details = document.getElementById('walletModeDetails');
|
|
const confirmBtn = document.getElementById('walletModeConfirm');
|
|
|
|
if (!modal || !title || !message || !details) return;
|
|
|
|
this.pendingModeSwitch = { coinName, direction, submitter };
|
|
|
|
const displayName = coinName.charAt(0).toUpperCase() + coinName.slice(1).toLowerCase();
|
|
|
|
details.innerHTML = `
|
|
<div class="flex items-center justify-center py-4">
|
|
<svg class="animate-spin h-5 w-5 text-blue-500 mr-2" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
</svg>
|
|
<span>Loading...</span>
|
|
</div>
|
|
`;
|
|
|
|
if (confirmBtn) {
|
|
confirmBtn.disabled = true;
|
|
confirmBtn.classList.add('opacity-50', 'cursor-not-allowed');
|
|
}
|
|
|
|
modal.classList.remove('hidden');
|
|
|
|
if (direction === 'lite') {
|
|
title.textContent = `Switch ${displayName} to Lite Wallet Mode`;
|
|
message.textContent = 'Write down this key before switching. It will only be shown ONCE.';
|
|
|
|
try {
|
|
const [infoResponse, seedResponse] = await Promise.all([
|
|
fetch('/json/modeswitchinfo', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ coin: coinName, direction: 'lite' })
|
|
}),
|
|
fetch('/json/getcoinseed', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ coin: coinName })
|
|
})
|
|
]);
|
|
const info = await infoResponse.json();
|
|
const data = await seedResponse.json();
|
|
|
|
let transferSection = '';
|
|
if (info.require_transfer && info.legacy_balance_sats > 0) {
|
|
transferSection = `
|
|
<div class="bg-gray-100 dark:bg-gray-700 border border-gray-300 dark:border-gray-500 rounded-lg p-3 mb-3">
|
|
<p class="text-sm font-medium text-gray-900 dark:text-white mb-2">Funds Transfer Required</p>
|
|
<p class="text-xs text-gray-700 dark:text-gray-200 mb-2">
|
|
<strong>${info.legacy_balance} ${info.coin}</strong> on non-derivable addresses will be automatically transferred to a BIP84 address.
|
|
</p>
|
|
<p class="text-xs text-gray-600 dark:text-gray-300 mb-2">
|
|
Est. fee: ${info.estimated_fee} ${info.coin}
|
|
</p>
|
|
<p class="text-xs text-gray-700 dark:text-gray-200">
|
|
This ensures your funds are recoverable using the extended key backup in external Electrum wallets.
|
|
</p>
|
|
<input type="hidden" name="transfer_choice" value="auto">
|
|
</div>
|
|
`;
|
|
} else if (info.legacy_balance_sats > 0 && !info.show_transfer_option) {
|
|
transferSection = `
|
|
<p class="text-gray-700 dark:text-gray-300 text-xs mb-3">
|
|
Some funds on non-derivable addresses (${info.legacy_balance} ${info.coin}) - too low to transfer.
|
|
</p>
|
|
`;
|
|
}
|
|
|
|
if (data.account_key) {
|
|
details.innerHTML = `
|
|
<p class="mb-2 text-red-600 dark:text-red-300 font-semibold">
|
|
IMPORTANT: Write down this key NOW. It will not be shown again.
|
|
</p>
|
|
<p class="mb-2 text-gray-800 dark:text-gray-100"><strong>Extended Private Key (for external wallet import):</strong></p>
|
|
<div class="bg-gray-50 dark:bg-gray-700 border border-gray-300 dark:border-gray-500 rounded p-2 mb-3">
|
|
<code id="extendedKeyDisplay" class="text-xs break-all font-mono text-gray-900 dark:text-gray-100">${'*'.repeat(Math.min(data.account_key.length, 80))}</code>
|
|
<code id="extendedKeyActual" class="text-xs break-all select-all font-mono text-gray-900 dark:text-gray-100 hidden">${data.account_key}</code>
|
|
</div>
|
|
<div class="mb-3">
|
|
<button type="button" id="toggleKeyVisibility" class="px-3 py-1 text-xs bg-blue-500 hover:bg-blue-600 text-white rounded">
|
|
Show Key
|
|
</button>
|
|
</div>
|
|
<div class="text-xs text-gray-600 dark:text-gray-300 mb-3 bg-gray-100 dark:bg-gray-600 border border-gray-300 dark:border-gray-500 rounded p-2">
|
|
<p class="font-medium mb-1 text-gray-800 dark:text-gray-100">To import in Electrum wallet:</p>
|
|
<ol class="list-decimal list-inside space-y-0.5">
|
|
<li>Open Electrum → File → New/Restore</li>
|
|
<li>Choose "Standard wallet" → "Use a master key"</li>
|
|
<li>Paste this key (starts with zprv... or yprv...)</li>
|
|
</ol>
|
|
</div>
|
|
${transferSection}
|
|
<div class="border-t border-gray-300 dark:border-gray-500 pt-3">
|
|
<label class="flex items-center cursor-pointer hover:bg-gray-200 dark:hover:bg-gray-500 rounded p-1 -m-1">
|
|
<input type="checkbox" id="walletModeKeyConfirmCheckbox" class="mr-2 h-4 w-4 text-blue-600 rounded border-gray-300 dark:border-gray-500 focus:ring-blue-500 dark:bg-gray-700">
|
|
<span class="text-sm font-medium text-gray-800 dark:text-gray-100">I have written down this key</span>
|
|
</label>
|
|
</div>
|
|
`;
|
|
|
|
const toggleBtn = document.getElementById('toggleKeyVisibility');
|
|
const keyDisplay = document.getElementById('extendedKeyDisplay');
|
|
const keyActual = document.getElementById('extendedKeyActual');
|
|
if (toggleBtn && keyDisplay && keyActual) {
|
|
toggleBtn.addEventListener('click', () => {
|
|
if (keyDisplay.classList.contains('hidden')) {
|
|
keyDisplay.classList.remove('hidden');
|
|
keyActual.classList.add('hidden');
|
|
toggleBtn.textContent = 'Show Key';
|
|
} else {
|
|
keyDisplay.classList.add('hidden');
|
|
keyActual.classList.remove('hidden');
|
|
toggleBtn.textContent = 'Hide Key';
|
|
}
|
|
});
|
|
}
|
|
|
|
const checkbox = document.getElementById('walletModeKeyConfirmCheckbox');
|
|
if (checkbox) {
|
|
checkbox.addEventListener('change', () => this.updateConfirmButtonState());
|
|
}
|
|
} else {
|
|
details.innerHTML = `
|
|
<p class="mb-2 text-gray-800 dark:text-gray-100"><strong>Before switching:</strong></p>
|
|
<ul class="list-disc list-inside space-y-1 text-gray-700 dark:text-gray-200">
|
|
<li>Active swaps must be completed first</li>
|
|
<li>Wait for any pending transactions to confirm</li>
|
|
</ul>
|
|
${transferSection}
|
|
<p class="mt-3 text-green-700 dark:text-green-300">
|
|
<strong>Note:</strong> Your balance will remain accessible - same seed means same funds in both modes.
|
|
</p>
|
|
`;
|
|
if (confirmBtn) {
|
|
confirmBtn.disabled = false;
|
|
confirmBtn.classList.remove('opacity-50', 'cursor-not-allowed');
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to fetch coin seed:', error);
|
|
details.innerHTML = `
|
|
<p class="text-red-600 dark:text-red-300 mb-2">Failed to retrieve extended key. Please try again.</p>
|
|
<p class="mb-2 text-gray-800 dark:text-gray-100"><strong>Before switching:</strong></p>
|
|
<ul class="list-disc list-inside space-y-1 text-gray-700 dark:text-gray-200">
|
|
<li>Active swaps must be completed first</li>
|
|
<li>Wait for any pending transactions to confirm</li>
|
|
</ul>
|
|
`;
|
|
if (confirmBtn) {
|
|
confirmBtn.disabled = false;
|
|
confirmBtn.classList.remove('opacity-50', 'cursor-not-allowed');
|
|
}
|
|
}
|
|
} else {
|
|
title.textContent = `Switch ${displayName} to Full Node Mode`;
|
|
message.textContent = 'Please confirm you want to switch to full node mode.';
|
|
|
|
try {
|
|
const response = await fetch('/json/modeswitchinfo', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ coin: coinName, direction: 'rpc' })
|
|
});
|
|
const info = await response.json();
|
|
|
|
let transferSection = '';
|
|
if (info.error) {
|
|
transferSection = `<p class="text-yellow-700 dark:text-yellow-300 text-sm">${info.error}</p>`;
|
|
} else if (info.balance_sats === 0) {
|
|
transferSection = `<p class="text-gray-600 dark:text-gray-300 text-sm">No funds to transfer.</p>`;
|
|
} else if (!info.can_transfer) {
|
|
transferSection = `
|
|
<p class="text-yellow-700 dark:text-yellow-300 text-sm">
|
|
Balance (${info.balance} ${info.coin}) is too low to transfer - fee would exceed funds.
|
|
</p>
|
|
`;
|
|
} else {
|
|
transferSection = `
|
|
<div class="bg-gray-200 dark:bg-gray-700 border border-gray-300 dark:border-gray-500 rounded-lg p-3 mb-3">
|
|
<p class="text-sm font-medium text-gray-900 dark:text-white mb-2">Fund Transfer Options</p>
|
|
<p class="text-xs text-gray-700 dark:text-gray-300 mb-3">
|
|
Balance: ${info.balance} ${info.coin} | Est. fee: ${info.estimated_fee} ${info.coin}
|
|
</p>
|
|
<div class="space-y-2">
|
|
<label class="flex items-start cursor-pointer hover:bg-gray-300 dark:hover:bg-gray-600 rounded p-1.5 -m-1">
|
|
<input type="radio" name="transfer_choice" value="auto" checked class="mt-0.5 mr-2 h-4 w-4 text-blue-600 border-gray-400 dark:border-gray-400 focus:ring-blue-500 bg-white dark:bg-gray-500">
|
|
<div>
|
|
<span class="text-sm font-medium text-gray-900 dark:text-white">Auto-transfer funds to RPC wallet</span>
|
|
<p class="text-xs text-gray-600 dark:text-gray-300">Recommended. Ensures all funds visible in full node wallet.</p>
|
|
</div>
|
|
</label>
|
|
<label class="flex items-start cursor-pointer hover:bg-gray-300 dark:hover:bg-gray-600 rounded p-1.5 -m-1">
|
|
<input type="radio" name="transfer_choice" value="manual" class="mt-0.5 mr-2 h-4 w-4 text-blue-600 border-gray-400 dark:border-gray-400 focus:ring-blue-500 bg-white dark:bg-gray-500">
|
|
<div>
|
|
<span class="text-sm font-medium text-gray-900 dark:text-white">Keep funds on current addresses</span>
|
|
<p class="text-xs text-gray-600 dark:text-gray-300">Transfer manually later if needed.</p>
|
|
</div>
|
|
</label>
|
|
</div>
|
|
<p class="text-xs text-gray-600 dark:text-gray-400 mt-3">
|
|
If you skip transfer, you will need to manually send funds from lite wallet addresses to your RPC wallet.
|
|
</p>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
details.innerHTML = `
|
|
<p class="mb-2 text-gray-800 dark:text-gray-100"><strong>Switching to full node mode:</strong></p>
|
|
<ul class="list-disc list-inside space-y-1 mb-3 text-gray-700 dark:text-gray-200">
|
|
<li>Requires synced ${displayName} blockchain</li>
|
|
<li>Your wallet addresses will be synced</li>
|
|
<li>Active swaps must be completed first</li>
|
|
<li>Restart required after switch</li>
|
|
</ul>
|
|
${transferSection}
|
|
`;
|
|
|
|
if (confirmBtn) {
|
|
confirmBtn.disabled = false;
|
|
confirmBtn.classList.remove('opacity-50', 'cursor-not-allowed');
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to fetch mode switch info:', error);
|
|
details.innerHTML = `
|
|
<p class="mb-2 text-gray-800 dark:text-gray-100"><strong>Switching to full node mode:</strong></p>
|
|
<ul class="list-disc list-inside space-y-1 text-gray-700 dark:text-gray-200">
|
|
<li>Requires synced ${displayName} blockchain</li>
|
|
<li>Your wallet addresses will be synced</li>
|
|
<li>Active swaps must be completed first</li>
|
|
<li>Restart required after switch</li>
|
|
</ul>
|
|
`;
|
|
if (confirmBtn) {
|
|
confirmBtn.disabled = false;
|
|
confirmBtn.classList.remove('opacity-50', 'cursor-not-allowed');
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
hideWalletModeModal: function() {
|
|
const modal = document.getElementById('walletModeModal');
|
|
if (modal) {
|
|
modal.classList.add('hidden');
|
|
}
|
|
},
|
|
|
|
showMigrationModal: function(coinName, direction) {
|
|
const modal = document.getElementById('migrationModal');
|
|
const title = document.getElementById('migrationTitle');
|
|
const message = document.getElementById('migrationMessage');
|
|
|
|
if (modal && title && message) {
|
|
if (direction === 'lite') {
|
|
title.textContent = `Migrating ${coinName} to Lite Wallet`;
|
|
message.textContent = 'Checking wallet balance and migrating addresses. Please wait...';
|
|
} else {
|
|
title.textContent = `Switching ${coinName} to Full Node`;
|
|
message.textContent = 'Syncing wallet indices. Please wait...';
|
|
}
|
|
modal.classList.remove('hidden');
|
|
}
|
|
},
|
|
|
|
setupConfirmModal: function() {
|
|
const confirmYesBtn = document.getElementById('confirmYes');
|
|
if (confirmYesBtn) {
|
|
confirmYesBtn.addEventListener('click', () => {
|
|
if (typeof this.confirmCallback === 'function') {
|
|
this.confirmCallback();
|
|
}
|
|
this.hideConfirmDialog();
|
|
});
|
|
}
|
|
|
|
const confirmNoBtn = document.getElementById('confirmNo');
|
|
if (confirmNoBtn) {
|
|
confirmNoBtn.addEventListener('click', () => {
|
|
this.hideConfirmDialog();
|
|
});
|
|
}
|
|
},
|
|
|
|
showConfirmDialog: function(title, message, callback) {
|
|
this.confirmCallback = callback;
|
|
document.getElementById('confirmTitle').textContent = title;
|
|
document.getElementById('confirmMessage').textContent = message;
|
|
const modal = document.getElementById('confirmModal');
|
|
if (modal) {
|
|
modal.classList.remove('hidden');
|
|
}
|
|
return false;
|
|
},
|
|
|
|
hideConfirmDialog: function() {
|
|
const modal = document.getElementById('confirmModal');
|
|
if (modal) {
|
|
modal.classList.add('hidden');
|
|
}
|
|
this.confirmCallback = null;
|
|
return false;
|
|
},
|
|
|
|
confirmDisableCoin: function() {
|
|
this.triggerElement = document.activeElement;
|
|
return this.showConfirmDialog(
|
|
"Confirm Disable Coin",
|
|
"Are you sure you want to disable this coin?",
|
|
() => {
|
|
if (this.triggerElement) {
|
|
const form = this.triggerElement.form;
|
|
const hiddenInput = document.createElement('input');
|
|
hiddenInput.type = 'hidden';
|
|
hiddenInput.name = this.triggerElement.name;
|
|
hiddenInput.value = this.triggerElement.value;
|
|
form.appendChild(hiddenInput);
|
|
form.submit();
|
|
}
|
|
}
|
|
);
|
|
},
|
|
|
|
setupNotificationSettings: function() {
|
|
const notificationsTab = document.getElementById('notifications-tab');
|
|
if (notificationsTab) {
|
|
notificationsTab.addEventListener('click', () => {
|
|
CleanupManager.setTimeout(() => this.syncNotificationSettings(), 100);
|
|
});
|
|
}
|
|
|
|
document.addEventListener('change', (e) => {
|
|
if (e.target.closest('#notifications')) {
|
|
this.syncNotificationSettings();
|
|
}
|
|
});
|
|
|
|
this.syncNotificationSettings();
|
|
},
|
|
|
|
syncNotificationSettings: function() {
|
|
if (window.NotificationManager && typeof window.NotificationManager.updateSettings === 'function') {
|
|
const backendSettings = {
|
|
showNewOffers: document.getElementById('notifications_new_offers')?.checked || false,
|
|
showNewBids: document.getElementById('notifications_new_bids')?.checked || false,
|
|
showBidAccepted: document.getElementById('notifications_bid_accepted')?.checked || false,
|
|
showBalanceChanges: document.getElementById('notifications_balance_changes')?.checked || false,
|
|
showOutgoingTransactions: document.getElementById('notifications_outgoing_transactions')?.checked || false,
|
|
showSwapCompleted: document.getElementById('notifications_swap_completed')?.checked || false,
|
|
showUpdateNotifications: document.getElementById('check_updates')?.checked || false,
|
|
notificationDuration: parseInt(document.getElementById('notifications_duration')?.value || '5') * 1000
|
|
};
|
|
|
|
window.NotificationManager.updateSettings(backendSettings);
|
|
}
|
|
},
|
|
|
|
testUpdateNotification: function() {
|
|
if (window.NotificationManager) {
|
|
window.NotificationManager.createToast(
|
|
'Update Available: v0.15.0',
|
|
'update_available',
|
|
{
|
|
subtitle: 'Current: v0.14.6 • Click to view release (Test/Dummy)',
|
|
releaseUrl: 'https://github.com/basicswap/basicswap/releases/tag/v0.15.0',
|
|
releaseNotes: 'New version v0.15.0 is available. Click to view details on GitHub.'
|
|
}
|
|
);
|
|
}
|
|
},
|
|
|
|
testLiveUpdateCheck: function(event) {
|
|
const button = event?.target || event?.currentTarget || document.querySelector('[onclick*="testLiveUpdateCheck"]');
|
|
if (!button) return;
|
|
|
|
const originalText = button.textContent;
|
|
button.textContent = 'Checking...';
|
|
button.disabled = true;
|
|
|
|
fetch('/json/checkupdates', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (window.NotificationManager) {
|
|
const currentVer = data.current_version || 'Unknown';
|
|
const latestVer = data.latest_version || currentVer;
|
|
|
|
if (data.update_available) {
|
|
window.NotificationManager.createToast(
|
|
`Live Update Available: v${latestVer}`,
|
|
'update_available',
|
|
{
|
|
latest_version: latestVer,
|
|
current_version: currentVer,
|
|
subtitle: `Current: v${currentVer} • Click to view release`,
|
|
releaseUrl: `https://github.com/basicswap/basicswap/releases/tag/v${latestVer}`,
|
|
releaseNotes: 'This is a real update check from GitHub API.'
|
|
}
|
|
);
|
|
} else {
|
|
window.NotificationManager.createToast(
|
|
'No Updates Available',
|
|
'success',
|
|
{
|
|
subtitle: `Current version v${currentVer} is up to date`
|
|
}
|
|
);
|
|
}
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Update check failed:', error);
|
|
if (window.NotificationManager) {
|
|
window.NotificationManager.createToast(
|
|
'Update Check Failed',
|
|
'error',
|
|
{
|
|
subtitle: 'Could not check for updates. See console for details.'
|
|
}
|
|
);
|
|
}
|
|
})
|
|
.finally(() => {
|
|
if (button) {
|
|
button.textContent = originalText;
|
|
button.disabled = false;
|
|
}
|
|
});
|
|
},
|
|
|
|
checkForUpdatesNow: function(event) {
|
|
const button = event?.target || event?.currentTarget || document.querySelector('[data-check-updates]');
|
|
if (!button) return;
|
|
|
|
const originalText = button.textContent;
|
|
button.textContent = 'Checking...';
|
|
button.disabled = true;
|
|
|
|
fetch('/json/checkupdates', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.error) {
|
|
if (window.NotificationManager) {
|
|
window.NotificationManager.createToast(
|
|
'Update Check Failed',
|
|
'error',
|
|
{
|
|
subtitle: data.error
|
|
}
|
|
);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (window.NotificationManager) {
|
|
const currentVer = data.current_version || 'Unknown';
|
|
const latestVer = data.latest_version || currentVer;
|
|
|
|
if (data.update_available) {
|
|
window.NotificationManager.createToast(
|
|
`Update Available: v${latestVer}`,
|
|
'update_available',
|
|
{
|
|
latest_version: latestVer,
|
|
current_version: currentVer,
|
|
subtitle: `Current: v${currentVer} • Click to view release`,
|
|
releaseUrl: `https://github.com/basicswap/basicswap/releases/tag/v${latestVer}`,
|
|
releaseNotes: `New version v${latestVer} is available. Click to view details on GitHub.`
|
|
}
|
|
);
|
|
} else {
|
|
window.NotificationManager.createToast(
|
|
'You\'re Up to Date!',
|
|
'success',
|
|
{
|
|
subtitle: `Current version v${currentVer} is the latest`
|
|
}
|
|
);
|
|
}
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Update check failed:', error);
|
|
if (window.NotificationManager) {
|
|
window.NotificationManager.createToast(
|
|
'Update Check Failed',
|
|
'error',
|
|
{
|
|
subtitle: 'Network error. Please try again later.'
|
|
}
|
|
);
|
|
}
|
|
})
|
|
.finally(() => {
|
|
if (button) {
|
|
button.textContent = originalText;
|
|
button.disabled = false;
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
SettingsPage.setupServerDiscovery = function() {
|
|
const discoverBtns = document.querySelectorAll('.discover-servers-btn');
|
|
discoverBtns.forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
const coin = btn.dataset.coin;
|
|
this.discoverServers(coin, btn);
|
|
});
|
|
});
|
|
|
|
const closeBtns = document.querySelectorAll('.close-discovered-btn');
|
|
closeBtns.forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
const coin = btn.dataset.coin;
|
|
const panel = document.getElementById(`discovered-servers-${coin}`);
|
|
if (panel) panel.classList.add('hidden');
|
|
});
|
|
});
|
|
};
|
|
|
|
SettingsPage.discoverServers = function(coin, button) {
|
|
const originalHtml = button.innerHTML;
|
|
button.innerHTML = `<svg class="w-3.5 h-3.5 mr-1 animate-spin inline-block" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>Discovering...`;
|
|
button.disabled = true;
|
|
|
|
const panel = document.getElementById(`discovered-servers-${coin}`);
|
|
const listContainer = document.getElementById(`discovered-list-${coin}`);
|
|
|
|
fetch('/json/electrumdiscover', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ coin: coin, ping: true })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.error) {
|
|
listContainer.innerHTML = `<div class="text-sm text-red-500">${data.error}</div>`;
|
|
} else {
|
|
let html = '';
|
|
|
|
if (data.current_server) {
|
|
html += `
|
|
<div class="flex items-center mb-4 p-3 bg-gray-100 dark:bg-gray-600 border border-gray-200 dark:border-gray-500 rounded-lg">
|
|
<span class="w-2 h-2 bg-green-500 rounded-full mr-3 animate-pulse"></span>
|
|
<span class="text-sm text-gray-900 dark:text-white">
|
|
Connected to: <span class="font-mono font-medium">${data.current_server.host}:${data.current_server.port}</span>
|
|
</span>
|
|
</div>`;
|
|
}
|
|
|
|
if (data.clearnet_servers && data.clearnet_servers.length > 0) {
|
|
html += `
|
|
<div class="mb-4">
|
|
<div class="text-sm font-semibold text-gray-900 dark:text-white mb-2 pb-2 border-b border-gray-200 dark:border-gray-600">
|
|
Clearnet
|
|
</div>
|
|
<div class="space-y-1">`;
|
|
data.clearnet_servers.forEach(srv => {
|
|
const statusClass = srv.online ? 'text-green-600 dark:text-green-400' : 'text-gray-400 dark:text-gray-500';
|
|
const statusText = srv.online ? (srv.latency_ms ? srv.latency_ms.toFixed(0) + 'ms' : 'online') : 'offline';
|
|
const statusDot = srv.online ? 'bg-green-500' : 'bg-gray-400';
|
|
html += `
|
|
<div class="flex items-center justify-between py-2 px-3 hover:bg-gray-100 dark:hover:bg-gray-600 rounded-lg cursor-pointer add-server-btn transition-colors border border-transparent hover:border-blue-500"
|
|
data-coin="${coin}" data-host="${srv.host}" data-port="${srv.port}" data-type="clearnet">
|
|
<div class="flex items-center flex-1 min-w-0">
|
|
<svg class="w-4 h-4 mr-2 text-blue-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
|
</svg>
|
|
<span class="font-mono text-sm text-gray-900 dark:text-white truncate">${srv.host}:${srv.port}</span>
|
|
</div>
|
|
<div class="flex items-center ml-3">
|
|
<span class="w-2 h-2 ${statusDot} rounded-full mr-2"></span>
|
|
<span class="text-xs ${statusClass}">${statusText}</span>
|
|
</div>
|
|
</div>`;
|
|
});
|
|
html += `
|
|
</div>
|
|
</div>`;
|
|
}
|
|
|
|
if (data.onion_servers && data.onion_servers.length > 0) {
|
|
html += `
|
|
<div class="mb-4">
|
|
<div class="text-sm font-semibold text-gray-900 dark:text-white mb-2 pb-2 border-b border-gray-200 dark:border-gray-600">
|
|
TOR (.onion)
|
|
</div>
|
|
<div class="space-y-1">`;
|
|
data.onion_servers.forEach(srv => {
|
|
const statusClass = srv.online ? 'text-green-600 dark:text-green-400' : 'text-gray-400 dark:text-gray-500';
|
|
const statusText = srv.online ? (srv.latency_ms ? srv.latency_ms.toFixed(0) + 'ms' : 'online') : 'offline';
|
|
const statusDot = srv.online ? 'bg-green-500' : 'bg-gray-400';
|
|
html += `
|
|
<div class="flex items-center justify-between py-2 px-3 hover:bg-gray-100 dark:hover:bg-gray-600 rounded-lg cursor-pointer add-server-btn transition-colors border border-transparent hover:border-blue-500"
|
|
data-coin="${coin}" data-host="${srv.host}" data-port="${srv.port}" data-type="onion">
|
|
<div class="flex items-center flex-1 min-w-0">
|
|
<svg class="w-4 h-4 mr-2 text-blue-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
|
|
</svg>
|
|
<span class="font-mono text-sm text-gray-900 dark:text-white truncate" title="${srv.host}">${srv.host.substring(0, 24)}...:${srv.port}</span>
|
|
</div>
|
|
<div class="flex items-center ml-3">
|
|
<span class="w-2 h-2 ${statusDot} rounded-full mr-2"></span>
|
|
<span class="text-xs ${statusClass}">${statusText}</span>
|
|
</div>
|
|
</div>`;
|
|
});
|
|
html += `
|
|
</div>
|
|
</div>`;
|
|
}
|
|
|
|
if (!data.clearnet_servers?.length && !data.onion_servers?.length) {
|
|
const serverName = data.current_server ? `${data.current_server.host}:${data.current_server.port}` : 'The connected server';
|
|
html = `<div class="text-sm text-gray-500 dark:text-gray-400 py-4 text-center">No servers discovered. <span class="font-mono">${serverName}</span> does not return peer lists.</div>`;
|
|
} else {
|
|
html += `<div class="text-xs text-gray-500 dark:text-gray-400 pt-3 border-t border-gray-200 dark:border-gray-600">Click a server to add it to your list</div>`;
|
|
}
|
|
|
|
listContainer.innerHTML = html;
|
|
|
|
listContainer.querySelectorAll('.add-server-btn').forEach(item => {
|
|
item.addEventListener('click', () => {
|
|
const host = item.dataset.host;
|
|
const port = item.dataset.port;
|
|
const type = item.dataset.type;
|
|
const coinName = item.dataset.coin;
|
|
|
|
const textareaId = type === 'onion' ?
|
|
`electrum_onion_${coinName}` : `electrum_clearnet_${coinName}`;
|
|
const textarea = document.getElementById(textareaId);
|
|
|
|
if (textarea) {
|
|
const serverLine = `${host}:${port}`;
|
|
const currentValue = textarea.value.trim();
|
|
|
|
if (currentValue.split('\n').some(line => line.trim() === serverLine)) {
|
|
item.classList.add('bg-yellow-100', 'dark:bg-yellow-800/30');
|
|
setTimeout(() => item.classList.remove('bg-yellow-100', 'dark:bg-yellow-800/30'), 500);
|
|
return;
|
|
}
|
|
|
|
textarea.value = currentValue ? currentValue + '\n' + serverLine : serverLine;
|
|
item.classList.add('bg-green-100', 'dark:bg-green-800/30');
|
|
setTimeout(() => item.classList.remove('bg-green-100', 'dark:bg-green-800/30'), 500);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
panel.classList.remove('hidden');
|
|
})
|
|
.catch(err => {
|
|
listContainer.innerHTML = `<div class="text-xs text-red-500">Failed to discover servers: ${err.message}</div>`;
|
|
panel.classList.remove('hidden');
|
|
})
|
|
.finally(() => {
|
|
button.innerHTML = originalHtml;
|
|
button.disabled = false;
|
|
});
|
|
};
|
|
|
|
SettingsPage.cleanup = function() {
|
|
};
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
SettingsPage.init();
|
|
|
|
if (window.CleanupManager) {
|
|
CleanupManager.registerResource('settingsPage', SettingsPage, (page) => {
|
|
if (page.cleanup) page.cleanup();
|
|
});
|
|
}
|
|
});
|
|
|
|
window.SettingsPage = SettingsPage;
|
|
window.syncNotificationSettings = SettingsPage.syncNotificationSettings.bind(SettingsPage);
|
|
window.testUpdateNotification = SettingsPage.testUpdateNotification.bind(SettingsPage);
|
|
window.testLiveUpdateCheck = SettingsPage.testLiveUpdateCheck.bind(SettingsPage);
|
|
window.checkForUpdatesNow = SettingsPage.checkForUpdatesNow.bind(SettingsPage);
|
|
window.showConfirmDialog = SettingsPage.showConfirmDialog.bind(SettingsPage);
|
|
window.hideConfirmDialog = SettingsPage.hideConfirmDialog.bind(SettingsPage);
|
|
window.confirmDisableCoin = SettingsPage.confirmDisableCoin.bind(SettingsPage);
|
|
|
|
})();
|