version 0.3.3

This commit is contained in:
Ray Lothian 2019-11-06 06:19:42 +00:00
parent b99fd158bb
commit b866177b86
9 changed files with 59 additions and 26 deletions

View file

@ -1,2 +1,2 @@
ua-parser.min.js: ua-parser.min.js:
https://github.com/faisalman/ua-parser-js/releases/tag/0.7.19 https://github.com/faisalman/ua-parser-js/releases/tag/0.7.20

View file

@ -2,6 +2,7 @@
'use strict'; 'use strict';
const cache = {}; const cache = {};
const tabs = {}; const tabs = {};
chrome.tabs.onRemoved.addListener(id => delete cache[id]); chrome.tabs.onRemoved.addListener(id => delete cache[id]);
@ -17,10 +18,15 @@ const prefs = {
cache: true, cache: true,
exactMatch: false, exactMatch: false,
protected: ['google.com/recaptcha', 'gstatic.com/recaptcha'], protected: ['google.com/recaptcha', 'gstatic.com/recaptcha'],
parser: {} // maps ua string to a ua object parser: {}, // maps ua string to a ua object,
log: false
}; };
const log = (...args) => prefs.log && console.log(...args);
// exand comma-separated keys of prefs.custom // exand comma-separated keys of prefs.custom
const expand = () => { const expand = () => {
log('expanding custom rules');
expand.rules = {}; expand.rules = {};
for (const key of Object.keys(prefs.custom)) { for (const key of Object.keys(prefs.custom)) {
for (const k of key.split(/\s*,\s*/)) { for (const k of key.split(/\s*,\s*/)) {
@ -82,14 +88,18 @@ const ua = {
'global': {} 'global': {}
}, },
diff(tabId) { // returns true if there is per window object diff(tabId) { // returns true if there is per window object
log('ua.diff is called', tabId);
const windowId = tabs[tabId]; const windowId = tabs[tabId];
return windowId in this._obj; return windowId in this._obj;
}, },
get windows() { get windows() {
log('ua.windows is called');
return Object.keys(this._obj).filter(id => id !== 'global').map(s => Number(s)); return Object.keys(this._obj).filter(id => id !== 'global').map(s => Number(s));
}, },
parse: s => { parse: s => {
log('ua.parse is called', s);
if (prefs.parser[s]) { if (prefs.parser[s]) {
log('ua.parse is resolved using parser');
return Object.assign({ return Object.assign({
userAgent: s userAgent: s
}, prefs.parser[s]); }, prefs.parser[s]);
@ -140,10 +150,12 @@ const ua = {
return o; return o;
}, },
object(tabId, windowId) { object(tabId, windowId) {
log('ua.object is called', tabId, windowId);
windowId = windowId || (tabId ? tabs[tabId] : 'global'); windowId = windowId || (tabId ? tabs[tabId] : 'global');
return this._obj[windowId] || this._obj.global; return this._obj[windowId] || this._obj.global;
}, },
string(str, windowId) { string(str, windowId) {
log('ua.string is called', str, windowId);
if (str) { if (str) {
this._obj[windowId] = this.parse(str); this._obj[windowId] = this.parse(str);
} }
@ -152,12 +164,14 @@ const ua = {
} }
}, },
tooltip(title, tabId) { tooltip(title, tabId) {
log('ua.tooltip is called', title, tabId);
chrome.browserAction.setTitle({ chrome.browserAction.setTitle({
title, title,
tabId tabId
}); });
}, },
icon(mode, tabId) { icon(mode, tabId) {
log('ua.icon is called', mode, tabId);
chrome.browserAction.setIcon({ chrome.browserAction.setIcon({
tabId, tabId,
path: { path: {
@ -172,6 +186,7 @@ const ua = {
}); });
}, },
toolbar: ({windowId, tabId}) => { toolbar: ({windowId, tabId}) => {
log('ua.toolbar is called', windowId, tabId);
if (windowId) { if (windowId) {
chrome.tabs.query({ chrome.tabs.query({
windowId windowId
@ -191,9 +206,13 @@ const ua = {
} }
}, },
update(str = prefs.ua, windowId = 'global') { update(str = prefs.ua, windowId = 'global') {
log('ua.update is called', str, windowId);
// clear caching
Object.keys(cache).forEach(key => delete cache[key]);
// remove old listeners
chrome.webRequest.onBeforeSendHeaders.removeListener(onBeforeSendHeaders); chrome.webRequest.onBeforeSendHeaders.removeListener(onBeforeSendHeaders);
chrome.webNavigation.onCommitted.removeListener(onCommitted); chrome.webNavigation.onCommitted.removeListener(onCommitted);
// apply new ones
if (str || prefs.mode === 'custom' || this.windows.length) { if (str || prefs.mode === 'custom' || this.windows.length) {
ua.string(str, windowId); ua.string(str, windowId);
chrome.webRequest.onBeforeSendHeaders.addListener(onBeforeSendHeaders, { chrome.webRequest.onBeforeSendHeaders.addListener(onBeforeSendHeaders, {
@ -224,6 +243,7 @@ if (chrome.windows) { // FF on Android
} }
function hostname(url) { function hostname(url) {
log('hostname', url);
const s = url.indexOf('//') + 2; const s = url.indexOf('//') + 2;
if (s > 1) { if (s > 1) {
let o = url.indexOf('/', s); let o = url.indexOf('/', s);
@ -246,6 +266,7 @@ function hostname(url) {
} }
// returns true, false or an object; true: ignore, false: use from ua object. // returns true, false or an object; true: ignore, false: use from ua object.
function match({url, tabId}) { function match({url, tabId}) {
log('match', url, tabId);
const h = hostname(url); const h = hostname(url);
if (prefs.mode === 'blacklist') { if (prefs.mode === 'blacklist') {
@ -364,10 +385,16 @@ const onCommitted = ({frameId, url, tabId}) => {
}\`; }\`;
document.documentElement.appendChild(script); document.documentElement.appendChild(script);
script.remove(); script.remove();
navigator.userAgent
}` }`
}, () => { }, r => {
if (chrome.runtime.lastError) { const lastError = chrome.runtime.lastError;
if (lastError &&
lastError.message !== 'No matching message handler' && // Firefox on Windows
lastError.message !== 'document.documentElement is null' // Firefox on Windows
) {
if (frameId === 0) { if (frameId === 0) {
console.log(lastError);
ua.tooltip('[Default] ' + navigator.userAgent, tabId); ua.tooltip('[Default] ' + navigator.userAgent, tabId);
ua.icon('ignored', tabId); ua.icon('ignored', tabId);
} }

View file

@ -24,7 +24,7 @@
<table width=100%> <table width=100%>
<tr> <tr>
<td> <td>
<label><input type="radio" name="mode" value="blacklist" id="mode-blacklist"> <span class="h">Black-list mode</span>: 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.</label> <label><input type="radio" name="mode" value="blacklist" id="mode-blacklist"> <span class="h">Black-List Mode</span>: 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.</label>
</td> </td>
</tr> </tr>
<tr> <tr>
@ -32,7 +32,7 @@
</tr> </tr>
<tr> <tr>
<td> <td>
<label><input type="radio" name="mode" value="whitelist" id="mode-whitelist"> <span class="h">White-list mode</span>: 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.</label> <label><input type="radio" name="mode" value="whitelist" id="mode-whitelist"> <span class="h">White-List Mode</span>: 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.</label>
</td> </td>
</tr> </tr>
<tr> <tr>
@ -40,20 +40,12 @@
</tr> </tr>
<tr> <tr>
<td> <td>
<label><input type="radio" name="mode" value="custom" id="mode-custom"> <span class="h">Custom mode</span>: 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.</label> Press <a href="#" id="sample">here</a> to insert a sample JSON object. <label><input type="radio" name="mode" value="custom" id="mode-custom"> <span class="h">Custom Mode</span>: 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.</label> Press <a href="#" id="sample">here</a> to insert a sample JSON object.
</td> </td>
</tr> </tr>
<tr> <tr>
<td class="spacer"><textarea id="custom" rows="5" wrap="off"></textarea></td> <td class="spacer"><textarea id="custom" rows="5" wrap="off"></textarea></td>
</tr> </tr>
<tr>
<td>
<label><span class="h">Custom user-agent string parsing</span>: 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.</label> Press <a href="#" id="sample-2">here</a> to insert a sample JSON object.
</td>
</tr>
<tr>
<td class="spacer"><textarea id="parser" rows="5" wrap="off"></textarea></td>
</tr>
<tr> <tr>
<td class="spacer"><label><input type="checkbox" id="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></td> <td class="spacer"><label><input type="checkbox" id="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></td>
</tr> </tr>
@ -64,11 +56,22 @@
<td class="spacer"><label><input type="checkbox" id="faqs"> Open FAQs page on updates</label></td> <td class="spacer"><label><input type="checkbox" id="faqs"> Open FAQs page on updates</label></td>
</tr> </tr>
<tr> <tr>
<td>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.</td> <td class="spacer"><label><input type="checkbox" id="log"> Display debuging info in the browser console</label></td>
</tr>
<tr>
<td><span class="h">Disable Spoofing</span> 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.</td>
</tr> </tr>
<tr> <tr>
<td><textarea id="protected" rows="3" wrap="off"></textarea></td> <td><textarea id="protected" rows="3" wrap="off"></textarea></td>
</tr> </tr>
<tr>
<td>
<span class="h">Custom User-Agent Parsing</span>: 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. Press <a href="#" id="sample-2">here</a> to insert a sample JSON object.
</td>
</tr>
<tr>
<td class="spacer"><textarea id="parser" rows="5" wrap="off"></textarea></td>
</tr>
</table> </table>
<p> <p>

View file

@ -43,6 +43,7 @@ function save() {
chrome.storage.local.set({ chrome.storage.local.set({
exactMatch: document.getElementById('exactMatch').checked, exactMatch: document.getElementById('exactMatch').checked,
faqs: document.getElementById('faqs').checked, faqs: document.getElementById('faqs').checked,
log: document.getElementById('log').checked,
cache: document.getElementById('cache').checked, cache: document.getElementById('cache').checked,
blacklist: prepare(document.getElementById('blacklist').value), blacklist: prepare(document.getElementById('blacklist').value),
whitelist: prepare(document.getElementById('whitelist').value), whitelist: prepare(document.getElementById('whitelist').value),
@ -63,6 +64,7 @@ function restore() {
chrome.storage.local.get({ chrome.storage.local.get({
exactMatch: false, exactMatch: false,
faqs: true, faqs: true,
log: false,
cache: true, cache: true,
mode: 'blacklist', mode: 'blacklist',
whitelist: [], whitelist: [],
@ -73,6 +75,7 @@ function restore() {
}, prefs => { }, prefs => {
document.getElementById('exactMatch').checked = prefs.exactMatch; document.getElementById('exactMatch').checked = prefs.exactMatch;
document.getElementById('faqs').checked = prefs.faqs; document.getElementById('faqs').checked = prefs.faqs;
document.getElementById('log').checked = prefs.log;
document.getElementById('cache').checked = prefs.cache; document.getElementById('cache').checked = prefs.cache;
document.querySelector(`[name="mode"][value="${prefs.mode}"`).checked = true; document.querySelector(`[name="mode"][value="${prefs.mode}"`).checked = true;
document.getElementById('blacklist').value = prefs.blacklist.join(', '); document.getElementById('blacklist').value = prefs.blacklist.join(', ');

View file

@ -26,7 +26,7 @@ body {
background-color: #fff; background-color: #fff;
font-family: "Helvetica Neue", Helvetica, sans-serif; font-family: "Helvetica Neue", Helvetica, sans-serif;
font-size: 13px; font-size: 13px;
width: 650px; min-width: 650px;
margin: 0; margin: 0;
} }
table { table {
@ -89,10 +89,10 @@ select {
#list { #list {
overflow: auto; overflow: auto;
scroll-behavior: smooth; scroll-behavior: smooth;
height: 200px; height: 268px;
margin-bottom: 16px; margin-bottom: 16px;
color: #000; color: #000;
background-position: top 88px center; background-position: top 120px center;
background-repeat: no-repeat; background-repeat: no-repeat;
font-size: 11px; font-size: 11px;
} }
@ -210,6 +210,7 @@ body[data-android="true"] [data-cmd="window"] {
} }
#view { #view {
background-color: #f5f5f5; background-color: #f5f5f5;
padding: 5px 0;
} }
#view td { #view td {
text-align: right; text-align: right;

View file

@ -85,7 +85,6 @@ function update(ua) {
} }
document.addEventListener('change', ({target}) => { document.addEventListener('change', ({target}) => {
console.log(target);
if (target.closest('#filter')) { if (target.closest('#filter')) {
localStorage.setItem(target.id, target.value); localStorage.setItem(target.id, target.value);
chrome.storage.local.get({ chrome.storage.local.get({

BIN
extension/extension.zip Normal file

Binary file not shown.

View file

@ -2,7 +2,7 @@
"manifest_version": 2, "manifest_version": 2,
"name": "User-Agent Switcher and Manager", "name": "User-Agent Switcher and Manager",
"short_name": "useragent-switcher", "short_name": "useragent-switcher",
"version": "0.3.2", "version": "0.3.3",
"description": "A highly customizable extension to spoof the User-Agent string of your browser with a new one globally, randomly or per hostname", "description": "A highly customizable extension to spoof the User-Agent string of your browser with a new one globally, randomly or per hostname",

8
extension/ua-parser.min.js vendored Executable file → Normal file

File diff suppressed because one or more lines are too long