258
README.md
@ -1,181 +1,165 @@
|
||||
# ANyONe Extension - Manage Socks5 Proxy Settings
|
||||
# ANyONe Extension v2.0
|
||||
|
||||
## Overview
|
||||
**The ANyONe Proxy Extension** is a powerful Chromium-based browser extension designed to help users manage and switch between different proxy settings effortlessly. It offers:
|
||||
|
||||
- **Quick access** through the browser's toolbar.
|
||||
- **Detailed control** via an options page.
|
||||
|
||||
Additionally, the extension features a **dApp Store**, enabling users to access decentralized applications directly from the extension. **This tool also simplifies access to the Socks5 Proxy of your official ANyONe router.** Created by DeBros, it is a community-driven project and **not an official ANyONe product**.
|
||||
|
||||
**Note:** It's recommended to leverage local Socks5 Proxies within our internal network for optimal security and performance. Always confirm you are within the ANyONe network by pressing the 'Check ANyONe' button.
|
||||
|
||||
##
|
||||
<p align="center">
|
||||
<img src="https://git.debros.io/DeBros/anyone-extension/raw/branch/main/images/screenshot.png" alt="Alt Text" width="800">
|
||||
</p>
|
||||
A privacy-focused Chromium browser extension for managing SOCKS5 proxy connections to the ANyONe network.
|
||||
|
||||
## Features
|
||||
|
||||
### 1. Quick Proxy Toggle
|
||||
- **Enable/Disable Proxy**: A toggle switch in the popup allows users to quickly turn the proxy on or off.
|
||||
- **Public Proxies**:
|
||||
- **Default Activation**: Automatically uses community-contributed public proxy servers when no custom settings are applied.
|
||||
- **Dynamic List**: Easily update the proxy list with a dedicated button within the extension.
|
||||
### Connection Modes
|
||||
|
||||
### 2. Custom Proxy Settings via Options Page
|
||||
#### Public Proxies Mode
|
||||
- **One-click connect** to community-powered ANyONe proxy servers
|
||||
- **Auto-selection** of the fastest available proxy
|
||||
- **Load balancing** with "Next Proxy" button to switch servers
|
||||
- **Multiple sources**: Arweave, GitBros, or GitHub
|
||||
- **Automatic fallback**: If one source fails, tries the next automatically
|
||||
|
||||
- **Access Custom Settings**: Users can configure custom proxies by navigating to the options page.
|
||||
- **Host and Port**: Specify the host IP and port number for your custom proxy.
|
||||
- **No Proxy Exceptions**: Define specific websites or local addresses where the proxy should not be applied.
|
||||
- **Detailed Configuration**: The options page allows for:
|
||||
- Setting up custom proxy configurations.
|
||||
- Managing exceptions to proxy use.
|
||||
- Saving and applying changes for a tailored browsing experience.
|
||||
#### Custom Proxy Mode
|
||||
- **Full SOCKS5 configuration** with IP/hostname and port
|
||||
- **Authentication support** for proxies requiring username/password
|
||||
- **Test connection** before connecting
|
||||
|
||||
### 3. Proxy Status Indication
|
||||
- **Status Messages**: The extension provides clear feedback on the current proxy status both in the popup and options page, including:
|
||||
- Whether a proxy is enabled or disabled.
|
||||
- The type of proxy in use (public or custom).
|
||||
- The specific host and port being routed through.
|
||||
### Privacy & Security
|
||||
|
||||
### 4. External Links
|
||||
- **Check ANyONe**: A button to directly check the external IP and proxy status via the ANyONe service.
|
||||
- **Credits**: Links to the developer's website and the GitHub repository for the extension.
|
||||
- **Popup**: Links to the developer's website, X account, GitHub repository for the extension, and ANyONe website.
|
||||
| Feature | Description |
|
||||
|---------|-------------|
|
||||
| **WebRTC Leak Protection** | Prevents real IP leaks through WebRTC |
|
||||
| **Kill Switch** | Blocks all traffic if proxy connection drops unexpectedly |
|
||||
| **Local Network Access** | Toggle to allow/block access to local devices (printers, NAS, routers) while connected |
|
||||
| **Bypass List** | Domains and IPs that skip the proxy
|
||||
|
||||
## Usage
|
||||
### Settings & Customization
|
||||
|
||||
### Enabling/Disabling Proxy
|
||||
- Click the extension icon to open the popup.
|
||||
- Use the toggle switch to enable or disable the proxy.
|
||||
- The status message will update to reflect the current state.
|
||||
- **Auto-connect on startup** - Automatically connect when browser starts
|
||||
- **Default connection mode** - Choose Public or Custom as default
|
||||
- **Proxy source selection** - Arweave (default), GitBros, or GitHub with automatic fallback
|
||||
- **Update interval** - Manual, hourly, or periodic auto-updates of proxy list
|
||||
|
||||
### Public Proxies:
|
||||
### User Interface
|
||||
|
||||
- **Default Usage**: When no custom settings are specified, the extension automatically uses public proxy servers, which are contributed and maintained by the ANyONe community.
|
||||
- **Update Mechanism**: You can easily refresh the proxy list directly within the extension using an update button.
|
||||
- **View Proxies**: To see the current list of community-powered public proxies, visit [https://git.debros.io/DeBros/anyone-proxy-list](https://git.debros.io/DeBros/anyone-proxy-list).
|
||||
|
||||
### Setting a Custom Proxy
|
||||
- Navigate to the options page by clicking the "Custom Settings" button in the popup.
|
||||
- Enter the Host IP and Port for your custom proxy.
|
||||
- Optionally, specify IP addresses or domains that should bypass the proxy in the "No Proxy for" field.
|
||||
- Click "Save & Enable" to apply your settings.
|
||||
|
||||
### Disabling the Proxy
|
||||
- From the options page, click "Disable" to turn off the proxy settings, or from the popup, turn the toggle off.
|
||||
|
||||
### Accessing the dApp Store
|
||||
- Click the "dApp Store" button in the popup to open the dApp Store page.
|
||||
- The dApp Store page will display available decentralized applications.
|
||||
- **Modern dark theme** with clean, intuitive design
|
||||
- **Real-time status** showing connection state and current proxy
|
||||
- **Quick actions** - Check IP, refresh proxies, access settings
|
||||
- **Toast notifications** for instant feedback
|
||||
|
||||
## Installation
|
||||
|
||||
### 1. Clone or Download the Repository
|
||||
You have two options to get the extension on your system:
|
||||
### Option 1: Clone with Git
|
||||
```bash
|
||||
git clone "https://git.debros.io/DeBros/anyone-extension.git"
|
||||
```
|
||||
|
||||
- **Option A: Clone with Git**
|
||||
```bash
|
||||
git clone "https://git.debros.io/DeBros/anyone-extension.git"
|
||||
```
|
||||
### Option 2: Download ZIP
|
||||
[Download ZIP](https://git.debros.io/DeBros/anyone-extension/archive/main.zip)
|
||||
|
||||
- **Option B: [Download ZIP](https://git.debros.io/DeBros/anyone-extension/archive/main.zip)**
|
||||
### Load in Browser
|
||||
1. Open your Chromium-based browser (Chrome, Brave, Edge, etc.)
|
||||
2. Navigate to `chrome://extensions/`
|
||||
3. Enable **Developer mode** (toggle in top right)
|
||||
4. Click **Load unpacked**
|
||||
5. Select the extension folder containing `manifest.json`
|
||||
|
||||
### 2. Load Unpacked Extension in Chromium-based Browser
|
||||
- Open your browser and navigate to the extensions page.
|
||||
- Enable "Developer mode".
|
||||
- Depending on your method in step 1:
|
||||
- If you cloned the repository, click "Load unpacked" and select the cloned directory containing the `manifest.json` file.
|
||||
- If you downloaded and unpacked, drag and drop the unpacked folder into the extensions window.
|
||||
|
||||
## Contribution
|
||||
Contributions are welcome! Please fork the repository and submit pull requests for any enhancements or bug fixes.
|
||||
## Usage
|
||||
|
||||
For questions or further discussion, reach out to us on <a href="https://t.me/debrosportal" target="_blank">Telegram</a>
|
||||
|
||||
---
|
||||
### Quick Start
|
||||
1. Click the extension icon in your browser toolbar
|
||||
2. Choose **Public** or **Custom** mode
|
||||
3. Click the **Connect** button
|
||||
|
||||
# Optional: Enhancing Security with Custom DNS Configuration
|
||||
### Public Proxies
|
||||
- Automatically fetches and connects to community proxies
|
||||
- Use **Next Proxy** to switch to a different server
|
||||
- Click **Refresh** to update the proxy list
|
||||
|
||||
Manually configuring your network's DNS can significantly boost your online privacy and security. Below, you'll find a selection of well-regarded, secure DNS servers that provide enhanced protection and privacy features:
|
||||
### Custom Proxy
|
||||
1. Enter your SOCKS5 proxy IP/hostname and port
|
||||
2. (Settings/Optional) Add username and password for authentication
|
||||
3. (Settings/Optional) Configure bypass list for specific domains
|
||||
4. Click **Test Connection** to verify, then **Connect**
|
||||
|
||||
## Cloudflare DNS (1.1.1.1)
|
||||
### Check Your Connection
|
||||
Click **Check IP** to verify your connection is routed through the ANyONe network.
|
||||
|
||||
- **IPv4**: 1.1.1.1 and 1.0.0.1
|
||||
- **IPv6**: 2606:4700:4700::1111 and 2606:4700:4700::1001
|
||||
- **Features**: Fast performance, does not log DNS queries, supports DNS over HTTPS (DoH) and DNS over TLS (DoT).
|
||||
- **Website**: [https://1.1.1.1/dns](https://1.1.1.1/dns)
|
||||
## Proxy Sources
|
||||
|
||||
## Quad9 (9.9.9.9)
|
||||
The extension fetches proxy lists from multiple sources with automatic fallback:
|
||||
|
||||
- **IPv4**: 9.9.9.9 and 149.112.112.112
|
||||
- **IPv6**: 2620:fe::9 and 2620:fe::fe:9
|
||||
- **Features**: Offers protection against malware and phishing, privacy with no logging of DNS queries. Supports DoT.
|
||||
- **Website**: [https://quad9.net](https://quad9.net)
|
||||
| Source | URL | Type |
|
||||
|--------|-----|------|
|
||||
| **Arweave** (Default) | [arweave.net/FjxfWIbS...](https://arweave.net/FjxfWIbSnZb7EaJWbeuWCsBBFWjTppfS3_KHxUP__B8) | Decentralized, permanent |
|
||||
| **GitBros** | [git.debros.io/DeBros/anyone-proxy-list](https://git.debros.io/DeBros/anyone-proxy-list) | Self-hosted Git |
|
||||
| **GitHub** | [github.com/DeBrosOfficial/anyone-proxy-list](https://github.com/DeBrosOfficial/anyone-proxy-list) | Centralized backup |
|
||||
|
||||
## Mullvad DNS
|
||||
**Fallback order:** Arweave → GitBros → GitHub
|
||||
|
||||
- **IPv4**: 194.242.2.1 and 194.242.2.2
|
||||
- **IPv6**: 2a07:e340::1 and 2a07:e340::2
|
||||
- **Features**: Complete privacy with no logging, supports DoH and DoT. Ideal for users concerned with anonymity.
|
||||
- **Website**: [https://mullvad.net/en](https://mullvad.net/en)
|
||||
## Privacy Settings Explained
|
||||
|
||||
## AdGuard DNS
|
||||
### WebRTC Leak Protection
|
||||
WebRTC can expose your real IP even when using a proxy. Enable this to prevent leaks.
|
||||
|
||||
- **IPv4**: 94.140.14.14 and 94.140.15.15 (without filters), 176.103.130.130 and 176.103.130.131 (with filters)
|
||||
- **IPv6**: 2a10:50c0::ad1:ff and 2a10:50c0::ad2:ff (without filters), 2a10:50c0::bad1:ff and 2a10:50c0::bad2:ff (with filters)
|
||||
- **Features**: Provides protection against ads, trackers, and phishing, as well as privacy with no logging.
|
||||
- **Website**: [https://adguard-dns.io](https://adguard-dns.io)
|
||||
### Kill Switch
|
||||
When enabled, if your proxy connection drops unexpectedly, all internet traffic will be blocked until you reconnect or disable the kill switch. This prevents accidental exposure of your real IP.
|
||||
|
||||
## How to Configure:
|
||||
### Local Network Access
|
||||
- **Enabled (default)**: Access local devices (192.168.x.x, 10.x.x.x, .local domains) directly
|
||||
- **Disabled**: All traffic goes through proxy, local devices unreachable
|
||||
|
||||
- **For Windows**: Go to "Settings" > "Network & Internet" > "Change adapter options", right-click on your connection, select "Properties", then select "Internet Protocol Version 4 (TCP/IPv4)" or "Internet Protocol Version 6 (TCP/IPv6)" and enter the DNS addresses you want.
|
||||
- **For macOS**: Navigate to "System Preferences" > "Network", select your connection, click on the "Advanced" button, go to the "DNS" tab, and add your DNS addresses.
|
||||
- **For Linux**: Depending on the distribution, you can usually modify the `/etc/resolv.conf` file to add DNS addresses.
|
||||
- **For Routers**: You'll typically find DNS settings in the advanced settings of your router. This will change the DNS for all devices connected to the network.
|
||||
## Technical Details
|
||||
|
||||
- **Manifest V3** compliant
|
||||
- **SOCKS5** proxy protocol
|
||||
- **Chrome Proxy API** for system-level proxy configuration
|
||||
- **Chrome Privacy API** for WebRTC protection
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please fork the repository and submit pull requests.
|
||||
|
||||
For questions or discussion, join us on [Telegram](https://t.me/debrosportal).
|
||||
|
||||
---
|
||||
|
||||
# ANyONe Protocol: Connection and Setup Guide
|
||||
## Optional: Secure DNS Configuration
|
||||
|
||||
**About ANyONe Protocol**: ANyONe is a decentralized network protocol focused on providing privacy, security, and freedom on the internet. Whether you're looking to browse anonymously or secure your online communications, ANyONe offers versatile solutions for different needs.
|
||||
Enhance your privacy by using secure DNS servers:
|
||||
|
||||
Explore multiple ways to interact with the ANyONe, whether you're connecting directly from your OS, setting up your own relay for personalized control, or using dedicated hardware for an optimized experience. Here's your guide:
|
||||
### Recommended DNS Providers
|
||||
|
||||
- **Linux**: Enjoy a seamless one-click setup. Learn more in the [Linux Connection Guide](https://docs.anyone.io/connect/connecting-to-linux).
|
||||
- **macOS**: Connect easily with or without npm. Check the [macOS Connection Guide](https://docs.anyone.io/connect/connecting-to-macos).
|
||||
- **Windows**: Benefit from a straightforward one-click setup. See the [Windows Connection Guide](https://docs.anyone.io/connect/connecting-to-windows).
|
||||
|
||||
**Setting Up Your Own Relay**: For those interested in customizing your network participation or contributing to the ANyONe ecosystem, follow the [Relay Setup Guide](https://docs.anyone.io/relay).
|
||||
|
||||
**Dedicated Hardware**: For a user-friendly, plug-and-play experience, ANyONe offers specialized hardware like the Anyone Router. This hardware is designed for non-technical users to contribute to and use the network seamlessly, offering:
|
||||
|
||||
- **Ease of Use**: Power on, connect to Wi-Fi or Ethernet, and earn tokens for contributing your bandwidth.
|
||||
- **Security**: Includes custom components like encryption chips similar to those in hardware wallets.
|
||||
- **Diversity**: Enhances network coverage across various ISPs and introduces more independent operators.
|
||||
|
||||
Check out the [Hardware Setup Guide](https://docs.anyone.io/hardware) or visit [Anyone Hardware](https://www.anyone.io/hardware) to learn more and pre-order.
|
||||
| Provider | IPv4 | Features |
|
||||
|----------|------|----------|
|
||||
| **Cloudflare** | 1.1.1.1, 1.0.0.1 | Fast, no logging, DoH/DoT |
|
||||
| **Quad9** | 9.9.9.9, 149.112.112.112 | Malware protection, no logging |
|
||||
| **Mullvad** | 194.242.2.2 | Full privacy, no logging |
|
||||
| **AdGuard** | 94.140.14.14, 94.140.15.15 | Ad blocking, no logging |
|
||||
|
||||
---
|
||||
|
||||
###
|
||||
## ANyONe Protocol Resources
|
||||
|
||||
<br clear="both">
|
||||
- [Linux Connection Guide](https://docs.anyone.io/connect/connecting-to-linux)
|
||||
- [macOS Connection Guide](https://docs.anyone.io/connect/connecting-to-macos)
|
||||
- [Windows Connection Guide](https://docs.anyone.io/connect/connecting-to-windows)
|
||||
- [Relay Setup Guide](https://docs.anyone.io/relay)
|
||||
- [Hardware Setup Guide](https://docs.anyone.io/hardware)
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<a href="https://linktr.ee/debrosofficial" target="_blank">
|
||||
<img src="https://img.shields.io/static/v1?message=Linktree&logo=linktree&label=&color=1de9b6&logoColor=white&labelColor=&style=for-the-badge" height="35" alt="linktree logo" />
|
||||
</a>
|
||||
<a href="https://x.com/debrosofficial" target="_blank">
|
||||
<img src="https://img.shields.io/static/v1?message=Twitter&logo=twitter&label=&color=1DA1F2&logoColor=white&labelColor=&style=for-the-badge" height="35" alt="twitter logo" />
|
||||
</a>
|
||||
<a href="https://t.me/debrosportal" target="_blank">
|
||||
<img src="https://img.shields.io/static/v1?message=Telegram&logo=telegram&label=&color=2CA5E0&logoColor=white&labelColor=&style=for-the-badge" height="35" alt="telegram logo" />
|
||||
</a>
|
||||
<a href="https://www.youtube.com/@DeBrosOfficial" target="_blank">
|
||||
<img src="https://img.shields.io/static/v1?message=Youtube&logo=youtube&label=&color=FF0000&logoColor=white&labelColor=&style=for-the-badge" height="35" alt="youtube logo" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
###
|
||||
**Created by [DeBros](https://debros.io)** | **Version 2.0.1**
|
||||
|
||||
[](https://debros.io/donate)
|
||||
|
||||
<a href="https://linktr.ee/debrosofficial" target="_blank">
|
||||
<img src="https://img.shields.io/static/v1?message=Linktree&logo=linktree&label=&color=1de9b6&logoColor=white&labelColor=&style=for-the-badge" height="35" alt="linktree logo" />
|
||||
</a>
|
||||
<a href="https://x.com/debrosofficial" target="_blank">
|
||||
<img src="https://img.shields.io/static/v1?message=Twitter&logo=twitter&label=&color=1DA1F2&logoColor=white&labelColor=&style=for-the-badge" height="35" alt="twitter logo" />
|
||||
</a>
|
||||
<a href="https://t.me/debrosportal" target="_blank">
|
||||
<img src="https://img.shields.io/static/v1?message=Telegram&logo=telegram&label=&color=2CA5E0&logoColor=white&labelColor=&style=for-the-badge" height="35" alt="telegram logo" />
|
||||
</a>
|
||||
<a href="https://www.youtube.com/@DeBrosOfficial" target="_blank">
|
||||
<img src="https://img.shields.io/static/v1?message=Youtube&logo=youtube&label=&color=FF0000&logoColor=white&labelColor=&style=for-the-badge" height="35" alt="youtube logo" />
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
395
css/common.css
Normal file
@ -0,0 +1,395 @@
|
||||
/* ANyONe Extension v2 - Common Components */
|
||||
|
||||
@import url('variables.css');
|
||||
|
||||
/* Reset & Base */
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-family);
|
||||
font-size: var(--font-size-md);
|
||||
color: var(--color-text-primary);
|
||||
background-color: var(--color-bg-primary);
|
||||
line-height: 1.5;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
/* Typography */
|
||||
h1, h2, h3, h4 {
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
h1 { font-size: var(--font-size-2xl); }
|
||||
h2 { font-size: var(--font-size-xl); }
|
||||
h3 { font-size: var(--font-size-lg); }
|
||||
h4 { font-size: var(--font-size-md); }
|
||||
|
||||
.text-muted {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.text-secondary {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.text-success {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.text-error {
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
.text-warning {
|
||||
color: var(--color-warning);
|
||||
}
|
||||
|
||||
/* Cards */
|
||||
.card {
|
||||
background: var(--color-bg-secondary);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--spacing-md);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-sm);
|
||||
padding: var(--spacing-md) var(--spacing-lg);
|
||||
font-size: var(--font-size-md);
|
||||
font-weight: var(--font-weight-medium);
|
||||
border: none;
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn .icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--color-primary);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
background: var(--color-primary-light);
|
||||
box-shadow: var(--shadow-glow);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--color-bg-tertiary);
|
||||
color: var(--color-text-primary);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.btn-secondary:hover:not(:disabled) {
|
||||
background: var(--color-bg-hover);
|
||||
border-color: var(--color-border-light);
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: var(--color-success);
|
||||
color: var(--color-bg-primary);
|
||||
}
|
||||
|
||||
.btn-success:hover:not(:disabled) {
|
||||
background: var(--color-secondary-light);
|
||||
box-shadow: var(--shadow-glow-success);
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
padding: 0;
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
padding: var(--spacing-lg) var(--spacing-xl);
|
||||
font-size: var(--font-size-lg);
|
||||
}
|
||||
|
||||
/* Connect Button (Special) */
|
||||
.btn-connect {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, var(--color-primary), var(--color-primary-dark));
|
||||
color: var(--color-text-primary);
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
border: 3px solid var(--color-primary-light);
|
||||
transition: all var(--transition-normal);
|
||||
}
|
||||
|
||||
.btn-connect:hover:not(:disabled) {
|
||||
transform: scale(1.05);
|
||||
box-shadow: var(--shadow-glow);
|
||||
}
|
||||
|
||||
.btn-connect.connected {
|
||||
background: linear-gradient(135deg, var(--color-success), var(--color-secondary-dark));
|
||||
border-color: var(--color-secondary-light);
|
||||
}
|
||||
|
||||
.btn-connect.connected:hover:not(:disabled) {
|
||||
box-shadow: var(--shadow-glow-success);
|
||||
}
|
||||
|
||||
.btn-connect.connecting {
|
||||
animation: pulse 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { transform: scale(1); opacity: 1; }
|
||||
50% { transform: scale(1.05); opacity: 0.8; }
|
||||
}
|
||||
|
||||
/* Inputs */
|
||||
.input {
|
||||
width: 100%;
|
||||
padding: var(--spacing-md);
|
||||
font-size: var(--font-size-md);
|
||||
font-family: var(--font-family);
|
||||
color: var(--color-text-primary);
|
||||
background: var(--color-bg-tertiary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.input:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 3px rgba(3, 189, 197, 0.2);
|
||||
}
|
||||
|
||||
.input::placeholder {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.input-label {
|
||||
display: block;
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-text-secondary);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
/* Select / Dropdown */
|
||||
.select {
|
||||
width: 100%;
|
||||
padding: var(--spacing-md);
|
||||
font-size: var(--font-size-md);
|
||||
font-family: var(--font-family);
|
||||
color: var(--color-text-primary);
|
||||
background: var(--color-bg-tertiary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%238B8CA7' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 12px center;
|
||||
padding-right: 36px;
|
||||
}
|
||||
|
||||
.select:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* Toggle Switch */
|
||||
.toggle {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 48px;
|
||||
height: 26px;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.toggle input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.toggle-slider {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 48px;
|
||||
height: 26px;
|
||||
background: var(--color-bg-tertiary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 13px;
|
||||
transition: all var(--transition-fast);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.toggle-slider::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
left: 3px;
|
||||
top: 3px;
|
||||
background: var(--color-text-muted);
|
||||
border-radius: 50%;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.toggle input:checked + .toggle-slider {
|
||||
background: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.toggle input:checked + .toggle-slider::before {
|
||||
left: 25px;
|
||||
background: var(--color-text-primary);
|
||||
}
|
||||
|
||||
/* Mode Tabs */
|
||||
.mode-tabs {
|
||||
display: flex;
|
||||
background: var(--color-bg-tertiary);
|
||||
border-radius: var(--radius-full);
|
||||
padding: 4px;
|
||||
gap: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mode-tab {
|
||||
flex: 1;
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-text-muted);
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 9999px;
|
||||
cursor: pointer;
|
||||
transition: color var(--transition-fast), background var(--transition-fast);
|
||||
outline: none;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.mode-tab:hover:not(.active) {
|
||||
color: var(--color-text-secondary);
|
||||
background: var(--color-bg-hover);
|
||||
}
|
||||
|
||||
.mode-tab:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.mode-tab.active {
|
||||
background: var(--color-primary);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
/* Badge */
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 2px 8px;
|
||||
font-size: var(--font-size-xs);
|
||||
font-weight: var(--font-weight-medium);
|
||||
border-radius: var(--radius-full);
|
||||
background: var(--color-bg-tertiary);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.badge-success {
|
||||
background: rgba(2, 175, 80, 0.15);
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.badge-warning {
|
||||
background: rgba(243, 156, 18, 0.15);
|
||||
color: var(--color-warning);
|
||||
}
|
||||
|
||||
.badge-error {
|
||||
background: rgba(231, 76, 60, 0.15);
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
/* Divider */
|
||||
.divider {
|
||||
height: 1px;
|
||||
background: var(--color-border);
|
||||
margin: var(--spacing-lg) 0;
|
||||
}
|
||||
|
||||
/* Loading Spinner */
|
||||
.spinner {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid var(--color-bg-tertiary);
|
||||
border-top-color: var(--color-primary);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Utility Classes */
|
||||
.flex { display: flex; }
|
||||
.flex-col { flex-direction: column; }
|
||||
.items-center { align-items: center; }
|
||||
.justify-center { justify-content: center; }
|
||||
.justify-between { justify-content: space-between; }
|
||||
.flex-wrap { flex-wrap: wrap; }
|
||||
.gap-xs { gap: var(--spacing-xs); }
|
||||
.gap-sm { gap: var(--spacing-sm); }
|
||||
.gap-md { gap: var(--spacing-md); }
|
||||
.gap-lg { gap: var(--spacing-lg); }
|
||||
.w-full { width: 100%; }
|
||||
.text-center { text-align: center; }
|
||||
.mt-sm { margin-top: var(--spacing-sm); }
|
||||
.mt-md { margin-top: var(--spacing-md); }
|
||||
.mt-lg { margin-top: var(--spacing-lg); }
|
||||
.mb-sm { margin-bottom: var(--spacing-sm); }
|
||||
.mb-md { margin-bottom: var(--spacing-md); }
|
||||
.mb-lg { margin-bottom: var(--spacing-lg); }
|
||||
636
css/options.css
Normal file
@ -0,0 +1,636 @@
|
||||
/* ANyONe Extension v2 - Options/Settings Page Styles */
|
||||
|
||||
@import url('common.css');
|
||||
|
||||
/* Page Container */
|
||||
body {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.options-container {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: var(--spacing-xl);
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.options-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-lg);
|
||||
margin-bottom: var(--spacing-2xl);
|
||||
padding-bottom: var(--spacing-lg);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.options-title {
|
||||
font-size: 28px;
|
||||
font-weight: var(--font-weight-semibold);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.header-logo-img {
|
||||
height: 38px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
/* Icons */
|
||||
.icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
stroke: currentColor;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Toggle Switch - uses styles from common.css */
|
||||
|
||||
/* Settings Sections */
|
||||
.settings-section {
|
||||
margin-bottom: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.settings-card {
|
||||
background: var(--color-bg-secondary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Settings Row */
|
||||
.setting-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: var(--spacing-lg);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
transition: background var(--transition-fast);
|
||||
}
|
||||
|
||||
.setting-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.setting-row:hover {
|
||||
background: var(--color-bg-tertiary);
|
||||
}
|
||||
|
||||
.setting-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.setting-label {
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.setting-hint {
|
||||
font-weight: normal;
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.setting-desc {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.setting-control {
|
||||
margin-left: var(--spacing-lg);
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Settings with input */
|
||||
.setting-row.vertical {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.setting-row.vertical .setting-control {
|
||||
margin-left: 0;
|
||||
margin-top: var(--spacing-md);
|
||||
}
|
||||
|
||||
/* Select in settings */
|
||||
.select {
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
font-size: var(--font-size-sm);
|
||||
color: #FFFFFF;
|
||||
background-color: var(--color-bg-tertiary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
min-width: 160px;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%238B8CA7' d='M6 8L1 3h10z'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 12px center;
|
||||
padding-right: 36px;
|
||||
}
|
||||
|
||||
.select:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.select option {
|
||||
background-color: #1A2E3D;
|
||||
color: #FFFFFF;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* Input */
|
||||
.input {
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
font-size: var(--font-size-base);
|
||||
color: var(--color-text-primary);
|
||||
background: var(--color-bg-tertiary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.input:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* Port Inputs */
|
||||
.port-inputs {
|
||||
display: flex;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
.port-input-group {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.port-input-group label {
|
||||
display: block;
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-muted);
|
||||
margin-bottom: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.port-input-group input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Exceptions Textarea */
|
||||
.exceptions-input {
|
||||
width: 100%;
|
||||
min-height: 80px;
|
||||
padding: var(--spacing-md);
|
||||
font-size: var(--font-size-sm);
|
||||
font-family: monospace;
|
||||
color: var(--color-text-primary);
|
||||
background: var(--color-bg-tertiary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.exceptions-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* Override browser autofill styling */
|
||||
.exceptions-input:-webkit-autofill,
|
||||
.exceptions-input:-webkit-autofill:hover,
|
||||
.exceptions-input:-webkit-autofill:focus,
|
||||
.exceptions-input:-webkit-autofill:active {
|
||||
-webkit-box-shadow: 0 0 0 1000px var(--color-bg-tertiary) inset !important;
|
||||
-webkit-text-fill-color: var(--color-text-primary) !important;
|
||||
background-color: var(--color-bg-tertiary) !important;
|
||||
border-color: var(--color-border) !important;
|
||||
caret-color: var(--color-text-primary) !important;
|
||||
}
|
||||
|
||||
.input:-webkit-autofill,
|
||||
.input:-webkit-autofill:hover,
|
||||
.input:-webkit-autofill:focus,
|
||||
.input:-webkit-autofill:active {
|
||||
-webkit-box-shadow: 0 0 0 1000px var(--color-bg-tertiary) inset !important;
|
||||
-webkit-text-fill-color: var(--color-text-primary) !important;
|
||||
background-color: var(--color-bg-tertiary) !important;
|
||||
border-color: var(--color-border) !important;
|
||||
caret-color: var(--color-text-primary) !important;
|
||||
}
|
||||
|
||||
.exceptions-input.large {
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
/* Proxy Source Card */
|
||||
.proxy-source-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-md);
|
||||
padding: var(--spacing-lg);
|
||||
background: var(--color-bg-tertiary);
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.proxy-source-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--color-bg-secondary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.proxy-source-icon .icon {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.proxy-source-details {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.proxy-source-name {
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.proxy-source-url {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-primary);
|
||||
font-family: monospace;
|
||||
opacity: 0.8;
|
||||
word-break: break-all;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.proxy-source-url:hover {
|
||||
opacity: 1;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.proxy-source-url .icon-external {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
flex-shrink: 0;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
stroke: currentColor;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.proxy-source-updated {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
/* About Section */
|
||||
.about-logos {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 80px;
|
||||
padding: var(--spacing-xl);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.about-logo-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.about-logo-link:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.about-logo {
|
||||
height: 35px;
|
||||
width: auto;
|
||||
filter: drop-shadow(0 0 8px rgba(3, 189, 197, 0.3));
|
||||
}
|
||||
|
||||
.about-links {
|
||||
display: flex;
|
||||
gap: var(--spacing-md);
|
||||
padding: var(--spacing-lg);
|
||||
}
|
||||
|
||||
.about-link {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-xs);
|
||||
padding: var(--spacing-md);
|
||||
background: var(--color-bg-tertiary);
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--color-text-secondary);
|
||||
text-decoration: none;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.about-link .icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.about-link:hover {
|
||||
background: var(--color-bg-hover);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.about-link span {
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
.donate-section {
|
||||
padding: var(--spacing-lg);
|
||||
text-align: center;
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.donate-message {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-muted);
|
||||
margin-bottom: var(--spacing-md);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.btn-donate {
|
||||
background: linear-gradient(135deg, var(--color-primary), #e74c3c);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: var(--spacing-md) var(--spacing-xl);
|
||||
font-size: var(--font-size-base);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.btn-donate:hover {
|
||||
background: linear-gradient(135deg, var(--color-primary-hover), #c0392b);
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.btn-donate .icon {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.disclaimer {
|
||||
text-align: center;
|
||||
padding: var(--spacing-md) var(--spacing-lg);
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--font-size-xs);
|
||||
border-top: 1px solid var(--color-border);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.disclaimer a {
|
||||
color: var(--color-primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.disclaimer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.version-info {
|
||||
text-align: center;
|
||||
padding: var(--spacing-lg);
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--font-size-sm);
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.version-info .open-source {
|
||||
margin-top: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.version-info .open-source a {
|
||||
color: var(--color-primary);
|
||||
text-decoration: none;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.version-info .open-source a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Action Buttons */
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: var(--spacing-md);
|
||||
padding: var(--spacing-lg);
|
||||
border-top: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.action-buttons .btn {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-sm);
|
||||
padding: var(--spacing-sm) var(--spacing-lg);
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: var(--font-weight-medium);
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--color-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: var(--color-primary-hover);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--color-bg-tertiary);
|
||||
color: var(--color-text-secondary);
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--color-bg-hover);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Toast Notification */
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: var(--spacing-xl);
|
||||
left: 50%;
|
||||
transform: translateX(-50%) translateY(100px);
|
||||
padding: var(--spacing-md) var(--spacing-xl);
|
||||
background: var(--color-bg-secondary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-lg);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-md);
|
||||
opacity: 0;
|
||||
transition: all var(--transition-normal);
|
||||
z-index: var(--z-tooltip);
|
||||
}
|
||||
|
||||
.toast.show {
|
||||
transform: translateX(-50%) translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.toast.success {
|
||||
border-color: var(--color-success);
|
||||
}
|
||||
|
||||
.toast.success .icon {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.toast.error {
|
||||
border-color: var(--color-error);
|
||||
}
|
||||
|
||||
.toast.error .icon {
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
.toast .icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
/* Confirmation Modal */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.modal-overlay.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.modal {
|
||||
background: var(--color-bg-secondary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: 32px 24px 24px;
|
||||
max-width: 400px;
|
||||
width: 90%;
|
||||
text-align: center;
|
||||
transform: scale(0.9);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
.modal-overlay.show .modal {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.modal-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
margin: 0 auto var(--spacing-md);
|
||||
color: var(--color-warning);
|
||||
}
|
||||
|
||||
.modal-icon svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: 600;
|
||||
color: var(--color-text-primary);
|
||||
margin: 0 0 var(--spacing-sm);
|
||||
}
|
||||
|
||||
.modal-message {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-muted);
|
||||
margin: 0 0 var(--spacing-lg);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.modal-buttons {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-buttons .btn {
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: var(--color-error);
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: #c0392b;
|
||||
}
|
||||
|
||||
/* Bypass List Buttons */
|
||||
.bypass-buttons {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
justify-content: flex-end;
|
||||
margin-top: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.btn-sm .icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
493
css/popup.css
Normal file
@ -0,0 +1,493 @@
|
||||
/* ANyONe Extension v2 - Popup Styles */
|
||||
|
||||
@import url('common.css');
|
||||
|
||||
/* Popup Container */
|
||||
html {
|
||||
width: 320px;
|
||||
height: 600px;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
html::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body {
|
||||
width: 320px;
|
||||
height: 600px;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
overflow: hidden !important;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
body::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.popup-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 320px;
|
||||
height: 600px;
|
||||
max-height: 600px;
|
||||
padding: var(--spacing-md);
|
||||
box-sizing: border-box;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.popup-header {
|
||||
text-align: center;
|
||||
padding: var(--spacing-sm) 0;
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.logo-img {
|
||||
height: 32px;
|
||||
width: auto;
|
||||
filter: drop-shadow(0 0 12px rgba(3, 189, 197, 0.5));
|
||||
}
|
||||
|
||||
.logo-subtitle {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
/* Icons */
|
||||
.icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
stroke: currentColor;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.icon-chevron {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: var(--color-text-muted);
|
||||
transition: transform var(--transition-fast);
|
||||
}
|
||||
|
||||
/* Status Circle */
|
||||
.status-circle {
|
||||
display: inline-block;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid var(--color-error);
|
||||
background: rgba(231, 76, 60, 0.1);
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.status-circle.online {
|
||||
border-color: var(--color-success);
|
||||
background: rgba(2, 175, 80, 0.1);
|
||||
box-shadow: 0 0 12px rgba(2, 175, 80, 0.4);
|
||||
}
|
||||
|
||||
.status-circle.checking {
|
||||
border-color: var(--color-warning);
|
||||
background: rgba(243, 156, 18, 0.1);
|
||||
animation: pulse-status 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse-status {
|
||||
0%, 100% { transform: scale(1); opacity: 1; }
|
||||
50% { transform: scale(1.1); opacity: 0.7; }
|
||||
}
|
||||
|
||||
/* Mode Selection */
|
||||
.mode-section {
|
||||
margin-bottom: var(--spacing-lg);
|
||||
}
|
||||
|
||||
/* Mode Content */
|
||||
.mode-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mode-content.active {
|
||||
display: block;
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.mode-content .card {
|
||||
height: 140px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
/* Proxy Info Card */
|
||||
.proxy-info-card {
|
||||
margin-bottom: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.proxy-info {
|
||||
margin-bottom: var(--spacing-md);
|
||||
}
|
||||
|
||||
.proxy-info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: var(--spacing-xs) 0;
|
||||
}
|
||||
|
||||
.proxy-info-label {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.proxy-info-value {
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.proxy-info-value.muted {
|
||||
color: var(--color-text-muted);
|
||||
font-weight: var(--font-weight-normal);
|
||||
}
|
||||
|
||||
.proxy-actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
}
|
||||
|
||||
.proxy-actions .btn {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Connect Button Section */
|
||||
.connect-section {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: var(--spacing-lg) 0 var(--spacing-sm) 0;
|
||||
}
|
||||
|
||||
.connect-btn {
|
||||
position: relative;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-bg-tertiary);
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all var(--transition-normal);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.connect-btn-ring {
|
||||
position: absolute;
|
||||
inset: -4px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid var(--color-border-light);
|
||||
opacity: 0.6;
|
||||
transition: all var(--transition-normal);
|
||||
}
|
||||
|
||||
.connect-btn-icon {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
filter: grayscale(100%) brightness(0.6);
|
||||
transition: all var(--transition-normal);
|
||||
}
|
||||
|
||||
|
||||
.connect-btn:hover {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 0 30px rgba(139, 140, 167, 0.3);
|
||||
}
|
||||
|
||||
.connect-btn:hover .connect-btn-ring {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Connected State */
|
||||
.connect-btn.connected {
|
||||
background: rgba(2, 175, 80, 0.1);
|
||||
}
|
||||
|
||||
.connect-btn.connected .connect-btn-ring {
|
||||
border-color: var(--color-success);
|
||||
box-shadow: 0 0 20px rgba(2, 175, 80, 0.4);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.connect-btn.connected .connect-btn-icon {
|
||||
filter: grayscale(100%) brightness(0.6) sepia(100%) hue-rotate(70deg) saturate(500%);
|
||||
}
|
||||
|
||||
.connect-btn.connected:hover {
|
||||
box-shadow: 0 0 40px rgba(2, 175, 80, 0.4);
|
||||
}
|
||||
|
||||
/* Connecting State */
|
||||
.connect-btn.connecting .connect-btn-ring {
|
||||
border-color: var(--color-warning);
|
||||
animation: pulse-ring 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.connect-btn.connecting .connect-btn-icon {
|
||||
filter: grayscale(100%) brightness(0.6) sepia(100%) hue-rotate(0deg) saturate(500%);
|
||||
animation: pulse-icon 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* Error State */
|
||||
.connect-btn.error .connect-btn-ring {
|
||||
border-color: var(--color-error);
|
||||
box-shadow: 0 0 20px rgba(231, 76, 60, 0.4);
|
||||
}
|
||||
|
||||
.connect-btn.error .connect-btn-icon {
|
||||
filter: grayscale(100%) brightness(0.5) sepia(100%) hue-rotate(-50deg) saturate(600%);
|
||||
}
|
||||
|
||||
/* Blocked State (Kill Switch) */
|
||||
.connect-btn.blocked {
|
||||
background: rgba(231, 76, 60, 0.15);
|
||||
}
|
||||
|
||||
.connect-btn.blocked .connect-btn-ring {
|
||||
border-color: var(--color-error);
|
||||
box-shadow: 0 0 20px rgba(231, 76, 60, 0.6);
|
||||
animation: pulse-blocked 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.connect-btn.blocked .connect-btn-icon {
|
||||
filter: grayscale(100%) brightness(0.5) sepia(100%) hue-rotate(-50deg) saturate(600%);
|
||||
}
|
||||
|
||||
@keyframes pulse-blocked {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
box-shadow: 0 0 20px rgba(231, 76, 60, 0.6);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 0 30px rgba(231, 76, 60, 0.8);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse-ring {
|
||||
0%, 100% {
|
||||
transform: scale(1);
|
||||
opacity: 0.6;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse-icon {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.1); }
|
||||
}
|
||||
|
||||
/* Status Card */
|
||||
.status-card {
|
||||
background: transparent;
|
||||
padding: var(--spacing-sm);
|
||||
margin-top: var(--spacing-sm);
|
||||
margin-bottom: var(--spacing-sm);
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.status-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.status-dot-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.status-dot-container .status-dot {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
margin: 0;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.status-dot-container .status-dot.online {
|
||||
background: var(--color-success);
|
||||
box-shadow: 0 0 12px rgba(2, 175, 80, 0.6);
|
||||
}
|
||||
|
||||
.status-dot-container .status-dot.offline {
|
||||
background: var(--color-error);
|
||||
box-shadow: 0 0 8px rgba(231, 76, 60, 0.4);
|
||||
}
|
||||
|
||||
.status-dot-container .status-dot.connecting {
|
||||
background: var(--color-warning);
|
||||
box-shadow: 0 0 12px rgba(243, 156, 18, 0.6);
|
||||
animation: pulse-dot 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.status-dot-container .status-dot.error {
|
||||
background: var(--color-error);
|
||||
box-shadow: 0 0 12px rgba(231, 76, 60, 0.6);
|
||||
}
|
||||
|
||||
.status-dot-container .status-dot.blocked {
|
||||
background: var(--color-error);
|
||||
box-shadow: 0 0 12px rgba(231, 76, 60, 0.8);
|
||||
animation: pulse-blocked-dot 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse-blocked-dot {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.3); }
|
||||
}
|
||||
|
||||
@keyframes pulse-dot {
|
||||
0%, 100% { transform: scale(1); opacity: 1; }
|
||||
50% { transform: scale(1.2); opacity: 0.7; }
|
||||
}
|
||||
|
||||
.status-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.status-card.error .status-text {
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
.status-card.connected .status-text {
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.status-card.connecting .status-text {
|
||||
color: var(--color-warning);
|
||||
}
|
||||
|
||||
.status-card.disconnected .status-text {
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.status-card.blocked .status-text {
|
||||
color: var(--color-error);
|
||||
animation: blink-text 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes blink-text {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
|
||||
.status-ip {
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
/* Custom Form Labels */
|
||||
.custom-form .input-label {
|
||||
color: var(--color-text-muted);
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* Quick Actions */
|
||||
.quick-actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-sm);
|
||||
padding: var(--spacing-sm) 0;
|
||||
}
|
||||
|
||||
.quick-action {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
padding: var(--spacing-sm) var(--spacing-xs);
|
||||
background: var(--color-bg-secondary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.quick-action:hover {
|
||||
background: var(--color-bg-tertiary);
|
||||
border-color: var(--color-border-light);
|
||||
}
|
||||
|
||||
.quick-action .icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: var(--color-text-secondary);
|
||||
transition: color var(--transition-fast);
|
||||
}
|
||||
|
||||
.quick-action:hover .icon {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.quick-action .icon.spinning {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.quick-action-label {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
/* Custom Proxy Form */
|
||||
.custom-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
.input-row {
|
||||
display: flex;
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
.input-row .input-group {
|
||||
flex: 1;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.input-row .input-group.port {
|
||||
flex: 0 0 80px;
|
||||
}
|
||||
98
css/variables.css
Normal file
@ -0,0 +1,98 @@
|
||||
/* ANyONe Extension v2 - Design System */
|
||||
|
||||
:root {
|
||||
/* Colors - Primary (ANyONe Teal/Cyan) */
|
||||
--color-primary: #03BDC5;
|
||||
--color-primary-light: #2DD4DB;
|
||||
--color-primary-dark: #0A9BA2;
|
||||
|
||||
/* Colors - Secondary (ANyONe Deep Teal) */
|
||||
--color-secondary: #0D6B7C;
|
||||
--color-secondary-light: #1A8A9C;
|
||||
--color-secondary-dark: #084B56;
|
||||
|
||||
/* Colors - Accent (ANyONe Blue) */
|
||||
--color-accent: #0280AF;
|
||||
--color-accent-light: #0A9FD4;
|
||||
--color-accent-dark: #065A7A;
|
||||
|
||||
/* Colors - Background (Dark Theme) */
|
||||
--color-bg-primary: #0A1218;
|
||||
--color-bg-secondary: #0F1A22;
|
||||
--color-bg-tertiary: #152530;
|
||||
--color-bg-hover: #1C3040;
|
||||
|
||||
/* Colors - Text */
|
||||
--color-text-primary: #FFFFFF;
|
||||
--color-text-secondary: #B0C4CC;
|
||||
--color-text-muted: #6B8A95;
|
||||
|
||||
/* Colors - Status */
|
||||
--color-success: #02AF50;
|
||||
--color-warning: #F39C12;
|
||||
--color-error: #E74C3C;
|
||||
--color-info: #0280AF;
|
||||
|
||||
/* Colors - Border */
|
||||
--color-border: #1E3A47;
|
||||
--color-border-light: #2A4A58;
|
||||
|
||||
/* Spacing */
|
||||
--spacing-xs: 4px;
|
||||
--spacing-sm: 8px;
|
||||
--spacing-md: 12px;
|
||||
--spacing-lg: 16px;
|
||||
--spacing-xl: 24px;
|
||||
--spacing-2xl: 32px;
|
||||
|
||||
/* Border Radius */
|
||||
--radius-sm: 6px;
|
||||
--radius-md: 10px;
|
||||
--radius-lg: 14px;
|
||||
--radius-xl: 20px;
|
||||
--radius-full: 9999px;
|
||||
|
||||
/* Typography */
|
||||
--font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
--font-size-xs: 10px;
|
||||
--font-size-sm: 12px;
|
||||
--font-size-md: 14px;
|
||||
--font-size-lg: 16px;
|
||||
--font-size-xl: 20px;
|
||||
--font-size-2xl: 24px;
|
||||
|
||||
--font-weight-normal: 400;
|
||||
--font-weight-medium: 500;
|
||||
--font-weight-semibold: 600;
|
||||
--font-weight-bold: 700;
|
||||
|
||||
/* Shadows */
|
||||
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);
|
||||
--shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.5);
|
||||
--shadow-glow: 0 0 20px rgba(3, 189, 197, 0.4);
|
||||
--shadow-glow-success: 0 0 20px rgba(2, 175, 80, 0.4);
|
||||
|
||||
/* Transitions */
|
||||
--transition-fast: 150ms ease;
|
||||
--transition-normal: 250ms ease;
|
||||
--transition-slow: 350ms ease;
|
||||
|
||||
/* Z-index */
|
||||
--z-dropdown: 100;
|
||||
--z-modal: 200;
|
||||
--z-tooltip: 300;
|
||||
}
|
||||
|
||||
/* Light theme option */
|
||||
[data-theme="light"] {
|
||||
--color-bg-primary: #F0F5F7;
|
||||
--color-bg-secondary: #FFFFFF;
|
||||
--color-bg-tertiary: #E5EDEF;
|
||||
--color-bg-hover: #D5E0E5;
|
||||
--color-text-primary: #0A1218;
|
||||
--color-text-secondary: #3A5A68;
|
||||
--color-text-muted: #6B8A95;
|
||||
--color-border: #C5D5DC;
|
||||
--color-border-light: #D5E0E5;
|
||||
}
|
||||
@ -1,342 +1,323 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Proxy Settings</title>
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ANyONe Extension Settings</title>
|
||||
<link rel="stylesheet" href="../css/options.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="options-container">
|
||||
<!-- Header -->
|
||||
<header class="options-header">
|
||||
<img src="../images/anonlogo.png" alt="ANyONe" class="header-logo-img">
|
||||
<h1 class="options-title">Extension Settings</h1>
|
||||
</header>
|
||||
|
||||
body {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
color: #ffffff;
|
||||
background-image: url('../images/optionsback.png');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
min-height: 100vh;
|
||||
overflow-y: hidden;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
<!-- General Settings -->
|
||||
<section class="settings-section">
|
||||
<div class="section-title">General</div>
|
||||
<div class="settings-card">
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<div class="setting-label">Auto-connect on startup</div>
|
||||
<div class="setting-desc">Automatically connect when browser starts</div>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<label class="toggle">
|
||||
<input type="checkbox" id="auto-connect">
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
h1 {
|
||||
margin-top: 20px;
|
||||
white-space: nowrap;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 400px;
|
||||
margin: 15px auto;
|
||||
padding: 20px;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
border-radius: 10px;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 20px;
|
||||
margin-left: 5px;
|
||||
white-space: nowrap;
|
||||
color: #ecf0f1;
|
||||
}
|
||||
|
||||
select, input {
|
||||
width: 170px;
|
||||
padding: 5px;
|
||||
margin: 0;
|
||||
border-radius: 5px;
|
||||
border: 1px solid rgb(0, 114, 117);
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
background-color: rgb(10, 18, 30);
|
||||
transition: border-color 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
select:focus, input:focus {
|
||||
outline: none;
|
||||
border-color: #00ced1;
|
||||
box-shadow: 0 0 5px rgba(0, 206, 209, 0.5);
|
||||
}
|
||||
|
||||
#proxyPort {
|
||||
width: 90px;
|
||||
}
|
||||
|
||||
.no-proxy-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.no-proxy-group label {
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
#noProxyFor {
|
||||
width: 95%;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
padding: 5px;
|
||||
margin: 0 auto;
|
||||
border-radius: 5px;
|
||||
border: 1px solid rgb(0, 114, 117);
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
background-color: rgb(10, 18, 30);
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
#noProxyFor::placeholder {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#noProxyFor:focus {
|
||||
outline: none;
|
||||
border-color: #00ced1;
|
||||
box-shadow: 0 0 5px rgba(0, 206, 209, 0.5);
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 7px 10px;
|
||||
margin: 10px 0;
|
||||
border-radius: 5px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
background-color: transparent;
|
||||
transition: background-color 0.3s ease, transform 0.2s ease, box-shadow 0.3s ease, filter 0.3s ease;
|
||||
width: 120px;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
#saveSettings {
|
||||
border: 1px solid rgb(3, 189, 197);
|
||||
background-color: rgba(3, 189, 197, 0.1);
|
||||
}
|
||||
|
||||
#saveSettings:hover {
|
||||
background-color: rgb(3, 189, 197);
|
||||
}
|
||||
|
||||
#disableProxy {
|
||||
width: 80px;
|
||||
border: 1px solid #a22020;
|
||||
background-color: rgba(162, 32, 32, 0.3);
|
||||
}
|
||||
|
||||
#disableProxy:hover {
|
||||
background-color: #bf2626;
|
||||
}
|
||||
|
||||
#checkAnyoneButton {
|
||||
border: 1px solid #0280AF;
|
||||
background-color: rgba(2, 128, 175, 0.1);
|
||||
}
|
||||
|
||||
#checkAnyoneButton:hover {
|
||||
background-color: #0280AF;
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
|
||||
.container .button-group {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.credits {
|
||||
margin-top: 15px;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.credits-link {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.credits-link:hover {
|
||||
opacity: 0.8;
|
||||
transform: translateY(-2px);
|
||||
color: #2ecc71;
|
||||
}
|
||||
|
||||
.credits-link span {
|
||||
font-size: 14px;
|
||||
transition: color 0.1s ease;
|
||||
}
|
||||
|
||||
.credits img {
|
||||
margin-right: 10px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.credits-link:hover img {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
#anonLogo {
|
||||
margin: 10px auto 10px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#statusMessage {
|
||||
min-height: 40px;
|
||||
margin: 10px auto;
|
||||
padding: 0 10px;
|
||||
max-width: calc(100% - 20px);
|
||||
width: 100%;
|
||||
text-align: center !important;
|
||||
white-space: normal !important;
|
||||
word-wrap: break-word !important;
|
||||
word-break: break-all !important;
|
||||
overflow: hidden !important;
|
||||
box-sizing: border-box !important;
|
||||
font-family: Arial, sans-serif !important;
|
||||
font-size: 14px;
|
||||
font-weight: bold !important;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
|
||||
.form-group, .no-proxy-group {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
#disableProxy {
|
||||
width: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.container {
|
||||
width: 90%;
|
||||
padding: 15px;
|
||||
margin: 10px auto;
|
||||
}
|
||||
|
||||
#statusMessage {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
select, input, button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
label {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.credits {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.no-proxy-group label {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#noProxyFor {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<img src="../images/anonlogo.png" alt="Anon Logo" width="120" height="120" id="anonLogo">
|
||||
<h1>My Socks5 Proxy Settings</h1>
|
||||
<label for="proxyIP">
|
||||
Host
|
||||
<input type="text" id="proxyIP" placeholder="enter host" aria-label="Proxy Host">
|
||||
</label>
|
||||
|
||||
<label for="proxyPort">
|
||||
Port
|
||||
<input type="number" id="proxyPort" placeholder="port" aria-label="Proxy Port">
|
||||
</label>
|
||||
|
||||
<div class="no-proxy-group">
|
||||
<label for="noProxyFor">No Proxy for</label>
|
||||
<input type="text" id="noProxyFor" placeholder="e.g., localhost, *.example.com" name="noProxyFor" aria-label="No Proxy For">
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<div class="setting-label">Default connection mode</div>
|
||||
<div class="setting-desc">Mode to use when auto-connecting</div>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<select class="select" id="default-mode">
|
||||
<option value="public">Public Proxies</option>
|
||||
<option value="custom">Custom Proxy</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="button-group">
|
||||
<button id="saveSettings">Save & Enable</button>
|
||||
<button id="disableProxy">Disable</button>
|
||||
<button id="checkAnyoneButton">Check ANyONe</button>
|
||||
<!-- Privacy Settings -->
|
||||
<section class="settings-section">
|
||||
<div class="section-title">Privacy</div>
|
||||
<div class="settings-card">
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<div class="setting-label">Local Network Access</div>
|
||||
<div class="setting-desc">Access routers, printers, NAS, and local devices while connected</div>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<label class="toggle">
|
||||
<input type="checkbox" id="bypass-local" checked>
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<div class="setting-label">WebRTC Leak Protection</div>
|
||||
<div class="setting-desc">Prevent real IP leaks through WebRTC</div>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<label class="toggle">
|
||||
<input type="checkbox" id="webrtc-protection">
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<div class="setting-label">Kill Switch</div>
|
||||
<div class="setting-desc">Block all traffic if proxy disconnects</div>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<label class="toggle">
|
||||
<input type="checkbox" id="kill-switch">
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-row vertical">
|
||||
<div class="setting-info" style="width: 100%;">
|
||||
<div class="setting-label">Bypass List <span class="setting-hint">(comma separated)</span></div>
|
||||
<div class="setting-desc">Domains and IPs that should bypass the proxy (applies to both Public and Custom modes)</div>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<textarea class="exceptions-input large" id="exceptions" placeholder="example.com, *.mydomain.com, 203.0.113.*" autocomplete="off" spellcheck="false"></textarea>
|
||||
</div>
|
||||
<div class="bypass-buttons">
|
||||
<button class="btn btn-secondary btn-sm" id="btn-clear-bypass">
|
||||
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="3 6 5 6 21 6"/>
|
||||
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
|
||||
</svg>
|
||||
Clear
|
||||
</button>
|
||||
<button class="btn btn-primary btn-sm" id="btn-save-bypass">
|
||||
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/>
|
||||
<polyline points="17 21 17 13 7 13 7 21"/>
|
||||
<polyline points="7 3 7 8 15 8"/>
|
||||
</svg>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Public Proxies Settings -->
|
||||
<section class="settings-section">
|
||||
<div class="section-title">Public Proxies</div>
|
||||
<div class="settings-card">
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<div class="setting-label">Proxy list source</div>
|
||||
<div class="setting-desc">Where to fetch the public proxy list from</div>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<select class="select" id="proxy-source">
|
||||
<option value="arweave">Arweave</option>
|
||||
<option value="git">GitBros</option>
|
||||
<option value="github">GitHub</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<div class="setting-label">Auto-update interval</div>
|
||||
<div class="setting-desc">How often to refresh proxy list</div>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<select class="select" id="update-interval">
|
||||
<option value="1">Every hour</option>
|
||||
<option value="6">Every 6 hours</option>
|
||||
<option value="12">Every 12 hours</option>
|
||||
<option value="24">Every 24 hours</option>
|
||||
<option value="0" selected>Manual only</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="proxy-source-info">
|
||||
<div class="proxy-source-icon">
|
||||
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/>
|
||||
<polyline points="3.27 6.96 12 12.01 20.73 6.96"/>
|
||||
<line x1="12" y1="22.08" x2="12" y2="12"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="proxy-source-details">
|
||||
<div class="proxy-source-name" id="source-name">GitBros</div>
|
||||
<a class="proxy-source-url" id="source-url" href="https://git.debros.io/DeBros/anyone-proxy-list" target="_blank">
|
||||
git.debros.io/DeBros/anyone-proxy-list
|
||||
<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>
|
||||
</a>
|
||||
<div class="proxy-source-updated" id="source-updated">Last updated: Never</div>
|
||||
</div>
|
||||
<button class="btn btn-secondary" id="btn-refresh-proxies">
|
||||
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="23 4 23 10 17 10"/>
|
||||
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/>
|
||||
</svg>
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p id="statusMessage"></p>
|
||||
</section>
|
||||
|
||||
<div class="credits">
|
||||
<a href="https://debros.io" target="_blank" rel="noopener noreferrer" class="credits-link" aria-label="Visit DeBros website">
|
||||
<img src="../images/debroslogo.png" alt="DeBros Logo" width="30" height="30">
|
||||
<span>This extension was created by DeBros</span>
|
||||
</a>
|
||||
<a href="https://git.debros.io/DeBros/anyone-extension" target="_blank" rel="noopener noreferrer" class="credits-link" aria-label="Explore the open source code and documentation">
|
||||
<span>It's open source - explore the code and docs here</span>
|
||||
</a>
|
||||
<!-- Custom Proxy Settings -->
|
||||
<section class="settings-section">
|
||||
<div class="section-title">Custom Proxy</div>
|
||||
<div class="settings-card">
|
||||
<div class="setting-row vertical">
|
||||
<div class="setting-info">
|
||||
<div class="setting-label">Proxy Address</div>
|
||||
<div class="setting-desc">Your custom SOCKS5 proxy server</div>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<div class="port-inputs">
|
||||
<div class="port-input-group" style="flex: 2;">
|
||||
<label>IP / Hostname</label>
|
||||
<input type="text" class="input" id="custom-ip" placeholder="192.168.1.100 or relayup.local" autocomplete="off" spellcheck="false">
|
||||
</div>
|
||||
<div class="port-input-group" style="flex: 1;">
|
||||
<label>Port</label>
|
||||
<input type="number" class="input" id="custom-port" placeholder="9050" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-row vertical">
|
||||
<div class="setting-info">
|
||||
<div class="setting-label">Authentication <span class="setting-hint">(optional)</span></div>
|
||||
<div class="setting-desc">Username and password if your proxy requires authentication</div>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<div class="port-inputs">
|
||||
<div class="port-input-group">
|
||||
<label>Username</label>
|
||||
<input type="text" class="input" id="custom-username" placeholder="username" autocomplete="off" spellcheck="false">
|
||||
</div>
|
||||
<div class="port-input-group">
|
||||
<label>Password</label>
|
||||
<input type="password" class="input" id="custom-password" placeholder="password" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="action-buttons">
|
||||
<button class="btn btn-secondary" id="btn-clear-custom">
|
||||
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="3 6 5 6 21 6"/>
|
||||
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
|
||||
</svg>
|
||||
Clear
|
||||
</button>
|
||||
<button class="btn btn-secondary" id="btn-test-custom">
|
||||
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
|
||||
<polyline points="22 4 12 14.01 9 11.01"/>
|
||||
</svg>
|
||||
Test Connection
|
||||
</button>
|
||||
<button class="btn btn-primary" id="btn-save-custom">
|
||||
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/>
|
||||
<polyline points="17 21 17 13 7 13 7 21"/>
|
||||
<polyline points="7 3 7 8 15 8"/>
|
||||
</svg>
|
||||
Save Settings
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- About Section -->
|
||||
<section class="settings-section">
|
||||
<div class="section-title">About</div>
|
||||
<div class="settings-card">
|
||||
<div class="about-logos">
|
||||
<a href="https://anyone.io" target="_blank" class="about-logo-link">
|
||||
<img src="../images/anyone2.png" alt="ANyONe" class="about-logo">
|
||||
</a>
|
||||
<a href="https://debros.io" target="_blank" class="about-logo-link">
|
||||
<img src="../images/DeBros_White_Transparent.png" alt="DeBros" class="about-logo">
|
||||
</a>
|
||||
</div>
|
||||
<div class="donate-section">
|
||||
<p class="donate-message">DeBros creates privacy-first and decentralized tools for a freer world. Support our mission.</p>
|
||||
<a href="https://debros.io/donate" target="_blank" class="btn btn-donate">
|
||||
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/>
|
||||
</svg>
|
||||
Support DeBros
|
||||
</a>
|
||||
</div>
|
||||
<div class="disclaimer">
|
||||
This software is provided "as is" without warranty. Use at your own risk.
|
||||
See <a href="https://github.com/DeBrosOfficial/anyone-extension/blob/main/LICENSE" target="_blank">LICENSE</a> for details.
|
||||
</div>
|
||||
<div class="version-info">
|
||||
<div>Created by DeBros | Version 2.0.1</div>
|
||||
<div class="open-source">
|
||||
<a href="https://git.debros.io/DeBros/anyone-extension" target="_blank">View Source Code</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- Confirmation Modal -->
|
||||
<div class="modal-overlay" id="modal-overlay">
|
||||
<div class="modal">
|
||||
<div class="modal-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/>
|
||||
<line x1="12" y1="9" x2="12" y2="13" stroke-width="2.5"/>
|
||||
<circle cx="12" cy="17" r="1" fill="currentColor" stroke="none"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="modal-title" id="modal-title">Disable Local Network Access?</h3>
|
||||
<p class="modal-message" id="modal-message">
|
||||
This will make local devices (printers, NAS, router etc) unreachable while connected to the proxy.
|
||||
</p>
|
||||
<div class="modal-buttons">
|
||||
<button class="btn btn-secondary" id="modal-cancel">Cancel</button>
|
||||
<button class="btn btn-danger" id="modal-confirm">Disable</button>
|
||||
</div>
|
||||
</div>
|
||||
<script src="../js/options.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</div>
|
||||
|
||||
<!-- Toast Notification -->
|
||||
<div class="toast" id="toast">
|
||||
<svg class="icon" id="toast-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
|
||||
<polyline points="22 4 12 14.01 9 11.01"/>
|
||||
</svg>
|
||||
<span id="toast-message">Settings saved</span>
|
||||
</div>
|
||||
|
||||
<script type="module" src="../js/options.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
330
html/popup.html
@ -1,225 +1,121 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>ANyONe Network Gateway</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
margin: 0;
|
||||
padding: 10px 30px 10px;
|
||||
background-image: url('../images/popupback.png');
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-color: #6d8392;
|
||||
color: white;
|
||||
text-align: center;
|
||||
width: 220px;
|
||||
height: 500px;
|
||||
overflow: hidden;
|
||||
}
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ANyONe Extension</title>
|
||||
<link rel="stylesheet" href="../css/popup.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="popup-container">
|
||||
<!-- Header -->
|
||||
<header class="popup-header">
|
||||
<div class="logo">
|
||||
<img src="../images/anyone2.png" alt="ANyONe" class="logo-img">
|
||||
<span class="logo-subtitle">Privacy Extension</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
#anonLogo {
|
||||
margin: 10px auto 10px;
|
||||
display: block;
|
||||
}
|
||||
<!-- Mode Selection -->
|
||||
<section class="mode-section">
|
||||
<div class="mode-tabs">
|
||||
<button class="mode-tab active" data-mode="public">Public Proxies</button>
|
||||
<button class="mode-tab" data-mode="custom">Custom Proxy</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 15px;
|
||||
}
|
||||
<!-- Mode Content: Public -->
|
||||
<section class="mode-content active" id="mode-public">
|
||||
<div class="card">
|
||||
<div class="proxy-info">
|
||||
<div class="proxy-info-row">
|
||||
<span class="proxy-info-label">Mode</span>
|
||||
<span class="proxy-info-value" id="proxy-mode">Auto (Fastest)</span>
|
||||
</div>
|
||||
<div class="proxy-info-row">
|
||||
<span class="proxy-info-label">Available</span>
|
||||
<span class="proxy-info-value" id="proxy-count">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="proxy-actions">
|
||||
<button class="btn btn-secondary" id="btn-connect-text">Connect</button>
|
||||
<button class="btn btn-secondary" id="btn-next-proxy">Next Proxy</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
.switch input {
|
||||
opacity: 1;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
<!-- Mode Content: Custom -->
|
||||
<section class="mode-content" id="mode-custom">
|
||||
<div class="card">
|
||||
<div class="custom-form">
|
||||
<div class="input-row">
|
||||
<div class="input-group">
|
||||
<label class="input-label">IP / Hostname</label>
|
||||
<input type="text" class="input" id="custom-ip" placeholder="relayup.local">
|
||||
</div>
|
||||
<div class="input-group port">
|
||||
<label class="input-label">Port</label>
|
||||
<input type="number" class="input" id="custom-port" placeholder="9050">
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-secondary w-full" id="btn-test-custom">
|
||||
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
|
||||
<polyline points="22 4 12 14.01 9 11.01"/>
|
||||
</svg>
|
||||
Test Connection
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #e74c3c;
|
||||
transition: 0.4s;
|
||||
border-radius: 34px;
|
||||
}
|
||||
<!-- Connect Button -->
|
||||
<section class="connect-section">
|
||||
<button class="connect-btn" id="btn-connect">
|
||||
<div class="connect-btn-ring"></div>
|
||||
<img src="../images/anonlogo.png" alt="Connect" class="connect-btn-icon">
|
||||
</button>
|
||||
</section>
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
left: 0px;
|
||||
bottom: -4px;
|
||||
background-color: #ffffff;
|
||||
transition: 0.4s;
|
||||
border-radius: 50%;
|
||||
}
|
||||
<!-- Status Card -->
|
||||
<section class="status-card" id="status-card">
|
||||
<div class="status-main">
|
||||
<div class="status-dot-container">
|
||||
<span class="status-dot offline" id="status-dot"></span>
|
||||
</div>
|
||||
<div class="status-info">
|
||||
<span class="status-text" id="status-text">Disconnected</span>
|
||||
<span class="status-ip" id="status-ip" style="display: none;">-</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: #02af50;
|
||||
}
|
||||
<!-- Quick Actions -->
|
||||
<section class="quick-actions">
|
||||
<button class="quick-action" id="btn-check-ip">
|
||||
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="11" cy="11" r="8"/>
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65"/>
|
||||
</svg>
|
||||
<span class="quick-action-label">Check IP</span>
|
||||
</button>
|
||||
<button class="quick-action" id="btn-refresh">
|
||||
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="23 4 23 10 17 10"/>
|
||||
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/>
|
||||
</svg>
|
||||
<span class="quick-action-label">Refresh</span>
|
||||
</button>
|
||||
<button class="quick-action" id="btn-settings">
|
||||
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
<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"/>
|
||||
</svg>
|
||||
<span class="quick-action-label">Settings</span>
|
||||
</button>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
input:checked + .slider:before {
|
||||
transform: translateX(26px);
|
||||
}
|
||||
|
||||
#statusMessage {
|
||||
margin-top: 30px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
min-height: 30px;
|
||||
max-width: 100%;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.button-container-1 {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 5px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
bottom: 140px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.button-container-2 {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
bottom: 65px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
color: white;
|
||||
padding: 5px 15px;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
margin: 0 5px;
|
||||
width: 100px;
|
||||
height: auto;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
white-space: normal;
|
||||
overflow: visible;
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#checkAnyoneButton {
|
||||
background-color: #0280AF;
|
||||
border: 1px solid #0280AF;
|
||||
}
|
||||
|
||||
#checkAnyoneButton:hover {
|
||||
background-color: #0074f0;
|
||||
}
|
||||
|
||||
#updateProxiesButton {
|
||||
background-color: #0a121e;
|
||||
border: 1px solid #0280AF;
|
||||
}
|
||||
|
||||
#updateProxiesButton:hover {
|
||||
background-color: #0280AF;
|
||||
}
|
||||
|
||||
#optionsButton {
|
||||
background-color: #0a121e;
|
||||
border: 1px solid #03bdc5;
|
||||
}
|
||||
|
||||
#optionsButton:hover {
|
||||
background-color: #03bdc5;
|
||||
}
|
||||
|
||||
#dappStoreButton {
|
||||
background-color: #0a121e;
|
||||
border: 1px solid #03bdc5;
|
||||
}
|
||||
|
||||
#dappStoreButton:hover {
|
||||
background-color: #03bdc5;
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 30px;
|
||||
}
|
||||
|
||||
.social-link {
|
||||
display: block;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 30px;
|
||||
transition: transform 0.3s ease-in-out, filter 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.social-link:hover {
|
||||
transform: scale(1.2) translateY(-1px);
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
.social-link.debros { background-image: url('../images/debroslogo.png'); }
|
||||
.social-link.x { background-image: url('../images/x.png'); }
|
||||
.social-link.docs { background-image: url('../images/gitbros.png'); }
|
||||
.social-link.anyone { background-image: url('../images/anonlogo.png'); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<img src="../images/anonlogo.png" alt="AnON Logo" width="120" height="120" id="anonLogo">
|
||||
<h1>ANyONe Proxy</h1>
|
||||
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="proxyToggle">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
|
||||
<p id="statusMessage"></p>
|
||||
|
||||
<div class="button-container-1">
|
||||
<button id="checkAnyoneButton">Check ANyONe</button>
|
||||
<button id="updateProxiesButton">Update Proxies</button>
|
||||
</div>
|
||||
|
||||
<div class="button-container-2">
|
||||
<button id="optionsButton">Custom Settings</button>
|
||||
<button id="dappStoreButton">dApp Store</button>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<a href="https://debros.io" class="social-link debros" target="_blank"></a>
|
||||
<a href="https://x.com/debrosofficial" class="social-link x" target="_blank"></a>
|
||||
<a href="https://git.debros.io/DeBros/anyone-extension" class="social-link docs" target="_blank"></a>
|
||||
<a href="https://anyone.io" class="social-link anyone" target="_blank"></a>
|
||||
</div>
|
||||
|
||||
<script src="../js/popup.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
<script type="module" src="../js/popup.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -1,46 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>dApp Store</title>
|
||||
<style>
|
||||
body, html {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: black;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
#fullScreenImage {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
z-index: -1;
|
||||
opacity: 0;
|
||||
transition: opacity 2s ease-in-out;
|
||||
}
|
||||
|
||||
#comingSoon {
|
||||
font-size: 3em;
|
||||
color: white;
|
||||
opacity: 0;
|
||||
transition: opacity 2s ease-in-out;
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<img id="fullScreenImage" src="../images/comingsoon.png" alt="Coming Soon Background">
|
||||
<div id="comingSoon">Coming Soon</div>
|
||||
<script src="../js/store.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
BIN
images/DeBros_White_Transparent.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
images/anyone2.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 975 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 1.9 MiB |
|
Before Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 1.2 MiB |
BIN
images/x.png
|
Before Width: | Height: | Size: 28 KiB |
980
js/background.js
@ -1,286 +1,728 @@
|
||||
let proxies = []; // Initially empty, will be populated from local storage or fetched from the server
|
||||
let currentProxyIndex = 0; // Start with the first proxy
|
||||
let proxyEnabled = false;
|
||||
/* ANyONe Extension v2 - Background Service Worker */
|
||||
|
||||
console.log("background.js is running");
|
||||
// ES Module imports
|
||||
import { CONFIG } from './config.js';
|
||||
import { Utils } from './utils.js';
|
||||
import { Storage } from './storage.js';
|
||||
import { ProxyManager } from './proxy-manager.js';
|
||||
|
||||
function applyProxySettings(host, port, exceptions = []) {
|
||||
const proxyConfig = {
|
||||
mode: "fixed_servers",
|
||||
rules: {
|
||||
singleProxy: {
|
||||
scheme: "socks5",
|
||||
host,
|
||||
port,
|
||||
},
|
||||
bypassList: exceptions.concat([""]),
|
||||
},
|
||||
};
|
||||
// ============================================
|
||||
// Initialization
|
||||
// ============================================
|
||||
|
||||
chrome.proxy.settings.set({ value: proxyConfig, scope: "regular" }, () => {
|
||||
const lastError = chrome.runtime.lastError;
|
||||
if (lastError) {
|
||||
console.error("Error applying proxy settings:", lastError);
|
||||
fallbackToNextProxy(); // Move to the next proxy if the current one fails
|
||||
} else {
|
||||
console.log(`Proxy applied: ${host}:${port}`);
|
||||
proxyEnabled = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
Utils.log('info', 'Background service worker starting...');
|
||||
|
||||
function clearProxySettings() {
|
||||
chrome.proxy.settings.clear({}, () => {
|
||||
const lastError = chrome.runtime.lastError;
|
||||
if (lastError) {
|
||||
console.error("Error clearing proxy settings:", lastError);
|
||||
} else {
|
||||
console.log("Proxy settings cleared.");
|
||||
proxyEnabled = false;
|
||||
currentProxyIndex = 0; // Reset to the first proxy
|
||||
}
|
||||
});
|
||||
}
|
||||
// Initialize on startup
|
||||
chrome.runtime.onInstalled.addListener(async (details) => {
|
||||
Utils.log('info', `Extension ${details.reason}`, { version: CONFIG.VERSION });
|
||||
|
||||
function fallbackToNextProxy() {
|
||||
currentProxyIndex = (currentProxyIndex + 1) % proxies.length; // Move to the next proxy in the list
|
||||
if (proxies.length > 0) {
|
||||
const { host, port } = proxies[currentProxyIndex];
|
||||
console.log(`Falling back to proxy: ${host}:${port}`);
|
||||
applyProxySettings(host, port);
|
||||
} else {
|
||||
console.error("No proxies available to fall back to.");
|
||||
}
|
||||
}
|
||||
if (details.reason === 'install') {
|
||||
// First install
|
||||
Utils.log('info', 'First install, setting up defaults...');
|
||||
|
||||
function checkProxyFunctionality(proxy, callback) {
|
||||
const proxyConfig = {
|
||||
mode: "fixed_servers",
|
||||
rules: {
|
||||
singleProxy: {
|
||||
scheme: "socks5",
|
||||
host: proxy.host,
|
||||
port: proxy.port
|
||||
},
|
||||
bypassList: [""]
|
||||
}
|
||||
};
|
||||
|
||||
chrome.proxy.settings.set({ value: proxyConfig, scope: "regular" }, () => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.error("Error setting proxy for check:", chrome.runtime.lastError);
|
||||
callback(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 second timeout
|
||||
|
||||
fetch('https://check.en.anyone.tech', {
|
||||
mode: 'no-cors',
|
||||
signal: controller.signal
|
||||
})
|
||||
.then(response => {
|
||||
clearTimeout(timeoutId);
|
||||
if (response.ok) {
|
||||
console.log("Proxy check successful");
|
||||
callback(true); // Connection successful
|
||||
} else {
|
||||
console.log("Proxy check failed");
|
||||
callback(false); // Connection failed
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.name === 'AbortError') {
|
||||
console.log("Request timed out");
|
||||
} else {
|
||||
console.error("Error in proxy check:", error);
|
||||
}
|
||||
callback(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function enableFirstWorkingProxy(callback) {
|
||||
let index = 0;
|
||||
|
||||
function tryNextProxy() {
|
||||
if (index >= proxies.length) {
|
||||
console.log("No working proxy found");
|
||||
callback(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const proxy = proxies[index];
|
||||
console.log("Checking proxy:", proxy.host + ":" + proxy.port);
|
||||
checkProxyFunctionality(proxy, (isWorking) => {
|
||||
if (isWorking) {
|
||||
currentProxyIndex = index; // Update currentProxyIndex
|
||||
console.log("Found working proxy:", proxy.host + ":" + proxy.port);
|
||||
applyProxySettings(proxy.host, proxy.port);
|
||||
chrome.storage.local.set({ proxyEnabled: true, currentProxy: proxy });
|
||||
callback(true);
|
||||
} else {
|
||||
index++;
|
||||
tryNextProxy();
|
||||
}
|
||||
// Set defaults
|
||||
await Storage.set({
|
||||
[CONFIG.STORAGE_KEYS.MODE]: CONFIG.DEFAULTS.MODE,
|
||||
[CONFIG.STORAGE_KEYS.PROXY_ENABLED]: false,
|
||||
[CONFIG.STORAGE_KEYS.AUTO_CONNECT]: CONFIG.DEFAULTS.AUTO_CONNECT,
|
||||
[CONFIG.STORAGE_KEYS.WEBRTC_PROTECTION]: CONFIG.DEFAULTS.WEBRTC_PROTECTION,
|
||||
[CONFIG.STORAGE_KEYS.BYPASS_LOCAL]: CONFIG.DEFAULTS.BYPASS_LOCAL
|
||||
});
|
||||
}
|
||||
|
||||
tryNextProxy();
|
||||
}
|
||||
|
||||
function fetchProxies() {
|
||||
console.log("Starting to fetch proxies...");
|
||||
return fetch('https://git.debros.io/DeBros/anyone-proxy-list/raw/branch/main/anonproxies.json') // list of public proxies
|
||||
.then(response => {
|
||||
console.log("Response status:", response.status);
|
||||
if (!response.ok) {
|
||||
console.error("Response not OK");
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log("Data received:", data);
|
||||
proxies = data;
|
||||
// Save the proxies to local storage
|
||||
chrome.storage.local.set({ 'proxyList': proxies }, () => {
|
||||
console.log('Proxies updated and saved:', proxies);
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Failed to fetch proxies:', error);
|
||||
});
|
||||
}
|
||||
|
||||
// Load proxies from storage on extension startup if they exist
|
||||
chrome.storage.local.get('proxyList', function(result) {
|
||||
if (result.proxyList) {
|
||||
proxies = result.proxyList;
|
||||
console.log('Loaded proxies from storage:', proxies);
|
||||
} else {
|
||||
console.log('No proxies found in local storage, fetch required.');
|
||||
// Auto-fetch proxies on first install
|
||||
Utils.log('info', 'Fetching initial proxy list...');
|
||||
try {
|
||||
const source = CONFIG.DEFAULTS.PROXY_SOURCE;
|
||||
await handleFetchProxies(source);
|
||||
Utils.log('info', 'Initial proxy list fetched successfully');
|
||||
} catch (error) {
|
||||
Utils.log('error', 'Failed to fetch initial proxy list', error);
|
||||
}
|
||||
} else if (details.reason === 'update') {
|
||||
Utils.log('info', 'Extension updated');
|
||||
}
|
||||
});
|
||||
|
||||
// This event listener runs when the extension is first installed or updated
|
||||
chrome.runtime.onInstalled.addListener((details) => {
|
||||
if (details.reason === "install") {
|
||||
console.log("Extension installed for the first time. Fetching proxies...");
|
||||
// Fetch proxies automatically on first install but don't enable them
|
||||
fetchProxies().then(() => {
|
||||
console.log("Proxies updated on first install.");
|
||||
// Don't enable proxy automatically, just fetch and store it
|
||||
});
|
||||
// Load state on startup
|
||||
chrome.runtime.onStartup.addListener(async () => {
|
||||
Utils.log('info', 'Browser startup');
|
||||
await ProxyManager.init();
|
||||
|
||||
// Check auto-connect setting
|
||||
const autoConnect = await Storage.getValue(CONFIG.STORAGE_KEYS.AUTO_CONNECT, false);
|
||||
if (autoConnect) {
|
||||
Utils.log('info', 'Auto-connect enabled, connecting...');
|
||||
const mode = await Storage.getMode();
|
||||
await handleConnect(mode);
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// Message Handlers
|
||||
// ============================================
|
||||
|
||||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
console.log("Received message in background:", message);
|
||||
if (message.action === "enableProxy") {
|
||||
console.log("Enabling proxy...");
|
||||
chrome.runtime.sendMessage({ action: "showLoadingMessage", message: "Please wait..." });
|
||||
enableFirstWorkingProxy((success) => {
|
||||
if (success) {
|
||||
console.log("Proxy enabled successfully");
|
||||
sendResponse({ status: "enabled", proxy: proxies[currentProxyIndex] });
|
||||
} else {
|
||||
console.log("Failed to enable proxy");
|
||||
sendResponse({ status: "error", message: "No public proxy available at this moment. Please configure a custom proxy in the settings." });
|
||||
}
|
||||
Utils.log('debug', 'Message received', message);
|
||||
|
||||
// Handle async responses
|
||||
handleMessage(message, sender)
|
||||
.then(response => sendResponse(response))
|
||||
.catch(error => {
|
||||
Utils.log('error', 'Message handler error', error);
|
||||
sendResponse({ success: false, error: error.message });
|
||||
});
|
||||
return true; // For async response
|
||||
} else if (message.action === "disableProxy") {
|
||||
console.log("Disabling proxy...");
|
||||
clearProxySettings();
|
||||
chrome.storage.local.set({ proxyEnabled: false, proxyType: null });
|
||||
sendResponse({ status: "disabled" });
|
||||
chrome.tabs.query({}, function(tabs) {
|
||||
for (let tab of tabs) {
|
||||
chrome.tabs.sendMessage(tab.id, {action: "disableProxy"}, function(response) {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.warn("Warning: Could not send message to tab " + tab.id + ". Tab might have been closed.", chrome.runtime.lastError.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (message.action === "updateProxy") {
|
||||
console.log("Updating proxy settings...");
|
||||
if (message.type === "custom") {
|
||||
// Custom proxy enable
|
||||
applyProxySettings(message.proxy.host, parseInt(message.proxy.port), message.exceptions || []);
|
||||
chrome.storage.local.set({
|
||||
proxyEnabled: true,
|
||||
proxyType: "custom",
|
||||
proxyIP: message.proxy.host,
|
||||
proxyPort: message.proxy.port
|
||||
});
|
||||
sendResponse({ status: "enabled", proxy: message.proxy });
|
||||
chrome.tabs.query({}, function(tabs) {
|
||||
for (let tab of tabs) {
|
||||
chrome.tabs.sendMessage(tab.id, {action: "updatePopupState"}, function(response) {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.warn("Warning: Could not send message to tab " + tab.id + ". Tab might have been closed.", chrome.runtime.lastError.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (message.type === "public") {
|
||||
// Public proxy enable
|
||||
chrome.runtime.sendMessage({ action: "showLoadingMessage", message: "Please wait..." });
|
||||
enableFirstWorkingProxy((success) => {
|
||||
if (success) {
|
||||
console.log("Public proxy enabled successfully");
|
||||
sendResponse({ status: "enabled", proxy: proxies[currentProxyIndex] });
|
||||
} else {
|
||||
console.log("Failed to enable public proxy");
|
||||
sendResponse({ status: "error", message: "No public proxy available at this moment. Please configure a custom proxy in the settings." });
|
||||
}
|
||||
});
|
||||
return true; // For async response
|
||||
} else if (message.type === "disabled") {
|
||||
clearProxySettings();
|
||||
chrome.storage.local.set({ proxyEnabled: false, proxyType: null });
|
||||
sendResponse({ status: "disabled" });
|
||||
chrome.tabs.query({}, function(tabs) {
|
||||
for (let tab of tabs) {
|
||||
chrome.tabs.sendMessage(tab.id, {action: "disableProxy"}, function(response) {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.warn("Warning: Could not send message to tab " + tab.id + ". Tab might have been closed.", chrome.runtime.lastError.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (message.action === "proxyFailed") {
|
||||
console.log("Proxy setup failed:", message.error);
|
||||
clearProxySettings();
|
||||
chrome.storage.local.set({ proxyEnabled: false, proxyType: null });
|
||||
chrome.tabs.query({}, function(tabs) {
|
||||
for (let tab of tabs) {
|
||||
chrome.tabs.sendMessage(tab.id, {action: "toggleOff"}, function(response) {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.warn("Warning: Could not send message to tab " + tab.id + ". Tab might have been closed.", chrome.runtime.lastError.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (message.action === "updateProxies") {
|
||||
console.log("Attempting to update proxies...");
|
||||
fetchProxies().then(() => {
|
||||
console.log("Proxies fetched successfully");
|
||||
sendResponse({success: true, proxies: proxies});
|
||||
chrome.runtime.sendMessage({ action: "updateStatus", message: "Proxy list updated successfully!", color: "#2ecc71" });
|
||||
}).catch(error => {
|
||||
console.error("Failed to fetch proxies:", error);
|
||||
sendResponse({success: false, message: "Failed to update proxy list."});
|
||||
chrome.runtime.sendMessage({ action: "updateStatus", message: "Failed to update proxy list.", color: "#e74c3c" });
|
||||
});
|
||||
return true; // Indicates this is an async response
|
||||
} else {
|
||||
console.log("Unknown action received:", message.action);
|
||||
sendResponse({ status: "error", message: "Unknown action." });
|
||||
|
||||
return true; // Keep channel open for async response
|
||||
});
|
||||
|
||||
/**
|
||||
* Handle incoming messages
|
||||
* @param {object} message - Message object
|
||||
* @param {object} sender - Sender info
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
async function handleMessage(message, sender) {
|
||||
switch (message.action) {
|
||||
// ============================================
|
||||
// Connection Actions
|
||||
// ============================================
|
||||
|
||||
case 'connect':
|
||||
return handleConnect(message.mode, message.options);
|
||||
|
||||
case 'disconnect':
|
||||
return handleDisconnect();
|
||||
|
||||
case 'getStatus':
|
||||
return handleGetStatus();
|
||||
|
||||
// ============================================
|
||||
// Proxy List Actions
|
||||
// ============================================
|
||||
|
||||
case 'fetchProxies':
|
||||
return handleFetchProxies(message.source);
|
||||
|
||||
case 'getProxyList':
|
||||
return handleGetProxyList();
|
||||
|
||||
case 'testProxy':
|
||||
return handleTestProxy(message.proxy);
|
||||
|
||||
// ============================================
|
||||
// Settings Actions
|
||||
// ============================================
|
||||
|
||||
case 'getSettings':
|
||||
return handleGetSettings();
|
||||
|
||||
case 'saveSettings':
|
||||
return handleSaveSettings(message.settings);
|
||||
|
||||
case 'clearCustomProxy':
|
||||
return handleClearCustomProxy();
|
||||
|
||||
case 'setMode':
|
||||
return handleSetMode(message.mode);
|
||||
|
||||
case 'nextProxy':
|
||||
return handleNextProxy();
|
||||
|
||||
// ============================================
|
||||
// Utility Actions
|
||||
// ============================================
|
||||
|
||||
case 'openOptions':
|
||||
chrome.runtime.openOptionsPage();
|
||||
return { success: true };
|
||||
|
||||
case 'openUrl':
|
||||
chrome.tabs.create({ url: message.url });
|
||||
return { success: true };
|
||||
|
||||
default:
|
||||
Utils.log('warn', 'Unknown action', message.action);
|
||||
return { success: false, error: 'Unknown action' };
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Action Handlers
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Handle connect action
|
||||
* @param {string} mode - Connection mode
|
||||
* @param {object} options - Additional options
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
async function handleConnect(mode, options = {}) {
|
||||
Utils.log('info', 'Connecting', { mode, options });
|
||||
|
||||
// Deactivate kill switch if it was active
|
||||
const killSwitchActive = await Storage.getValue('killSwitchActive', false);
|
||||
if (killSwitchActive) {
|
||||
await applyKillSwitch(false);
|
||||
Utils.log('info', 'Kill switch deactivated for new connection');
|
||||
}
|
||||
|
||||
// Notify popup of loading state
|
||||
broadcastMessage({ action: 'statusUpdate', status: 'connecting' });
|
||||
|
||||
// Get local network access setting and exceptions (bypass list)
|
||||
const bypassLocal = await Storage.getValue(CONFIG.STORAGE_KEYS.BYPASS_LOCAL, true);
|
||||
const exceptions = await Storage.getValue(CONFIG.STORAGE_KEYS.EXCEPTIONS, []);
|
||||
|
||||
let result;
|
||||
|
||||
switch (mode) {
|
||||
case CONFIG.MODES.PUBLIC:
|
||||
await ProxyManager.init();
|
||||
result = await ProxyManager.connectToFastest(options.country, bypassLocal, exceptions);
|
||||
break;
|
||||
|
||||
case CONFIG.MODES.CUSTOM:
|
||||
const customProxy = await Storage.getCustomProxy();
|
||||
Utils.log('info', 'Custom proxy settings loaded', {
|
||||
ip: customProxy.ip,
|
||||
port: customProxy.port,
|
||||
exceptions: customProxy.exceptions,
|
||||
bypassLocal
|
||||
});
|
||||
if (!customProxy.ip || !customProxy.port) {
|
||||
result = { success: false, error: 'Custom proxy not configured' };
|
||||
} else {
|
||||
result = await ProxyManager.connectCustom(
|
||||
customProxy.ip,
|
||||
customProxy.port,
|
||||
customProxy.exceptions,
|
||||
bypassLocal
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
result = { success: false, error: 'Invalid mode' };
|
||||
}
|
||||
|
||||
if (result.success) {
|
||||
await Storage.setMode(mode);
|
||||
await Storage.setProxyEnabled(true);
|
||||
broadcastMessage({
|
||||
action: 'statusUpdate',
|
||||
status: 'connected',
|
||||
proxy: result.proxy,
|
||||
mode
|
||||
});
|
||||
} else {
|
||||
broadcastMessage({
|
||||
action: 'statusUpdate',
|
||||
status: 'error',
|
||||
error: result.error
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle disconnect action
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
async function handleDisconnect() {
|
||||
Utils.log('info', 'Disconnecting');
|
||||
|
||||
// Check if kill switch should be activated
|
||||
const killSwitch = await Storage.getValue(CONFIG.STORAGE_KEYS.KILL_SWITCH, false);
|
||||
|
||||
const success = await ProxyManager.disconnect();
|
||||
|
||||
// If kill switch is enabled, block traffic after disconnect
|
||||
if (killSwitch) {
|
||||
await applyKillSwitch(true);
|
||||
broadcastMessage({
|
||||
action: 'statusUpdate',
|
||||
status: 'blocked',
|
||||
message: 'Kill Switch active - all traffic blocked'
|
||||
});
|
||||
} else {
|
||||
broadcastMessage({
|
||||
action: 'statusUpdate',
|
||||
status: success ? 'disconnected' : 'error'
|
||||
});
|
||||
}
|
||||
|
||||
await Storage.setProxyEnabled(false);
|
||||
return { success };
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle get status action
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
async function handleGetStatus() {
|
||||
const [enabled, mode, currentProxy, killSwitchActive] = await Promise.all([
|
||||
Storage.isProxyEnabled(),
|
||||
Storage.getMode(),
|
||||
Storage.getCurrentProxy(),
|
||||
Storage.getValue('killSwitchActive', false)
|
||||
]);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
enabled,
|
||||
mode,
|
||||
currentProxy,
|
||||
killSwitchActive
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle next proxy action (load balancing)
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
async function handleNextProxy() {
|
||||
Utils.log('info', 'Switching to next proxy');
|
||||
await ProxyManager.init();
|
||||
|
||||
// Check if proxy list is empty and fetch if needed
|
||||
const status = ProxyManager.getStatus();
|
||||
if (status.proxyCount === 0) {
|
||||
Utils.log('info', 'Proxy list empty, fetching first...');
|
||||
const source = await Storage.getValue(CONFIG.STORAGE_KEYS.PROXY_SOURCE, 'git');
|
||||
const fetchResult = await ProxyManager.fetchProxyList(source);
|
||||
if (!fetchResult.success || fetchResult.proxies.length === 0) {
|
||||
return { success: false, error: 'Failed to fetch proxy list' };
|
||||
}
|
||||
}
|
||||
|
||||
const result = await ProxyManager.fallbackToNext();
|
||||
|
||||
if (result.success) {
|
||||
broadcastMessage({
|
||||
action: 'statusUpdate',
|
||||
status: 'connected',
|
||||
proxy: result.proxy
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle fetch proxies action
|
||||
* @param {string} source - Proxy source
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
async function handleFetchProxies(source = 'arweave') {
|
||||
const result = await ProxyManager.fetchProxyList(source);
|
||||
|
||||
if (result.success) {
|
||||
broadcastMessage({
|
||||
action: 'proxiesUpdated',
|
||||
count: result.proxies.length,
|
||||
usedSource: result.usedSource
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle get proxy list action
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
async function handleGetProxyList() {
|
||||
const proxyList = await Storage.getProxyList();
|
||||
const lastUpdate = await Storage.getLastUpdate();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
proxies: proxyList,
|
||||
lastUpdate,
|
||||
count: proxyList.length
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle test proxy action
|
||||
* @param {object} proxy - Proxy to test
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
async function handleTestProxy(proxy) {
|
||||
const result = await ProxyManager.testProxy(proxy);
|
||||
return { success: true, ...result };
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle get settings action
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
async function handleGetSettings() {
|
||||
const settings = await Storage.getAllSettings();
|
||||
return { success: true, settings };
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle save settings action
|
||||
* @param {object} settings - Settings to save
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
async function handleSaveSettings(settings) {
|
||||
try {
|
||||
await Storage.set(settings);
|
||||
|
||||
// Apply WebRTC protection if changed
|
||||
if (settings.webrtcProtection !== undefined) {
|
||||
await applyWebRTCProtection(settings.webrtcProtection);
|
||||
}
|
||||
|
||||
// Apply Kill Switch if changed
|
||||
if (settings.killSwitch !== undefined) {
|
||||
const proxyEnabled = await Storage.isProxyEnabled();
|
||||
|
||||
if (settings.killSwitch && !proxyEnabled) {
|
||||
// Kill switch enabled while not connected - block traffic
|
||||
await applyKillSwitch(true);
|
||||
broadcastMessage({
|
||||
action: 'statusUpdate',
|
||||
status: 'blocked',
|
||||
message: 'Kill Switch active - connect to unblock'
|
||||
});
|
||||
} else if (!settings.killSwitch) {
|
||||
// Kill switch disabled - restore normal traffic
|
||||
const killSwitchActive = await Storage.getValue('killSwitchActive', false);
|
||||
if (killSwitchActive) {
|
||||
await applyKillSwitch(false);
|
||||
broadcastMessage({
|
||||
action: 'statusUpdate',
|
||||
status: 'disconnected'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if custom proxy connection settings changed (IP, port, credentials)
|
||||
const customProxyConnectionChanged =
|
||||
settings.proxyIP !== undefined ||
|
||||
settings.proxyPort !== undefined ||
|
||||
settings.proxyUsername !== undefined ||
|
||||
settings.proxyPassword !== undefined;
|
||||
|
||||
// Check if bypass settings changed (applies to both public and custom modes)
|
||||
const bypassSettingsChanged =
|
||||
settings.bypassLocal !== undefined ||
|
||||
settings.noProxyFor !== undefined;
|
||||
|
||||
// Handle custom proxy connection changes (only affects custom mode)
|
||||
if (customProxyConnectionChanged) {
|
||||
const proxyEnabled = await Storage.isProxyEnabled();
|
||||
const mode = await Storage.getMode();
|
||||
|
||||
if (proxyEnabled && mode === CONFIG.MODES.CUSTOM) {
|
||||
Utils.log('info', 'Custom proxy settings changed while connected, re-applying...');
|
||||
|
||||
// Get the updated custom proxy settings
|
||||
const customProxy = await Storage.getCustomProxy();
|
||||
const bypassLocal = await Storage.getValue(CONFIG.STORAGE_KEYS.BYPASS_LOCAL, true);
|
||||
const exceptions = await Storage.getValue(CONFIG.STORAGE_KEYS.EXCEPTIONS, []);
|
||||
|
||||
// Validate the new settings
|
||||
if (!customProxy.ip || !customProxy.port) {
|
||||
// Invalid settings - disconnect
|
||||
Utils.log('warn', 'Custom proxy settings invalid, disconnecting...');
|
||||
await ProxyManager.disconnect();
|
||||
await Storage.setProxyEnabled(false);
|
||||
broadcastMessage({
|
||||
action: 'statusUpdate',
|
||||
status: 'error',
|
||||
error: 'Custom proxy not configured'
|
||||
});
|
||||
} else {
|
||||
// Try to apply the new proxy settings
|
||||
const result = await ProxyManager.applyProxy(
|
||||
customProxy.ip,
|
||||
customProxy.port,
|
||||
exceptions,
|
||||
bypassLocal
|
||||
);
|
||||
|
||||
if (result) {
|
||||
broadcastMessage({
|
||||
action: 'statusUpdate',
|
||||
status: 'connected',
|
||||
proxy: { host: customProxy.ip, port: customProxy.port },
|
||||
mode: CONFIG.MODES.CUSTOM
|
||||
});
|
||||
} else {
|
||||
// Failed to apply - disconnect
|
||||
await ProxyManager.disconnect();
|
||||
await Storage.setProxyEnabled(false);
|
||||
broadcastMessage({
|
||||
action: 'statusUpdate',
|
||||
status: 'error',
|
||||
error: 'Failed to apply proxy settings'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle bypass settings changes (affects both public and custom modes)
|
||||
// Skip if custom proxy connection was already re-applied above
|
||||
if (bypassSettingsChanged && !customProxyConnectionChanged) {
|
||||
const proxyEnabled = await Storage.isProxyEnabled();
|
||||
if (proxyEnabled) {
|
||||
const mode = await Storage.getMode();
|
||||
const currentProxy = await Storage.getCurrentProxy();
|
||||
|
||||
if (currentProxy) {
|
||||
const bypassLocal = await Storage.getValue(CONFIG.STORAGE_KEYS.BYPASS_LOCAL, true);
|
||||
const exceptions = await Storage.getValue(CONFIG.STORAGE_KEYS.EXCEPTIONS, []);
|
||||
|
||||
Utils.log('info', 'Bypass settings changed, re-applying proxy settings', { bypassLocal, exceptions });
|
||||
|
||||
if (mode === CONFIG.MODES.CUSTOM) {
|
||||
const customProxy = await Storage.getCustomProxy();
|
||||
await ProxyManager.applyProxy(
|
||||
customProxy.ip,
|
||||
customProxy.port,
|
||||
exceptions,
|
||||
bypassLocal
|
||||
);
|
||||
} else {
|
||||
await ProxyManager.applyProxy(
|
||||
currentProxy.host,
|
||||
currentProxy.port,
|
||||
exceptions,
|
||||
bypassLocal
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle clear custom proxy action
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
async function handleClearCustomProxy() {
|
||||
try {
|
||||
Utils.log('info', 'Clearing custom proxy settings...');
|
||||
await Storage.clearCustomProxy();
|
||||
Utils.log('info', 'Custom proxy settings cleared successfully');
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
Utils.log('error', 'Failed to clear custom proxy settings', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle set mode action
|
||||
* @param {string} mode - Mode to set
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
async function handleSetMode(mode) {
|
||||
await Storage.setMode(mode);
|
||||
return { success: true, mode };
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Utility Functions
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Broadcast message to all extension pages
|
||||
* @param {object} message - Message to broadcast
|
||||
*/
|
||||
function broadcastMessage(message) {
|
||||
chrome.runtime.sendMessage(message).catch(() => {
|
||||
// Ignore errors when no listeners
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle proxy errors
|
||||
*/
|
||||
let lastProxyErrorTime = 0;
|
||||
const PROXY_ERROR_DEBOUNCE_MS = 5000; // Prevent multiple errors within 5 seconds
|
||||
|
||||
chrome.proxy.onProxyError.addListener(async (details) => {
|
||||
// Debounce rapid proxy errors (e.g., when network goes down)
|
||||
const now = Date.now();
|
||||
if (now - lastProxyErrorTime < PROXY_ERROR_DEBOUNCE_MS) {
|
||||
Utils.log('debug', 'Proxy error debounced', details);
|
||||
return;
|
||||
}
|
||||
lastProxyErrorTime = now;
|
||||
|
||||
Utils.log('error', 'Proxy error', details);
|
||||
|
||||
// Check current mode - only attempt fallback for public proxies
|
||||
const mode = await Storage.getMode();
|
||||
const killSwitch = await Storage.getValue(CONFIG.STORAGE_KEYS.KILL_SWITCH, false);
|
||||
|
||||
// For non-fatal errors in public mode, attempt fallback
|
||||
if (!details.fatal && mode === CONFIG.MODES.PUBLIC) {
|
||||
const result = await ProxyManager.fallbackToNext();
|
||||
if (result.success) {
|
||||
broadcastMessage({
|
||||
action: 'statusUpdate',
|
||||
status: 'connected',
|
||||
proxy: result.proxy,
|
||||
message: 'Switched to backup proxy'
|
||||
});
|
||||
return; // Successfully switched, no error to report
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback failed or fatal error or custom mode - handle disconnect
|
||||
if (killSwitch) {
|
||||
// Kill switch is ON - block all traffic
|
||||
await applyKillSwitch(true);
|
||||
await Storage.setProxyEnabled(false);
|
||||
broadcastMessage({
|
||||
action: 'statusUpdate',
|
||||
status: 'blocked',
|
||||
error: details.fatal ? 'Fatal error - Kill switch activated' : 'Connection lost - Kill switch activated'
|
||||
});
|
||||
} else {
|
||||
// Kill switch is OFF - just disconnect and show error
|
||||
await ProxyManager.disconnect();
|
||||
await Storage.setProxyEnabled(false);
|
||||
broadcastMessage({
|
||||
action: 'statusUpdate',
|
||||
status: 'error',
|
||||
error: details.error || 'Proxy connection error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// Privacy Protection Functions
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Apply WebRTC Leak Protection
|
||||
* @param {boolean} enabled - Whether to enable protection
|
||||
*/
|
||||
async function applyWebRTCProtection(enabled) {
|
||||
try {
|
||||
const value = enabled ? 'disable_non_proxied_udp' : 'default';
|
||||
await chrome.privacy.network.webRTCIPHandlingPolicy.set({ value });
|
||||
Utils.log('info', `WebRTC protection ${enabled ? 'enabled' : 'disabled'}`, { policy: value });
|
||||
return true;
|
||||
} catch (error) {
|
||||
Utils.log('error', 'Failed to set WebRTC policy', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply Kill Switch - blocks all traffic when proxy disconnects unexpectedly
|
||||
* @param {boolean} activate - Whether to activate (block) or deactivate (unblock)
|
||||
*/
|
||||
async function applyKillSwitch(activate) {
|
||||
try {
|
||||
if (activate) {
|
||||
// Kill switch active: block all traffic by setting invalid proxy
|
||||
const config = {
|
||||
mode: 'fixed_servers',
|
||||
rules: {
|
||||
singleProxy: {
|
||||
scheme: 'socks5',
|
||||
host: '127.0.0.1', // Localhost with closed port - blocks all traffic
|
||||
port: 65535
|
||||
}
|
||||
}
|
||||
};
|
||||
await chrome.proxy.settings.set({ value: config, scope: 'regular' });
|
||||
await Storage.setValue('killSwitchActive', true);
|
||||
Utils.log('info', 'Kill switch ACTIVATED - all traffic blocked');
|
||||
|
||||
// Update icon to show blocked state
|
||||
try {
|
||||
await chrome.action.setBadgeText({ text: '!' });
|
||||
await chrome.action.setBadgeBackgroundColor({ color: '#e74c3c' });
|
||||
} catch (e) {}
|
||||
} else {
|
||||
// Deactivate kill switch - clear proxy settings
|
||||
await chrome.proxy.settings.clear({ scope: 'regular' });
|
||||
await Storage.setValue('killSwitchActive', false);
|
||||
Utils.log('info', 'Kill switch DEACTIVATED - traffic restored');
|
||||
|
||||
// Clear badge
|
||||
try {
|
||||
await chrome.action.setBadgeText({ text: '' });
|
||||
} catch (e) {}
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
Utils.log('error', 'Failed to apply kill switch', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize privacy settings on startup
|
||||
*/
|
||||
async function initializePrivacySettings() {
|
||||
const webrtcProtection = await Storage.getValue(CONFIG.STORAGE_KEYS.WEBRTC_PROTECTION, true);
|
||||
await applyWebRTCProtection(webrtcProtection);
|
||||
Utils.log('info', 'Privacy settings initialized');
|
||||
}
|
||||
|
||||
// Initialize privacy settings
|
||||
initializePrivacySettings();
|
||||
|
||||
// ============================================
|
||||
// Proxy Authentication Handler
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Handle proxy authentication requests
|
||||
* This is called when a proxy requires username/password
|
||||
*/
|
||||
chrome.webRequest.onAuthRequired.addListener(
|
||||
async (details, callback) => {
|
||||
Utils.log('info', 'Proxy authentication required', { challenger: details.challenger });
|
||||
|
||||
// Only handle proxy authentication
|
||||
if (!details.isProxy) {
|
||||
callback({});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Get stored credentials
|
||||
const customProxy = await Storage.getCustomProxy();
|
||||
|
||||
if (customProxy.username && customProxy.password) {
|
||||
Utils.log('info', 'Providing proxy credentials');
|
||||
callback({
|
||||
authCredentials: {
|
||||
username: customProxy.username,
|
||||
password: customProxy.password
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Utils.log('warn', 'No proxy credentials stored');
|
||||
callback({});
|
||||
}
|
||||
} catch (error) {
|
||||
Utils.log('error', 'Error handling proxy auth', error);
|
||||
callback({});
|
||||
}
|
||||
},
|
||||
{ urls: ['<all_urls>'] },
|
||||
['asyncBlocking']
|
||||
);
|
||||
|
||||
Utils.log('info', 'Background service worker ready');
|
||||
|
||||
87
js/config.js
Normal file
@ -0,0 +1,87 @@
|
||||
/* ANyONe Extension v2 - Configuration */
|
||||
|
||||
const CONFIG = {
|
||||
// Version
|
||||
VERSION: '2.0.1',
|
||||
|
||||
// Connection Modes
|
||||
MODES: {
|
||||
PUBLIC: 'public',
|
||||
CUSTOM: 'custom'
|
||||
},
|
||||
|
||||
// Default Settings
|
||||
DEFAULTS: {
|
||||
MODE: 'public',
|
||||
PROXY_TIMEOUT: 5000,
|
||||
AUTO_CONNECT: false,
|
||||
WEBRTC_PROTECTION: false,
|
||||
BYPASS_LOCAL: true,
|
||||
PROXY_SOURCE: 'arweave',
|
||||
UPDATE_INTERVAL: 0 // manual only
|
||||
},
|
||||
|
||||
// Proxy Sources
|
||||
PROXY_SOURCES: {
|
||||
arweave: {
|
||||
name: 'Arweave',
|
||||
url: 'https://arweave.net/FjxfWIbSnZb7EaJWbeuWCsBBFWjTppfS3_KHxUP__B8',
|
||||
icon: 'AR'
|
||||
},
|
||||
git: {
|
||||
name: 'GitBros',
|
||||
url: 'https://git.debros.io/DeBros/anyone-proxy-list/raw/branch/main/anonproxies.json',
|
||||
icon: 'GIT'
|
||||
},
|
||||
github: {
|
||||
name: 'GitHub',
|
||||
url: 'https://raw.githubusercontent.com/DeBrosOfficial/anyone-proxy-list/refs/heads/main/anonproxies.json',
|
||||
icon: 'GH'
|
||||
}
|
||||
},
|
||||
|
||||
// External URLs
|
||||
URLS: {
|
||||
CHECK_IP: 'https://check.en.anyone.tech/',
|
||||
DOCS: 'https://docs.anyone.io/',
|
||||
GITHUB: 'https://github.com/anyone-protocol',
|
||||
WEBSITE: 'https://anyone.io/'
|
||||
},
|
||||
|
||||
// Timeouts (ms)
|
||||
TIMEOUTS: {
|
||||
PROXY_CHECK: 5000,
|
||||
FETCH: 10000
|
||||
},
|
||||
|
||||
// Storage Keys
|
||||
STORAGE_KEYS: {
|
||||
MODE: 'connectionMode',
|
||||
PROXY_ENABLED: 'proxyEnabled',
|
||||
PROXY_LIST: 'proxyList',
|
||||
PROXY_SOURCE: 'proxySource',
|
||||
CURRENT_PROXY: 'currentProxy',
|
||||
CUSTOM_IP: 'proxyIP',
|
||||
CUSTOM_PORT: 'proxyPort',
|
||||
CUSTOM_USERNAME: 'proxyUsername',
|
||||
CUSTOM_PASSWORD: 'proxyPassword',
|
||||
EXCEPTIONS: 'noProxyFor',
|
||||
AUTO_CONNECT: 'autoConnect',
|
||||
WEBRTC_PROTECTION: 'webrtcProtection',
|
||||
KILL_SWITCH: 'killSwitch',
|
||||
BYPASS_LOCAL: 'bypassLocal',
|
||||
LAST_UPDATE: 'lastProxyUpdate'
|
||||
}
|
||||
};
|
||||
|
||||
// Freeze config to prevent modifications
|
||||
Object.freeze(CONFIG);
|
||||
Object.freeze(CONFIG.MODES);
|
||||
Object.freeze(CONFIG.DEFAULTS);
|
||||
Object.freeze(CONFIG.PROXY_SOURCES);
|
||||
Object.freeze(CONFIG.URLS);
|
||||
Object.freeze(CONFIG.TIMEOUTS);
|
||||
Object.freeze(CONFIG.STORAGE_KEYS);
|
||||
|
||||
// ES Module export
|
||||
export { CONFIG };
|
||||
726
js/options.js
@ -1,226 +1,558 @@
|
||||
const proxyIP = document.getElementById("proxyIP");
|
||||
const proxyPort = document.getElementById("proxyPort");
|
||||
const noProxyFor = document.getElementById("noProxyFor");
|
||||
const saveSettings = document.getElementById("saveSettings");
|
||||
const disableProxy = document.getElementById("disableProxy");
|
||||
const statusMessage = document.getElementById("statusMessage");
|
||||
const checkAnyoneButton = document.getElementById('checkAnyoneButton');
|
||||
/* ANyONe Extension v2 - Options Page Controller */
|
||||
|
||||
// Validate IP address
|
||||
function isValidIP(ip) {
|
||||
const ipRegex = /^(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})\.(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})\.(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})\.(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})$/;
|
||||
return ipRegex.test(ip);
|
||||
// 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');
|
||||
}
|
||||
|
||||
// Validate port number
|
||||
function isValidPort(port) {
|
||||
const num = parseInt(port, 10);
|
||||
return num > 0 && num <= 65535;
|
||||
}
|
||||
// ============================================
|
||||
// Load Settings
|
||||
// ============================================
|
||||
|
||||
// Load saved settings on page load
|
||||
chrome.storage.local.get(["proxyIP", "proxyPort", "proxyType", "noProxyFor"], (settings) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.error("Error retrieving settings:", chrome.runtime.lastError);
|
||||
async function loadSettings() {
|
||||
const response = await sendMessage({ action: 'getSettings' });
|
||||
|
||||
if (!response.success) {
|
||||
console.log('Failed to load settings');
|
||||
return;
|
||||
}
|
||||
proxyIP.value = settings.proxyIP || "";
|
||||
proxyPort.value = settings.proxyPort || "";
|
||||
noProxyFor.value = settings.noProxyFor || "";
|
||||
});
|
||||
|
||||
// Function to check internet connectivity with a timeout
|
||||
function checkInternetConnection(host, port) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 second timeout
|
||||
const settings = response.settings;
|
||||
|
||||
const proxyConfig = {
|
||||
mode: "fixed_servers",
|
||||
rules: {
|
||||
singleProxy: {
|
||||
scheme: "socks5",
|
||||
host: host,
|
||||
port: parseInt(port, 10)
|
||||
},
|
||||
bypassList: [""]
|
||||
// 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');
|
||||
}
|
||||
}
|
||||
|
||||
chrome.proxy.settings.set({ value: proxyConfig, scope: "regular" }, () => {
|
||||
if (chrome.runtime.lastError) {
|
||||
clearTimeout(timeoutId);
|
||||
reject(chrome.runtime.lastError.message);
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('https://check.en.anyone.tech', {
|
||||
method: 'GET',
|
||||
mode: 'no-cors',
|
||||
signal: controller.signal
|
||||
})
|
||||
.then(response => {
|
||||
clearTimeout(timeoutId);
|
||||
if (response.ok) {
|
||||
resolve(true); // Connection successful
|
||||
} else {
|
||||
reject("Failed to connect via proxy"); // Connection failed
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
clearTimeout(timeoutId);
|
||||
if (error.name === 'AbortError') {
|
||||
reject("Connection timed out");
|
||||
} else {
|
||||
reject(error.message || "Network error encountered");
|
||||
}
|
||||
});
|
||||
});
|
||||
async function saveBypassLocalSetting() {
|
||||
await sendMessage({
|
||||
action: 'saveSettings',
|
||||
settings: {
|
||||
webrtcProtection: elements.webrtcProtection.checked,
|
||||
killSwitch: elements.killSwitch.checked,
|
||||
bypassLocal: elements.bypassLocal.checked
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Function to apply Proxy Settings after saving
|
||||
function applyProxySettings(host, port, exceptions = []) {
|
||||
const proxyConfig = {
|
||||
mode: "fixed_servers",
|
||||
rules: {
|
||||
singleProxy: {
|
||||
scheme: "socks5",
|
||||
host: host,
|
||||
port: parseInt(port, 10)
|
||||
},
|
||||
bypassList: exceptions.concat([""])
|
||||
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'
|
||||
};
|
||||
|
||||
chrome.proxy.settings.set({ value: proxyConfig, scope: "regular" }, () => {
|
||||
if (chrome.runtime.lastError) {
|
||||
statusMessage.textContent = "Error applying proxy: " + chrome.runtime.lastError.message;
|
||||
statusMessage.style.color = "red";
|
||||
} else {
|
||||
console.log(`Proxy applied: ${host}:${port}`);
|
||||
statusMessage.textContent = `Proxy applied: ${host}:${port}`;
|
||||
statusMessage.style.color = "#2ecc71";
|
||||
clearStatusMessage();
|
||||
}
|
||||
});
|
||||
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;
|
||||
}
|
||||
|
||||
// Function to clear status message after a delay
|
||||
function clearStatusMessage() {
|
||||
setTimeout(() => {
|
||||
statusMessage.textContent = "";
|
||||
}, 5000); // Clear message after 5 seconds (5000 milliseconds)
|
||||
}
|
||||
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;
|
||||
|
||||
let isCheckingProxy = false;
|
||||
|
||||
// Save settings when clicking "Save Settings"
|
||||
saveSettings.addEventListener("click", () => {
|
||||
if (isCheckingProxy) return; // If a check is already in progress, do nothing
|
||||
|
||||
if (!isValidIP(proxyIP.value)) {
|
||||
statusMessage.textContent = "Invalid IP address.";
|
||||
statusMessage.style.color = "red";
|
||||
return;
|
||||
}
|
||||
if (!isValidPort(proxyPort.value)) {
|
||||
statusMessage.textContent = "Invalid port number. Must be between 1 and 65535.";
|
||||
statusMessage.style.color = "red";
|
||||
// Validate
|
||||
if (!ip) {
|
||||
showToast('Please enter host address', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
statusMessage.textContent = "Please wait...";
|
||||
statusMessage.style.color = "#f39c12";
|
||||
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 the saveSettings button
|
||||
saveSettings.disabled = true;
|
||||
isCheckingProxy = true;
|
||||
// Disable button during save
|
||||
elements.btnSaveCustom.disabled = true;
|
||||
const originalText = elements.btnSaveCustom.innerHTML;
|
||||
elements.btnSaveCustom.innerHTML = '<span>Saving...</span>';
|
||||
|
||||
const noProxyExceptions = noProxyFor.value.split(',').map(ex => ex.trim());
|
||||
const filteredExceptions = noProxyExceptions.filter(ex => ex !== '');
|
||||
|
||||
checkInternetConnection(proxyIP.value, proxyPort.value)
|
||||
.then(() => {
|
||||
chrome.storage.local.set({
|
||||
proxyIP: proxyIP.value,
|
||||
proxyPort: proxyPort.value,
|
||||
proxyType: "custom",
|
||||
noProxyFor: filteredExceptions.join(", "),
|
||||
proxyEnabled: true
|
||||
}, () => {
|
||||
if (chrome.runtime.lastError) {
|
||||
statusMessage.textContent = "Error saving settings: " + chrome.runtime.lastError.message;
|
||||
statusMessage.style.color = "red";
|
||||
} else {
|
||||
statusMessage.textContent = "Proxy settings saved and connection verified!";
|
||||
statusMessage.style.color = "#2ecc71";
|
||||
applyProxySettings(proxyIP.value, proxyPort.value, filteredExceptions);
|
||||
|
||||
// Send response to the message sender
|
||||
chrome.runtime.sendMessage({ action: "updateProxy", type: "custom", proxy: { host: proxyIP.value, port: parseInt(proxyPort.value) }, exceptions: filteredExceptions }, (response) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.error("Error sending message:", chrome.runtime.lastError);
|
||||
} else {
|
||||
console.log("Response received:", response);
|
||||
}
|
||||
clearStatusMessage();
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
statusMessage.textContent = `Proxy connection failed: ${error}. Settings not applied.`;
|
||||
statusMessage.style.color = "red";
|
||||
chrome.proxy.settings.clear({});
|
||||
|
||||
// Send a message indicating proxy setup failed
|
||||
chrome.runtime.sendMessage({ action: "proxyFailed", error: error }, (response) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.error("Error sending message:", chrome.runtime.lastError);
|
||||
} else {
|
||||
console.log("Response received for proxy failure:", response);
|
||||
}
|
||||
clearStatusMessage();
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
// Re-enable the saveSettings button after the check is complete
|
||||
saveSettings.disabled = false;
|
||||
isCheckingProxy = false;
|
||||
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
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
disableProxy.addEventListener("click", () => {
|
||||
chrome.proxy.settings.clear({}, () => {
|
||||
if (chrome.runtime.lastError) {
|
||||
statusMessage.textContent = "Error disabling proxy: " + chrome.runtime.lastError.message;
|
||||
statusMessage.style.color = "red";
|
||||
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 {
|
||||
statusMessage.textContent = "Proxy has been disabled!";
|
||||
statusMessage.style.color = "#e74c3c";
|
||||
clearStatusMessage();
|
||||
console.log("Proxy settings disabled.");
|
||||
chrome.storage.local.get(["noProxyFor"], (result) => {
|
||||
chrome.storage.local.set({
|
||||
proxyType: null,
|
||||
noProxyFor: result.noProxyFor,
|
||||
proxyEnabled: false
|
||||
}, () => {
|
||||
if (chrome.runtime.lastError) {
|
||||
console.error("Error updating storage:", chrome.runtime.lastError);
|
||||
} else {
|
||||
chrome.runtime.sendMessage({ action: "disableProxy" });
|
||||
}
|
||||
});
|
||||
});
|
||||
clearStatusMessage();
|
||||
showToast('Failed to clear settings', 'error');
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
showToast('Failed to clear settings', 'error');
|
||||
} finally {
|
||||
elements.btnClearCustom.disabled = false;
|
||||
elements.btnClearCustom.innerHTML = originalText;
|
||||
}
|
||||
}
|
||||
|
||||
// Open Check Anyone page
|
||||
checkAnyoneButton.addEventListener("click", () => {
|
||||
window.open("https://check.en.anyone.tech/", "_blank");
|
||||
});
|
||||
// ============================================
|
||||
// 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');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
845
js/popup.js
@ -1,188 +1,701 @@
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const proxyToggle = document.getElementById("proxyToggle");
|
||||
const statusMessage = document.getElementById("statusMessage");
|
||||
const optionsButton = document.getElementById("optionsButton");
|
||||
const checkAnyoneButton = document.getElementById("checkAnyoneButton");
|
||||
const dappStoreButton = document.getElementById("dappStoreButton");
|
||||
const updateProxiesButton = document.getElementById("updateProxiesButton");
|
||||
/* ANyONe Extension v2 - Popup Controller */
|
||||
|
||||
if (!checkAnyoneButton) {
|
||||
checkAnyoneButton = document.createElement("button");
|
||||
checkAnyoneButton.id = "checkAnyoneButton";
|
||||
checkAnyoneButton.textContent = "Check ANyONe";
|
||||
checkAnyoneButton.style.marginBottom = "10px";
|
||||
checkAnyoneButton.addEventListener("click", () => {
|
||||
window.open("https://check.en.anyone.tech/", "_blank");
|
||||
});
|
||||
// ES Module imports
|
||||
import { CONFIG } from './config.js';
|
||||
import { Utils } from './utils.js';
|
||||
|
||||
// ============================================
|
||||
// State
|
||||
// ============================================
|
||||
|
||||
const state = {
|
||||
mode: 'public',
|
||||
connected: false,
|
||||
connecting: false,
|
||||
blocked: false, // Kill switch active
|
||||
currentProxy: null,
|
||||
proxyCount: 0
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// DOM Elements
|
||||
// ============================================
|
||||
|
||||
const elements = {
|
||||
// Mode tabs
|
||||
modeTabs: document.querySelectorAll('.mode-tab'),
|
||||
modeContents: document.querySelectorAll('.mode-content'),
|
||||
|
||||
// Public mode
|
||||
proxyMode: document.getElementById('proxy-mode'),
|
||||
proxyCount: document.getElementById('proxy-count'),
|
||||
btnNextProxy: document.getElementById('btn-next-proxy'),
|
||||
|
||||
// Custom mode
|
||||
customIp: document.getElementById('custom-ip'),
|
||||
customPort: document.getElementById('custom-port'),
|
||||
btnTestCustom: document.getElementById('btn-test-custom'),
|
||||
|
||||
// Connect button
|
||||
btnConnect: document.getElementById('btn-connect'),
|
||||
btnConnectText: document.getElementById('btn-connect-text'),
|
||||
|
||||
// Status
|
||||
statusCard: document.getElementById('status-card'),
|
||||
statusDot: document.getElementById('status-dot'),
|
||||
statusText: document.getElementById('status-text'),
|
||||
statusIp: document.getElementById('status-ip'),
|
||||
|
||||
// Quick actions
|
||||
btnCheckIp: document.getElementById('btn-check-ip'),
|
||||
btnRefresh: document.getElementById('btn-refresh'),
|
||||
btnSettings: document.getElementById('btn-settings')
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// Initialization
|
||||
// ============================================
|
||||
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
|
||||
async function init() {
|
||||
console.log('[Popup] Initializing...');
|
||||
|
||||
// Prevent scroll
|
||||
document.body.style.overflow = 'hidden';
|
||||
document.documentElement.style.overflow = 'hidden';
|
||||
window.addEventListener('scroll', (e) => {
|
||||
window.scrollTo(0, 0);
|
||||
});
|
||||
document.addEventListener('wheel', (e) => {
|
||||
e.preventDefault();
|
||||
}, { passive: false });
|
||||
document.addEventListener('touchmove', (e) => {
|
||||
e.preventDefault();
|
||||
}, { passive: false });
|
||||
|
||||
// Setup event listeners
|
||||
setupEventListeners();
|
||||
|
||||
// Load initial state
|
||||
await loadState();
|
||||
|
||||
// Load custom proxy settings
|
||||
await loadCustomProxy();
|
||||
|
||||
// Load proxy info for public mode
|
||||
await loadProxyInfo();
|
||||
|
||||
console.log('[Popup] Initialized', state);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Event Listeners
|
||||
// ============================================
|
||||
|
||||
function setupEventListeners() {
|
||||
// Mode tabs
|
||||
elements.modeTabs.forEach(tab => {
|
||||
tab.addEventListener('click', () => switchMode(tab.dataset.mode));
|
||||
});
|
||||
|
||||
// Connect button
|
||||
elements.btnConnect.addEventListener('click', handleConnect);
|
||||
elements.btnConnectText.addEventListener('click', handleConnect);
|
||||
|
||||
// Public mode
|
||||
elements.btnNextProxy.addEventListener('click', handleNextProxy);
|
||||
|
||||
// Custom mode
|
||||
elements.btnTestCustom.addEventListener('click', testCustomProxy);
|
||||
|
||||
// Quick actions
|
||||
elements.btnCheckIp.addEventListener('click', () => {
|
||||
sendMessage({ action: 'openUrl', url: CONFIG.URLS.CHECK_IP });
|
||||
});
|
||||
|
||||
elements.btnRefresh.addEventListener('click', refreshProxies);
|
||||
|
||||
elements.btnSettings.addEventListener('click', () => {
|
||||
sendMessage({ action: 'openOptions' });
|
||||
});
|
||||
|
||||
// Listen for status updates from background
|
||||
chrome.runtime.onMessage.addListener(handleBackgroundMessage);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// State Management
|
||||
// ============================================
|
||||
|
||||
async function loadState() {
|
||||
const response = await sendMessage({ action: 'getStatus' });
|
||||
|
||||
if (response.success) {
|
||||
state.mode = response.mode || 'public';
|
||||
state.connected = response.enabled;
|
||||
state.blocked = response.killSwitchActive || false;
|
||||
state.currentProxy = response.currentProxy;
|
||||
|
||||
// Update UI
|
||||
updateModeUI();
|
||||
updateConnectionUI();
|
||||
}
|
||||
}
|
||||
|
||||
async function loadProxyInfo() {
|
||||
console.log('[Popup] Loading proxy info...');
|
||||
const response = await sendMessage({ action: 'getProxyList' });
|
||||
console.log('[Popup] Proxy list response:', response);
|
||||
|
||||
if (response.success) {
|
||||
state.proxyCount = response.count || 0;
|
||||
updateProxyInfo();
|
||||
|
||||
// Show warning if no proxies available
|
||||
if (state.proxyCount === 0 && !state.connected && !state.connecting) {
|
||||
showNoProxiesWarning();
|
||||
}
|
||||
} else {
|
||||
console.log('[Popup] Failed to load proxy info:', response.error);
|
||||
elements.proxyCount.textContent = 'Error loading';
|
||||
showNoProxiesWarning();
|
||||
}
|
||||
}
|
||||
|
||||
function updateProxyInfo() {
|
||||
elements.proxyMode.textContent = 'Auto (Fastest)';
|
||||
elements.proxyCount.textContent = `${state.proxyCount} nodes`;
|
||||
}
|
||||
|
||||
function showNoProxiesWarning() {
|
||||
elements.statusText.textContent = 'No proxies';
|
||||
elements.statusText.style.color = 'var(--color-warning)';
|
||||
elements.statusIp.textContent = 'Click Refresh to load proxy list';
|
||||
elements.statusIp.style.display = 'block';
|
||||
elements.statusIp.style.color = 'var(--color-text-muted)';
|
||||
}
|
||||
|
||||
async function loadCustomProxy() {
|
||||
const response = await sendMessage({ action: 'getSettings' });
|
||||
|
||||
if (response.success && response.settings) {
|
||||
const settings = response.settings;
|
||||
if (settings.proxyIP) {
|
||||
elements.customIp.value = settings.proxyIP;
|
||||
}
|
||||
if (settings.proxyPort) {
|
||||
elements.customPort.value = settings.proxyPort;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Mode Switching
|
||||
// ============================================
|
||||
|
||||
function switchMode(mode) {
|
||||
state.mode = mode;
|
||||
|
||||
// Update tabs
|
||||
elements.modeTabs.forEach(tab => {
|
||||
tab.classList.toggle('active', tab.dataset.mode === mode);
|
||||
});
|
||||
|
||||
// Update content
|
||||
elements.modeContents.forEach(content => {
|
||||
content.classList.toggle('active', content.id === `mode-${mode}`);
|
||||
});
|
||||
|
||||
// Save mode
|
||||
sendMessage({ action: 'setMode', mode });
|
||||
}
|
||||
|
||||
function updateModeUI() {
|
||||
// Activate correct tab and content
|
||||
elements.modeTabs.forEach(tab => {
|
||||
tab.classList.toggle('active', tab.dataset.mode === state.mode);
|
||||
});
|
||||
|
||||
elements.modeContents.forEach(content => {
|
||||
content.classList.toggle('active', content.id === `mode-${state.mode}`);
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Next Proxy (Load Balancing)
|
||||
// ============================================
|
||||
|
||||
async function handleNextProxy() {
|
||||
if (!state.connected) {
|
||||
showError('Connect first to switch proxy');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!dappStoreButton) {
|
||||
dappStoreButton = document.createElement("button");
|
||||
dappStoreButton.id = "dappStoreButton";
|
||||
dappStoreButton.textContent = "dApp Store";
|
||||
dappStoreButton.style.marginBottom = "10px";
|
||||
dappStoreButton.addEventListener("click", () => {
|
||||
const storeUrl = chrome.runtime.getURL("store.html");
|
||||
chrome.tabs.create({ url: storeUrl });
|
||||
});
|
||||
// Show switching state (orange)
|
||||
state.connecting = true;
|
||||
updateConnectionUI();
|
||||
elements.statusText.textContent = 'Switching...';
|
||||
elements.statusText.style.color = 'var(--color-warning)';
|
||||
elements.statusIp.style.display = 'none';
|
||||
|
||||
elements.btnNextProxy.disabled = true;
|
||||
elements.btnNextProxy.textContent = 'Switching...';
|
||||
|
||||
const response = await sendMessage({ action: 'nextProxy' });
|
||||
|
||||
state.connecting = false;
|
||||
elements.statusText.style.color = '';
|
||||
|
||||
elements.btnNextProxy.disabled = false;
|
||||
elements.btnNextProxy.textContent = 'Next Proxy';
|
||||
|
||||
if (response.success) {
|
||||
state.currentProxy = response.proxy;
|
||||
showSuccess(`Switched to ${response.proxy?.host || 'next proxy'}`);
|
||||
updateConnectionUI();
|
||||
} else {
|
||||
showError(response.error || 'Failed to switch proxy');
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Connection
|
||||
// ============================================
|
||||
|
||||
async function handleConnect() {
|
||||
if (state.connecting) return;
|
||||
|
||||
if (state.connected) {
|
||||
await disconnect();
|
||||
} else {
|
||||
await connect();
|
||||
}
|
||||
}
|
||||
|
||||
async function connect() {
|
||||
state.connecting = true;
|
||||
updateConnectionUI();
|
||||
|
||||
// If already connected, disconnect first before switching modes
|
||||
if (state.connected) {
|
||||
await sendMessage({ action: 'disconnect' });
|
||||
state.connected = false;
|
||||
state.currentProxy = null;
|
||||
}
|
||||
|
||||
if (updateProxiesButton) {
|
||||
updateProxiesButton.addEventListener("click", () => {
|
||||
console.log("Update Proxies button clicked in popup.js");
|
||||
statusMessage.textContent = "Updating proxies...";
|
||||
statusMessage.style.color = "#f39c12"; // Orange for loading
|
||||
chrome.runtime.sendMessage({ action: "updateProxies" }, (response) => {
|
||||
if (response && response.success) {
|
||||
console.log('Proxy list update was successful');
|
||||
statusMessage.textContent = 'Proxies updated successfully!';
|
||||
statusMessage.style.color = "#2ecc71"; // Green for success
|
||||
} else {
|
||||
console.log('Proxy list update failed');
|
||||
statusMessage.textContent = 'Failed to update proxies.';
|
||||
statusMessage.style.color = "#e74c3c"; // Red for failure
|
||||
}
|
||||
// Clear the update message after 3 seconds and reinitialize the UI
|
||||
setTimeout(() => {
|
||||
initializeUI();
|
||||
}, 3000);
|
||||
});
|
||||
});
|
||||
const options = {};
|
||||
let customIp = null;
|
||||
let customPort = null;
|
||||
|
||||
if (state.mode === 'public') {
|
||||
options.country = 'auto';
|
||||
}
|
||||
|
||||
const buttonContainer = document.querySelector(".button-container");
|
||||
if (buttonContainer) {
|
||||
buttonContainer.innerHTML = '';
|
||||
buttonContainer.appendChild(optionsButton);
|
||||
buttonContainer.appendChild(dappStoreButton);
|
||||
buttonContainer.appendChild(updateProxiesButton);
|
||||
}
|
||||
// Save custom proxy before connecting if in custom mode
|
||||
if (state.mode === 'custom') {
|
||||
customIp = elements.customIp.value.trim();
|
||||
customPort = elements.customPort.value.trim();
|
||||
|
||||
if (statusMessage && !checkAnyoneButton.parentNode) {
|
||||
statusMessage.parentNode.insertBefore(checkAnyoneButton, statusMessage.nextSibling);
|
||||
}
|
||||
if (!customIp || !customPort) {
|
||||
state.connecting = false;
|
||||
showError('Please enter host and port');
|
||||
return;
|
||||
}
|
||||
|
||||
function updateStatusMessage(isEnabled, proxyType, proxy) {
|
||||
if (isEnabled) {
|
||||
if (proxyType === "custom") {
|
||||
statusMessage.textContent = `Custom Proxy is ENABLED and routing through ${proxy.host}:${proxy.port}`;
|
||||
statusMessage.style.color = "#2ecc71"; // Green for custom
|
||||
} else {
|
||||
statusMessage.textContent = `Public Proxy is ENABLED and routing through ${proxy.host}:${proxy.port}`;
|
||||
statusMessage.style.color = "#03bdc5"; // Blue for public
|
||||
// Validate host and port
|
||||
if (!Utils.isValidHost(customIp)) {
|
||||
state.connecting = false;
|
||||
showError('Invalid IP address or hostname');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Utils.isValidPort(customPort)) {
|
||||
state.connecting = false;
|
||||
showError('Invalid port (1-65535)');
|
||||
return;
|
||||
}
|
||||
|
||||
// Show testing state with IP:port
|
||||
elements.statusText.textContent = 'Connecting...';
|
||||
elements.statusText.style.color = 'var(--color-warning)';
|
||||
elements.statusIp.textContent = `${customIp}:${customPort}`;
|
||||
elements.statusIp.style.display = 'block';
|
||||
|
||||
// Save custom proxy settings
|
||||
await sendMessage({
|
||||
action: 'saveSettings',
|
||||
settings: {
|
||||
proxyIP: customIp,
|
||||
proxyPort: parseInt(customPort, 10)
|
||||
}
|
||||
} else {
|
||||
statusMessage.textContent = "Proxy is DISABLED";
|
||||
statusMessage.style.color = "#e74c3c";
|
||||
}
|
||||
}
|
||||
|
||||
function updateUI(isEnabled, proxyType, proxy) {
|
||||
proxyToggle.checked = isEnabled;
|
||||
updateStatusMessage(isEnabled, proxyType, proxy);
|
||||
}
|
||||
|
||||
function updateStatusFromBackground(data) {
|
||||
statusMessage.textContent = data.message;
|
||||
statusMessage.style.color = data.color;
|
||||
setTimeout(() => {
|
||||
statusMessage.textContent = "";
|
||||
initializeUI(); // Reinitialize the UI to show the current status
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// Initialize the toggle state
|
||||
function initializeUI() {
|
||||
chrome.storage.local.get(["proxyEnabled", "proxyType", "currentProxy", "proxyIP", "proxyPort"], (data) => {
|
||||
const isEnabled = data.proxyEnabled || false;
|
||||
const proxyType = data.proxyType || "public";
|
||||
const currentProxy = proxyType === "custom"
|
||||
? { host: data.proxyIP || "127.0.0.1", port: data.proxyPort || 9050 }
|
||||
: (data.currentProxy || { host: "82.208.21.140", port: 9052 }); // Default public Proxy
|
||||
|
||||
updateUI(isEnabled, proxyType, currentProxy);
|
||||
});
|
||||
}
|
||||
|
||||
initializeUI();
|
||||
const response = await sendMessage({
|
||||
action: 'connect',
|
||||
mode: state.mode,
|
||||
options
|
||||
});
|
||||
|
||||
proxyToggle.addEventListener("change", () => {
|
||||
const isEnabled = proxyToggle.checked;
|
||||
state.connecting = false;
|
||||
elements.statusText.style.color = '';
|
||||
|
||||
if (isEnabled) {
|
||||
chrome.storage.local.get(["proxyType"], (data) => {
|
||||
if (data.proxyType === "custom") {
|
||||
chrome.storage.local.get(["proxyIP", "proxyPort"], (settings) => {
|
||||
chrome.runtime.sendMessage({ action: "updateProxy", type: "custom", proxy: { host: settings.proxyIP, port: settings.proxyPort } }, (response) => {
|
||||
if (response && response.status === "enabled") {
|
||||
updateUI(true, "custom", { host: settings.proxyIP, port: settings.proxyPort });
|
||||
} else {
|
||||
alert("Failed to enable custom proxy. Please try again.");
|
||||
proxyToggle.checked = false;
|
||||
initializeUI();
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
chrome.runtime.sendMessage({ action: "enableProxy" }, (response) => {
|
||||
if (response && response.status === "enabled" && response.proxy) {
|
||||
chrome.storage.local.set({ proxyEnabled: true, currentProxy: response.proxy, proxyType: "public" });
|
||||
updateUI(true, "public", response.proxy);
|
||||
} else {
|
||||
alert(response.message || "Failed to enable public proxy. Please try again.");
|
||||
proxyToggle.checked = false;
|
||||
initializeUI();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
if (response.success) {
|
||||
state.connected = true;
|
||||
state.currentProxy = response.proxy;
|
||||
} else {
|
||||
if (state.mode === 'custom') {
|
||||
state.connected = false;
|
||||
state.currentProxy = null;
|
||||
showErrorPersistent(`Proxy ${customIp}:${customPort} is not responding`);
|
||||
return;
|
||||
}
|
||||
showError(response.error || 'Connection failed');
|
||||
}
|
||||
|
||||
updateConnectionUI();
|
||||
}
|
||||
|
||||
async function disconnect() {
|
||||
state.connecting = true;
|
||||
updateConnectionUI();
|
||||
|
||||
const response = await sendMessage({ action: 'disconnect' });
|
||||
|
||||
state.connecting = false;
|
||||
state.connected = !response.success;
|
||||
state.currentProxy = null;
|
||||
|
||||
updateConnectionUI();
|
||||
}
|
||||
|
||||
function updateConnectionUI() {
|
||||
const btn = elements.btnConnect;
|
||||
const btnText = elements.btnConnectText;
|
||||
const card = elements.statusCard;
|
||||
const dot = elements.statusDot;
|
||||
const statusText = elements.statusText;
|
||||
const statusIp = elements.statusIp;
|
||||
|
||||
// Remove all state classes
|
||||
btn.classList.remove('connected', 'connecting', 'error', 'blocked');
|
||||
card.classList.remove('connected', 'connecting', 'disconnected', 'error', 'blocked');
|
||||
dot.classList.remove('online', 'offline', 'connecting', 'error', 'blocked');
|
||||
|
||||
// Reset text styles
|
||||
statusText.style.color = '';
|
||||
statusIp.style.color = '';
|
||||
|
||||
if (state.connecting) {
|
||||
btn.classList.add('connecting');
|
||||
card.classList.add('connecting');
|
||||
dot.classList.add('connecting');
|
||||
statusText.textContent = 'Connecting...';
|
||||
statusIp.style.display = 'none';
|
||||
btn.disabled = true;
|
||||
btnText.disabled = true;
|
||||
btnText.textContent = 'Connecting...';
|
||||
} else if (state.blocked) {
|
||||
// Kill switch is active - traffic blocked
|
||||
btn.classList.add('blocked');
|
||||
card.classList.add('blocked');
|
||||
dot.classList.add('blocked');
|
||||
statusText.textContent = 'BLOCKED';
|
||||
statusText.style.color = 'var(--color-error)';
|
||||
statusIp.textContent = 'Kill Switch active - Connect to unblock';
|
||||
statusIp.style.display = 'block';
|
||||
statusIp.style.color = 'var(--color-warning)';
|
||||
btn.disabled = false;
|
||||
btnText.disabled = false;
|
||||
btnText.textContent = 'Connect';
|
||||
} else if (state.connected) {
|
||||
btn.classList.add('connected');
|
||||
card.classList.add('connected');
|
||||
dot.classList.add('online');
|
||||
statusText.textContent = 'Connected';
|
||||
btn.disabled = false;
|
||||
btnText.disabled = false;
|
||||
btnText.textContent = 'Disconnect';
|
||||
|
||||
// Show IP
|
||||
if (state.currentProxy) {
|
||||
statusIp.textContent = state.currentProxy.host || '-';
|
||||
statusIp.style.display = 'block';
|
||||
}
|
||||
} else {
|
||||
card.classList.add('disconnected');
|
||||
dot.classList.add('offline');
|
||||
statusText.textContent = 'Disconnected';
|
||||
statusIp.style.display = 'none';
|
||||
btn.disabled = false;
|
||||
btnText.disabled = false;
|
||||
btnText.textContent = 'Connect';
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Custom Proxy
|
||||
// ============================================
|
||||
|
||||
async function testCustomProxy() {
|
||||
const ip = elements.customIp.value.trim();
|
||||
const port = elements.customPort.value.trim();
|
||||
|
||||
if (!ip || !port) {
|
||||
showError('Please enter host and port');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate host and port
|
||||
if (!Utils.isValidHost(ip)) {
|
||||
showError('Invalid IP address or hostname');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Utils.isValidPort(port)) {
|
||||
showError('Invalid port (1-65535)');
|
||||
return;
|
||||
}
|
||||
|
||||
// Save previous state
|
||||
const previousConnected = state.connected;
|
||||
const previousProxy = state.currentProxy;
|
||||
|
||||
// Set connecting state (orange)
|
||||
state.connecting = true;
|
||||
updateConnectionUI();
|
||||
elements.statusText.textContent = 'Testing...';
|
||||
elements.statusText.style.color = 'var(--color-warning)';
|
||||
elements.statusIp.textContent = `${ip}:${port}`;
|
||||
elements.statusIp.style.display = 'block';
|
||||
|
||||
elements.btnTestCustom.disabled = true;
|
||||
elements.btnTestCustom.textContent = 'Testing...';
|
||||
|
||||
const response = await sendMessage({
|
||||
action: 'testProxy',
|
||||
proxy: { host: ip, port: parseInt(port, 10) }
|
||||
});
|
||||
|
||||
// Reset connecting state
|
||||
state.connecting = false;
|
||||
elements.statusText.style.color = '';
|
||||
|
||||
elements.btnTestCustom.disabled = false;
|
||||
elements.btnTestCustom.textContent = 'Test Connection';
|
||||
|
||||
if (response.working) {
|
||||
// Proxy is working, now actually connect to it
|
||||
showSuccess(`Proxy ${ip}:${port} is working!`);
|
||||
|
||||
// Save custom proxy settings and connect
|
||||
await sendMessage({
|
||||
action: 'saveSettings',
|
||||
settings: {
|
||||
proxyIP: ip,
|
||||
proxyPort: parseInt(port, 10)
|
||||
}
|
||||
});
|
||||
|
||||
// Actually connect through the proxy
|
||||
const connectResponse = await sendMessage({
|
||||
action: 'connect',
|
||||
mode: 'custom'
|
||||
});
|
||||
|
||||
if (connectResponse.success) {
|
||||
state.connected = true;
|
||||
state.currentProxy = connectResponse.proxy || { host: ip, port: parseInt(port, 10) };
|
||||
}
|
||||
updateConnectionUI();
|
||||
} else {
|
||||
// Stay in error state, don't restore
|
||||
state.connected = false;
|
||||
state.currentProxy = null;
|
||||
showErrorPersistent(`Proxy ${ip}:${port} is not responding`);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Utilities
|
||||
// ============================================
|
||||
|
||||
async function refreshProxies() {
|
||||
const refreshIcon = elements.btnRefresh.querySelector('.icon');
|
||||
if (refreshIcon) {
|
||||
refreshIcon.classList.add('spinning');
|
||||
}
|
||||
|
||||
// Get the stored proxy source setting
|
||||
const settings = await sendMessage({ action: 'getSettings' });
|
||||
const source = settings.settings?.proxySource || 'arweave';
|
||||
|
||||
const response = await sendMessage({ action: 'fetchProxies', source });
|
||||
|
||||
if (refreshIcon) {
|
||||
refreshIcon.classList.remove('spinning');
|
||||
}
|
||||
|
||||
if (response.success) {
|
||||
// Show which source was used (especially if fallback occurred)
|
||||
const sourceNames = { arweave: 'Arweave', git: 'GitBros', github: 'GitHub' };
|
||||
const usedName = sourceNames[response.usedSource] || response.usedSource;
|
||||
|
||||
if (response.usedSource && response.usedSource !== source) {
|
||||
showSuccess(`${response.proxies.length} proxies (fallback: ${usedName})`);
|
||||
} else {
|
||||
chrome.runtime.sendMessage({ action: "disableProxy" }, (response) => {
|
||||
if (response && response.status === "disabled") {
|
||||
chrome.storage.local.set({ proxyEnabled: false, proxyType: null });
|
||||
updateUI(false);
|
||||
} else {
|
||||
alert("Failed to disable proxy. Please try again.");
|
||||
proxyToggle.checked = true;
|
||||
initializeUI();
|
||||
}
|
||||
});
|
||||
showSuccess(`Updated: ${response.proxies.length} proxies`);
|
||||
}
|
||||
});
|
||||
await loadProxyInfo();
|
||||
} else {
|
||||
showError('All sources failed');
|
||||
}
|
||||
}
|
||||
|
||||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||
if (message.action === "updatePopupState" || message.action === "disableProxy") {
|
||||
initializeUI();
|
||||
} else if (message.action === "showLoadingMessage") {
|
||||
statusMessage.textContent = message.message;
|
||||
statusMessage.style.color = "#f39c12"; // Orange for loading
|
||||
} else if (message.action === "updateStatus") {
|
||||
updateStatusFromBackground(message);
|
||||
} else if (message.action === "toggleOff") {
|
||||
// This is for when a proxy setup fails
|
||||
proxyToggle.checked = false;
|
||||
statusMessage.textContent = "Failed to set up proxy. Please check your settings.";
|
||||
statusMessage.style.color = "#e74c3c"; // Red for error
|
||||
// You might want to clear this message after some time or on interaction
|
||||
setTimeout(() => {
|
||||
statusMessage.textContent = "";
|
||||
}, 5000); // Clear after 5 seconds
|
||||
}
|
||||
function sendMessage(message) {
|
||||
return new Promise((resolve) => {
|
||||
chrome.runtime.sendMessage(message, (response) => {
|
||||
resolve(response || { success: false, error: 'No response' });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Open options page
|
||||
optionsButton.addEventListener("click", () => {
|
||||
chrome.runtime.openOptionsPage();
|
||||
});
|
||||
function handleBackgroundMessage(message) {
|
||||
console.log('[Popup] Background message:', message);
|
||||
|
||||
// Open Check Anyone page
|
||||
checkAnyoneButton.addEventListener("click", () => {
|
||||
window.open("https://check.en.anyone.tech/", "_blank");
|
||||
});
|
||||
switch (message.action) {
|
||||
case 'statusUpdate':
|
||||
if (message.status === 'connected') {
|
||||
state.connected = true;
|
||||
state.connecting = false;
|
||||
state.blocked = false;
|
||||
state.currentProxy = message.proxy;
|
||||
updateConnectionUI();
|
||||
} else if (message.status === 'disconnected') {
|
||||
state.connected = false;
|
||||
state.connecting = false;
|
||||
state.blocked = false;
|
||||
state.currentProxy = null;
|
||||
updateConnectionUI();
|
||||
} else if (message.status === 'connecting') {
|
||||
state.connecting = true;
|
||||
state.blocked = false;
|
||||
updateConnectionUI();
|
||||
} else if (message.status === 'blocked') {
|
||||
state.connected = false;
|
||||
state.connecting = false;
|
||||
state.blocked = true;
|
||||
state.currentProxy = null;
|
||||
updateConnectionUI();
|
||||
} else if (message.status === 'error') {
|
||||
state.connected = false;
|
||||
state.connecting = false;
|
||||
state.currentProxy = null;
|
||||
// showError handles its own UI update, don't call updateConnectionUI
|
||||
showError(message.error || 'Connection error');
|
||||
}
|
||||
break;
|
||||
|
||||
// Open dApp Store page
|
||||
dappStoreButton.addEventListener("click", () => {
|
||||
const storeUrl = chrome.runtime.getURL("html/store.html");
|
||||
chrome.tabs.create({ url: storeUrl });
|
||||
});
|
||||
});
|
||||
case 'proxyError':
|
||||
showError(`Proxy error: ${message.error}`);
|
||||
break;
|
||||
|
||||
case 'proxiesUpdated':
|
||||
loadProxyInfo();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
console.log('[Popup] Error:', message);
|
||||
|
||||
// Reset state to disconnected
|
||||
state.connected = false;
|
||||
state.connecting = false;
|
||||
state.currentProxy = null;
|
||||
|
||||
// Update status card
|
||||
elements.statusCard.classList.remove('connected', 'connecting', 'disconnected');
|
||||
elements.statusCard.classList.add('error');
|
||||
|
||||
// "Error" in red
|
||||
elements.statusText.textContent = 'Error';
|
||||
elements.statusText.style.color = 'var(--color-error)';
|
||||
|
||||
// Message below in default color
|
||||
elements.statusIp.textContent = message;
|
||||
elements.statusIp.style.display = 'block';
|
||||
elements.statusIp.style.color = '';
|
||||
|
||||
// Update dot to error state
|
||||
elements.statusDot.classList.remove('online', 'offline', 'connecting');
|
||||
elements.statusDot.classList.add('error');
|
||||
|
||||
// Update button to error state
|
||||
elements.btnConnect.classList.remove('connected', 'connecting');
|
||||
elements.btnConnect.classList.add('error');
|
||||
elements.btnConnect.disabled = false;
|
||||
|
||||
// After 3 seconds, just reset the error styling but keep showing "Disconnected" state
|
||||
setTimeout(() => {
|
||||
elements.statusCard.classList.remove('error');
|
||||
elements.statusCard.classList.add('disconnected');
|
||||
elements.statusText.textContent = 'Disconnected';
|
||||
elements.statusText.style.color = '';
|
||||
elements.statusDot.classList.remove('error');
|
||||
elements.statusDot.classList.add('offline');
|
||||
elements.btnConnect.classList.remove('error');
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function showErrorPersistent(message) {
|
||||
console.log('[Popup] Error:', message);
|
||||
|
||||
// Update status card
|
||||
elements.statusCard.classList.remove('connected', 'connecting', 'disconnected');
|
||||
elements.statusCard.classList.add('error');
|
||||
|
||||
// "Error" in red
|
||||
elements.statusText.textContent = 'Error';
|
||||
elements.statusText.style.color = 'var(--color-error)';
|
||||
|
||||
// Message below in default color
|
||||
elements.statusIp.textContent = message;
|
||||
elements.statusIp.style.display = 'block';
|
||||
elements.statusIp.style.color = '';
|
||||
|
||||
// Update button
|
||||
elements.btnConnect.classList.remove('connected', 'connecting');
|
||||
elements.btnConnect.classList.add('error');
|
||||
elements.btnConnect.disabled = false;
|
||||
|
||||
// Return to disconnected state after 5 seconds
|
||||
setTimeout(() => {
|
||||
state.connected = false;
|
||||
state.currentProxy = null;
|
||||
elements.statusCard.classList.remove('error');
|
||||
elements.statusText.style.color = '';
|
||||
elements.statusDot.classList.remove('error');
|
||||
elements.btnConnect.classList.remove('error');
|
||||
updateConnectionUI();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
function showSuccess(message) {
|
||||
console.log('[Popup] Success:', message);
|
||||
|
||||
// "Success" in green
|
||||
elements.statusText.textContent = 'Success';
|
||||
elements.statusText.style.color = 'var(--color-success)';
|
||||
|
||||
// Message below in default color
|
||||
elements.statusIp.textContent = message;
|
||||
elements.statusIp.style.display = 'block';
|
||||
elements.statusIp.style.color = '';
|
||||
|
||||
setTimeout(() => {
|
||||
elements.statusText.style.color = '';
|
||||
updateConnectionUI();
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
432
js/proxy-manager.js
Normal file
@ -0,0 +1,432 @@
|
||||
/* ANyONe Extension v2 - Proxy Manager */
|
||||
|
||||
import { CONFIG } from './config.js';
|
||||
import { Utils } from './utils.js';
|
||||
import { Storage } from './storage.js';
|
||||
|
||||
const ProxyManager = {
|
||||
// Current state
|
||||
_currentProxy: null,
|
||||
_isEnabled: false,
|
||||
_proxyList: [],
|
||||
_currentIndex: 0,
|
||||
|
||||
/**
|
||||
* Initialize proxy manager
|
||||
*/
|
||||
async init() {
|
||||
Utils.log('info', 'Initializing ProxyManager');
|
||||
|
||||
// Load state from storage
|
||||
const [enabled, proxyList, currentProxy] = await Promise.all([
|
||||
Storage.isProxyEnabled(),
|
||||
Storage.getProxyList(),
|
||||
Storage.getCurrentProxy()
|
||||
]);
|
||||
|
||||
this._isEnabled = enabled;
|
||||
this._proxyList = proxyList;
|
||||
this._currentProxy = currentProxy;
|
||||
|
||||
Utils.log('info', 'ProxyManager initialized', {
|
||||
enabled,
|
||||
proxyCount: proxyList.length
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Create SOCKS5 proxy configuration
|
||||
* @param {string} host - Proxy host
|
||||
* @param {number} port - Proxy port
|
||||
* @param {string[]} exceptions - Bypass list
|
||||
* @param {boolean} bypassLocal - Allow local network access
|
||||
* @returns {object}
|
||||
*/
|
||||
createProxyConfig(host, port, exceptions = [], bypassLocal = true) {
|
||||
// Always include localhost and 127.0.0.1
|
||||
let bypassList = exceptions.concat(['localhost', '127.0.0.1']);
|
||||
|
||||
// Add local network ranges if local network access is enabled
|
||||
if (bypassLocal) {
|
||||
bypassList.push(
|
||||
'<local>', // Simple hostnames (no dots)
|
||||
'10.*', // Class A private network
|
||||
'172.16.*', // Class B private (172.16.0.0 - 172.31.255.255)
|
||||
'172.17.*',
|
||||
'172.18.*',
|
||||
'172.19.*',
|
||||
'172.20.*',
|
||||
'172.21.*',
|
||||
'172.22.*',
|
||||
'172.23.*',
|
||||
'172.24.*',
|
||||
'172.25.*',
|
||||
'172.26.*',
|
||||
'172.27.*',
|
||||
'172.28.*',
|
||||
'172.29.*',
|
||||
'172.30.*',
|
||||
'172.31.*',
|
||||
'192.168.*', // Class C private network
|
||||
'169.254.*', // Link-local
|
||||
'*.local' // mDNS/Bonjour domains (.local TLD)
|
||||
);
|
||||
}
|
||||
|
||||
Utils.log('info', 'Creating proxy config', { host, port, bypassList, bypassLocal });
|
||||
return {
|
||||
mode: 'fixed_servers',
|
||||
rules: {
|
||||
singleProxy: {
|
||||
scheme: 'socks5',
|
||||
host: host,
|
||||
port: parseInt(port, 10)
|
||||
},
|
||||
bypassList: bypassList
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Apply proxy settings
|
||||
* @param {string} host - Proxy host
|
||||
* @param {number} port - Proxy port
|
||||
* @param {string[]} exceptions - Bypass list
|
||||
* @param {boolean} bypassLocal - Allow local network access
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async applyProxy(host, port, exceptions = [], bypassLocal = true) {
|
||||
return new Promise((resolve) => {
|
||||
const config = this.createProxyConfig(host, port, exceptions, bypassLocal);
|
||||
|
||||
chrome.proxy.settings.set({ value: config, scope: 'regular' }, () => {
|
||||
if (chrome.runtime.lastError) {
|
||||
Utils.log('error', 'Failed to apply proxy', chrome.runtime.lastError);
|
||||
resolve(false);
|
||||
} else {
|
||||
Utils.log('info', `Proxy applied: ${host}:${port}`);
|
||||
this._currentProxy = { host, port };
|
||||
this._isEnabled = true;
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear proxy settings
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async clearProxy() {
|
||||
return new Promise((resolve) => {
|
||||
chrome.proxy.settings.clear({}, () => {
|
||||
if (chrome.runtime.lastError) {
|
||||
Utils.log('error', 'Failed to clear proxy', chrome.runtime.lastError);
|
||||
resolve(false);
|
||||
} else {
|
||||
Utils.log('info', 'Proxy cleared');
|
||||
this._currentProxy = null;
|
||||
this._isEnabled = false;
|
||||
this._currentIndex = 0;
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Test proxy connectivity
|
||||
* @param {object} proxy - Proxy object {host, port}
|
||||
* @returns {Promise<{working: boolean, latency: number|null}>}
|
||||
*/
|
||||
async testProxy(proxy) {
|
||||
Utils.log('debug', `Testing proxy ${proxy.host}:${proxy.port}`);
|
||||
|
||||
// First, set the proxy
|
||||
const config = this.createProxyConfig(proxy.host, proxy.port);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
chrome.proxy.settings.set({ value: config, scope: 'regular' }, async () => {
|
||||
if (chrome.runtime.lastError) {
|
||||
Utils.log('error', 'Failed to set proxy for test', chrome.runtime.lastError);
|
||||
resolve({ working: false, latency: null });
|
||||
return;
|
||||
}
|
||||
|
||||
// Longer delay to ensure proxy settings are applied
|
||||
await new Promise(r => setTimeout(r, 300));
|
||||
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), CONFIG.TIMEOUTS.PROXY_CHECK);
|
||||
|
||||
// Measure latency only for the actual request
|
||||
const startTime = Date.now();
|
||||
|
||||
// Use HEAD request with cache disabled for reliable test
|
||||
const response = await fetch(CONFIG.URLS.CHECK_IP, {
|
||||
method: 'HEAD',
|
||||
cache: 'no-store',
|
||||
signal: controller.signal
|
||||
});
|
||||
|
||||
const latency = Date.now() - startTime;
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (response.ok || response.type === 'opaque') {
|
||||
Utils.log('debug', `Proxy ${proxy.host}:${proxy.port} working, latency: ${latency}ms`);
|
||||
resolve({ working: true, latency });
|
||||
} else {
|
||||
Utils.log('debug', `Proxy ${proxy.host}:${proxy.port} returned ${response.status}`);
|
||||
// Clear proxy settings on failure
|
||||
chrome.proxy.settings.clear({});
|
||||
resolve({ working: false, latency: null });
|
||||
}
|
||||
} catch (error) {
|
||||
Utils.log('debug', `Proxy ${proxy.host}:${proxy.port} failed: ${error.name} - ${error.message}`);
|
||||
// Clear proxy settings on failure
|
||||
chrome.proxy.settings.clear({});
|
||||
resolve({ working: false, latency: null });
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Test multiple proxies in parallel
|
||||
* @param {object[]} proxies - Array of proxy objects
|
||||
* @param {number} concurrency - Max concurrent tests
|
||||
* @returns {Promise<object[]>}
|
||||
*/
|
||||
async testProxiesParallel(proxies, concurrency = 5) {
|
||||
const results = [];
|
||||
const chunks = [];
|
||||
|
||||
// Split into chunks
|
||||
for (let i = 0; i < proxies.length; i += concurrency) {
|
||||
chunks.push(proxies.slice(i, i + concurrency));
|
||||
}
|
||||
|
||||
// Process chunks sequentially, proxies within chunk in parallel
|
||||
for (const chunk of chunks) {
|
||||
const chunkResults = await Promise.all(
|
||||
chunk.map(async (proxy) => {
|
||||
const result = await this.testProxy(proxy);
|
||||
return { ...proxy, ...result };
|
||||
})
|
||||
);
|
||||
results.push(...chunkResults);
|
||||
}
|
||||
|
||||
return results;
|
||||
},
|
||||
|
||||
/**
|
||||
* Find and connect to fastest working proxy
|
||||
* @param {string} country - Country filter (optional)
|
||||
* @param {boolean} bypassLocal - Allow local network access
|
||||
* @param {string[]} exceptions - Bypass list
|
||||
* @returns {Promise<{success: boolean, proxy: object|null, error: string|null}>}
|
||||
*/
|
||||
async connectToFastest(country = null, bypassLocal = true, exceptions = []) {
|
||||
Utils.log('info', 'Finding fastest proxy', { country, exceptions });
|
||||
|
||||
let proxies = this._proxyList;
|
||||
|
||||
// Test proxies in parallel
|
||||
const results = await this.testProxiesParallel(proxies);
|
||||
|
||||
// Filter working proxies and sort by latency
|
||||
const working = results
|
||||
.filter(p => p.working)
|
||||
.sort((a, b) => a.latency - b.latency);
|
||||
|
||||
if (working.length === 0) {
|
||||
return { success: false, proxy: null, error: 'No working proxies found' };
|
||||
}
|
||||
|
||||
// Connect to fastest
|
||||
const fastest = working[0];
|
||||
const applied = await this.applyProxy(fastest.host, fastest.port, exceptions, bypassLocal);
|
||||
|
||||
if (applied) {
|
||||
await Storage.setCurrentProxy(fastest);
|
||||
await Storage.setProxyEnabled(true);
|
||||
return { success: true, proxy: fastest, error: null };
|
||||
}
|
||||
|
||||
return { success: false, proxy: null, error: 'Failed to apply proxy settings' };
|
||||
},
|
||||
|
||||
/**
|
||||
* Connect using custom proxy
|
||||
* @param {string} ip - Proxy IP
|
||||
* @param {number} port - Proxy port
|
||||
* @param {string[]} exceptions - Bypass list
|
||||
* @param {boolean} bypassLocal - Allow local network access
|
||||
* @returns {Promise<{success: boolean, error: string|null}>}
|
||||
*/
|
||||
async connectCustom(ip, port, exceptions = [], bypassLocal = true) {
|
||||
// Validate
|
||||
const validation = Utils.validateProxy(ip, port);
|
||||
if (!validation.valid) {
|
||||
return { success: false, error: validation.error };
|
||||
}
|
||||
|
||||
// Test connection
|
||||
const testResult = await this.testProxy({ host: ip, port });
|
||||
if (!testResult.working) {
|
||||
return { success: false, error: 'Proxy is not responding' };
|
||||
}
|
||||
|
||||
// Apply
|
||||
const applied = await this.applyProxy(ip, port, exceptions, bypassLocal);
|
||||
if (applied) {
|
||||
const proxy = { host: ip, port, type: 'custom' };
|
||||
// Get existing credentials to preserve them when updating storage
|
||||
const existingProxy = await Storage.getCustomProxy();
|
||||
await Storage.setCustomProxy(ip, port, exceptions, existingProxy.username, existingProxy.password);
|
||||
await Storage.setCurrentProxy(proxy);
|
||||
await Storage.setProxyEnabled(true);
|
||||
return { success: true, proxy, error: null };
|
||||
}
|
||||
|
||||
return { success: false, proxy: null, error: 'Failed to apply proxy settings' };
|
||||
},
|
||||
|
||||
/**
|
||||
* Disconnect proxy
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async disconnect() {
|
||||
const cleared = await this.clearProxy();
|
||||
if (cleared) {
|
||||
await Storage.setProxyEnabled(false);
|
||||
await Storage.setCurrentProxy(null);
|
||||
}
|
||||
return cleared;
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch proxy list from source with fallback
|
||||
* @param {string} source - Source key from CONFIG.PROXY_SOURCES
|
||||
* @returns {Promise<{success: boolean, proxies: object[], error: string|null, usedSource: string|null}>}
|
||||
*/
|
||||
async fetchProxyList(source = 'arweave') {
|
||||
// Define fallback order
|
||||
const fallbackOrder = ['arweave', 'git', 'github'];
|
||||
|
||||
// Start with the requested source, then try others
|
||||
const sourcesToTry = [source, ...fallbackOrder.filter(s => s !== source)];
|
||||
|
||||
for (const currentSource of sourcesToTry) {
|
||||
const sourceConfig = CONFIG.PROXY_SOURCES[currentSource];
|
||||
if (!sourceConfig) continue;
|
||||
|
||||
Utils.log('info', `Fetching proxies from ${currentSource}`);
|
||||
|
||||
try {
|
||||
const response = await Utils.fetchWithTimeout(
|
||||
sourceConfig.url,
|
||||
{},
|
||||
CONFIG.TIMEOUTS.FETCH
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Normalize data format
|
||||
let proxies = Array.isArray(data) ? data : data.proxies || [];
|
||||
|
||||
// Ensure each proxy has required fields
|
||||
proxies = proxies.map(p => ({
|
||||
host: p.host || p.ip,
|
||||
port: parseInt(p.port, 10),
|
||||
country: p.country || null,
|
||||
country_name: p.country_name || null,
|
||||
city: p.city || null,
|
||||
latency: null
|
||||
}));
|
||||
|
||||
this._proxyList = proxies;
|
||||
await Storage.setProxyList(proxies);
|
||||
|
||||
if (currentSource !== source) {
|
||||
Utils.log('info', `Fallback: fetched ${proxies.length} proxies from ${currentSource} (${source} failed)`);
|
||||
} else {
|
||||
Utils.log('info', `Fetched ${proxies.length} proxies from ${currentSource}`);
|
||||
}
|
||||
|
||||
return { success: true, proxies, error: null, usedSource: currentSource };
|
||||
|
||||
} catch (error) {
|
||||
Utils.log('warn', `Failed to fetch from ${currentSource}: ${error.message}`);
|
||||
// Continue to next source
|
||||
}
|
||||
}
|
||||
|
||||
// All sources failed
|
||||
Utils.log('error', 'All proxy sources failed');
|
||||
return { success: false, proxies: [], error: 'All proxy sources failed', usedSource: null };
|
||||
},
|
||||
|
||||
/**
|
||||
* Fallback to next proxy
|
||||
* @returns {Promise<{success: boolean, proxy: object|null, error: string|null}>}
|
||||
*/
|
||||
async fallbackToNext() {
|
||||
if (this._proxyList.length === 0) {
|
||||
return { success: false, proxy: null, error: 'No proxies available. Please refresh the proxy list.' };
|
||||
}
|
||||
|
||||
if (this._proxyList.length === 1) {
|
||||
return { success: false, proxy: null, error: 'Only one proxy available' };
|
||||
}
|
||||
|
||||
this._currentIndex = (this._currentIndex + 1) % this._proxyList.length;
|
||||
const proxy = this._proxyList[this._currentIndex];
|
||||
|
||||
Utils.log('info', `Switching to proxy ${this._currentIndex + 1}/${this._proxyList.length}: ${proxy.host}:${proxy.port}`);
|
||||
|
||||
// Test the proxy first to get latency
|
||||
const testResult = await this.testProxy(proxy);
|
||||
if (!testResult.working) {
|
||||
// Try next proxy if this one doesn't work
|
||||
Utils.log('warn', `Proxy ${proxy.host}:${proxy.port} not working, trying next...`);
|
||||
return this.fallbackToNext();
|
||||
}
|
||||
|
||||
// Merge test results (latency) with proxy info
|
||||
const proxyWithLatency = { ...proxy, latency: testResult.latency };
|
||||
|
||||
// Get bypass local setting and exceptions
|
||||
const bypassLocal = await Storage.getValue(CONFIG.STORAGE_KEYS.BYPASS_LOCAL, true);
|
||||
const exceptions = await Storage.getValue(CONFIG.STORAGE_KEYS.EXCEPTIONS, []);
|
||||
|
||||
const applied = await this.applyProxy(proxy.host, proxy.port, exceptions, bypassLocal);
|
||||
if (applied) {
|
||||
await Storage.setCurrentProxy(proxyWithLatency);
|
||||
return { success: true, proxy: proxyWithLatency, error: null };
|
||||
}
|
||||
|
||||
return { success: false, proxy: null, error: 'Failed to apply proxy settings' };
|
||||
},
|
||||
|
||||
/**
|
||||
* Get current status
|
||||
* @returns {object}
|
||||
*/
|
||||
getStatus() {
|
||||
return {
|
||||
enabled: this._isEnabled,
|
||||
currentProxy: this._currentProxy,
|
||||
proxyCount: this._proxyList.length
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// ES Module export
|
||||
export { ProxyManager };
|
||||
257
js/storage.js
Normal file
@ -0,0 +1,257 @@
|
||||
/* ANyONe Extension v2 - Storage Manager */
|
||||
|
||||
import { CONFIG } from './config.js';
|
||||
|
||||
const Storage = {
|
||||
/**
|
||||
* Get value from storage
|
||||
* @param {string|string[]} keys - Key(s) to retrieve
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
async get(keys) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
chrome.storage.local.get(keys, (result) => {
|
||||
if (chrome.runtime.lastError) {
|
||||
reject(chrome.runtime.lastError);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Set value in storage
|
||||
* @param {object} data - Data to store
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async set(data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
chrome.storage.local.set(data, () => {
|
||||
if (chrome.runtime.lastError) {
|
||||
reject(chrome.runtime.lastError);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove keys from storage
|
||||
* @param {string|string[]} keys - Key(s) to remove
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async remove(keys) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
chrome.storage.local.remove(keys, () => {
|
||||
if (chrome.runtime.lastError) {
|
||||
reject(chrome.runtime.lastError);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear all storage
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async clear() {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
chrome.storage.local.clear(() => {
|
||||
if (chrome.runtime.lastError) {
|
||||
reject(chrome.runtime.lastError);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get single value with default
|
||||
* @param {string} key - Key to retrieve
|
||||
* @param {*} defaultValue - Default value if not found
|
||||
* @returns {Promise<*>}
|
||||
*/
|
||||
async getValue(key, defaultValue = null) {
|
||||
const result = await this.get(key);
|
||||
return result[key] !== undefined ? result[key] : defaultValue;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set single value
|
||||
* @param {string} key - Key to set
|
||||
* @param {*} value - Value to store
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async setValue(key, value) {
|
||||
return this.set({ [key]: value });
|
||||
},
|
||||
|
||||
// ============================================
|
||||
// Convenience methods for common operations
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Get current connection mode
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async getMode() {
|
||||
return this.getValue(CONFIG.STORAGE_KEYS.MODE, CONFIG.DEFAULTS.MODE);
|
||||
},
|
||||
|
||||
/**
|
||||
* Set connection mode
|
||||
* @param {string} mode - Mode to set
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async setMode(mode) {
|
||||
return this.setValue(CONFIG.STORAGE_KEYS.MODE, mode);
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if proxy is enabled
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async isProxyEnabled() {
|
||||
return this.getValue(CONFIG.STORAGE_KEYS.PROXY_ENABLED, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Set proxy enabled state
|
||||
* @param {boolean} enabled - Enabled state
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async setProxyEnabled(enabled) {
|
||||
return this.setValue(CONFIG.STORAGE_KEYS.PROXY_ENABLED, enabled);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get proxy list
|
||||
* @returns {Promise<Array>}
|
||||
*/
|
||||
async getProxyList() {
|
||||
return this.getValue(CONFIG.STORAGE_KEYS.PROXY_LIST, []);
|
||||
},
|
||||
|
||||
/**
|
||||
* Set proxy list
|
||||
* @param {Array} list - Proxy list
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async setProxyList(list) {
|
||||
await this.set({
|
||||
[CONFIG.STORAGE_KEYS.PROXY_LIST]: list,
|
||||
[CONFIG.STORAGE_KEYS.LAST_UPDATE]: Date.now()
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get current proxy
|
||||
* @returns {Promise<object|null>}
|
||||
*/
|
||||
async getCurrentProxy() {
|
||||
return this.getValue(CONFIG.STORAGE_KEYS.CURRENT_PROXY, null);
|
||||
},
|
||||
|
||||
/**
|
||||
* Set current proxy
|
||||
* @param {object} proxy - Proxy object
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async setCurrentProxy(proxy) {
|
||||
return this.setValue(CONFIG.STORAGE_KEYS.CURRENT_PROXY, proxy);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get custom proxy settings
|
||||
* @returns {Promise<{ip: string, port: number, username: string, password: string, exceptions: string[]}>}
|
||||
*/
|
||||
async getCustomProxy() {
|
||||
const result = await this.get([
|
||||
CONFIG.STORAGE_KEYS.CUSTOM_IP,
|
||||
CONFIG.STORAGE_KEYS.CUSTOM_PORT,
|
||||
CONFIG.STORAGE_KEYS.CUSTOM_USERNAME,
|
||||
CONFIG.STORAGE_KEYS.CUSTOM_PASSWORD,
|
||||
CONFIG.STORAGE_KEYS.EXCEPTIONS
|
||||
]);
|
||||
return {
|
||||
ip: result[CONFIG.STORAGE_KEYS.CUSTOM_IP] || '',
|
||||
port: result[CONFIG.STORAGE_KEYS.CUSTOM_PORT] || '',
|
||||
username: result[CONFIG.STORAGE_KEYS.CUSTOM_USERNAME] || '',
|
||||
password: result[CONFIG.STORAGE_KEYS.CUSTOM_PASSWORD] || '',
|
||||
exceptions: result[CONFIG.STORAGE_KEYS.EXCEPTIONS] || []
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Set custom proxy settings
|
||||
* @param {string} ip - IP address
|
||||
* @param {number} port - Port number
|
||||
* @param {string[]} exceptions - Exception list
|
||||
* @param {string} username - Proxy username (optional)
|
||||
* @param {string} password - Proxy password (optional)
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async setCustomProxy(ip, port, exceptions = [], username = '', password = '') {
|
||||
return this.set({
|
||||
[CONFIG.STORAGE_KEYS.CUSTOM_IP]: ip,
|
||||
[CONFIG.STORAGE_KEYS.CUSTOM_PORT]: port,
|
||||
[CONFIG.STORAGE_KEYS.CUSTOM_USERNAME]: username,
|
||||
[CONFIG.STORAGE_KEYS.CUSTOM_PASSWORD]: password,
|
||||
[CONFIG.STORAGE_KEYS.EXCEPTIONS]: exceptions
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear custom proxy settings
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async clearCustomProxy() {
|
||||
return this.remove([
|
||||
CONFIG.STORAGE_KEYS.CUSTOM_IP,
|
||||
CONFIG.STORAGE_KEYS.CUSTOM_PORT,
|
||||
CONFIG.STORAGE_KEYS.CUSTOM_USERNAME,
|
||||
CONFIG.STORAGE_KEYS.CUSTOM_PASSWORD
|
||||
]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all settings for options page
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
async getAllSettings() {
|
||||
const keys = Object.values(CONFIG.STORAGE_KEYS);
|
||||
return this.get(keys);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get last proxy update timestamp
|
||||
* @returns {Promise<number|null>}
|
||||
*/
|
||||
async getLastUpdate() {
|
||||
return this.getValue(CONFIG.STORAGE_KEYS.LAST_UPDATE, null);
|
||||
}
|
||||
};
|
||||
|
||||
// ES Module export
|
||||
export { Storage };
|
||||
27
js/store.js
@ -1,27 +0,0 @@
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const comingSoonText = document.getElementById("comingSoon");
|
||||
const comingSoonImage = document.getElementById("fullScreenImage");
|
||||
|
||||
// Check if the domain is working
|
||||
fetch('https://dapps.debros.io', { method: 'HEAD', mode: 'no-cors' })
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
window.location.href = 'https://dapps.debros.io';
|
||||
} else {
|
||||
showComingSoonMessage();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
showComingSoonMessage();
|
||||
});
|
||||
|
||||
function showComingSoonMessage() {
|
||||
setTimeout(() => {
|
||||
comingSoonImage.style.opacity = 1;
|
||||
}, 200);
|
||||
|
||||
setTimeout(() => {
|
||||
comingSoonText.style.opacity = 1;
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
273
js/utils.js
Normal file
@ -0,0 +1,273 @@
|
||||
/* ANyONe Extension v2 - Utility Functions */
|
||||
|
||||
const Utils = {
|
||||
/**
|
||||
* Validate IP address format
|
||||
* @param {string} ip - IP address to validate
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isValidIP(ip) {
|
||||
if (!ip || typeof ip !== 'string') return false;
|
||||
const ipRegex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
||||
return ipRegex.test(ip.trim());
|
||||
},
|
||||
|
||||
/**
|
||||
* Validate hostname/domain format
|
||||
* @param {string} hostname - Hostname to validate
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isValidHostname(hostname) {
|
||||
if (!hostname || typeof hostname !== 'string') return false;
|
||||
const trimmed = hostname.trim();
|
||||
|
||||
// Must have at least one dot for a valid domain
|
||||
if (!trimmed.includes('.')) return false;
|
||||
|
||||
// Split by dots and validate each part
|
||||
const parts = trimmed.split('.');
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const part = parts[i];
|
||||
|
||||
// Each part must be 1-63 chars
|
||||
if (part.length === 0 || part.length > 63) return false;
|
||||
|
||||
// Only alphanumeric and hyphens allowed
|
||||
if (!/^[A-Za-z0-9-]+$/.test(part)) return false;
|
||||
|
||||
// Cannot start or end with hyphen
|
||||
if (part.startsWith('-') || part.endsWith('-')) return false;
|
||||
}
|
||||
|
||||
// TLD must be at least 2 letters (no numbers)
|
||||
const tld = parts[parts.length - 1];
|
||||
if (!/^[A-Za-z]{2,}$/.test(tld)) return false;
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Validate host (IP or hostname)
|
||||
* @param {string} host - Host to validate (IP or hostname)
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isValidHost(host) {
|
||||
return this.isValidIP(host) || this.isValidHostname(host);
|
||||
},
|
||||
|
||||
/**
|
||||
* Validate port number
|
||||
* @param {number|string} port - Port to validate
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isValidPort(port) {
|
||||
const portNum = parseInt(port, 10);
|
||||
return !isNaN(portNum) && portNum >= 1 && portNum <= 65535;
|
||||
},
|
||||
|
||||
/**
|
||||
* Validate proxy configuration
|
||||
* @param {string} host - IP address or hostname
|
||||
* @param {number|string} port - Port number
|
||||
* @returns {{valid: boolean, error?: string}}
|
||||
*/
|
||||
validateProxy(host, port) {
|
||||
if (!this.isValidHost(host)) {
|
||||
return { valid: false, error: 'Invalid IP address or hostname' };
|
||||
}
|
||||
if (!this.isValidPort(port)) {
|
||||
return { valid: false, error: 'Port must be between 1 and 65535' };
|
||||
}
|
||||
return { valid: true };
|
||||
},
|
||||
|
||||
/**
|
||||
* Format bytes to human readable size
|
||||
* @param {number} bytes - Bytes to format
|
||||
* @returns {string}
|
||||
*/
|
||||
formatBytes(bytes) {
|
||||
if (bytes === 0) return '0 B';
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
},
|
||||
|
||||
/**
|
||||
* Format duration in seconds to human readable
|
||||
* @param {number} seconds - Duration in seconds
|
||||
* @returns {string}
|
||||
*/
|
||||
formatDuration(seconds) {
|
||||
const hrs = Math.floor(seconds / 3600);
|
||||
const mins = Math.floor((seconds % 3600) / 60);
|
||||
const secs = seconds % 60;
|
||||
return [hrs, mins, secs]
|
||||
.map(v => v.toString().padStart(2, '0'))
|
||||
.join(':');
|
||||
},
|
||||
|
||||
/**
|
||||
* Format timestamp to relative time
|
||||
* @param {number} timestamp - Unix timestamp
|
||||
* @returns {string}
|
||||
*/
|
||||
formatRelativeTime(timestamp) {
|
||||
const now = Date.now();
|
||||
const diff = now - timestamp;
|
||||
const minutes = Math.floor(diff / 60000);
|
||||
const hours = Math.floor(diff / 3600000);
|
||||
const days = Math.floor(diff / 86400000);
|
||||
|
||||
if (minutes < 1) return 'Just now';
|
||||
if (minutes < 60) return `${minutes} minute${minutes > 1 ? 's' : ''} ago`;
|
||||
if (hours < 24) return `${hours} hour${hours > 1 ? 's' : ''} ago`;
|
||||
return `${days} day${days > 1 ? 's' : ''} ago`;
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse exceptions string to array
|
||||
* @param {string} exceptions - Comma or newline separated exceptions
|
||||
* @returns {string[]}
|
||||
*/
|
||||
parseExceptions(exceptions) {
|
||||
if (!exceptions || typeof exceptions !== 'string') return [];
|
||||
return exceptions
|
||||
.split(/[,\n]/)
|
||||
.map(e => e.trim())
|
||||
.filter(e => e.length > 0);
|
||||
},
|
||||
|
||||
/**
|
||||
* Format exceptions array to string
|
||||
* @param {string[]} exceptions - Array of exceptions
|
||||
* @returns {string}
|
||||
*/
|
||||
formatExceptions(exceptions) {
|
||||
if (!Array.isArray(exceptions)) return '';
|
||||
return exceptions.join(', ');
|
||||
},
|
||||
|
||||
/**
|
||||
* Debounce function calls
|
||||
* @param {Function} func - Function to debounce
|
||||
* @param {number} wait - Wait time in ms
|
||||
* @returns {Function}
|
||||
*/
|
||||
debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a promise that rejects after timeout
|
||||
* @param {number} ms - Timeout in milliseconds
|
||||
* @returns {Promise}
|
||||
*/
|
||||
timeout(ms) {
|
||||
return new Promise((_, reject) => {
|
||||
setTimeout(() => reject(new Error('Timeout')), ms);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetch with timeout
|
||||
* @param {string} url - URL to fetch
|
||||
* @param {object} options - Fetch options
|
||||
* @param {number} timeoutMs - Timeout in ms
|
||||
* @returns {Promise<Response>}
|
||||
*/
|
||||
async fetchWithTimeout(url, options = {}, timeoutMs = 10000) {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
signal: controller.signal
|
||||
});
|
||||
clearTimeout(timeoutId);
|
||||
return response;
|
||||
} catch (error) {
|
||||
clearTimeout(timeoutId);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate unique ID
|
||||
* @returns {string}
|
||||
*/
|
||||
generateId() {
|
||||
return Date.now().toString(36) + Math.random().toString(36).substr(2);
|
||||
},
|
||||
|
||||
/**
|
||||
* Safely parse JSON
|
||||
* @param {string} json - JSON string
|
||||
* @param {*} fallback - Fallback value on error
|
||||
* @returns {*}
|
||||
*/
|
||||
safeParseJSON(json, fallback = null) {
|
||||
try {
|
||||
return JSON.parse(json);
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Deep clone object
|
||||
* @param {*} obj - Object to clone
|
||||
* @returns {*}
|
||||
*/
|
||||
deepClone(obj) {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if running in extension context
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isExtensionContext() {
|
||||
return typeof chrome !== 'undefined' && chrome.runtime && chrome.runtime.id;
|
||||
},
|
||||
|
||||
/**
|
||||
* Log with prefix
|
||||
* @param {string} level - Log level
|
||||
* @param {string} message - Message
|
||||
* @param {*} data - Optional data
|
||||
*/
|
||||
log(level, message, data = null) {
|
||||
const prefix = '[ANyONe]';
|
||||
const timestamp = new Date().toISOString();
|
||||
const logMessage = `${prefix} ${timestamp} [${level.toUpperCase()}] ${message}`;
|
||||
|
||||
switch (level) {
|
||||
case 'error':
|
||||
console.log(logMessage, data || '');
|
||||
break;
|
||||
case 'warn':
|
||||
console.warn(logMessage, data || '');
|
||||
break;
|
||||
case 'debug':
|
||||
console.debug(logMessage, data || '');
|
||||
break;
|
||||
default:
|
||||
console.log(logMessage, data || '');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ES Module export
|
||||
export { Utils };
|
||||
@ -1,21 +1,30 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "ANyONe Extension",
|
||||
"version": "1.0.5",
|
||||
"description": "Manage Socks5 proxy settings",
|
||||
"version": "2.0.1",
|
||||
"description": "Privacy-focused Socks5 proxy management",
|
||||
"permissions": [
|
||||
"proxy",
|
||||
"storage",
|
||||
"tabs",
|
||||
"scripting"
|
||||
"scripting",
|
||||
"privacy",
|
||||
"webRequest",
|
||||
"webRequestAuthProvider"
|
||||
],
|
||||
"host_permissions": ["<all_urls>"],
|
||||
"background": {
|
||||
"service_worker": "js/background.js"
|
||||
"service_worker": "js/background.js",
|
||||
"type": "module"
|
||||
},
|
||||
"action": {
|
||||
"default_popup": "html/popup.html",
|
||||
"default_title": "Proxy Settings"
|
||||
"default_title": "ANyONe - Privacy Extension",
|
||||
"default_icon": {
|
||||
"16": "icons/icon16.png",
|
||||
"48": "icons/icon48.png",
|
||||
"128": "icons/icon128.png"
|
||||
}
|
||||
},
|
||||
"options_ui": {
|
||||
"page": "html/options.html",
|
||||
|
||||