Features: - New modern dark UI with gradient accents - Hostname support for custom proxy (e.g. relayup.local) - Full private IP range bypass (10.x, 172.16-31.x, 192.168.x) - WebRTC Leak Protection setting - Kill Switch - block traffic if proxy disconnects - Auto-connect on browser startup - Custom proxy authentication (username/password) - Bypass list for custom exceptions - Local Network Access for printers, NAS, routers, etc - Multiple proxy sources with automatic fallback (Arweave → GitBros → GitHub) - Arweave as default proxy source for decentralized, permanent storage - Auto-update interval for proxy list
559 lines
17 KiB
JavaScript
559 lines
17 KiB
JavaScript
/* ANyONe Extension v2 - Options Page Controller */
|
|
|
|
// ES Module imports
|
|
import { Utils } from './utils.js';
|
|
|
|
// ============================================
|
|
// DOM Elements
|
|
// ============================================
|
|
|
|
const elements = {
|
|
// General
|
|
autoConnect: document.getElementById('auto-connect'),
|
|
defaultMode: document.getElementById('default-mode'),
|
|
|
|
// Privacy
|
|
webrtcProtection: document.getElementById('webrtc-protection'),
|
|
killSwitch: document.getElementById('kill-switch'),
|
|
bypassLocal: document.getElementById('bypass-local'),
|
|
|
|
// Public Proxies
|
|
proxySource: document.getElementById('proxy-source'),
|
|
updateInterval: document.getElementById('update-interval'),
|
|
sourceName: document.getElementById('source-name'),
|
|
sourceUrl: document.getElementById('source-url'),
|
|
sourceUpdated: document.getElementById('source-updated'),
|
|
btnRefreshProxies: document.getElementById('btn-refresh-proxies'),
|
|
|
|
// Bypass List
|
|
exceptions: document.getElementById('exceptions'),
|
|
btnClearBypass: document.getElementById('btn-clear-bypass'),
|
|
btnSaveBypass: document.getElementById('btn-save-bypass'),
|
|
|
|
// Custom Proxy
|
|
customIp: document.getElementById('custom-ip'),
|
|
customPort: document.getElementById('custom-port'),
|
|
customUsername: document.getElementById('custom-username'),
|
|
customPassword: document.getElementById('custom-password'),
|
|
btnClearCustom: document.getElementById('btn-clear-custom'),
|
|
btnTestCustom: document.getElementById('btn-test-custom'),
|
|
btnSaveCustom: document.getElementById('btn-save-custom'),
|
|
|
|
// Toast
|
|
toast: document.getElementById('toast'),
|
|
toastIcon: document.getElementById('toast-icon'),
|
|
toastMessage: document.getElementById('toast-message'),
|
|
|
|
// Modal
|
|
modalOverlay: document.getElementById('modal-overlay'),
|
|
modalTitle: document.getElementById('modal-title'),
|
|
modalMessage: document.getElementById('modal-message'),
|
|
modalCancel: document.getElementById('modal-cancel'),
|
|
modalConfirm: document.getElementById('modal-confirm')
|
|
};
|
|
|
|
// ============================================
|
|
// Initialization
|
|
// ============================================
|
|
|
|
document.addEventListener('DOMContentLoaded', init);
|
|
|
|
async function init() {
|
|
console.log('[Options] Initializing...');
|
|
|
|
// Load settings
|
|
await loadSettings();
|
|
|
|
// Setup event listeners
|
|
setupEventListeners();
|
|
|
|
// Update proxy source info
|
|
await updateProxySourceInfo();
|
|
|
|
console.log('[Options] Initialized');
|
|
}
|
|
|
|
// ============================================
|
|
// Load Settings
|
|
// ============================================
|
|
|
|
async function loadSettings() {
|
|
const response = await sendMessage({ action: 'getSettings' });
|
|
|
|
if (!response.success) {
|
|
console.log('Failed to load settings');
|
|
return;
|
|
}
|
|
|
|
const settings = response.settings;
|
|
|
|
// General
|
|
elements.autoConnect.checked = settings.autoConnect || false;
|
|
elements.defaultMode.value = settings.connectionMode || 'public';
|
|
|
|
// Privacy
|
|
elements.webrtcProtection.checked = settings.webrtcProtection !== false;
|
|
elements.killSwitch.checked = settings.killSwitch || false;
|
|
elements.bypassLocal.checked = settings.bypassLocal !== false;
|
|
|
|
// Public Proxies
|
|
elements.proxySource.value = settings.proxySource || 'git';
|
|
elements.updateInterval.value = settings.updateInterval ?? 0;
|
|
updateSourceNameDisplay(settings.proxySource || 'git');
|
|
|
|
// Custom Proxy
|
|
elements.customIp.value = settings.proxyIP || '';
|
|
elements.customPort.value = settings.proxyPort || '';
|
|
elements.customUsername.value = settings.proxyUsername || '';
|
|
elements.customPassword.value = settings.proxyPassword || '';
|
|
elements.exceptions.value = Array.isArray(settings.noProxyFor)
|
|
? settings.noProxyFor.join(', ')
|
|
: settings.noProxyFor || '';
|
|
}
|
|
|
|
// ============================================
|
|
// Event Listeners
|
|
// ============================================
|
|
|
|
function setupEventListeners() {
|
|
// Auto-save on toggle changes
|
|
elements.autoConnect.addEventListener('change', saveGeneralSettings);
|
|
elements.defaultMode.addEventListener('change', saveGeneralSettings);
|
|
elements.webrtcProtection.addEventListener('change', savePrivacySettings);
|
|
elements.killSwitch.addEventListener('change', savePrivacySettings);
|
|
elements.bypassLocal.addEventListener('change', handleBypassLocalChange);
|
|
elements.proxySource.addEventListener('change', handleProxySourceChange);
|
|
elements.updateInterval.addEventListener('change', saveProxySourceSettings);
|
|
|
|
// Bypass list buttons
|
|
elements.btnClearBypass.addEventListener('click', clearBypassList);
|
|
elements.btnSaveBypass.addEventListener('click', saveBypassList);
|
|
|
|
// Buttons
|
|
elements.btnRefreshProxies.addEventListener('click', refreshProxies);
|
|
elements.btnClearCustom.addEventListener('click', clearCustomProxy);
|
|
elements.btnTestCustom.addEventListener('click', testCustomProxy);
|
|
elements.btnSaveCustom.addEventListener('click', saveCustomProxy);
|
|
}
|
|
|
|
// ============================================
|
|
// Save Settings
|
|
// ============================================
|
|
|
|
async function saveGeneralSettings() {
|
|
await sendMessage({
|
|
action: 'saveSettings',
|
|
settings: {
|
|
autoConnect: elements.autoConnect.checked,
|
|
connectionMode: elements.defaultMode.value
|
|
}
|
|
});
|
|
showToast('General settings saved');
|
|
}
|
|
|
|
async function savePrivacySettings() {
|
|
await sendMessage({
|
|
action: 'saveSettings',
|
|
settings: {
|
|
webrtcProtection: elements.webrtcProtection.checked,
|
|
killSwitch: elements.killSwitch.checked,
|
|
bypassLocal: elements.bypassLocal.checked
|
|
}
|
|
});
|
|
showToast('Privacy settings saved');
|
|
}
|
|
|
|
async function handleBypassLocalChange() {
|
|
if (!elements.bypassLocal.checked) {
|
|
// Revert toggle immediately, wait for confirmation
|
|
elements.bypassLocal.checked = true;
|
|
|
|
// Show confirmation modal
|
|
showModal(
|
|
'Disable Local Network Access?',
|
|
'This will make local devices (printers, NAS, router, etc) unreachable while connected to the proxy.',
|
|
async () => {
|
|
// User confirmed - disable local network access
|
|
elements.bypassLocal.checked = false;
|
|
await saveBypassLocalSetting();
|
|
showToast('Local network access disabled', 'error');
|
|
}
|
|
);
|
|
} else {
|
|
// Enabling - no confirmation needed
|
|
await saveBypassLocalSetting();
|
|
showToast('Local network access enabled', 'success');
|
|
}
|
|
}
|
|
|
|
async function saveBypassLocalSetting() {
|
|
await sendMessage({
|
|
action: 'saveSettings',
|
|
settings: {
|
|
webrtcProtection: elements.webrtcProtection.checked,
|
|
killSwitch: elements.killSwitch.checked,
|
|
bypassLocal: elements.bypassLocal.checked
|
|
}
|
|
});
|
|
}
|
|
|
|
function showModal(title, message, onConfirm) {
|
|
elements.modalTitle.textContent = title;
|
|
elements.modalMessage.textContent = message;
|
|
elements.modalOverlay.classList.add('show');
|
|
|
|
// Remove old listeners
|
|
const newCancelBtn = elements.modalCancel.cloneNode(true);
|
|
const newConfirmBtn = elements.modalConfirm.cloneNode(true);
|
|
elements.modalCancel.parentNode.replaceChild(newCancelBtn, elements.modalCancel);
|
|
elements.modalConfirm.parentNode.replaceChild(newConfirmBtn, elements.modalConfirm);
|
|
elements.modalCancel = newCancelBtn;
|
|
elements.modalConfirm = newConfirmBtn;
|
|
|
|
// Add new listeners
|
|
elements.modalCancel.addEventListener('click', () => {
|
|
elements.modalOverlay.classList.remove('show');
|
|
});
|
|
|
|
elements.modalConfirm.addEventListener('click', () => {
|
|
elements.modalOverlay.classList.remove('show');
|
|
if (onConfirm) onConfirm();
|
|
});
|
|
|
|
// Close on overlay click
|
|
elements.modalOverlay.addEventListener('click', (e) => {
|
|
if (e.target === elements.modalOverlay) {
|
|
elements.modalOverlay.classList.remove('show');
|
|
}
|
|
});
|
|
}
|
|
|
|
async function saveProxySourceSettings() {
|
|
await sendMessage({
|
|
action: 'saveSettings',
|
|
settings: {
|
|
updateInterval: parseInt(elements.updateInterval.value, 10)
|
|
}
|
|
});
|
|
|
|
showToast('Proxy settings saved');
|
|
}
|
|
|
|
async function saveBypassList() {
|
|
const exceptions = elements.exceptions.value
|
|
.split(',')
|
|
.map(e => e.trim())
|
|
.filter(e => e.length > 0);
|
|
|
|
await sendMessage({
|
|
action: 'saveSettings',
|
|
settings: {
|
|
noProxyFor: exceptions
|
|
}
|
|
});
|
|
|
|
showToast('Bypass list saved', 'success');
|
|
}
|
|
|
|
async function clearBypassList() {
|
|
elements.exceptions.value = '';
|
|
|
|
await sendMessage({
|
|
action: 'saveSettings',
|
|
settings: {
|
|
noProxyFor: []
|
|
}
|
|
});
|
|
|
|
showToast('Bypass list cleared', 'success');
|
|
}
|
|
|
|
async function handleProxySourceChange() {
|
|
const source = elements.proxySource.value;
|
|
|
|
await sendMessage({
|
|
action: 'saveSettings',
|
|
settings: {
|
|
proxySource: source
|
|
}
|
|
});
|
|
|
|
updateSourceNameDisplay(source);
|
|
showToast('Proxy source changed');
|
|
}
|
|
|
|
function updateSourceNameDisplay(source) {
|
|
const names = {
|
|
git: 'GitBros',
|
|
github: 'GitHub',
|
|
arweave: 'Arweave'
|
|
};
|
|
const urls = {
|
|
git: 'git.debros.io/DeBros/anyone-proxy-list',
|
|
github: 'github.com/DeBrosOfficial/anyone-proxy-list',
|
|
arweave: 'arweave.net/FjxfWIbS...B8'
|
|
};
|
|
const fullUrls = {
|
|
git: 'https://git.debros.io/DeBros/anyone-proxy-list',
|
|
github: 'https://github.com/DeBrosOfficial/anyone-proxy-list',
|
|
arweave: 'https://arweave.net/FjxfWIbSnZb7EaJWbeuWCsBBFWjTppfS3_KHxUP__B8'
|
|
};
|
|
const externalIcon = `<svg class="icon-external" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
|
|
<polyline points="15 3 21 3 21 9"/>
|
|
<line x1="10" y1="14" x2="21" y2="3"/>
|
|
</svg>`;
|
|
elements.sourceName.textContent = names[source] || 'GitBros';
|
|
elements.sourceUrl.innerHTML = (urls[source] || urls.git) + ' ' + externalIcon;
|
|
elements.sourceUrl.href = fullUrls[source] || fullUrls.git;
|
|
}
|
|
|
|
async function saveCustomProxy() {
|
|
const ip = elements.customIp.value.trim();
|
|
const port = elements.customPort.value.trim();
|
|
const username = elements.customUsername.value.trim();
|
|
const password = elements.customPassword.value;
|
|
|
|
// Validate
|
|
if (!ip) {
|
|
showToast('Please enter host address', 'error');
|
|
return;
|
|
}
|
|
|
|
if (!Utils.isValidHost(ip)) {
|
|
showToast('Invalid IP address or hostname', 'error');
|
|
return;
|
|
}
|
|
|
|
if (!port || !Utils.isValidPort(port)) {
|
|
showToast('Invalid port number (1-65535)', 'error');
|
|
return;
|
|
}
|
|
|
|
// Disable button during save
|
|
elements.btnSaveCustom.disabled = true;
|
|
const originalText = elements.btnSaveCustom.innerHTML;
|
|
elements.btnSaveCustom.innerHTML = '<span>Saving...</span>';
|
|
|
|
try {
|
|
// Save settings (bypass list is saved separately in Privacy section)
|
|
await sendMessage({
|
|
action: 'saveSettings',
|
|
settings: {
|
|
proxyIP: ip,
|
|
proxyPort: parseInt(port, 10),
|
|
proxyUsername: username,
|
|
proxyPassword: password
|
|
}
|
|
});
|
|
|
|
showToast('Custom proxy settings saved', 'success');
|
|
|
|
} finally {
|
|
elements.btnSaveCustom.disabled = false;
|
|
elements.btnSaveCustom.innerHTML = originalText;
|
|
}
|
|
}
|
|
|
|
async function clearCustomProxy() {
|
|
// Disable button during clear
|
|
elements.btnClearCustom.disabled = true;
|
|
const originalText = elements.btnClearCustom.innerHTML;
|
|
elements.btnClearCustom.innerHTML = '<span>Clearing...</span>';
|
|
|
|
try {
|
|
// Clear from storage first
|
|
const response = await sendMessage({
|
|
action: 'clearCustomProxy'
|
|
});
|
|
|
|
if (response.success) {
|
|
// Clear custom proxy UI fields
|
|
elements.customIp.value = '';
|
|
elements.customPort.value = '';
|
|
elements.customUsername.value = '';
|
|
elements.customPassword.value = '';
|
|
|
|
showToast('Custom proxy settings cleared', 'success');
|
|
} else {
|
|
showToast('Failed to clear settings', 'error');
|
|
}
|
|
} catch (error) {
|
|
showToast('Failed to clear settings', 'error');
|
|
} finally {
|
|
elements.btnClearCustom.disabled = false;
|
|
elements.btnClearCustom.innerHTML = originalText;
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// Proxy Actions
|
|
// ============================================
|
|
|
|
async function refreshProxies() {
|
|
elements.btnRefreshProxies.disabled = true;
|
|
const originalText = elements.btnRefreshProxies.innerHTML;
|
|
elements.btnRefreshProxies.innerHTML = '<span>Refreshing...</span>';
|
|
|
|
const source = elements.proxySource.value || 'arweave';
|
|
const response = await sendMessage({ action: 'fetchProxies', source });
|
|
|
|
elements.btnRefreshProxies.disabled = false;
|
|
elements.btnRefreshProxies.innerHTML = originalText;
|
|
|
|
if (response.success) {
|
|
const sourceNames = { arweave: 'Arweave', git: 'GitBros', github: 'GitHub' };
|
|
const usedName = sourceNames[response.usedSource] || response.usedSource;
|
|
|
|
if (response.usedSource && response.usedSource !== source) {
|
|
showToast(`${response.proxies.length} proxies from ${usedName} (fallback)`, 'success');
|
|
// Update dropdown to show actual source used
|
|
elements.proxySource.value = response.usedSource;
|
|
updateSourceNameDisplay(response.usedSource);
|
|
} else {
|
|
showToast(`Updated: ${response.proxies.length} proxies`, 'success');
|
|
}
|
|
await updateProxySourceInfo();
|
|
} else {
|
|
showToast('All proxy sources failed', 'error');
|
|
}
|
|
}
|
|
|
|
async function updateProxySourceInfo() {
|
|
const response = await sendMessage({ action: 'getProxyList' });
|
|
|
|
if (response.success) {
|
|
const lastUpdate = response.lastUpdate;
|
|
if (lastUpdate) {
|
|
const relative = Utils.formatRelativeTime(lastUpdate);
|
|
elements.sourceUpdated.textContent = `Last updated: ${relative}`;
|
|
} else {
|
|
elements.sourceUpdated.textContent = 'Last updated: Never';
|
|
}
|
|
}
|
|
}
|
|
|
|
async function testCustomProxy() {
|
|
const ip = elements.customIp.value.trim();
|
|
const port = elements.customPort.value.trim();
|
|
const username = elements.customUsername.value.trim();
|
|
const password = elements.customPassword.value;
|
|
|
|
if (!ip || !port) {
|
|
showToast('Please enter IP and port', 'error');
|
|
return;
|
|
}
|
|
|
|
elements.btnTestCustom.disabled = true;
|
|
const originalText = elements.btnTestCustom.innerHTML;
|
|
elements.btnTestCustom.innerHTML = '<span>Testing...</span>';
|
|
|
|
// Get current connection state BEFORE testing
|
|
const statusBefore = await sendMessage({ action: 'getStatus' });
|
|
const wasConnected = statusBefore.enabled;
|
|
const previousMode = statusBefore.mode;
|
|
|
|
// Save credentials temporarily so auth handler can use them
|
|
if (username || password) {
|
|
await sendMessage({
|
|
action: 'saveSettings',
|
|
settings: {
|
|
proxyUsername: username,
|
|
proxyPassword: password
|
|
}
|
|
});
|
|
}
|
|
|
|
// Show testing state
|
|
showToast(`Testing ${ip}:${port}...`, 'info');
|
|
|
|
const response = await sendMessage({
|
|
action: 'testProxy',
|
|
proxy: { host: ip, port: parseInt(port, 10) }
|
|
});
|
|
|
|
elements.btnTestCustom.disabled = false;
|
|
elements.btnTestCustom.innerHTML = originalText;
|
|
|
|
if (response.working) {
|
|
// Save the proxy settings
|
|
await sendMessage({
|
|
action: 'saveSettings',
|
|
settings: {
|
|
proxyIP: ip,
|
|
proxyPort: parseInt(port, 10),
|
|
proxyUsername: username,
|
|
proxyPassword: password
|
|
}
|
|
});
|
|
|
|
// Connect to the proxy
|
|
const connectResponse = await sendMessage({
|
|
action: 'connect',
|
|
mode: 'custom'
|
|
});
|
|
|
|
if (connectResponse.success) {
|
|
showToast(`Connected to ${ip}:${port}`, 'success');
|
|
} else {
|
|
showToast(`Proxy working but failed to connect`, 'error');
|
|
}
|
|
} else {
|
|
// Test failed - handle based on previous connection state
|
|
if (wasConnected && previousMode === 'public') {
|
|
// User was connected to public proxy - restore that connection
|
|
showToast(`Test failed. Restoring public proxy...`, 'info');
|
|
const reconnectResponse = await sendMessage({
|
|
action: 'connect',
|
|
mode: 'public'
|
|
});
|
|
if (reconnectResponse.success) {
|
|
showToast(`Proxy ${ip}:${port} not responding. Reconnected to public proxy.`, 'error');
|
|
} else {
|
|
showToast(`Proxy ${ip}:${port} not responding. Failed to restore connection.`, 'error');
|
|
}
|
|
} else if (wasConnected && previousMode === 'custom') {
|
|
// User was connected to custom proxy - they're testing a different one, disconnect
|
|
await sendMessage({ action: 'disconnect' });
|
|
showToast(`Proxy ${ip}:${port} is not responding`, 'error');
|
|
} else {
|
|
// User was not connected - just show error
|
|
showToast(`Proxy ${ip}:${port} is not responding`, 'error');
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============================================
|
|
// Utilities
|
|
// ============================================
|
|
|
|
function sendMessage(message) {
|
|
return new Promise((resolve) => {
|
|
chrome.runtime.sendMessage(message, (response) => {
|
|
resolve(response || { success: false, error: 'No response' });
|
|
});
|
|
});
|
|
}
|
|
|
|
function showToast(message, type = 'success') {
|
|
elements.toastMessage.textContent = message;
|
|
elements.toast.className = `toast show ${type}`;
|
|
|
|
setTimeout(() => {
|
|
elements.toast.classList.remove('show');
|
|
}, 3000);
|
|
}
|
|
|
|
// Listen for messages from background
|
|
chrome.runtime.onMessage.addListener((message) => {
|
|
if (message.action === 'statusUpdate') {
|
|
if (message.status === 'connected') {
|
|
showToast('Proxy connected', 'success');
|
|
} else if (message.status === 'disconnected') {
|
|
showToast('Proxy disconnected');
|
|
} else if (message.status === 'error') {
|
|
showToast(message.error || 'Connection error', 'error');
|
|
}
|
|
}
|
|
});
|