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';
const IF_DEBUG = false;
{
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) {
const IF_DEBUG = false;
_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. ' +
'Be explicit if you swallow errors.');
throw new TypeError('Missing required argument. ' +
'Be explicit if you swallow errors.');
},
},
getProp(obj, path = this.mandatory()) {
throwIfError(err) {
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;
if(err) {
throw err;
}
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') +
` <a href="${ this.searchSettingsForUrl('proxy') }">
${ chrome.i18n.getMessage('which') }
</a>`;
// 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;
},
},
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) => {
chrome.tabs.create(
@ -78,6 +61,8 @@
window.apis.errorHandlers = {
state: window.utils.createStorage('handlers-'),
viewErrorVoid(type = window.utils.mandatory(), err) {
let errors = {};
@ -113,14 +98,14 @@
for(
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) {
return handlersState( ifPrefix + eventName);
return this.state( ifPrefix + eventName );
},

View File

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

View File

@ -22,14 +22,9 @@
{ // Private namespace starts.
const mandatory = window.utils.mandatory;
const throwIfError = function throwIfError(err) {
if(err) {
throw err;
}
};
const throwIfError = window.utils.throwIfError;
const chromified = window.utils.chromified;
const checkChromeError = window.utils.checkChromeError;
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 {
constructor(message = mandatory(), prevClarification) {

View File

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

View File

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