Add incontinence/hunting mode for kitchen, amend utils and kitchen, refactor informer

This commit is contained in:
Ilya Ig. Petrov 2017-01-17 15:00:53 +00:00
parent bbd134b871
commit a9eef144a9
7 changed files with 245 additions and 179 deletions

View File

@ -1,74 +1,139 @@
'use strict'; 'use strict';
const IF_DEBUG = false; {
if (!IF_DEBUG) { const IF_DEBUG = false;
// I believe logging objects precludes them from being GCed.
// I also don't remove logs for sake of client-side troubleshooting
// (though no one sent me logs so far).
['log', 'warn', 'error'].forEach( (meth) => {
const _meth = window.console[meth].bind(console);
window.console[meth] = function(...args) {
_meth(...args.map((a) => '' + a)); if (!IF_DEBUG) {
// I believe logging objects precludes them from being GCed.
// I also don't remove logs for sake of client-side troubleshooting
// (though no one sent me logs so far).
['log', 'warn', 'error'].forEach( (meth) => {
const _meth = window.console[meth].bind(console);
window.console[meth] = function(...args) {
}; _meth(...args.map((a) => '' + a));
});
}
};
});
}
window.utils = { const self = window.utils = {
mandatory() { mandatory() {
throw new TypeError('Missing required argument. ' + throw new TypeError('Missing required argument. ' +
'Be explicit if you swallow errors.'); 'Be explicit if you swallow errors.');
}, },
getProp(obj, path = this.mandatory()) { throwIfError(err) {
const props = path.split('.'); if(err) {
if (!props.length) { throw err;
throw new TypeError('Property must be supplied.');
}
const lastProp = props.pop();
for( const prop of props ) {
if (!(prop in obj)) {
return undefined;
} }
obj = obj[prop];
}
return obj[lastProp];
},
areSettingsNotControlledFor(details) {
return ['controlled_by_other', 'not_controllable']
.some( (prefix) => details.levelOfControl.startsWith(prefix) );
},
messages: {
searchSettingsForUrl(niddle) {
return 'chrome://settings/search#' + (chrome.i18n.getMessage(niddle) || niddle);
}, },
whichExtensionHtml() { checkChromeError(betterStack) {
return chrome.i18n.getMessage('noControl') + // Chrome API calls your cb in a context different from the point of API
` <a href="${ this.searchSettingsForUrl('proxy') }"> // method invokation.
${ chrome.i18n.getMessage('which') } const err = chrome.runtime.lastError || chrome.extension.lastError;
</a>`; if (err) {
const args = ['API returned error:', err];
if (betterStack) {
args.push('\n' + betterStack);
}
console.warn(...args);
}
return err;
}, },
}, chromified(cb = self.mandatory(), ...replaceArgs) {
}; const stack = (new Error()).stack;
// Take error first callback and convert it to chrome api callback.
return function(...args) {
window.apis = {}; if (replaceArgs.length) {
args = replaceArgs;
}
const err = self.checkChromeError(stack);
// setTimeout fixes error context.
setTimeout( cb.bind(null, err, ...args), 0 );
};
},
getProp(obj, path = self.mandatory()) {
const props = path.split('.');
if (!props.length) {
throw new TypeError('Property must be supplied.');
}
const lastProp = props.pop();
for( const prop of props ) {
if (!(prop in obj)) {
return undefined;
}
obj = obj[prop];
}
return obj[lastProp];
},
createStorage(prefix) {
return function state(key, value) {
key = prefix + key;
if (value === null) {
return localStorage.removeItem(key);
}
if (value === undefined) {
const item = localStorage.getItem(key);
return item && JSON.parse(item);
}
if (value instanceof Date) {
throw new TypeError('Converting Date format to JSON is not supported.');
}
localStorage.setItem(key, JSON.stringify(value));
}
},
areSettingsNotControlledFor(details) {
return ['controlled_by_other', 'not_controllable']
.some( (prefix) => details.levelOfControl.startsWith(prefix) );
},
messages: {
searchSettingsForUrl(niddle) {
return 'chrome://settings/search#' + (chrome.i18n.getMessage(niddle) || niddle);
},
whichExtensionHtml() {
return chrome.i18n.getMessage('noControl') +
` <a href="${ this.searchSettingsForUrl('proxy') }">
${ chrome.i18n.getMessage('which') }
</a>`;
},
},
};
window.apis = {};
}

View File

@ -47,23 +47,6 @@
} }
const handlersState = function(key, value) {
key = 'handlers-' + key;
if (value === null) {
return localStorage.removeItem(key);
}
if (value === undefined) {
const item = localStorage.getItem(key);
return item && JSON.parse(item);
}
if (value instanceof Date) {
throw new TypeError('Converting Date format to JSON is not supported.');
}
localStorage.setItem(key, JSON.stringify(value));
};
const openAndFocus = (url) => { const openAndFocus = (url) => {
chrome.tabs.create( chrome.tabs.create(
@ -78,6 +61,8 @@
window.apis.errorHandlers = { window.apis.errorHandlers = {
state: window.utils.createStorage('handlers-'),
viewErrorVoid(type = window.utils.mandatory(), err) { viewErrorVoid(type = window.utils.mandatory(), err) {
let errors = {}; let errors = {};
@ -113,14 +98,14 @@
for( for(
const name of (eventName ? [eventName] : this.getEventsMap().keys() ) const name of (eventName ? [eventName] : this.getEventsMap().keys() )
) { ) {
handlersState( ifPrefix + name, onOffStr === 'on' ? 'on' : null ); this.state( ifPrefix + name, onOffStr === 'on' ? 'on' : null );
} }
}, },
isOn(eventName) { isOn(eventName) {
return handlersState( ifPrefix + eventName); return this.state( ifPrefix + eventName );
}, },

View File

@ -2,21 +2,26 @@
{ // Private namespace starts. { // Private namespace starts.
const kitchenStorageKey = 'pac-kitchen-kept-mods'; const mandatory = window.utils.mandatory;
const throwIfError = window.utils.throwIfError;
const chromified = window.utils.chromified;
const kitchenStartsMark = '\n\n//%#@@@@@@ PAC_KITCHEN_STARTS @@@@@@#%'; const kitchenStartsMark = '\n\n//%#@@@@@@ PAC_KITCHEN_STARTS @@@@@@#%';
const kitchenState = window.utils.createStorage('pac-kitchen-');
const ifIncontinence = 'if-incontinence';
const configs = { const configs = {
ifProxyHttpsUrlsOnly: { ifProxyHttpsUrlsOnly: {
dflt: false, dflt: false,
label: 'проксировать только HTTP<span style="border-bottom: 1px solid black">S</span>-сайты', label: 'проксировать только HTTP<span style="border-bottom: 1px solid black">S</span>-сайты',
desc: 'Проксировать только сайты, доступные по шифрованному протоколу HTTPS. Прокси и провайдер смогут видеть только адреса посещаемых вами ресурсов, но не их содержимое.', desc: 'Проксировать только сайты, доступные по шифрованному протоколу HTTPS. Прокси и провайдер смогут видеть только адреса проксируемых ресурсов, но не их содержимое.',
index: 0, index: 0,
}, },
ifUseSecureProxiesOnly: { ifUseSecureProxiesOnly: {
dflt: false, dflt: false,
label: 'только шифрованная связь с прокси', label: 'только шифрованная связь с прокси',
desc: 'Шифровать соединение до прокси от провайдера. Провайдер всё же сможет видеть адреса (но не содержимое) посещаемых вами ресурсов из протокола DNS.', desc: 'Шифровать соединение до прокси от провайдера. Провайдер всё же сможет видеть адреса (но не содержимое) проксируемых ресурсов из протокола DNS.',
index: 1, index: 1,
}, },
ifUsePacScriptProxies: { ifUsePacScriptProxies: {
@ -53,11 +58,11 @@
const getCurrentConfigs = function getCurrentConfigs() { const getCurrentConfigs = function getCurrentConfigs() {
const json = localStorage.getItem(kitchenStorageKey); const mods = kitchenState('mods');
if (!json) { if (!mods) {
return null; return null;
} }
return new PacModifiers(JSON.parse(json)); return new PacModifiers(mods);
}; };
@ -122,7 +127,7 @@
getConfigs: getOrderedConfigsForUser, getConfigs: getOrderedConfigsForUser,
cook(pacData, pacMods = window.utils.mandatory()) { cook(pacData, pacMods = mandatory()) {
return pacMods.ifNoMods ? pacData : pacData + `${ kitchenStartsMark } return pacMods.ifNoMods ? pacData : pacData + `${ kitchenStartsMark }
;+function(global) { ;+function(global) {
@ -183,23 +188,20 @@
}, },
keepCookedNow(pacMods = window.utils.mandatory(), cb) { _tryNowAsync(details, cb = throwIfError) {
if (typeof(pacMods) === 'function') { if (typeof(details) === 'function') {
cb = pacMods; cb = details;
const pacMods = getCurrentConfigs(); details = undefined;
if (!pacMods) {
return cb(TypeError('PAC mods were never initialized.'));
}
} else {
try {
pacMods = new PacModifiers(pacMods);
} catch(e) {
return cb(e);
}
localStorage.setItem(kitchenStorageKey, JSON.stringify(pacMods));
} }
chrome.proxy.settings.get({}, (details) => {
new Promise((resolve) =>
details
? resolve(details)
: chrome.proxy.settings.get({}, resolve)
).then( (details) => {
if ( if (
details.levelOfControl === 'controlled_by_this_extension' details.levelOfControl === 'controlled_by_this_extension'
@ -211,28 +213,60 @@
new RegExp(kitchenStartsMark + '[\\s\\S]*$', 'g'), new RegExp(kitchenStartsMark + '[\\s\\S]*$', 'g'),
'' ''
); );
return chrome.proxy.settings.set(details, cb); return chrome.proxy.settings.set(details, chromified(cb));
} }
} }
return cb(
null, kitchenState(ifIncontinence, true);
null, cb(new TypeError(
[new TypeError('PAC-скрипт не обнаружен, но настройки будут активированы при установке PAC-скрипта.')] 'Не найдено активного PAC-скрипта! Изменения будут применены при возвращении контроля настроек прокси или установке нового PAC-скрипта.'
); ));
}); });
}, },
checkIncontinence(details) {
if ( kitchenState(ifIncontinence) ) {
this._tryNowAsync(details, () => {/* Swallow. */});
}
},
keepCookedNowAsync(pacMods = mandatory(), cb = throwIfError) {
if (typeof(pacMods) === 'function') {
cb = pacMods;
const pacMods = getCurrentConfigs();
if (!pacMods) {
return cb(TypeError('PAC mods were never initialized and you haven\'t supplied any.'));
}
} else {
try {
pacMods = new PacModifiers(pacMods);
} catch(e) {
return cb(e);
}
kitchenState('mods', pacMods);
}
this._tryNowAsync( (err) => cb(null, null, err && [err]) );
},
resetToDefaultsVoid() { resetToDefaultsVoid() {
delete localStorage[kitchenStorageKey]; kitchenState('mods', null);
this.keepCookedNow({}); kitchenState(ifIncontinence, null);
this.keepCookedNowAsync({});
}, },
}; };
const pacKitchen = window.apis.pacKitchen;
const originalSet = chrome.proxy.settings.set.bind( chrome.proxy.settings ); const originalSet = chrome.proxy.settings.set.bind( chrome.proxy.settings );
chrome.proxy.settings.set = function(details, cb) { chrome.proxy.settings.set = function(details, cb) {
@ -243,10 +277,18 @@
} }
const pacMods = getCurrentConfigs(); const pacMods = getCurrentConfigs();
if (pacMods) { if (pacMods) {
pac.data = window.apis.pacKitchen.cook( pac.data, pacMods ); pac.data = pacKitchen.cook( pac.data, pacMods );
} }
originalSet({ value: details.value }, cb); originalSet({ value: details.value }, (/* No args. */) => {
kitchenState(ifIncontinence, null);
cb && cb();
});
}; };
pacKitchen.checkIncontinence();
chrome.proxy.settings.onChange.addListener( pacKitchen.checkIncontinence.bind(pacKitchen) );
} // Private namespace ends. } // Private namespace ends.

View File

@ -22,14 +22,9 @@
{ // Private namespace starts. { // Private namespace starts.
const mandatory = window.utils.mandatory; const mandatory = window.utils.mandatory;
const throwIfError = window.utils.throwIfError;
const throwIfError = function throwIfError(err) { const chromified = window.utils.chromified;
const checkChromeError = window.utils.checkChromeError;
if(err) {
throw err;
}
};
const asyncLogGroup = function asyncLogGroup(...args) { const asyncLogGroup = function asyncLogGroup(...args) {
@ -48,39 +43,6 @@
}; };
const checkChromeError = function checkChromeError(betterStack) {
// Chrome API calls your cb in a context different from the point of API
// method invokation.
const err = chrome.runtime.lastError || chrome.extension.lastError;
if (err) {
const args = ['API returned error:', err];
if (betterStack) {
args.push('\n' + betterStack);
}
console.warn(...args);
}
return err;
};
const chromified = function chromified(cb = mandatory(), ...replaceArgs) {
const stack = (new Error()).stack;
// Take error first callback and convert it to chrome api callback.
return function(...args) {
if (replaceArgs.length) {
args = replaceArgs;
}
const err = checkChromeError(stack);
// setTimeout fixes error context.
setTimeout( cb.bind(null, err, ...args), 0 );
};
};
class Clarification { class Clarification {
constructor(message = mandatory(), prevClarification) { constructor(message = mandatory(), prevClarification) {

View File

@ -14,50 +14,38 @@
Crazy parallel Chrome. Crazy parallel Chrome.
**/ **/
const antiCensorRu = window.apis.antiCensorRu;
window.chrome.browserAction.setBadgeBackgroundColor({
color: '#db4b2f',
});
window.tabWithError2ip = {}; // For errors only: Error? -> Check this IP!
{ {
window.chrome.browserAction.setBadgeBackgroundColor({
color: '#db4b2f',
});
const _tabCallbacks = {}; const _tabCallbacks = {};
function afterTabUpdated(tabId, cb) { const afterTabUpdated = function afterTabUpdated(tabId, cb) {
if (_tabCallbacks[tabId])
_tabCallbacks[tabId].push(cb); if (_tabCallbacks[tabId]) {
else _tabCallbacks[tabId] = [cb]; _tabCallbacks[tabId].push(cb);
} } else {
_tabCallbacks[tabId] = [cb];
}
};
const onTabUpdate = function onTabUpdate(tabId) {
function onTabUpdate(tabId) {
if (_tabCallbacks[tabId]) { if (_tabCallbacks[tabId]) {
_tabCallbacks[tabId].map( (f) => f() ); _tabCallbacks[tabId].map( (f) => f() );
delete _tabCallbacks[tabId]; delete _tabCallbacks[tabId];
} }
}
};
chrome.tabs.onUpdated.addListener( onTabUpdate ); chrome.tabs.onUpdated.addListener( onTabUpdate );
function isInsideTabWithIp(requestDetails) { const antiCensorRu = window.apis.antiCensorRu;
return requestDetails.tabId !== -1 && requestDetails.ip;
}
chrome.webRequest.onErrorOccurred.addListener( const updateTitle = function updateTitle(requestDetails, cb) {
(requestDetails) =>
isInsideTabWithIp(requestDetails)
&& isProxiedAndInformed(requestDetails),
{urls: ['<all_urls>']}
);
chrome.tabs.onRemoved.addListener( (tabId) => {
onTabUpdate(tabId);
delete window.tabWithError2ip[tabId];
});
function updateTitle(requestDetails, cb) {
chrome.browserAction.getTitle( chrome.browserAction.getTitle(
{tabId: requestDetails.tabId}, {tabId: requestDetails.tabId},
@ -74,6 +62,7 @@ window.tabWithError2ip = {}; // For errors only: Error? -> Check this IP!
const proxyTitle = 'Прокси:'; const proxyTitle = 'Прокси:';
if (!ifTitleSetAlready) { if (!ifTitleSetAlready) {
title = 'Разблокированы:\n' + indent + hostname + '\n' title = 'Разблокированы:\n' + indent + hostname + '\n'
+ proxyTitle + '\n' + indent + proxyHost; + proxyTitle + '\n' + indent + proxyHost;
ifShouldUpdateTitle = true; ifShouldUpdateTitle = true;
@ -84,6 +73,7 @@ window.tabWithError2ip = {}; // For errors only: Error? -> Check this IP!
}); });
} else { } else {
const hostsProxiesPair = title.split(proxyTitle); const hostsProxiesPair = title.split(proxyTitle);
if (hostsProxiesPair[1].indexOf(proxyHost) === -1) { if (hostsProxiesPair[1].indexOf(proxyHost) === -1) {
@ -95,6 +85,7 @@ window.tabWithError2ip = {}; // For errors only: Error? -> Check this IP!
} }
if (hostsProxiesPair[0].indexOf(hostname) === -1) { if (hostsProxiesPair[0].indexOf(hostname) === -1) {
title = title.replace( title = title.replace(
proxyTitle, proxyTitle,
indent + hostname + '\n' + proxyTitle indent + hostname + '\n' + proxyTitle
@ -119,6 +110,7 @@ window.tabWithError2ip = {}; // For errors only: Error? -> Check this IP!
); );
} }
} }
if (ifShouldUpdateTitle) { if (ifShouldUpdateTitle) {
@ -132,11 +124,12 @@ window.tabWithError2ip = {}; // For errors only: Error? -> Check this IP!
} }
); );
}
};
let previousUpdateTitleFinished = Promise.resolve(); let previousUpdateTitleFinished = Promise.resolve();
function isProxiedAndInformed(requestDetails) { const isProxiedAndInformed = function isProxiedAndInformed(requestDetails) {
if ( !(requestDetails.ip if ( !(requestDetails.ip
&& antiCensorRu.isProxied( requestDetails.ip )) ) { && antiCensorRu.isProxied( requestDetails.ip )) ) {
@ -156,7 +149,14 @@ window.tabWithError2ip = {}; // For errors only: Error? -> Check this IP!
); );
return true; return true;
}
};
const isInsideTabWithIp = function isInsideTabWithIp(requestDetails) {
return requestDetails.tabId !== -1 && requestDetails.ip;
};
chrome.webRequest.onResponseStarted.addListener( chrome.webRequest.onResponseStarted.addListener(
(requestDetails) => isInsideTabWithIp(requestDetails) (requestDetails) => isInsideTabWithIp(requestDetails)
@ -164,4 +164,11 @@ window.tabWithError2ip = {}; // For errors only: Error? -> Check this IP!
{urls: ['<all_urls>']} {urls: ['<all_urls>']}
); );
chrome.webRequest.onErrorOccurred.addListener(
(requestDetails) =>
isInsideTabWithIp(requestDetails)
&& isProxiedAndInformed(requestDetails),
{urls: ['<all_urls>']}
);
} }

View File

@ -48,6 +48,9 @@
margin: 0.6em 0; margin: 0.6em 0;
padding: 0; padding: 0;
} }
#none:checked + label {
color: red;
}
#configs-panel > header { #configs-panel > header {
margin: 0 0 0.4em; margin: 0 0 0.4em;
} }

View File

@ -79,10 +79,11 @@ chrome.runtime.getBackgroundPage( (backgroundPage) =>
const showErrors = (err, warns) => { const showErrors = (err, warns) => {
warns = warns || []; warns = warns || [];
backgroundPage.console.log('eeeEEEEE',warns)
const warning = warns const warning = warns
.map( .map(
(w) => '✘ ' + (w) => '✘ ' +
(w.clarification && w.clarification.message || w.message || '') (w && w.clarification && w.clarification.message || w.message || '')
) )
.join('<br/>'); .join('<br/>');
@ -241,6 +242,7 @@ chrome.runtime.getBackgroundPage( (backgroundPage) =>
} else { } else {
li.innerHTML += `<a href="${conf.url}" class="info-sign info-url">🛈</a><br/> li.innerHTML += `<a href="${conf.url}" class="info-sign info-url">🛈</a><br/>
<textarea <textarea
spellcheck="false"
placeholder="SOCKS5 localhost:9050; # TOR Expert placeholder="SOCKS5 localhost:9050; # TOR Expert
SOCKS5 localhost:9150; # TOR Browser SOCKS5 localhost:9150; # TOR Browser
HTTPS foobar.com:3143; HTTPS foobar.com:3143;