This commit is contained in:
Ray Lothian 2020-12-28 14:17:45 +01:00
parent b9a23d8ce2
commit ae8219dd9e
303 changed files with 321 additions and 24 deletions

View file

@ -0,0 +1,147 @@
:root {
--color: #444;
--bg-color: #fff;
--color-admin: #444;
--color-desc: #2f2f2f;
--bg-desc: #e8e8e8;
--color-note: #2f2f2f;
--bg-note: #e8e8e8;
--bg-admin: #ffffed;
--border-admin: #e8ec3a;
--color-light: #636363;
--bg-color-light: #f5f5f5;
--bg-focus: #eff6f9;
}
@media (prefers-color-scheme: dark) {
:root {
--color: #d1d2cc;
--bg-color: #272d37;
--color-light: #f5f5f5;
--bg-color-light: #343946;
--bg-focus: #454b5d;
}
}
body {
min-width: 600px;
color: var(--color);
background-color: var(--bg-color);
}
@media (pointer: none), (pointer: coarse) {
body {
min-width: unset;
}
}
@supports (-moz-appearance:none) {
body {
font-size: 13px;
font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
margin: 10px;
min-width: unset;
}
textarea {
padding: 5px;
}
button,
input[type=submit],
input[type=button] {
height: 24px;
color: #444;
background-image: linear-gradient(rgb(237, 237, 237), rgb(237, 237, 237) 38%, rgb(222, 222, 222));
box-shadow: rgba(0, 0, 0, 0.08) 0 1px 0, rgba(255, 255, 255, 0.75) 0 1px 2px inset;
text-shadow: rgb(240, 240, 240) 0 1px 0;
border: solid 1px rgba(0, 0, 0, 0.25);
}
input[type=button]:disabled {
opacity: 0.5;
}
}
button,
input[type=submit],
input[type=button] {
cursor: pointer;
}
textarea {
color: var(--color-light);
background-color: var(--bg-color-light);
border: none;
width: 100%;
outline: none;
}
textarea:focus {
background-color: var(--bg-focus);
}
h1 {
font-size: 15px;
font-weight: normal;
}
.mode-2,
.mode {
display: grid;
white-space: nowrap;
align-items: center;
grid-gap: 5px;
}
.mode {
grid-template-columns: min-content min-content min-content;
}
.mode-2 {
grid-template-columns: min-content min-content;
}
#toggle-sibling-desc,
#toggle-parser-desc,
#toggle-protected-desc,
#toggle-custom-desc,
#toggle-whitelist-desc,
#toggle-blacklist-desc {
cursor: pointer;
color: var(--color-desc);
background-color: var(--bg-desc);
padding: 1px 4px;
}
.note {
background-color: var(--bg-note);
color: var(--color-note);
padding: 5px;
margin: 0 0 10px 0;
}
.hidden {
display: none;
}
.checked {
display: grid;
grid-template-columns: min-content 1fr;
grid-gap: 5px;
margin: 10px 0;
}
#tools,
#backup {
display: grid;
white-space: nowrap;
grid-gap: 5px;
}
#backup {
margin: 10px 0;
grid-template-columns: min-content min-content;
}
#tools {
grid-template-columns: repeat(4, min-content) 1fr;
align-items: center;
}
@media screen and (max-width: 600px) {
#backup {
grid-template-columns: 1fr 1fr;
}
#tools {
grid-template-columns: 1fr 1fr;
}
}
.admin {
color: var(--color-admin);
background-color: var(--bg-admin);
border: solid 1px var(--border-admin);
padding: 10px;
margin: 15px 0;
}

View file

@ -0,0 +1,81 @@
<!DOCTYPE html>
<html>
<head>
<title data-localize="userAgentSwitcherandManagerOptions">User-Agent Switcher and Manager :: Options</title>
<link rel="stylesheet" type="text/css" href="index.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div class="mode">
<input type="radio" name="mode" value="blacklist" id="mode-blacklist">
<label for="mode-blacklist"><h1 data-localize="blackListMode">Black-List Mode</h1></label>
<span id="toggle-blacklist-desc" data-localize="description">Description</span>
</div>
<p for="toggle-blacklist-desc" class="note hidden" data-localize="blackListModeDescription">Apply the custom user-agent string to all tabs except the tabs with the following top-level hostnames (comma-separated list of hostnames). Note that even if a window-based user-agent string is set from the toolbar popup, your browser's default user-agent string is used.</p>
<textarea id="blacklist" rows="3" placeholder="e.g.: www.google.com, www.bing.com"></textarea>
<div class="mode">
<input type="radio" name="mode" value="whitelist" id="mode-whitelist">
<label for="mode-whitelist"><h1 data-localize="whiteListMode">White-List Mode</h1></label>
<span id="toggle-whitelist-desc" data-localize="description">Description</span>
</div>
<p for="toggle-whitelist-desc" class="note hidden" data-localize="whiteListModeDescription">Only apply the custom user-agent string to the tabs with following top-level hostnames. Note that if a window-based user-agent string is set from the toolbar popup, this user-agent will overwrite the global one.</p>
<textarea id="whitelist" rows="3" placeholder="e.g.: www.google.com, www.bing.com"></textarea>
<div class="mode">
<input type="radio" name="mode" value="custom" id="mode-custom">
<label for="mode-custom"><h1 data-localize="customMode">Custom Mode</h1></label>
<span id="toggle-custom-desc" data-localize="description">Description</span>
</div>
<p for="toggle-custom-desc" class="note hidden"><span data-localize="customModeDescription">Try to resolve the user-agent string from a JSON object; otherwise either use the default user-agent string or use the one that the user is set from the popup interface. Use "*" as the hostname to match all domains. You can randomly select from multiple user-agent strings by providing an array instead of a fixed string. If there is a "_" key in your JSON object which refers to an array of hostnames, then the extension only randomly selects the user-agent string once for each hostname inside this list. This is useful if you don't want the random user-agent to change until this browser session is over.</span> <a href="#" id="sample" data-localize="insertSample">Insert a sample</a>.</p>
<textarea id="custom" rows="8" wrap="off"></textarea>
<div class="checked">
<input type="checkbox" id="cache">
<label for="cache" data-localize="cache">Use caching to improve performance (recommended value is true). Uncheck this option only if you are using the custom mode and also you need the user-agent string to be altered from the provided list on every single request.</label>
<input type="checkbox" id="exactMatch">
<label for="exactMatch" data-localize="exactMatch">Use exact matching (if checked, you will need to insert all sub-domains in the white-list and black-list modes to be considered. If unchecked, all the sub-domains are passing the matching condition (e.g: www.google.com passes the matching if google.com is in the list))</label>
<input type="checkbox" id="faqs">
<label for="faqs" data-localize="faqs">Open FAQs page on updates</label>
<input type="checkbox" id="log">
<label for="log" data-localize="log">Display debugging info in the browser console</label>
</div>
<div class="mode-2">
<h1 data-localize="disableSpoofing">Disable Spoofing</h1>
<span id="toggle-protected-desc" data-localize="description">Description</span>
</div>
<p for="toggle-protected-desc" class="note hidden" data-localize="disableSpoofingDescription">A comma-separated list of keywords that the extension should not spoof the user-agent header. Use this list to protect URLs that contain these protected keywords. Each keyword need to be at least 5 char long.</p>
<textarea id="protected" rows="3" wrap="off"></textarea>
<div class="mode-2">
<h1 data-localize="customUserAgentParsing">Custom User-Agent Parsing</h1>
<span id="toggle-parser-desc" data-localize="description">Description</span>
</div>
<p for="toggle-parser-desc" class="note hidden"><span data-localize="customUserAgentParsingDescription">A JSON object to bypass the internal user-agent string parsing method. The keys are the actual user-agent strings and the value of each key is an object of the keys that need to be set for the "navigator" object. You can use the "[delete]" keyword if you want a key in the "navigator" object to get deleted.</span> <a href="#" id="sample-2" data-localize="insertSample">Insert a sample</a>.</p>
<textarea id="parser" rows="5" wrap="off"></textarea>
<div class="mode-2">
<h1 data-localize="siblingHostnames">Sibling Hostnames</h1>
<span id="toggle-sibling-desc" data-localize="description">Description</span>
</div>
<p for="toggle-sibling-desc" class="note hidden"><span data-localize="siblingHostnamesDescription">A JSON array that contains one or more groups of hostnames to have a single user-agent string per group. For all hostnames in one group, the user-agent string calculation only occurs once, and all the other members use the same calculated string. This is useful to make sure a group of connected websites only access to the same user-agent string.</span> <a href="#" id="sample-3" data-localize="insertSample">Insert a sample</a>.</p>
<textarea id="siblings" rows="5" wrap="off"></textarea>
<div class="admin" data-localize="managedStorage">This extension supports managed storage. All the preferences can be pre-configured by the domain administrator</div>
<div id="backup">
<button id="import" data-localize="importSettings">Import Settings</button>
<button data-localized-title="exportSettingsTitle" title="To generate minified version, press Shift key while pressing this button" id="export" data-localize="exportSettings">Export Settings</button>
</div>
<div id="tools">
<button id="help" data-localize="help">FAQs Page (Help)</button>
<button id="donate" data-localize="donate">Support Development</button>
<button id="reset" data-localize="reset">Reset</button>
<button id="save" data-localize="save">Save</button>
<span id="status"></span>
</div>
<script src="index.js"></script>
</body>
</html>

View file

@ -0,0 +1,277 @@
'use strict';
// localization
document.querySelectorAll('[data-localize]').forEach(e => {
const ref = e.dataset.localize;
const translated = chrome.i18n.getMessage(ref);
if (translated) {
e.textContent = translated;
}
});
document.querySelectorAll('[data-localized-title]').forEach(e => {
const ref = e.dataset.localizedTitle;
const translated = chrome.i18n.getMessage(ref);
if (translated) {
e.title = translated;
}
});
function notify(msg, period = 750) {
// Update status to let user know options were saved.
const status = document.getElementById('status');
status.textContent = msg;
clearTimeout(notify.id);
notify.id = setTimeout(() => status.textContent = '', period);
}
function prepare(str) {
return str.split(/\s*,\s*/)
.map(s => s.replace('http://', '')
.replace('https://', '').split('/')[0].trim())
.filter((h, i, l) => h && l.indexOf(h) === i);
}
function save() {
let custom = {};
const c = document.getElementById('custom').value;
try {
custom = JSON.parse(c);
}
catch (e) {
window.setTimeout(() => {
notify('Custom JSON error: ' + e.message, 5000);
alert('Custom JSON error: ' + e.message);
document.getElementById('custom').value = c;
}, 1000);
}
let parser = {};
const p = document.getElementById('parser').value;
try {
parser = JSON.parse(p);
}
catch (e) {
window.setTimeout(() => {
notify('Parser JSON error: ' + e.message, 5000);
alert('Parser JSON error: ' + e.message);
document.getElementById('parser').value = p;
}, 1000);
}
let siblings = {};
const s = document.getElementById('siblings').value;
try {
siblings = JSON.parse(s);
siblings = siblings.reduce((p, c, i) => {
c.forEach(hostname => p[hostname] = i);
return p;
}, {});
}
catch (e) {
window.setTimeout(() => {
notify('Sibling JSON error: ' + e.message, 5000);
alert('Sibling JSON error: ' + e.message);
document.getElementById('siblings').value = s;
}, 1000);
}
chrome.storage.local.set({
exactMatch: document.getElementById('exactMatch').checked,
faqs: document.getElementById('faqs').checked,
log: document.getElementById('log').checked,
cache: document.getElementById('cache').checked,
blacklist: prepare(document.getElementById('blacklist').value),
whitelist: prepare(document.getElementById('whitelist').value),
custom,
parser,
siblings,
mode: document.querySelector('[name="mode"]:checked').value,
protected: document.getElementById('protected').value.split(/\s*,\s*/).filter(s => s.length > 4)
}, () => {
restore();
notify(chrome.i18n.getMessage('optionsSaved'));
chrome.contextMenus.update(document.querySelector('[name="mode"]:checked').value, {
checked: true
});
});
}
function restore() {
chrome.storage.local.get({
exactMatch: false,
faqs: true,
log: false,
cache: true,
mode: 'blacklist',
whitelist: [],
blacklist: [],
custom: {},
parser: {},
siblings: {},
protected: ['google.com/recaptcha', 'gstatic.com/recaptcha']
}, prefs => {
document.getElementById('exactMatch').checked = prefs.exactMatch;
document.getElementById('faqs').checked = prefs.faqs;
document.getElementById('log').checked = prefs.log;
document.getElementById('cache').checked = prefs.cache;
document.querySelector(`[name="mode"][value="${prefs.mode}"`).checked = true;
document.getElementById('blacklist').value = prefs.blacklist.join(', ');
document.getElementById('whitelist').value = prefs.whitelist.join(', ');
document.getElementById('custom').value = JSON.stringify(prefs.custom, null, 2);
document.getElementById('parser').value = JSON.stringify(prefs.parser, null, 2);
document.getElementById('siblings').value = JSON.stringify(Object.entries(prefs.siblings).reduce((p, [hostname, index]) => {
p[index] = p[index] || [];
p[index].push(hostname);
return p;
}, []), null, 2);
document.getElementById('protected').value = prefs.protected.join(', ');
});
}
document.addEventListener('DOMContentLoaded', restore);
document.getElementById('save').addEventListener('click', save);
document.getElementById('sample').addEventListener('click', e => {
e.preventDefault();
document.getElementById('custom').value = JSON.stringify({
'www.google.com': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36',
'www.bing.com, www.yahoo.com, www.wikipedia.org': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0',
'www.example.com': ['random-useragent-1', 'random-user-agent-2'],
'*': 'useragent-for-all-hostnames'
}, null, 2);
});
document.getElementById('sample-2').addEventListener('click', e => {
e.preventDefault();
document.getElementById('parser').value = JSON.stringify({
'my-custom-useragent': {
'appVersion': 'custom app version',
'platform': 'custom platform',
'vendor': '[delete]',
'product': 'custom product',
'oscpu': 'custom oscpu',
'custom-variable': 'this is a custom variable'
}
}, null, 2);
});
document.getElementById('sample-3').addEventListener('click', e => {
e.preventDefault();
document.getElementById('siblings').value = JSON.stringify([[
'www.google.com', 'www.youtube.com', 'www.youtube.be'
], [
'www.gmx.com', 'www.mail.com'
]], null, 2);
});
document.getElementById('donate').addEventListener('click', () => {
chrome.tabs.create({
url: chrome.runtime.getManifest().homepage_url + '?rd=donate'
});
});
document.getElementById('reset').addEventListener('click', e => {
if (e.detail === 1) {
notify(chrome.i18n.getMessage('dbReset'));
}
else {
localStorage.clear();
chrome.storage.local.clear(() => {
chrome.runtime.reload();
window.close();
});
}
});
document.getElementById('help').addEventListener('click', () => {
chrome.tabs.create({
url: chrome.runtime.getManifest().homepage_url
});
});
// export
document.getElementById('export').addEventListener('click', e => {
const guid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
const r = Math.random() * 16 | 0;
const v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
chrome.storage.local.get(null, prefs => {
for (const key of Object.keys(prefs)) {
if (key && key.startsWith('cache.')) {
delete prefs[key];
}
}
const text = JSON.stringify(Object.assign({}, prefs, {
'json-guid': guid,
'json-forced': false
}), null, e.shiftKey ? '' : ' ');
const blob = new Blob([text], {type: 'application/json'});
const objectURL = URL.createObjectURL(blob);
Object.assign(document.createElement('a'), {
href: objectURL,
type: 'application/json',
download: 'useragent-switcher-preferences.json'
}).dispatchEvent(new MouseEvent('click'));
setTimeout(() => URL.revokeObjectURL(objectURL));
});
});
// import
document.getElementById('import').addEventListener('click', () => {
const input = document.createElement('input');
input.style.display = 'none';
input.type = 'file';
input.accept = '.json';
input.acceptCharset = 'utf-8';
document.body.appendChild(input);
input.initialValue = input.value;
input.onchange = readFile;
input.click();
function readFile() {
if (input.value !== input.initialValue) {
const file = input.files[0];
if (file.size > 100e6) {
console.warn('100MB backup? I don\'t believe you.');
return;
}
const reader = new FileReader();
reader.onloadend = event => {
input.remove();
const json = JSON.parse(event.target.result);
chrome.storage.local.clear(() => {
chrome.storage.local.set(json, () => {
window.close();
chrome.runtime.reload();
});
});
};
reader.readAsText(file, 'utf-8');
}
}
});
/* toggle */
document.getElementById('toggle-blacklist-desc').addEventListener('click', () => {
document.querySelector('[for="toggle-blacklist-desc"]').classList.toggle('hidden');
});
document.getElementById('toggle-whitelist-desc').addEventListener('click', () => {
document.querySelector('[for="toggle-whitelist-desc"]').classList.toggle('hidden');
});
document.getElementById('toggle-custom-desc').addEventListener('click', () => {
document.querySelector('[for="toggle-custom-desc"]').classList.toggle('hidden');
});
document.getElementById('toggle-protected-desc').addEventListener('click', () => {
document.querySelector('[for="toggle-protected-desc"]').classList.toggle('hidden');
});
document.getElementById('toggle-parser-desc').addEventListener('click', () => {
document.querySelector('[for="toggle-parser-desc"]').classList.toggle('hidden');
});
document.getElementById('toggle-sibling-desc').addEventListener('click', () => {
document.querySelector('[for="toggle-sibling-desc"]').classList.toggle('hidden');
});