Compare commits
9
Commits
2f5568fb37
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b6f51a3e5 | ||
|
|
b8046e7b9a | ||
|
|
41456c5c0f | ||
|
|
dd50029bc5 | ||
|
|
4dd5f48e3b | ||
|
|
9e2ef513a0 | ||
|
|
319bcdefbd | ||
|
|
07a0f2c1e3 | ||
|
|
e88fb193df |
No files matched your search
@@ -9,7 +9,7 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
version:
|
version:
|
||||||
description: "Version to validate (for example 1.0.1)"
|
description: "Version to validate (for example 20261009.1)"
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ jobs:
|
|||||||
actual = json.load(open(path, encoding="utf-8"))["version"]
|
actual = json.load(open(path, encoding="utf-8"))["version"]
|
||||||
if actual != expected:
|
if actual != expected:
|
||||||
raise SystemExit(f"{path}: version {actual!r} does not match release {expected!r}")
|
raise SystemExit(f"{path}: version {actual!r} does not match release {expected!r}")
|
||||||
if not re.fullmatch(r"\d+(\.\d+){1,3}", expected):
|
if not re.fullmatch(r"\d+(\.\d+){0,3}", expected):
|
||||||
raise SystemExit(f"Release version must be numeric, got {expected!r}")
|
raise SystemExit(f"Release version must be numeric, got {expected!r}")
|
||||||
PY
|
PY
|
||||||
|
|
||||||
@@ -57,17 +57,24 @@ jobs:
|
|||||||
|
|
||||||
- name: Prepare Firefox source
|
- name: Prepare Firefox source
|
||||||
run: |
|
run: |
|
||||||
mkdir -p dist/firefox-source
|
python - <<'PY'
|
||||||
rsync -a \
|
from pathlib import Path
|
||||||
--exclude '.git/' \
|
import shutil
|
||||||
--exclude '.github/' \
|
source = Path('.')
|
||||||
--exclude 'web-ext-artifacts/' \
|
target = Path('dist/firefox-source')
|
||||||
--exclude 'manifest.chrome.json' \
|
excluded = {'.git', '.github', 'dist', 'web-ext-artifacts', 'scripts',
|
||||||
--exclude 'background-chrome.js' \
|
'manifest.chrome.json', 'background-chrome.js',
|
||||||
--exclude 'background-service-worker.js' \
|
'background-service-worker.js', 'browser-api.js'}
|
||||||
--exclude 'browser-api.js' \
|
target.mkdir(parents=True, exist_ok=True)
|
||||||
--exclude 'scripts/' \
|
for item in source.iterdir():
|
||||||
./ dist/firefox-source/
|
if item.name in excluded:
|
||||||
|
continue
|
||||||
|
destination = target / item.name
|
||||||
|
if item.is_dir():
|
||||||
|
shutil.copytree(item, destination, dirs_exist_ok=True)
|
||||||
|
else:
|
||||||
|
shutil.copy2(item, destination)
|
||||||
|
PY
|
||||||
|
|
||||||
- name: Build Firefox package
|
- name: Build Firefox package
|
||||||
run: |
|
run: |
|
||||||
@@ -110,6 +117,26 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
ref: ${{ github.event.release.tag_name }}
|
ref: ${{ github.event.release.tag_name }}
|
||||||
|
|
||||||
|
- name: Check store credentials
|
||||||
|
id: credentials
|
||||||
|
run: |
|
||||||
|
python - <<'PY'
|
||||||
|
import os
|
||||||
|
stores = {
|
||||||
|
'firefox': ('AMO_JWT_ISSUER', 'AMO_JWT_SECRET'),
|
||||||
|
'chrome': ('CWS_PUBLISHER_ID', 'CWS_EXTENSION_ID', 'CWS_CLIENT_ID',
|
||||||
|
'CWS_CLIENT_SECRET', 'CWS_REFRESH_TOKEN'),
|
||||||
|
}
|
||||||
|
with open(os.environ['GITHUB_OUTPUT'], 'a') as output:
|
||||||
|
for store, names in stores.items():
|
||||||
|
missing = [name for name in names if not os.environ.get(name, '').strip()]
|
||||||
|
output.write(f"{store}={'false' if missing else 'true'}\n")
|
||||||
|
if missing:
|
||||||
|
print(f"Skipping {store} deployment: missing credentials: {', '.join(missing)}")
|
||||||
|
else:
|
||||||
|
print(f"{store} deployment enabled")
|
||||||
|
PY
|
||||||
|
|
||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
@@ -122,21 +149,30 @@ jobs:
|
|||||||
path: dist/packages
|
path: dist/packages
|
||||||
|
|
||||||
- name: Set up Firefox signing tools
|
- name: Set up Firefox signing tools
|
||||||
|
if: steps.credentials.outputs.firefox == 'true'
|
||||||
run: |
|
run: |
|
||||||
npm install --global web-ext
|
npm install --global web-ext
|
||||||
mkdir -p dist/firefox-source
|
python - <<'PY'
|
||||||
rsync -a \
|
from pathlib import Path
|
||||||
--exclude '.git/' \
|
import shutil
|
||||||
--exclude '.github/' \
|
source = Path('.')
|
||||||
--exclude 'web-ext-artifacts/' \
|
target = Path('dist/firefox-source')
|
||||||
--exclude 'manifest.chrome.json' \
|
excluded = {'.git', '.github', 'dist', 'web-ext-artifacts', 'scripts',
|
||||||
--exclude 'background-chrome.js' \
|
'manifest.chrome.json', 'background-chrome.js',
|
||||||
--exclude 'background-service-worker.js' \
|
'background-service-worker.js', 'browser-api.js'}
|
||||||
--exclude 'browser-api.js' \
|
target.mkdir(parents=True, exist_ok=True)
|
||||||
--exclude 'scripts/' \
|
for item in source.iterdir():
|
||||||
./ dist/firefox-source/
|
if item.name in excluded:
|
||||||
|
continue
|
||||||
|
destination = target / item.name
|
||||||
|
if item.is_dir():
|
||||||
|
shutil.copytree(item, destination, dirs_exist_ok=True)
|
||||||
|
else:
|
||||||
|
shutil.copy2(item, destination)
|
||||||
|
PY
|
||||||
|
|
||||||
- name: Sign and submit Firefox package to AMO
|
- name: Sign and submit Firefox package to AMO
|
||||||
|
if: steps.credentials.outputs.firefox == 'true'
|
||||||
run: |
|
run: |
|
||||||
test -n "$AMO_JWT_ISSUER" || { echo 'Missing AMO_JWT_ISSUER secret'; exit 1; }
|
test -n "$AMO_JWT_ISSUER" || { echo 'Missing AMO_JWT_ISSUER secret'; exit 1; }
|
||||||
test -n "$AMO_JWT_SECRET" || { echo 'Missing AMO_JWT_SECRET secret'; exit 1; }
|
test -n "$AMO_JWT_SECRET" || { echo 'Missing AMO_JWT_SECRET secret'; exit 1; }
|
||||||
@@ -145,6 +181,7 @@ jobs:
|
|||||||
--artifacts-dir dist/firefox-signed \
|
--artifacts-dir dist/firefox-signed \
|
||||||
--api-key "$AMO_JWT_ISSUER" \
|
--api-key "$AMO_JWT_ISSUER" \
|
||||||
--api-secret "$AMO_JWT_SECRET" \
|
--api-secret "$AMO_JWT_SECRET" \
|
||||||
|
--amo-metadata amo-metadata.json \
|
||||||
--channel listed \
|
--channel listed \
|
||||||
--approval-timeout 900000 \
|
--approval-timeout 900000 \
|
||||||
--no-input
|
--no-input
|
||||||
@@ -153,6 +190,7 @@ jobs:
|
|||||||
cp "$firefox_xpi" dist/packages/proxyswitcherforkbywt-firefox-signed.xpi
|
cp "$firefox_xpi" dist/packages/proxyswitcherforkbywt-firefox-signed.xpi
|
||||||
|
|
||||||
- name: Upload Chrome package to Chrome Web Store
|
- name: Upload Chrome package to Chrome Web Store
|
||||||
|
if: steps.credentials.outputs.chrome == 'true'
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
for name in CWS_PUBLISHER_ID CWS_EXTENSION_ID CWS_CLIENT_ID CWS_CLIENT_SECRET CWS_REFRESH_TOKEN; do
|
for name in CWS_PUBLISHER_ID CWS_EXTENSION_ID CWS_CLIENT_ID CWS_CLIENT_SECRET CWS_REFRESH_TOKEN; do
|
||||||
@@ -177,20 +215,31 @@ jobs:
|
|||||||
-H "Authorization: Bearer $token" \
|
-H "Authorization: Bearer $token" \
|
||||||
"https://chromewebstore.googleapis.com/v2/publishers/$CWS_PUBLISHER_ID/items/$CWS_EXTENSION_ID:publish"
|
"https://chromewebstore.googleapis.com/v2/publishers/$CWS_PUBLISHER_ID/items/$CWS_EXTENSION_ID:publish"
|
||||||
|
|
||||||
- name: Attach signed Firefox and Chrome packages to release
|
- name: Prepare release assets
|
||||||
|
run: |
|
||||||
|
mkdir -p dist/release-assets
|
||||||
|
chrome_zip=$(find dist/packages -name 'proxyswitcherforkbywt-chrome.zip' -print -quit)
|
||||||
|
test -f "$chrome_zip"
|
||||||
|
cp "$chrome_zip" dist/release-assets/
|
||||||
|
firefox_xpi=dist/packages/proxyswitcherforkbywt-firefox-signed.xpi
|
||||||
|
if [ ! -f "$firefox_xpi" ]; then
|
||||||
|
firefox_xpi=$(find dist/packages -name 'proxyswitcherforkbywt-firefox.xpi' -print -quit)
|
||||||
|
fi
|
||||||
|
test -f "$firefox_xpi"
|
||||||
|
cp "$firefox_xpi" dist/release-assets/
|
||||||
|
|
||||||
|
- name: Attach Firefox and Chrome packages to release
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
RELEASE_TAG: ${{ github.event.release.tag_name }}
|
||||||
run: |
|
run: |
|
||||||
gh release upload "${{ github.event.release.tag_name }}" \
|
gh release upload "$RELEASE_TAG" \
|
||||||
dist/packages/proxyswitcherforkbywt-firefox-signed.xpi \
|
dist/release-assets/* \
|
||||||
"$chrome_zip" \
|
|
||||||
--clobber
|
--clobber
|
||||||
|
|
||||||
- name: Upload signed packages as workflow artifacts
|
- name: Upload release packages as workflow artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: signed-extension-packages-${{ github.event.release.tag_name }}
|
name: release-extension-packages-${{ github.event.release.tag_name }}
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
path: |
|
path: dist/release-assets/*
|
||||||
dist/packages/proxyswitcherforkbywt-firefox-signed.xpi
|
|
||||||
dist/packages/**/proxyswitcherforkbywt-chrome.zip
|
|
||||||
@@ -22,7 +22,7 @@ This repository contains the Firefox WebExtension **Proxy Switcher Fork By WT**.
|
|||||||
|
|
||||||
- Keep all configuration local. Do not add telemetry, remote requests, analytics, or external script dependencies.
|
- Keep all configuration local. Do not add telemetry, remote requests, analytics, or external script dependencies.
|
||||||
- Preserve the `browser.*` WebExtension API style in shared UI code. Firefox uses Manifest V2 and `proxy.onRequest`; Chromium uses Manifest V3, `chrome.proxy.settings`, and a PAC script.
|
- Preserve the `browser.*` WebExtension API style in shared UI code. Firefox uses Manifest V2 and `proxy.onRequest`; Chromium uses Manifest V3, `chrome.proxy.settings`, and a PAC script.
|
||||||
- Keep the Firefox Android minimum version at 113 or newer when using the `gecko_android` manifest key.
|
- Keep the Firefox minimum at 140 and Firefox Android minimum at 142 when using built-in `data_collection_permissions` consent.
|
||||||
- Keep the Firefox and Chromium manifests as separate build targets. Do not replace the Firefox proxy listener with the Chrome PAC implementation.
|
- Keep the Firefox and Chromium manifests as separate build targets. Do not replace the Firefox proxy listener with the Chrome PAC implementation.
|
||||||
- Proxy settings are domain-specific. A missing domain entry means direct connection; an entry with `enabled: false` is configured but disabled.
|
- Proxy settings are domain-specific. A missing domain entry means direct connection; an entry with `enabled: false` is configured but disabled.
|
||||||
- Keep popup and options theme behavior consistent. Supported theme values are `system`, `light`, and `dark`.
|
- Keep popup and options theme behavior consistent. Supported theme values are `system`, `light`, and `dark`.
|
||||||
@@ -30,6 +30,8 @@ This repository contains the Firefox WebExtension **Proxy Switcher Fork By WT**.
|
|||||||
- Never hand-edit or retain stale `META-INF` signatures after changing files. Build artifacts must be signed again before distribution.
|
- Never hand-edit or retain stale `META-INF` signatures after changing files. Build artifacts must be signed again before distribution.
|
||||||
- Do not commit generated `web-ext-artifacts/` packages unless explicitly requested.
|
- Do not commit generated `web-ext-artifacts/` packages unless explicitly requested.
|
||||||
- Keep publishing credentials only in GitHub Secrets. The release workflow expects AMO JWT credentials and Chrome Web Store OAuth credentials.
|
- Keep publishing credentials only in GitHub Secrets. The release workflow expects AMO JWT credentials and Chrome Web Store OAuth credentials.
|
||||||
|
- Keep `amo-metadata.json` valid for AMO listed submissions; keep `version.license` consistent with the MIT `LICENSE` file.
|
||||||
|
- Missing or incomplete store credentials must skip only that store's deployment. Keep package builds, release assets, and workflow artifacts available without store credentials.
|
||||||
|
|
||||||
## Validation
|
## Validation
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 WT
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -48,7 +48,7 @@ The manifest requests these permissions:
|
|||||||
- `webRequest` and `webRequestBlocking` — provide proxy authentication credentials.
|
- `webRequest` and `webRequestBlocking` — provide proxy authentication credentials.
|
||||||
- `<all_urls>` — apply the domain routing policy to web requests.
|
- `<all_urls>` — apply the domain routing policy to web requests.
|
||||||
|
|
||||||
These permissions are used for proxy management. The extension does not collect or transmit data.
|
These permissions are used for proxy management. The manifest declares `data_collection_permissions.required` as `none`; the extension does not collect or transmit data.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ For a normal install, use an XPI signed by Mozilla/AMO. Firefox may reject an un
|
|||||||
|
|
||||||
Firefox Android requires a signed extension package. Submit the build through [addons.mozilla.org/developers](https://addons.mozilla.org/developers/) and download the signed XPI after review, or follow Mozilla's [self-distributed extension instructions](https://extensionworkshop.com/documentation/publish/install-self-distributed/). The manifest includes `browser_specific_settings.gecko_android` so AMO can mark the extension as Android-compatible.
|
Firefox Android requires a signed extension package. Submit the build through [addons.mozilla.org/developers](https://addons.mozilla.org/developers/) and download the signed XPI after review, or follow Mozilla's [self-distributed extension instructions](https://extensionworkshop.com/documentation/publish/install-self-distributed/). The manifest includes `browser_specific_settings.gecko_android` so AMO can mark the extension as Android-compatible.
|
||||||
|
|
||||||
The manifest targets Firefox Android 113 or newer. The `proxy.onRequest` API is supported there, but the extension still needs to be tested on the target Firefox version and device.
|
The manifest targets Firefox 140+ on desktop and Firefox Android 142+. The `proxy.onRequest` API is supported there, but the extension still needs to be tested on the target Firefox version and device.
|
||||||
|
|
||||||
### Chrome and Chromium desktop
|
### Chrome and Chromium desktop
|
||||||
|
|
||||||
@@ -104,7 +104,11 @@ Configure these repository or environment secrets before publishing:
|
|||||||
- `CWS_PUBLISHER_ID` and `CWS_EXTENSION_ID` — Chrome Web Store publisher and existing item IDs.
|
- `CWS_PUBLISHER_ID` and `CWS_EXTENSION_ID` — Chrome Web Store publisher and existing item IDs.
|
||||||
- `CWS_CLIENT_ID`, `CWS_CLIENT_SECRET`, and `CWS_REFRESH_TOKEN` — Google OAuth credentials with Chrome Web Store API access.
|
- `CWS_CLIENT_ID`, `CWS_CLIENT_SECRET`, and `CWS_REFRESH_TOKEN` — Google OAuth credentials with Chrome Web Store API access.
|
||||||
|
|
||||||
The stores must already contain their initial listings and required privacy information. Store review still applies; the workflow submits the update and does not bypass review. Never commit any of these credentials.
|
Each store is optional: deployment runs only when its full set of credentials is present. Missing or incomplete credentials skip that store without failing the workflow or skipping the other store. Both packages are still built and attached to the release. If Firefox deployment is skipped, the release contains the unsigned Firefox XPI for development instead of a signed XPI. Invalid credentials or actual store API failures still fail the deployment step.
|
||||||
|
|
||||||
|
AMO can create the initial listing automatically from `amo-metadata.json` through `web-ext sign --channel listed`; manually uploading the first Firefox version is not required. Chrome Web Store requires an existing item and completed listing/privacy information. Store review still applies; the workflow submits the update and does not bypass review. Never commit any of these credentials.
|
||||||
|
|
||||||
|
The Firefox workflow passes [`amo-metadata.json`](amo-metadata.json) to AMO. It declares the MIT license, categories, and listing metadata for a listed submission.
|
||||||
|
|
||||||
To remove generated packages:
|
To remove generated packages:
|
||||||
|
|
||||||
@@ -124,4 +128,8 @@ python -m json.tool manifest.json >/dev/null
|
|||||||
|
|
||||||
## Project version
|
## Project version
|
||||||
|
|
||||||
Current version: **1.0.1**
|
Current version: **20261009.1**
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Distributed under the [MIT License](LICENSE).
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"version": {
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"default_locale": "en-US",
|
||||||
|
"name": {
|
||||||
|
"en-US": "Proxy Switcher Fork By WT"
|
||||||
|
},
|
||||||
|
"categories": {
|
||||||
|
"firefox": ["other"],
|
||||||
|
"android": ["other"]
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"en-US": "Route selected domains through HTTP or SOCKS5 proxy servers."
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "Proxy Switcher Fork By WT",
|
"name": "Proxy Switcher Fork By WT",
|
||||||
"version": "1.0.1",
|
"version": "20261009.1",
|
||||||
"description": "Selectively apply proxy servers to specific domains.",
|
"description": "Selectively apply proxy servers to specific domains.",
|
||||||
"minimum_chrome_version": "99",
|
"minimum_chrome_version": "99",
|
||||||
"icons": {
|
"icons": {
|
||||||
|
|||||||
+7
-4
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 2,
|
"manifest_version": 2,
|
||||||
"name": "Proxy Switcher Fork By WT",
|
"name": "Proxy Switcher Fork By WT",
|
||||||
"version": "1.0.1",
|
"version": "20261009.1",
|
||||||
"description": "Selectively apply proxy servers to specific domains. All settings are stored locally and no data is collected or transmitted externally.",
|
"description": "Selectively apply proxy servers to specific domains. All settings are stored locally and no data is collected or transmitted externally.",
|
||||||
"icons": {
|
"icons": {
|
||||||
"16": "icons/proxy-switcher-16.png",
|
"16": "icons/proxy-switcher-16.png",
|
||||||
@@ -35,11 +35,14 @@
|
|||||||
},
|
},
|
||||||
"browser_specific_settings": {
|
"browser_specific_settings": {
|
||||||
"gecko": {
|
"gecko": {
|
||||||
"id": "proxy-switcher-for-by-wt@9c0b2d3d",
|
"id": "proxy-switcher-fork-by-wt@9c0b2d3d",
|
||||||
"strict_min_version": "91.1.0"
|
"strict_min_version": "140.0",
|
||||||
|
"data_collection_permissions": {
|
||||||
|
"required": ["none"]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"gecko_android": {
|
"gecko_android": {
|
||||||
"strict_min_version": "113.0"
|
"strict_min_version": "142.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+326
-224
@@ -1,249 +1,351 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Proxy Switcher - Options</title>
|
<title>Proxy Switcher - Options</title>
|
||||||
<link rel="stylesheet" href="options.css">
|
<link rel="stylesheet" href="options.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Modal Overlay -->
|
<!-- Modal Overlay -->
|
||||||
<div id="modal-overlay" class="modal-overlay hidden"></div>
|
<div id="modal-overlay" class="modal-overlay hidden"></div>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<header>
|
<header>
|
||||||
<h1><img src="icons/proxy-switcher-64.png" alt="Proxy Switcher"> Proxy Switcher</h1>
|
<h1>
|
||||||
<p class="subtitle">Manage domain-specific proxy settings</p>
|
<img
|
||||||
</header>
|
src="icons/proxy-switcher-64.png"
|
||||||
|
alt="Proxy Switcher"
|
||||||
|
/>
|
||||||
|
Proxy Switcher
|
||||||
|
</h1>
|
||||||
|
<p class="subtitle">Manage domain-specific proxy settings</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<section class="profiles-section">
|
<section class="profiles-section">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h2>Proxy Profiles</h2>
|
<h2>Proxy Profiles</h2>
|
||||||
<button id="add-profile" class="btn btn-primary">
|
<button id="add-profile" class="btn btn-primary">
|
||||||
<span class="icon">+</span> Add Profile
|
<span class="icon">+</span> Add Profile
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="profiles-list" class="profiles-list">
|
|
||||||
<!-- Dynamically populated -->
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="domains-section">
|
|
||||||
<div class="section-header">
|
|
||||||
<h2>Configured Domains</h2>
|
|
||||||
<button id="add-domain" class="btn btn-primary">
|
|
||||||
<span class="icon">+</span> Add Domain
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="search-controls">
|
|
||||||
<div class="search-group">
|
|
||||||
<input type="text" id="domain-search" placeholder="Search domains...">
|
|
||||||
<select id="tag-filter">
|
|
||||||
<option value="">All tags</option>
|
|
||||||
<!-- Tags will be added dynamically -->
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="domains-list" class="domains-list">
|
|
||||||
<!-- Dynamically populated -->
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section id="domain-form-section" class="domain-form-section hidden">
|
|
||||||
<h2 id="form-title">Add Domain</h2>
|
|
||||||
|
|
||||||
<form id="domain-form" class="domain-form">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="domain-input">Domain *</label>
|
|
||||||
<input type="text" id="domain-input"
|
|
||||||
placeholder="example.com"
|
|
||||||
pattern="^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$"
|
|
||||||
required>
|
|
||||||
<small class="form-hint">Enter domain without protocol (e.g., example.com)</small>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div id="profiles-list" class="profiles-list">
|
||||||
<label for="proxy-type">Proxy Type *</label>
|
<!-- Dynamically populated -->
|
||||||
<select id="proxy-type" required>
|
</div>
|
||||||
<option value="http">HTTP</option>
|
</section>
|
||||||
<option value="socks">SOCKS5</option>
|
|
||||||
</select>
|
<section class="domains-section">
|
||||||
|
<div class="section-header">
|
||||||
|
<h2>Configured Domains</h2>
|
||||||
|
<button id="add-domain" class="btn btn-primary">
|
||||||
|
<span class="icon">+</span> Add Domain
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="search-controls">
|
||||||
<label for="profile-select">Use Profile (optional)</label>
|
<div class="search-group">
|
||||||
<select id="profile-select">
|
<input
|
||||||
<option value="">-- Select Profile --</option>
|
type="text"
|
||||||
<!-- Profiles will be added dynamically -->
|
id="domain-search"
|
||||||
</select>
|
placeholder="Search domains..."
|
||||||
<small class="form-hint">Selecting a profile will fill the fields below</small>
|
/>
|
||||||
</div>
|
<select id="tag-filter">
|
||||||
|
<option value="">All tags</option>
|
||||||
<div class="form-row">
|
<!-- Tags will be added dynamically -->
|
||||||
<div class="form-group">
|
</select>
|
||||||
<label for="proxy-host">Host *</label>
|
|
||||||
<input type="text" id="proxy-host"
|
|
||||||
placeholder="proxy.example.com"
|
|
||||||
required>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="proxy-port">Port *</label>
|
|
||||||
<input type="number" id="proxy-port"
|
|
||||||
min="1" max="65535"
|
|
||||||
placeholder="8080"
|
|
||||||
required>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-row">
|
<div id="domains-list" class="domains-list">
|
||||||
|
<!-- Dynamically populated -->
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section
|
||||||
|
id="domain-form-section"
|
||||||
|
class="domain-form-section hidden"
|
||||||
|
>
|
||||||
|
<h2 id="form-title">Add Domain</h2>
|
||||||
|
|
||||||
|
<form id="domain-form" class="domain-form">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="proxy-username">Username (optional)</label>
|
<label for="domain-input">Domain *</label>
|
||||||
<input type="text" id="proxy-username"
|
<input
|
||||||
placeholder="username">
|
type="text"
|
||||||
|
id="domain-input"
|
||||||
|
placeholder="example.com"
|
||||||
|
pattern="^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<small class="form-hint"
|
||||||
|
>Enter domain without protocol (e.g.,
|
||||||
|
example.com)</small
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="proxy-password">Password (optional)</label>
|
<label for="proxy-type">Proxy Type *</label>
|
||||||
<div class="password-input">
|
<select id="proxy-type" required>
|
||||||
<input type="password" id="proxy-password"
|
<option value="http">HTTP</option>
|
||||||
placeholder="••••••••">
|
<option value="socks">SOCKS5</option>
|
||||||
<button type="button" class="toggle-password"
|
</select>
|
||||||
data-target="proxy-password">
|
</div>
|
||||||
👁
|
|
||||||
</button>
|
<div class="form-group">
|
||||||
|
<label for="profile-select"
|
||||||
|
>Use Profile (optional)</label
|
||||||
|
>
|
||||||
|
<select id="profile-select">
|
||||||
|
<option value="">-- Select Profile --</option>
|
||||||
|
<!-- Profiles will be added dynamically -->
|
||||||
|
</select>
|
||||||
|
<small class="form-hint"
|
||||||
|
>Selecting a profile will fill the fields
|
||||||
|
below</small
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="proxy-host">Host *</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="proxy-host"
|
||||||
|
placeholder="proxy.example.com"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="proxy-port">Port *</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="proxy-port"
|
||||||
|
min="1"
|
||||||
|
max="65535"
|
||||||
|
placeholder="8080"
|
||||||
|
required
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-row">
|
||||||
<label for="domain-tags">Tags (optional)</label>
|
<div class="form-group">
|
||||||
<input type="text" id="domain-tags"
|
<label for="proxy-username"
|
||||||
placeholder="work, personal, blocked">
|
>Username (optional)</label
|
||||||
<small class="form-hint">Comma-separated tags for organization</small>
|
>
|
||||||
</div>
|
<input
|
||||||
|
type="text"
|
||||||
<div class="form-group checkbox-group">
|
id="proxy-username"
|
||||||
<label class="checkbox-label">
|
placeholder="username"
|
||||||
<input type="checkbox" id="proxy-enabled" checked>
|
/>
|
||||||
<span>Enable proxy for this domain</span>
|
</div>
|
||||||
</label>
|
<div class="form-group">
|
||||||
</div>
|
<label for="proxy-password"
|
||||||
|
>Password (optional)</label
|
||||||
<div class="form-actions">
|
>
|
||||||
<button type="button" id="cancel-form" class="btn btn-secondary">
|
<div class="password-input">
|
||||||
Cancel
|
<input
|
||||||
</button>
|
type="password"
|
||||||
<button type="submit" id="save-domain" class="btn btn-primary">
|
id="proxy-password"
|
||||||
Save Domain
|
placeholder="••••••••"
|
||||||
</button>
|
/>
|
||||||
</div>
|
<button
|
||||||
</form>
|
type="button"
|
||||||
</section>
|
class="toggle-password"
|
||||||
|
data-target="proxy-password"
|
||||||
<section id="profile-form-section" class="profile-form-section hidden">
|
>
|
||||||
<h2 id="profile-form-title">Add Profile</h2>
|
👁
|
||||||
|
</button>
|
||||||
<form id="profile-form" class="profile-form">
|
</div>
|
||||||
<div class="form-group">
|
|
||||||
<label for="profile-name-input">Profile Name *</label>
|
|
||||||
<input type="text" id="profile-name-input"
|
|
||||||
placeholder="My Proxy Profile"
|
|
||||||
required>
|
|
||||||
<small class="form-hint">Enter a unique name for this profile</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="profile-proxy-type">Proxy Type *</label>
|
|
||||||
<select id="profile-proxy-type" required>
|
|
||||||
<option value="http">HTTP</option>
|
|
||||||
<option value="socks">SOCKS5</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="profile-proxy-host">Host *</label>
|
|
||||||
<input type="text" id="profile-proxy-host"
|
|
||||||
placeholder="proxy.example.com"
|
|
||||||
required>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="profile-proxy-port">Port *</label>
|
|
||||||
<input type="number" id="profile-proxy-port"
|
|
||||||
min="1" max="65535"
|
|
||||||
placeholder="8080"
|
|
||||||
required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="profile-proxy-username">Username (optional)</label>
|
|
||||||
<input type="text" id="profile-proxy-username"
|
|
||||||
placeholder="username">
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="profile-proxy-password">Password (optional)</label>
|
|
||||||
<div class="password-input">
|
|
||||||
<input type="password" id="profile-proxy-password"
|
|
||||||
placeholder="••••••••">
|
|
||||||
<button type="button" class="toggle-password"
|
|
||||||
data-target="profile-proxy-password">
|
|
||||||
👁
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-actions">
|
<div class="form-group">
|
||||||
<button type="button" id="cancel-profile-form" class="btn btn-secondary">
|
<label for="domain-tags">Tags (optional)</label>
|
||||||
Cancel
|
<input
|
||||||
|
type="text"
|
||||||
|
id="domain-tags"
|
||||||
|
placeholder="work, personal, blocked"
|
||||||
|
/>
|
||||||
|
<small class="form-hint"
|
||||||
|
>Comma-separated tags for organization</small
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group checkbox-group">
|
||||||
|
<label class="checkbox-label">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="proxy-enabled"
|
||||||
|
checked
|
||||||
|
/>
|
||||||
|
<span>Enable proxy for this domain</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
id="cancel-form"
|
||||||
|
class="btn btn-secondary"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
id="save-domain"
|
||||||
|
class="btn btn-primary"
|
||||||
|
>
|
||||||
|
Save Domain
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section
|
||||||
|
id="profile-form-section"
|
||||||
|
class="profile-form-section hidden"
|
||||||
|
>
|
||||||
|
<h2 id="profile-form-title">Add Profile</h2>
|
||||||
|
|
||||||
|
<form id="profile-form" class="profile-form">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="profile-name-input"
|
||||||
|
>Profile Name *</label
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="profile-name-input"
|
||||||
|
placeholder="My Proxy Profile"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<small class="form-hint"
|
||||||
|
>Enter a unique name for this profile</small
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="profile-proxy-type">Proxy Type *</label>
|
||||||
|
<select id="profile-proxy-type" required>
|
||||||
|
<option value="http">HTTP</option>
|
||||||
|
<option value="socks">SOCKS5</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="profile-proxy-host">Host *</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="profile-proxy-host"
|
||||||
|
placeholder="proxy.example.com"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="profile-proxy-port">Port *</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="profile-proxy-port"
|
||||||
|
min="1"
|
||||||
|
max="65535"
|
||||||
|
placeholder="8080"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="profile-proxy-username"
|
||||||
|
>Username (optional)</label
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="profile-proxy-username"
|
||||||
|
placeholder="username"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="profile-proxy-password"
|
||||||
|
>Password (optional)</label
|
||||||
|
>
|
||||||
|
<div class="password-input">
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="profile-proxy-password"
|
||||||
|
placeholder="••••••••"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="toggle-password"
|
||||||
|
data-target="profile-proxy-password"
|
||||||
|
>
|
||||||
|
👁
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-actions">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
id="cancel-profile-form"
|
||||||
|
class="btn btn-secondary"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
id="save-profile"
|
||||||
|
class="btn btn-primary"
|
||||||
|
>
|
||||||
|
Save Profile
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
<section class="advanced-settings-section">
|
||||||
|
<h2>Advanced Settings</h2>
|
||||||
|
<div class="form-group settings-row">
|
||||||
|
<label for="theme-select">Theme</label>
|
||||||
|
<select id="theme-select">
|
||||||
|
<option value="system">System</option>
|
||||||
|
<option value="light">Light</option>
|
||||||
|
<option value="dark">Dark</option>
|
||||||
|
</select>
|
||||||
|
<small class="form-hint"
|
||||||
|
>Choose light, dark, or follow your device
|
||||||
|
theme.</small
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="management-section">
|
||||||
|
<h2>Management</h2>
|
||||||
|
<div class="management-buttons">
|
||||||
|
<button id="clear-domains" class="btn btn-danger">
|
||||||
|
Clear All Domains
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" id="save-profile" class="btn btn-primary">
|
<button id="clear-profiles" class="btn btn-danger">
|
||||||
Save Profile
|
Clear All Profiles
|
||||||
|
</button>
|
||||||
|
<button id="clear-all" class="btn btn-danger">
|
||||||
|
Clear Everything
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</section>
|
||||||
</section>
|
</main>
|
||||||
<section class="advanced-settings-section">
|
|
||||||
<h2>Advanced Settings</h2>
|
|
||||||
<div class="form-group settings-row">
|
|
||||||
<label for="theme-select">Theme</label>
|
|
||||||
<select id="theme-select">
|
|
||||||
<option value="system">System</option>
|
|
||||||
<option value="light">Light</option>
|
|
||||||
<option value="dark">Dark</option>
|
|
||||||
</select>
|
|
||||||
<small class="form-hint">Choose light, dark, or follow your device theme.</small>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section class="management-section">
|
|
||||||
<h2>Management</h2>
|
|
||||||
<div class="management-buttons">
|
|
||||||
<button id="clear-domains" class="btn btn-danger">
|
|
||||||
Clear All Domains
|
|
||||||
</button>
|
|
||||||
<button id="clear-profiles" class="btn btn-danger">
|
|
||||||
Clear All Profiles
|
|
||||||
</button>
|
|
||||||
<button id="clear-all" class="btn btn-danger">
|
|
||||||
Clear Everything
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<div class="footer-content">
|
<div class="footer-content">
|
||||||
<p>Proxy Switcher v1.0.1</p>
|
<p>Proxy Switcher v20261009.1</p>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="browser-api.js"></script>
|
<script src="browser-api.js"></script>
|
||||||
<script src="options.js"></script>
|
<script src="options.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 75 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 21 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
Reference in new issue
Block a user