diff --git a/src/content/about.html b/src/content/about.html
index 54858bd..ae3c1ac 100644
--- a/src/content/about.html
+++ b/src/content/about.html
@@ -17,7 +17,13 @@
Changelog
8.10
+ Added direct HTTP authentication (Firefox 125+)
Added new options to the proxy types
+ Added QUIC (HTTP) option (Chrome only) (experimental) (#124)
+ Updated browser detection (firefox-extension/issues/220, #139, #141)
+ Updated Options page user interface
+ Updated toolbar popup user interface
+ Added console log for Save file errors (#144)
8.9
Added "Log" to the toolbar popup buttons (#44)
@@ -75,7 +81,7 @@ Changelog
Updated Options save process to fill blank proxy header title display (#74)
8.3
- Added enterprise policy & managed storage feature (#42) (experimental)
+ Added enterprise policy & managed storage feature (experimental) (#42)
Added PAC "Store Locally" feature (#46) (experimental) (Chrome only)
Added PAC view feature
Fixed an issue with empty Global Exclude
@@ -108,7 +114,7 @@ Changelog
8.0
Added complete Light/Dark Theme
Added Exclude host feature
- Added experimental Firefox on Android support (#21)
+ Added Firefox on Android support (experimental) (#21)
Added Get Location feature
Added Global Exclude
Added Host Pattern to proxy feature
@@ -129,7 +135,6 @@ Changelog
Credits
-
Developer
erosman
@@ -138,10 +143,10 @@ Credits
es: Luis Alfredo Figueroa Bracamontes
fa: Matin Kargar
fr: Hugo-C
- ja_JP: Yuta Yamate
+ ja: Yuta Yamate
pl: Grzegorz Koryga
pt_BR:
- ru: Kirill Motkov
+ ru: Kirill Motkov , krolchonok
uk: Sviatoslav Sydorenko
zh_CN: FeralMeow
zh_TW: samuikaze
diff --git a/src/content/action.js b/src/content/action.js
index db43f23..8edc0f2 100644
--- a/src/content/action.js
+++ b/src/content/action.js
@@ -2,6 +2,7 @@
import {Location} from './location.js';
export class Action {
+
// https://github.com/w3c/webextensions/issues/72#issuecomment-1848874359
// 'prefers-color-scheme' detection in Chrome background service worker
static dark = false;
diff --git a/src/content/app.js b/src/content/app.js
index bf48b5c..0b4dd88 100644
--- a/src/content/app.js
+++ b/src/content/app.js
@@ -24,7 +24,12 @@ export const pref = {
// ---------- App ------------------------------------------
export class App {
- static firefox = navigator.userAgent.includes('Firefox');
+ // https://github.com/foxyproxy/firefox-extension/issues/220
+ // Proxy by patterns not working if firefox users change their userAgent to another platform
+ // Chrome does not support runtime.getBrowserInfo()
+ // Object.hasOwn(browser.runtime, 'getBrowserInfo')
+ // moz-extension: | chrome-extension: | safari-web-extension:
+ static firefox = browser.runtime.getURL('').startsWith('moz-extension:');
static android = navigator.userAgent.includes('Android');
// static chrome = navigator.userAgent.includes('Chrome');
static basic = browser.runtime.getManifest().name === browser.i18n.getMessage('extensionNameBasic');
@@ -63,11 +68,6 @@ export class App {
return JSON.stringify(a) === JSON.stringify(b);
}
- static getFlag(cc) {
- cc = /^[A-Z]{2}$/i.test(cc) && cc.toUpperCase();
- return cc ? String.fromCodePoint(...[...cc].map(i => i.charCodeAt() + 127397)) : '🌎';
- }
-
static parseURL(url) {
// rebuild file://
url.startsWith('file://') && (url = 'http' + url.substring(4));
@@ -86,4 +86,48 @@ export class App {
return url;
}
+
+ static getFlag(cc) {
+ cc = /^[A-Z]{2}$/i.test(cc) && cc.toUpperCase();
+ return cc ? String.fromCodePoint(...[...cc].map(i => i.charCodeAt() + 127397)) : '🌎';
+ }
+
+ static isLocal(host) {
+ // check local network
+ const isIP = /^[\d.:]+$/.test(host);
+ switch (true) {
+ // --- localhost &
+ // case host === 'localhost':
+ case !host.includes('.'): // plain hostname (no dots)
+ case host.endsWith('.localhost'): // *.localhost
+
+ // --- IPv4
+ // case host === '127.0.0.1':
+ case isIP && host.startsWith('127.'): // 127.0.0.1 up to 127.255.255.254
+ case isIP && host.startsWith('169.254.'): // 169.254.0.0/16 - 169.254.0.0 to 169.254.255.255
+ case isIP && host.startsWith('192.168.'): // 192.168.0.0/16 - 192.168.0.0 to 192.168.255.255
+
+ // --- IPv6
+ // case host === '[::1]':
+ case host.startsWith('[::1]'): // literal IPv6 [::1]:80 with/without port
+ case host.toUpperCase().startsWith('[FE80::]'): // literal IPv6 [FE80::]/10
+ return true;
+ }
+ }
+
+ static showFlag(item) {
+ switch (true) {
+ case !!item.cc:
+ return this.getFlag(item.cc);
+
+ case item.type === 'direct':
+ return '⮕';
+
+ case this.isLocal(item.hostname):
+ return '🖥️';
+
+ default:
+ return '🌎';
+ }
+ }
}
\ No newline at end of file
diff --git a/src/content/authentication.js b/src/content/authentication.js
index eb7ab02..27f18a2 100644
--- a/src/content/authentication.js
+++ b/src/content/authentication.js
@@ -5,6 +5,7 @@
// webRequest.onAuthRequired on Chrome mv3 is not usable due to removal of 'blocking'
// https://source.chromium.org/chromium/chromium/src/+/main:extensions/browser/api/web_request/web_request_api.cc;l=2857
// Chrome 108 new permission 'webRequestAuthProvider'
+// Firefox 126 added 'webRequestAuthProvider' permission support
import './app.js';
diff --git a/src/content/background.js b/src/content/background.js
index c24e05c..cbb34db 100644
--- a/src/content/background.js
+++ b/src/content/background.js
@@ -4,7 +4,6 @@ import {Proxy} from './proxy.js';
import './commands.js';
// ---------- Process Preferences --------------------------
-// eslint-disable-next-line no-unused-vars
class ProcessPref {
static {
diff --git a/src/content/color.js b/src/content/color.js
index 95fcb11..b7f0497 100644
--- a/src/content/color.js
+++ b/src/content/color.js
@@ -1,7 +1,7 @@
export class Color {
static getRandom() {
- return this.colors[Math.floor(Math.random()*this.colors.length)];
+ return this.colors[Math.floor(Math.random() * this.colors.length)];
}
static colors = [
diff --git a/src/content/commands.js b/src/content/commands.js
index 714ea24..663b74d 100644
--- a/src/content/commands.js
+++ b/src/content/commands.js
@@ -2,7 +2,7 @@
// https://developer.chrome.com/docs/extensions/reference/commands/#event-onCommand
// https://bugzilla.mozilla.org/show_bug.cgi?id=1843866
// Add tab parameter to commands.onCommand
-// Firefox commands only returns command name
+// Firefox commands only returns command name (tab added in FF126)
// Chrome commands returns command, tab
import {App} from './app.js';
@@ -10,7 +10,6 @@ import {Proxy} from './proxy.js';
import {OnRequest} from './on-request.js';
// ---------- Commands (Side Effect) ------------------------
-// eslint-disable-next-line no-unused-vars
class Commands {
static {
@@ -21,6 +20,8 @@ class Commands {
static async process(name, tab) {
const pref = await browser.storage.local.get();
const host = pref.commands[name];
+ const needTab = ['quickAdd', 'excludeHost', 'setTabProxy', 'unsetTabProxy'].includes(name);
+ tab ||= needTab && await browser.tabs.query({currentWindow: true, active: true});
switch (name) {
case 'proxyByPatterns':
@@ -36,7 +37,7 @@ class Commands {
break;
case 'quickAdd':
- host && Proxy.quickAdd(pref, host);
+ host && Proxy.quickAdd(pref, host, tab);
break;
case 'excludeHost':
@@ -47,13 +48,13 @@ class Commands {
if (!App.firefox || !host) { break; } // firefox only
const proxy = pref.data.find(i => i.active && host === `${i.hostname}:${i.port}`);
- proxy && OnRequest.setTabProxy(proxy);
+ proxy && OnRequest.setTabProxy(tab, proxy);
break;
case 'unsetTabProxy':
if (!App.firefox) { break; } // firefox only
- OnRequest.unsetTabProxy();
+ OnRequest.setTabProxy(tab);
break;
}
}
diff --git a/src/content/help.html b/src/content/help.html
index 84b63f3..ef7e224 100644
--- a/src/content/help.html
+++ b/src/content/help.html
@@ -423,10 +423,11 @@ Toolbar Popup
Add host (with its protocol) to the Global Exclude
Set Tab Proxy (Firefox only)
+ Select a proxy from the drop-down list for the current tab
+ Click button to set
Tab Proxy is processed before normal proxy settings
- Select a proxy for the current tab
Only top 10 active proxies are listed
- FoxyProxy icon will show in the selected tab and mouse-over title displays selection details
+ Toolbar badge shows the proxy and mouse-over title displays the selection details
Unset Tab Proxy (Firefox only)
Unset proxy for the current tab
@@ -534,7 +535,7 @@ Incognito/Container
Firefox
Incognito/Container are processed before standard proxy settings (and after Tab Proxy)
- Generic names are used for containers since getting their details requires "contextualIdentities" permissions which would enable containers automatically for the user
+ Generic names are used for containers since getting their details requires "contextualIdentities" permissions which would enable containers automatically for the user ⓘ
@@ -701,7 +702,7 @@ Restore Defaults
Browser settings are not changed (e.g. previously set proxy values or Limit WebRTC).
Preferences: Import/Export
- You can import/export Preferences (for backup or share) from/to a local file on your computer.
+ You can import/export Preferences (for backup or sharing) from/to a local file on your computer.
Import is non-destructive. Click Save to apply the changes.
Save
@@ -714,7 +715,7 @@ Proxies
FoxyProxy identifies proxies by their "hostname:post" or PAC URL.
- In case more than one proxy with the same "hostname:post" is needed, one or more letters can be added to the port to separate them, and FoxyProxy will remove the letters later. (v8.8)
+ In case more than one proxy with the same "hostname:post" is needed, one or more letters can be added to the port to separate them, and FoxyProxy will remove the letters later. (v8.8) (Firefox only, not practical on Chrome)
127.0.0.1:9050
@@ -766,6 +767,7 @@ Individual Proxy
Type
HTTP/HTTPS/SOCKS4/SOCKS5 to be used with hostname & port
PAC to be used with PAC URL
+ QUIC (HTTP/3) proxy is only supported by Chrome (not available on Firefox). Support for QUIC proxies in Chrome is currently experimental and not ready for production use. ⓘ
Country
Set the proxy country flag
@@ -781,10 +783,11 @@ Individual Proxy
Username & Password
To be used for HTTP/HTTPS/SOCKS types
- If not set, authentication will be handled by the browser
+ SOCKS5 username & password is only supported by Firefox (not available on Chrome)
+ If not set, HTTP/HTTPS authentication will be handled by the browser
Username & Password are port specific and different ones can be set for different ports
- Proxy DNS (Firefox only)
+ Proxy DNS (Firefox only)
Option to pass DNS to the SOCKS proxy when using SOCKS
See also:
+
PAC URL (not available on Android)
Required for PAC type
Proxy Auto-Configuration file (PAC) can be used to handle all proxying configurations
@@ -825,6 +832,57 @@ Individual Proxy
+
+
+ DNS (Domain Name System) Resolution
+ Following table demonstrates the DNS resolution when a proxy is used.
+
+
+ Is name resolution (DNS) done client side, or proxy side?
+
+
+ Scheme
+ Chrome
+ Firefox
+ DoH (DNS over HTTPS)
+
+
+
+
+ HTTP
+ Name resolution is always done proxy side ⓘ
+ proxy side
+ See Proxy DNS
+
+
+ HTTPS
+ (expected to be proxy side)
+ proxy side
+ See Proxy DNS
+
+
+ SOCKS4
+ Name resolution for target hosts is always done client side, and moreover must resolve to an IPv4 address (SOCKSv4 encodes target address as 4 octets, so IPv6 targets are not possible). ⓘ
+ See Proxy DNS
+ See Proxy DNS
+
+
+ SOCKS5
+ Name resolution is always done proxy side ⓘ
+ See Proxy DNS
+ See Proxy DNS
+
+
+ QUIC
+ A QUIC proxy uses QUIC (UDP) as the underlying transport, but otherwise behaves as an HTTP proxy. ⓘ
+ n/a
+ n/a
+
+
+
+
+
+
Local Servers
FoxyProxy can be used with local proxy servers:
@@ -846,10 +904,16 @@ Local Servers
- TOR
- SOCKS5
+ Burp Suit
+ HTTP
127.0.0.1
- 9050
+ 8080
+
+
+ Privoxy
+ HTTP
+ 127.0.0.1
+ 8118
Psiphon
@@ -858,22 +922,28 @@ Local Servers
60351
- Privoxy
- HTTPS
+ TOR
+ SOCKS5
127.0.0.1
- 8118
+ 9050
v2rayA
- SOCKS5
+ SOCKS5 http http with shunt rules
127.0.0.1
- 20170
+ 20170 20171 20172
- v2rayA
- HTTP
+ NekoRay/NekoBox
+ SOCKS5 http
+ 127.0.0.1
+ 2080 2081
+
+
+ Shadowsocks
+ SOCKS5
127.0.0.1
- 20171 20172 (http with shunt rules)
+ 1080
diff --git a/src/content/i18n.js b/src/content/i18n.js
index 8b193bb..47b3b1a 100644
--- a/src/content/i18n.js
+++ b/src/content/i18n.js
@@ -1,5 +1,4 @@
// ---------- Internationalization (Side Effect) -----------
-// eslint-disable-next-line no-unused-vars
class I18n {
static {
diff --git a/src/content/iframe.css b/src/content/iframe.css
index 324dad6..aac9b9a 100644
--- a/src/content/iframe.css
+++ b/src/content/iframe.css
@@ -220,15 +220,11 @@ th,
td {
border: 1px solid var(--border);
vertical-align: top;
-}
-
-td {
padding: 0.5em;
}
thead th {
font-size: 1.2em;
- padding: 0.5em;
}
tbody th {
diff --git a/src/content/import-export.js b/src/content/import-export.js
index e8d735e..982099f 100644
--- a/src/content/import-export.js
+++ b/src/content/import-export.js
@@ -24,7 +24,7 @@ export class ImportExport {
static readData(data, pref) {
try { data = JSON.parse(data); }
- catch(e) {
+ catch {
App.notify(browser.i18n.getMessage('fileParseError')); // display the error
return;
}
@@ -58,7 +58,8 @@ export class ImportExport {
saveAs,
conflictAction: 'uniquify'
})
- .catch(() => {}); // Suppress Error: Download canceled by the user
+ // eslint-disable-next-line no-console
+ .catch(console.log);
}
static fileReader(file, callback) {
diff --git a/src/content/on-request.js b/src/content/on-request.js
index 2c5922f..d327f02 100644
--- a/src/content/on-request.js
+++ b/src/content/on-request.js
@@ -2,10 +2,19 @@
// Dynamic import is not available yet in MV3 service worker
// Once implemented, module will be dynamically imported for Firefox only
-// Support non-ASCII username/password for socks proxy
// https://bugzilla.mozilla.org/show_bug.cgi?id=1853203
+// Support non-ASCII username/password for socks proxy
// Fixed in Firefox 119
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1794464
+// Allow HTTP authentication in proxy.onRequest
+// Fixed in Firefox 125
+
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1741375
+// Proxy DNS by default when using SOCKS v5
+// Firefox 128: defaults to true for SOCKS5 & false for SOCKS4
+
+import {App} from './app.js';
import {Pattern} from './pattern.js';
import {Location} from './location.js';
@@ -21,6 +30,7 @@ export class OnRequest {
this.net = []; // [start, end] strings
this.tabProxy = {}; // tab proxy, may be lost in MV3 if bg is unloaded
this.container = {}; // incognito/container proxy
+ this.browserVersion = 0; // used HTTP authentication
// --- Firefox only
if (browser?.proxy?.onRequest) {
@@ -30,6 +40,8 @@ export class OnRequest {
browser.tabs.onRemoved.addListener(tabId => delete this.tabProxy[tabId]);
// mark incognito/container
browser.tabs.onCreated.addListener(e => this.checkPageAction(e));
+ // check for HTTP authentication use
+ browser.runtime.getBrowserInfo().then(info => this.browserVersion = parseInt(info.version));
}
}
@@ -77,7 +89,7 @@ export class OnRequest {
switch (true) {
// --- check local & global passthrough
case this.bypass(e.url):
- this.setAction(null, tabId);
+ this.setAction(tabId);
return {type: 'direct'};
// --- tab proxy
@@ -96,7 +108,7 @@ export class OnRequest {
case this.mode === 'disable': // pass direct
case this.mode === 'direct': // pass direct
case this.mode.includes('://') && !/:\d+$/.test(this.mode): // PAC URL is set
- this.setAction(null, tabId);
+ this.setAction(tabId);
return {type: 'direct'};
case this.mode === 'pattern': // check if url matches patterns
@@ -117,12 +129,12 @@ export class OnRequest {
}
}
- this.setAction(null, tabId);
+ this.setAction(tabId);
return {type: 'direct'}; // no match
}
static processProxy(proxy, tabId) {
- this.setAction(proxy, tabId);
+ this.setAction(tabId, proxy);
const {type, hostname: host, port, username, password, proxyDNS} = proxy || {};
if (!type || type === 'direct') { return {type: 'direct'}; }
@@ -146,13 +158,15 @@ export class OnRequest {
// https://searchfox.org/mozilla-central/source/toolkit/components/extensions/ProxyChannelFilter.sys.mjs#167
// proxyAuthorizationHeader on Firefox only applies to HTTPS (not HTTP and it breaks the API and sends DIRECT)
// proxyAuthorizationHeader added to reduce the authentication request in webRequest.onAuthRequired
- type === 'https' && (response.proxyAuthorizationHeader = 'Basic ' + btoa(proxy.username + ':' + proxy.password));
+ // HTTP authentication fixed in Firefox 125
+ (type === 'https' || this.browserVersion >= 125) &&
+ (response.proxyAuthorizationHeader = 'Basic ' + btoa(proxy.username + ':' + proxy.password));
}
return response;
}
- static setAction(item, tabId) {
+ static setAction(tabId, item) {
// Set to -1 if the request isn't related to a tab
if (tabId === -1) { return; }
@@ -193,31 +207,12 @@ export class OnRequest {
// it can't catch a domain set by user to 127.0.0.1 in the hosts file
static localhost(url) {
const [, host] = url.split(/:\/\/|\//, 2); // hostname with/without port
- const isIP = /^[\d.:]+$/.test(host);
-
- switch (true) {
- // --- localhost &
- // case host === 'localhost':
- case !host.includes('.'): // plain hostname (no dots)
- case host.endsWith('.localhost'): // *.localhost
-
- // --- IPv4
- // case host === '127.0.0.1':
- case isIP && host.startsWith('127.'): // 127.0.0.1 up to 127.255.255.254
- case isIP && host.startsWith('169.254.'): // 169.254.0.0/16
- case isIP && host.startsWith('192.168.'): // 192.168.0.0/16 192.168.0.0 192.168.255.255
-
- // --- IPv6
- // case host === '[::1]':
- case host.startsWith('[::1]'): // literal IPv6 [::1]:80 with/without port
- case host.startsWith('[FE80::]'): // literal IPv6 [FE80::]/10
- return true;
- }
+ return App.isLocal(host);
}
static isInNet(url) {
// check if IP address
- if(!/^[a-z]+:\/\/\d+(\.\d+){3}(:\d+)?\//.test(url)) { return; }
+ if (!/^[a-z]+:\/\/\d+(\.\d+){3}(:\d+)?\//.test(url)) { return; }
const ipa = url.split(/[:/.]+/, 5).slice(1); // IP array
const ip = ipa.map(i => i.padStart(3, '0')).join(''); // convert to padded string
@@ -225,30 +220,31 @@ export class OnRequest {
}
// ---------- Tab Proxy ----------------------------------
- static async setTabProxy(pxy) {
- const [tab] = await browser.tabs.query({currentWindow: true, active: true});
+ static setTabProxy(tab, pxy) {
+ // const [tab] = await browser.tabs.query({currentWindow: true, active: true});
switch (true) {
case !/https?:\/\/.+/.test(tab.url): // unacceptable URLs
case this.bypass(tab.url): // check local & global passthrough
return;
}
- this.tabProxy[tab.id] = pxy;
- // PageAction.set(tab.id, pxy);
+ // set or unset
+ pxy ? this.tabProxy[tab.id] = pxy : delete this.tabProxy[tab.id];
+ this.setAction(tab.id, pxy);
}
- static async unsetTabProxy() {
- const [tab] = await browser.tabs.query({currentWindow: true, active: true});
- delete this.tabProxy[tab.id];
- // PageAction.unset(tab.id);
- }
+ // static async unsetTabProxy() {
+ // const [tab] = await browser.tabs.query({currentWindow: true, active: true});
+ // delete this.tabProxy[tab.id];
+ // // PageAction.unset(tab.id);
+ // }
// ---------- Update Page Action -------------------------
static onUpdated(tabId, changeInfo, tab) {
if (changeInfo.status !== 'complete') { return; }
const pxy = this.tabProxy[tabId];
- pxy ? this.setAction(pxy, tabId) : this.checkPageAction(tab);
+ pxy ? this.setAction(tab.id, pxy) : this.checkPageAction(tab);
}
// ---------- Incognito/Container ------------------------
@@ -256,6 +252,6 @@ export class OnRequest {
if (tab.id === -1 || this.tabProxy[tab.id]) { return; } // not if tab proxy is set
const pxy = tab.incognito ? this.container.incognito : this.container[tab.cookieStoreId];
- pxy && this.setAction(pxy, tab.id);
+ pxy && this.setAction(tab.id, pxy);
}
}
\ No newline at end of file
diff --git a/src/content/options.html b/src/content/options.html
index 28a808d..128037f 100644
--- a/src/content/options.html
+++ b/src/content/options.html
@@ -191,21 +191,28 @@
- HTTP
- HTTPS
- SOCKS4
- SOCKS5
-
- PAC
- DIRECT
- TOR
- Psiphon
- Privoxy
- v2rayA-socks5
- v2rayA-http
- v2rayA-http-rules
-
-
+
+ HTTP
+ HTTPS
+ SOCKS4
+ SOCKS5
+ QUIC
+ PAC
+ DIRECT
+
+
+ Burp
+ Privoxy
+ Psiphon
+ TOR
+ v2rayA-socks5
+ v2rayA-http
+ v2rayA-http-rules
+ NekoRay-socks5
+ NekoRay-http
+ Shadowsocks
+
+
diff --git a/src/content/options.js b/src/content/options.js
index 481442d..11dbd5c 100644
--- a/src/content/options.js
+++ b/src/content/options.js
@@ -33,7 +33,6 @@ export class Popup {
await App.getPref();
// ---------- Incognito Access -----------------------------
-// eslint-disable-next-line no-unused-vars
class IncognitoAccess {
static {
@@ -62,7 +61,6 @@ class Toggle {
// ---------- /Toggle --------------------------------------
// ---------- Theme ----------------------------------------
-// eslint-disable-next-line no-unused-vars
class Theme {
static {
this.elem = [document, ...[...document.querySelectorAll('iframe')].map(i => i.contentDocument)];
@@ -82,6 +80,9 @@ class Options {
static {
// --- container
+ // using generic names
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1386673
+ // Make Contextual Identity extensions be an optional permission
this.container = document.querySelectorAll('.options .container select');
// --- keyboard Shortcut
@@ -209,7 +210,7 @@ class Options {
browser.storage.sync.get()
.then(syncObj => {
// get & delete numerical keys that are equal or larger than data length, the rest are overwritten
- const del = Object.keys(syncObj).filter(i => /^\d+$/.test(i) && i*1 >= pref.data.length);
+ const del = Object.keys(syncObj).filter(i => /^\d+$/.test(i) && i * 1 >= pref.data.length);
del[0] && browser.storage.sync.remove(del);
});
})
@@ -247,7 +248,7 @@ class Options {
Object.hasOwn(obj, i.dataset.id) && (obj[i.dataset.id] = i.type === 'checkbox' ? i.checked : i.value.trim());
});
- // --- check type: http | https | socks4 | socks5 | pac | direct
+ // --- check type: http | https | socks4 | socks5 | quic | pac | direct
switch (true) {
// DIRECT
case obj.type === 'direct':
@@ -266,7 +267,7 @@ class Options {
obj.port = port;
break;
- // http | https | socks4 | socks5
+ // http | https | socks4 | socks5 | quic
case !obj.hostname:
this.setInvalid(elem, 'hostname');
alert(browser.i18n.getMessage('hostnamePortError'));
@@ -384,7 +385,6 @@ class Options {
// ---------- /Options -------------------------------------
// ---------- browsingData ---------------------------------
-// eslint-disable-next-line no-unused-vars
class BrowsingData {
static {
@@ -420,7 +420,6 @@ class BrowsingData {
// ---------- /browsingData --------------------------------
// ---------- WebRTC ---------------------------------------
-// eslint-disable-next-line no-unused-vars
class WebRTC {
static {
@@ -520,7 +519,7 @@ class Proxies {
down.addEventListener('click', () => pxy.nextElementSibling?.after(pxy));
// proxy data
- const [title, hostname, type, port, cc, username, city, passwordSpan, colorSpan, pacSpan, proxyDNS] = [...proxyBox.children].filter((e, i) => i%2);
+ const [title, hostname, type, port, cc, username, city, passwordSpan, colorSpan, pacSpan, proxyDNS] = [...proxyBox.children].filter((e, i) => i % 2);
title.addEventListener('change', e => sumTitle.textContent = e.target.value);
const [pac, storeLocallyLabel, view] = pacSpan.children;
@@ -529,59 +528,82 @@ class Proxies {
pxy.dataset.type = e.target.value; // show/hide elements
const id = e.target.options[e.target.selectedIndex].textContent;
- const fillData = () => {
- flag.textContent = id === 'DIRECT' ? '⮕' : '🌎';
+ const fillData = (local) => {
sumTitle.textContent = id;
title.value = id;
+
+ if (local) {
+ flag.textContent = '🖥️';
+ hostname.value = '127.0.0.1';
+ }
};
switch (id) {
case 'PAC':
fillData();
+ flag.textContent = '🌎';
break;
case 'DIRECT':
fillData();
+ flag.textContent = '⮕';
hostname.value = id;
break;
- // --- auto-fill helpers
- case 'TOR':
- fillData();
- hostname.value = '127.0.0.1';
- port.value = '9050';
+ // --- server auto-fill helpers
+ case 'Burp':
+ fillData(true);
+ port.value = '8080';
+ break;
+
+ case 'Privoxy':
+ fillData(true);
+ port.value = '8118';
break;
case 'Psiphon':
- fillData();
- hostname.value = '127.0.0.1';
+ fillData(true);
port.value = '60351';
break;
- case 'Privoxy':
- fillData();
- hostname.value = '127.0.0.1';
- port.value = '8118';
+ case 'TOR':
+ fillData(true);
+ port.value = '9050';
break;
// By default v2rayA will open 20170 (socks5), 20171 (http), 20172 (http with shunt rules) ports through the core
case 'v2rayA-socks5':
- fillData();
- hostname.value = '127.0.0.1';
+ fillData(true);
port.value = '20170';
break;
case 'v2rayA-http':
- fillData();
- hostname.value = '127.0.0.1';
+ fillData(true);
port.value = '20171';
break;
case 'v2rayA-http-rules':
- fillData();
- hostname.value = '127.0.0.1';
+ fillData(true);
port.value = '20172';
break;
+
+ case 'NekoRay-socks5':
+ fillData(true);
+ port.value = '2080';
+ break;
+
+ case 'NekoRay-http':
+ fillData(true);
+ port.value = '2081';
+ break;
+
+ case 'Shadowsocks':
+ fillData(true);
+ port.value = '1080';
+ break;
+
+ default:
+ flag.textContent = App.getFlag(cc.value);
}
});
@@ -643,7 +665,7 @@ class Proxies {
const pxyTitle = item.title || id;
// --- summary
- flag.textContent = App.getFlag(item.cc);
+ flag.textContent = App.showFlag(item);
sumTitle.textContent = pxyTitle;
active.checked = item.active;
@@ -742,7 +764,7 @@ class Proxies {
ImportExport.fileReader(file, data => {
try { data = JSON.parse(data); }
- catch(e) {
+ catch {
App.notify(browser.i18n.getMessage('fileParseError')); // display the error
return;
}
@@ -820,7 +842,6 @@ class Proxies {
// ---------- /Proxies -------------------------------------
// ---------- Drag and Drop --------------------------------
-// eslint-disable-next-line no-unused-vars
class Drag {
static {
@@ -845,7 +866,6 @@ class Drag {
// ---------- /Drag and Drop -------------------------------
// ---------- Import FP Account ----------------------------
-// eslint-disable-next-line no-unused-vars
class ImportFoxyProxyAccount {
static {
@@ -917,7 +937,7 @@ class ImportFoxyProxyAccount {
return fetch('https://getfoxyproxy.org/webservices/get-accounts.php', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
- body: `username=${encodeURIComponent(username)}&password=${(encodeURIComponent(password))}`
+ body: `username=${encodeURIComponent(username)}&password=${encodeURIComponent(password)}`
})
.then(response => response.json())
.then(data => {
@@ -936,7 +956,6 @@ class ImportFoxyProxyAccount {
// ---------- /Import FP Account ---------------------------
// ---------- Import From URL ------------------------------
-// eslint-disable-next-line no-unused-vars
class importFromUrl {
static {
@@ -971,7 +990,6 @@ class importFromUrl {
// ---------- /Import From URL -----------------------------
// ---------- Import List ----------------------------------
-// eslint-disable-next-line no-unused-vars
class ImportProxyList {
static {
@@ -1002,7 +1020,7 @@ class ImportProxyList {
static parseSimple(item) {
// example.com:3128:user:pass
const [hostname, port, username = '', password = ''] = item.split(':');
- if (!hostname || !port || !(port*1)) {
+ if (!hostname || !port || !(port * 1)) {
alert(`Error: ${item}`);
return;
}
@@ -1097,7 +1115,6 @@ class ImportProxyList {
// ---------- /Import List ---------------------------------
// ---------- Import Older Export --------------------------
-// eslint-disable-next-line no-unused-vars
class importFromOlder {
static {
@@ -1121,7 +1138,7 @@ class importFromOlder {
static parseJSON(data) {
try { data = JSON.parse(data); }
- catch(e) {
+ catch {
App.notify(browser.i18n.getMessage('fileParseError')); // display the error
return;
}
@@ -1156,7 +1173,7 @@ class Tester {
this.url.value = this.url.value.trim();
this.pattern.value = this.pattern.value.trim();
- if(!this.url.value || !this.pattern.value ) {
+ if (!this.url.value || !this.pattern.value) {
this.result.textContent = '❌';
return;
}
@@ -1198,4 +1215,11 @@ ImportExport.init(pref, () => {
// ---------- /Import/Export Preferences -------------------
// ---------- Navigation -----------------------------------
-Nav.get();
\ No newline at end of file
+Nav.get();
+
+// globalThis.FP = {
+// proxies: document.querySelectorAll('details.proxy'),
+// delete(n) {
+// n.forEach(i => i.remove());
+// }
+// };
\ No newline at end of file
diff --git a/src/content/pattern.js b/src/content/pattern.js
index 72d2544..9a62477 100644
--- a/src/content/pattern.js
+++ b/src/content/pattern.js
@@ -6,7 +6,7 @@ export class Pattern {
new RegExp(pat);
return true;
}
- catch(error) {
+ catch (error) {
showError && alert([browser.i18n.getMessage('regexError'), str, error].join('\n'));
}
}
@@ -80,7 +80,7 @@ export class Pattern {
return [...Array(4)].map(() => {
const n = Math.min(mask, 8);
mask -= n;
- return 256 - Math.pow(2, 8-n);
+ return 256 - Math.pow(2, 8 - n);
}).join('.');
}
@@ -88,7 +88,7 @@ export class Pattern {
static getRange(ip, mask) {
let st = ip.split('.'); // ip array
const ma = mask.split('.'); // mask array
- let end = st.map((v, i) => Math.min(v-ma[i]+255, 255) + ''); // netmask wildcard array
+ let end = st.map((v, i) => Math.min(v - ma[i] + 255, 255) + ''); // netmask wildcard array
st = st.map(i => i.padStart(3, '0')).join('');
end = end.map(i => i.padStart(3, '0')).join('');
diff --git a/src/content/popup.css b/src/content/popup.css
index 40bde39..ccd8f82 100644
--- a/src/content/popup.css
+++ b/src/content/popup.css
@@ -27,11 +27,16 @@ body {
transition: opacity 0.5s;
}
+/*
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1883896
+ Remove UA styles for :is(article, aside, nav, section) h1
+*/
h1 {
color: var(--nav-color);
background-color: var(--nav-bg);
margin: 0;
padding: 0.5em;
+ font-size: 1.2em;
}
h1 img {
@@ -116,6 +121,10 @@ div.list label:hover {
color: var(--dim);
}
+.data.off {
+ display: none;
+}
+
input[name="server"] {
grid-row: span 2;
transition: 0.5s ease-in-out;
@@ -131,7 +140,7 @@ input#filter {
background: url('../image/filter.svg') no-repeat left 0.5em center / 1em;
padding-left: 2em;
margin-bottom: 0.2em;
- /* grid-column: span 2; */
+ grid-column: span 2;
}
div.list label.off {
diff --git a/src/content/popup.html b/src/content/popup.html
index 12123f4..5962321 100644
--- a/src/content/popup.html
+++ b/src/content/popup.html
@@ -29,16 +29,26 @@
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
diff --git a/src/content/popup.js b/src/content/popup.js
index f7e742e..893e851 100644
--- a/src/content/popup.js
+++ b/src/content/popup.js
@@ -7,7 +7,6 @@ import './i18n.js';
await App.getPref();
// ---------- Popup ----------------------------------------
-// eslint-disable-next-line no-unused-vars
class Popup {
static {
@@ -18,8 +17,24 @@ class Popup {
document.querySelectorAll('button').forEach(i => i.addEventListener('click', e => this.processButtons(e)));
this.list = document.querySelector('div.list');
- this.select = document.querySelector('select');
- this.proxyCache = {}; // used to find proxy
+
+ // --- Quick Add (not for storage.managed)
+ this.quickAdd = document.querySelector('select#quickAdd');
+ !pref.managed && this.quickAdd.addEventListener('change', (e) => {
+ if (!this.quickAdd.value) { return; }
+
+ browser.runtime.sendMessage({id: 'quickAdd', pref, host: this.quickAdd.value, tab: this.tab});
+ this.quickAdd.selectedIndex = 0; // reset select option
+ });
+
+ // --- Tab Proxy (not for storage.managed, firefox only)
+ this.tabProxy = document.querySelector('select#tabProxy');
+ !pref.managed && App.firefox && this.tabProxy.addEventListener('change', () => {
+ if (!this.tab) { return; }
+
+ const proxy = this.tabProxy.value && this.proxyCache[this.tabProxy.selectedOptions[0].dataset.index];
+ browser.runtime.sendMessage({id: 'setTabProxy', proxy, tab: this.tab});
+ });
// disable buttons on storage.managed
pref.managed && document.body.classList.add('managed');
@@ -53,12 +68,12 @@ class Popup {
const id = item.type === 'pac' ? item.pac : `${item.hostname}:${item.port}`;
const label = labelTemplate.cloneNode(true);
const [flag, title, portNo, radio, data] = label.children;
- flag.textContent = App.getFlag(item.cc);
+ flag.textContent = App.showFlag(item);
title.textContent = item.title || id;
portNo.textContent = item.port;
radio.value = item.type === 'direct' ? 'direct' : id;
radio.checked = id === pref.mode;
- data.textContent = [item.city, ...Location.get(item.cc)].filter(Boolean).join('\n');
+ data.textContent = [item.city, ...Location.get(item.cc)].filter(Boolean).join('\n') || item.hostname;
docFrag.appendChild(label);
});
@@ -69,26 +84,31 @@ class Popup {
);
// --- Add Hosts to select
- // filter out PAC, limit to 10
- pref.data.filter(i => i.active && i.type !== 'pac').filter((i, idx) => idx < 10).forEach(item => {
- const flag = App.getFlag(item.cc);
+ // used to find proxy, filter out PAC, limit to 10
+ this.proxyCache = pref.data.filter(i => i.active && i.type !== 'pac').filter((i, idx) => idx < 10);
+
+ this.proxyCache.forEach((item, index) => {
+ const flag = App.showFlag(item);
const value = `${item.hostname}:${item.port}`;
const opt = new Option(flag + ' ' + (item.title || value), value);
+ opt.dataset.index = index;
// opt.style.color = item.color; // supported on Chrome, not on Firefox
docFrag.appendChild(opt);
-
- this.proxyCache[value] = item; // cache to find later
});
- // add a DIRECT option to the end
- // const opt = new Option('⮕ Direct', 'DIRECT');
- // docFrag.appendChild(opt);
- // this.proxyCache['DIRECT'] = {
- // type: 'direct',
- // hostname: 'DIRECT'
- // };
+ this.quickAdd.appendChild(docFrag.cloneNode(true));
+ this.tabProxy.appendChild(docFrag);
- this.select.appendChild(docFrag);
+ App.firefox && this.checkTabProxy();
+ }
+
+ static async checkTabProxy() {
+ const [tab] = await browser.tabs.query({currentWindow: true, active: true});
+ if (!/https?:\/\/.+/.test(tab.url)) { return; } // unacceptable URLs
+
+ this.tab = tab; // cache tab
+ const item = await browser.runtime.sendMessage({id: 'getTabProxy'});
+ item && (this.tabProxy.value = `${item.hostname}:${item.port}`);
}
static processSelect(mode) {
@@ -124,34 +144,34 @@ class Popup {
window.close();
break;
- case 'quickAdd':
- if (!this.select.value) { break; }
- if (pref.managed) { break; } // not for storage.managed
+ // case 'quickAdd':
+ // if (!this.quickAdd.value) { break; }
+ // if (pref.managed) { break; } // not for storage.managed
- browser.runtime.sendMessage({id: 'quickAdd', pref, host: this.select.value});
- this.select.selectedIndex = 0; // reset select option
- break;
+ // browser.runtime.sendMessage({id: 'quickAdd', pref, host: this.quickAdd.value});
+ // this.quickAdd.selectedIndex = 0; // reset select option
+ // break;
case 'excludeHost':
if (pref.managed) { break; } // not for storage.managed
- browser.runtime.sendMessage({id: 'excludeHost', pref});
+ browser.runtime.sendMessage({id: 'excludeHost', pref, tab: this.tab});
break;
- case 'setTabProxy':
- if (!App.firefox || !this.select.value) { break; } // firefox only
- if (pref.managed) { break; } // not for storage.managed
+ // case 'setTabProxy':
+ // if (!App.firefox || !this.tabProxy.value) { break; } // firefox only
+ // if (pref.managed) { break; } // not for storage.managed
- browser.runtime.sendMessage({id: 'setTabProxy', proxy: this.proxyCache[this.select.value]});
- this.select.selectedIndex = 0; // reset select option
- break;
+ // this.tabId && browser.runtime.sendMessage({id: 'setTabProxy', proxy: this.proxyCache[this.tabProxy.value], tabId: this.tabId});
+ // // this.tabProxy.selectedIndex = 0; // reset select option
+ // break;
- case 'unsetTabProxy':
- if (!App.firefox) { break; } // firefox only
- if (pref.managed) { break; } // not for storage.managed
+ // case 'unsetTabProxy':
+ // if (!App.firefox) { break; } // firefox only
+ // if (pref.managed) { break; } // not for storage.managed
- browser.runtime.sendMessage({id: 'unsetTabProxy'});
- break;
+ // browser.runtime.sendMessage({id: 'unsetTabProxy'});
+ // break;
}
}
diff --git a/src/content/proxy.js b/src/content/proxy.js
index 190996a..d093304 100644
--- a/src/content/proxy.js
+++ b/src/content/proxy.js
@@ -23,7 +23,7 @@ export class Proxy {
}
static onMessage(message) {
- const {id, pref, host, proxy, dark} = message;
+ const {id, pref, host, proxy, dark, tab} = message;
switch (id) {
case 'setProxy':
Action.dark = dark;
@@ -31,20 +31,23 @@ export class Proxy {
break;
case 'quickAdd':
- this.quickAdd(pref, host);
+ this.quickAdd(pref, host, tab);
break;
case 'excludeHost':
- this.excludeHost(pref);
+ this.excludeHost(pref, tab);
break;
case 'setTabProxy':
- OnRequest.setTabProxy(proxy);
+ OnRequest.setTabProxy(tab, proxy);
break;
- case 'unsetTabProxy':
- OnRequest.unsetTabProxy();
- break;
+ case 'getTabProxy':
+ return OnRequest.tabProxy[tab.id];
+
+ // case 'unsetTabProxy':
+ // OnRequest.unsetTabProxy();
+ // break;
}
}
@@ -233,6 +236,7 @@ if (find${idx} !== 'DIRECT') { return find${idx}; }`).join('\n\n');
// https://developer.chrome.com/docs/extensions/reference/proxy/#type-PacScript
// https://github.com/w3c/webextensions/issues/339
// Chrome pacScript doesn't support bypassList
+ // https://issues.chromium.org/issues/40286640
// isInNet(host, "192.0.2.172", "255.255.255.255")
@@ -268,13 +272,13 @@ String.raw`function FindProxyForURL(url, host) {
default:
type = type.toUpperCase();
}
- return `${type} ${hostname}:${parseInt(port)}`; // prepare for augmented port
+ return `${type} ${hostname}:${parseInt(port)}`; // prepare for augmented port
}
// ---------- Quick Add/Exclude Host ---------------------
- static async quickAdd(pref, host) {
- const activeTab = await this.getActiveTab();
- const url = this.getURL(activeTab[0].url);
+ static quickAdd(pref, host, tab) {
+ // const activeTab = tab ? [tab] : await this.getActiveTab();
+ const url = this.getURL(tab.url);
if (!url) { return; }
const pattern = '^' + url.origin.replaceAll('.', '\\.') + '/';
@@ -294,9 +298,9 @@ String.raw`function FindProxyForURL(url, host) {
}
// Chrome commands returns command, tab
- static async excludeHost(pref, tab) {
- const activeTab = tab ? [tab] : await this.getActiveTab();
- const url = this.getURL(activeTab[0].url);
+ static excludeHost(pref, tab) {
+ // const activeTab = tab ? [tab] : await this.getActiveTab();
+ const url = this.getURL(tab.url);
if (!url) { return; }
const pattern = url.host;
@@ -313,9 +317,9 @@ String.raw`function FindProxyForURL(url, host) {
this.set(pref); // update Proxy
}
- static getActiveTab() {
- return browser.tabs.query({currentWindow: true, active: true});
- }
+ // static getActiveTab() {
+ // return browser.tabs.query({currentWindow: true, active: true});
+ // }
static getURL(str) {
const url = new URL(str);
diff --git a/src/content/show.js b/src/content/show.js
index 2377171..2cc06bf 100644
--- a/src/content/show.js
+++ b/src/content/show.js
@@ -1,7 +1,6 @@
import {App} from './app.js';
// ---------- Show (Side Effect) -----------
-// eslint-disable-next-line no-unused-vars
class Show {
static {
diff --git a/src/content/theme.css b/src/content/theme.css
index 726eb85..5f0e4cb 100644
--- a/src/content/theme.css
+++ b/src/content/theme.css
@@ -33,7 +33,6 @@
background-color: var(--hover);
}
-
@media screen and (prefers-color-scheme: dark) {
:root.moonlight {
--bg: #333;