Remodulize, refactor, start adding exceptions

This commit is contained in:
Ilya Ig. Petrov 2017-01-23 13:25:44 +00:00
parent 170b101a48
commit ff5c51af11
11 changed files with 656 additions and 301 deletions

View File

@ -2,7 +2,7 @@
{ {
const IF_DEBUG = false; const IF_DEBUG = true;
if (!IF_DEBUG) { if (!IF_DEBUG) {
// I believe logging objects precludes them from being GCed. // I believe logging objects precludes them from being GCed.

View File

@ -0,0 +1,57 @@
'use strict';
/*
* Error Library
* PURPOSE 1:
* Allow wrapping errors with clarifications when they bubble up.
* Why:
* Sometimes low level errors may bubble up through a chain of callbacks.
* And when they reach top level of a user they loose context and convey
* nonsense like "Error 404: Can't find file".
* -- What file? WTH, I just hit update button?!
* PURPOSE 2:
* Supply separate class for warnings.
* Why:
* Some callbacks expect warnings which are like non-fatal errors.
* I want Warnings and FooError to be distinctable by code readers,
* so I create separate class for warnings.
**/
{
const mandatory = window.utils.mandatory;
const self = window.apis.errorsLib = {
// I don't use Error class, because we don't need stack here.
Warning: class {
constructor(message = mandatory()) {
this.message = message;
}
},
clarify: function(err, message = mandatory(), {data} = {}) {
if (!err) {
return err;
}
const warn = new self.Warning(message);
warn.wrapped = err;
if (data) {
warn.data = data;
}
return warn;
},
clarifyThen: function(message, cb = mandatory()) {
return (err, ...args) => cb( clarify(err, message), ...args );
},
};
}

View File

@ -0,0 +1,69 @@
'use strict';
{
const mandatory = window.utils.mandatory;
const errorsLib = window.apis.errorsLib;
const checkCon = 'Что-то не так с сетью, проверьте соединение.';
window.apis.httpLib = {
ifModifiedSince(
url,
lastModified,
cb = mandatory()
) {
const wasModified = new Date(0).toUTCString();
const notModifiedCode = 304;
fetch(url, {
method: 'HEAD',
headers: new Headers({
'If-Modified-Since': lastModified,
}),
}).then(
(res) => {
cb(
null,
res.status === notModifiedCode ?
false :
(res.headers.get('Last-Modified') || wasModified)
);
},
errorsLib.clarifyThen(checkCon, (err) => cb(err, wasModifed))
);
},
get(url, cb = mandatory()) {
const start = Date.now();
fetch(url, {cache: 'no-store'}).then(
(res) => {
const textCb =
(err) => res.text().then( (text) => cb(err, text), cb );
const status = res.status;
if ( !( status >= 200 && status < 300 || status === 304 ) ) {
return textCb(
errorsLib.clarify(
res,
'Получен ответ с неудачным HTTP-кодом ' + status + '.'
)
);
}
console.log('GETed with success:', url, Date.now() - start);
textCb();
},
errorsLib.clarifyThen(checkCon, cb)
);
},
};
}

View File

@ -0,0 +1,300 @@
'use strict';
{
const mandatory = window.utils.mandatory;
const httpLib = window.apis.httpLib;
const clarify = window.apis.errorsLib.clarify;
// IP REGEX starts.
const portOpt = '(:\\d+)?'; // The only capturing group, sic!
const ipv4portOpt = '(?:[0-9]{1,3}\\.){3}[0-9]{1,3}' + portOpt;
const ipv6nake = '(?:[0-9a-f]{0,4}:){2,7}[0-9a-f]{0,4}';
const ipv6portOpt = '(?:' + ipv6nake + '|' + '\\[' + ipv6nake + '\\]' + portOpt + ')';
const ipv4Re = new RegExp('^' + ipv4portOpt + '$');
const ipv6Re = new RegExp('^' + ipv6portOpt + '$');
const _match = function _match(ipRe, str) {
let m = (str.match(ipRe) || []).filter( (c) => c );
const port = m.length > 1 ? m.pop() : false;
return { ifMatched: m.length, port: port };
};
const _test = {
ipv4: _match.bind(null, ipv4Re),
ipv6: _match.bind(null, ipv6Re),
ipv4v6: function(str) {
let mr = this.ipv4(str);
if (mr.ifMatched) {
mr.ifv4 = true;
mr.canonical = str.replace(mr.port, '');
return mr;
}
mr = this.ipv6(str);
if (mr.ifMatched) {
mr.ifv6 = true;
mr.canonical = str.replace(mr.port, '').replace(/[\[\]]/g, '');
return mr;
}
return mr;
},
};
// IP REGEX ends.
const _state = window.utils.createStorage('ip-to-host');
const ip2host = '';
const privates = {};
const _getHostObj = function _getHostObj(hostStr) {
let hostObj = privates._strToHostObj[hostStr];
if (!hostObj) {
hostObj = privates._strToHostObj[hostStr] = { host: hostStr };
}
return hostObj;
};
const init = function init() {
// Defaults.
const _antizapret = {
/* Don't use directly, please.
Encoded to counter abuse. */
host: '\x70\x72\x6f\x78\x79\x2e\x61\x6e\x74\x69\x7a\x61\x70\x72\x65\x74\x2e\x70\x72\x6f\x73\x74\x6f\x76\x70\x6e\x2e\x6f\x72\x67',
};
privates._strToHostObj = {
[_antizapret.host]: _antizapret,
};
privates._ipToHostObj = {};
for( const ip of [
// IPs of Antizapret.
'195.123.209.38',
'137.74.171.91',
'51.15.39.201',
'2001:bc8:4700:2300::1:d07',
'2a02:27ac::10',
] ) {
privates._ipToHostObj[ip] = _antizapret;
}
// Persisted.
const ipToHost = _state(ip2host);
if (ipToHost) {
for( const ip of Object.keys(ipToHost) ) {
const host = ipToHost[ip];
privates._ipToHostObj[ip] = _getHostObj(host);
}
}
};
init();
const getIpsFor = function getIpsFor(host, cb = mandatory()) {
if (host.trim() === 'localhost') {
return cb(null, ['127.0.0.1', '::1']);
}
const types = [1, 28];
const promises = types.map(
(type) => new Promise((resolve) =>
httpLib.get(
'https://dns.google.com/resolve?type=' + type + '&name=' + host,
(err, res) => {
if (res) {
try {
res = JSON.parse(res);
console.log('Json parsed.');
if (err || res.Status) {
const msg = ['Answer', 'Comment', 'Status']
.filter( (prop) => res[prop] )
.map( (prop) => prop + ': ' + JSON.stringify( res[prop] ) )
.join(', \n');
err = clarify(err || {}, 'Сервер (json): ' + msg, {data: res});
} else {
res = res.Answer || [];
res = res.filter(
(record) => types.includes(record.type)
).map( (ans) => ans.data );
}
} catch(e) {
err = clarify(
e,
'Сервер (текст): ' + res, err ? {data: err} : undefined
);
}
}
resolve([err, res]);
}
)
)
);
Promise.all(promises).then(
([[v4err, v4res], [v6err, v6res]]) => {
if(v4err) {
return cb(v4err, v4res);
}
const ips = v4res;
let warns = [];
if (!v6err) {
ips.push(...v6res);
} else {
warns = [v6err];
}
cb(null, ips, ...warns);
}
);
};
const _canonize = function canonize(addrArr) {
const ipSet = new Set();
const hostSet = new Set();
for( const addr of addrArr ) {
const ipm = _test.ipv4v6(addr);
if (ipm.ifMatched) {
ipSet.add( ipm.canonical );
} else {
hostSet.add( addr.replace(/:\d+$/, '') );
}
}
console.log('Canonized hosts/ips:', hostSet.size + '/' + ipSet.size);
return [ipSet, hostSet];
}
const self = window.apis.ipToHost = {
persistVoid() {
console.log('Persisting ipToHost...', privates);
const ipToHost = {};
for( const ip of Object.keys(privates._ipToHostObj) ) {
ipToHost[ ip ] = privates._ipToHostObj[ ip ].host;
}
_state(ip2host, ipToHost);
},
resetToDefaultsVoid() {
_state(ip2host, null);
init();
},
_addAsync(hostStr, cb = mandatory()) {
getIpsFor(hostStr, (err, ips, ...warns) => {
console.log('IPS', ips);
if (!err) {
// Object may be shared, string can't.
const hostObj = _getHostObj(hostStr);
for(const ip of ips) {
privates._ipToHostObj[ip] = hostObj;
}
}
return cb(err, null, ...warns);
});
},
_replaceAllAsync(hostArr = mandatory(), cb) {
if (typeof(hostArr) === 'function') {
cb = hostArr;
hostArr = Object.keys(privates._strToHostObj);
}
this.resetToDefaultsVoid();
const promises = hostArr.map(
(hostStr) => new Promise( (resolve) => this._addAsync(hostStr, (...args) => resolve(args) ) )
);
Promise.all( promises ).then( (cbsRes) => {
const errors = cbsRes.map( ([err]) => err ).filter( (err) => err );
let newError;
const ifAllErrors = cbsRes.length === errors.length;
if (errors.length) {
if (ifAllErrors) {
newError = errors.shift();
} else {
newError = errors;
}
newError = clarify(
newError,
'Не удалось получить один или несколько IP адресов для' +
' прокси-серверов. Иконка для уведомления об обходе' +
' блокировок может не отображаться.'
);
if (ifAllErrors) {
return cb(newError);
}
}
cb(null, null, newError);
});
},
replaceAllAsync(addrArr, cb = mandatory()) {
console.log('Replacing...');
const [ipSet, hostSet] = _canonize(addrArr);
for( const ip of ipSet ) {
const host = _getHostObj(ip);
privates._ipToHostObj[ip] = host;
}
const hostArr = Array.from(hostSet);
this._replaceAllAsync(hostArr, (allErr, ...args) => {
if (!allErr) {
this.persistVoid();
}
cb(allErr, ...args);
});
},
updateAllAsync(cb = mandatory()) {
this._replaceAllAsync(cb);
},
get(ip) {
const tmp = privates._ipToHostObj[ip];
return tmp && tmp.host;
},
};
}

View File

@ -99,7 +99,9 @@
if (this.customProxyStringRaw) { if (this.customProxyStringRaw) {
customProxyArray = this.customProxyStringRaw customProxyArray = this.customProxyStringRaw
.replace(/#.*$/mg, '') // Strip comments. .replace(/#.*$/mg, '') // Strip comments.
.split( /(?:[^\S\r\n]*(?:;|\r?\n)+[^\S\r\n]*)+/g ).filter( (p) => p.trim() ); .split( /(?:[^\S\r\n]*(?:;|\r?\n)+[^\S\r\n]*)+/g )
.map( (p) => p.trim() )
.filter( (p) => p && /\s+/g.test(p) );
if (this.ifUseSecureProxiesOnly) { if (this.ifUseSecureProxiesOnly) {
customProxyArray = customProxyArray.filter( (p) => !p.startsWith('HTTP ') ); customProxyArray = customProxyArray.filter( (p) => !p.startsWith('HTTP ') );
} }
@ -218,7 +220,7 @@
} }
kitchenState(ifIncontinence, true); kitchenState(ifIncontinence, true);
cb(new TypeError( cb(null, null, new TypeError(
'Не найдено активного PAC-скрипта! Изменения будут применены при возвращении контроля настроек прокси или установке нового PAC-скрипта.' 'Не найдено активного PAC-скрипта! Изменения будут применены при возвращении контроля настроек прокси или установке нового PAC-скрипта.'
)); ));
@ -226,7 +228,7 @@
}, },
checkIncontinence(details) { checkIncontinenceVoid(details) {
if ( kitchenState(ifIncontinence) ) { if ( kitchenState(ifIncontinence) ) {
this._tryNowAsync(details, () => {/* Swallow. */}); this._tryNowAsync(details, () => {/* Swallow. */});
@ -237,12 +239,10 @@
keepCookedNowAsync(pacMods = mandatory(), cb = throwIfError) { keepCookedNowAsync(pacMods = mandatory(), cb = throwIfError) {
console.log('Keep cooked now...');
if (typeof(pacMods) === 'function') { if (typeof(pacMods) === 'function') {
cb = pacMods; cb = pacMods;
const pacMods = getCurrentConfigs(); pacMods = this.getCurrentConfigs();
if (!pacMods) {
return cb(TypeError('PAC mods were never initialized and you haven\'t supplied any.'));
}
} else { } else {
try { try {
pacMods = new PacModifiers(pacMods); pacMods = new PacModifiers(pacMods);
@ -251,7 +251,27 @@
} }
kitchenState('mods', pacMods); kitchenState('mods', pacMods);
} }
this._tryNowAsync( (err) => cb(null, null, err && [err]) ); this._tryNowAsync(
(err, res, ...warns) => {
console.log('Try now cb...', err);
if (err) {
return cb(err, res, ...warns);
}
const par = pacMods.customProxyArray;
if (!(par && par.length)) {
return cb(null, res, ...warns);
}
const hosts = par.map( (ps) => ps.split(/\s+/)[1] )
window.apis.ipToHost.replaceAllAsync(
hosts,
(...args) => cb(...args, ...warns)
);
}
);
}, },
@ -288,7 +308,7 @@
}; };
pacKitchen.checkIncontinence(); pacKitchen.checkIncontinenceVoid();
chrome.proxy.settings.onChange.addListener( pacKitchen.checkIncontinence.bind(pacKitchen) ); chrome.proxy.settings.onChange.addListener( pacKitchen.checkIncontinenceVoid.bind(pacKitchen) );
} // Private namespace ends. } // Private namespace ends.

View File

@ -26,6 +26,12 @@
const chromified = window.utils.chromified; const chromified = window.utils.chromified;
const checkChromeError = window.utils.checkChromeError; const checkChromeError = window.utils.checkChromeError;
const clarify = window.apis.errorsLib.clarify;
const clarifyThen = window.apis.errorsLib.clarifyThen;
const Warning = window.apis.errorsLib.Warning;
const httpLib = window.apis.httpLib;
const asyncLogGroup = function asyncLogGroup(...args) { const asyncLogGroup = function asyncLogGroup(...args) {
const cb = args.pop(); const cb = args.pop();
@ -43,43 +49,6 @@
}; };
class Clarification {
constructor(message = mandatory(), prevClarification) {
this.message = message;
if (prevClarification) {
this.prev = prevClarification;
}
}
};
const clarify = function clarify(
err = mandatory(),
message = mandatory(),
{data} = {}) {
err.clarification = new Clarification(message, err.clarification);
if (data) {
err.clarification.data = data;
}
return err;
};
class Warning {
constructor(message) {
clarify(this, message);
}
};
const setPacAsync = function setPacAsync( const setPacAsync = function setPacAsync(
pacData = mandatory(), cb = throwIfError pacData = mandatory(), cb = throwIfError
) { ) {
@ -104,7 +73,7 @@
console.warn('Failed, other extension is in control.'); console.warn('Failed, other extension is in control.');
return cb( return cb(
new Warning( window.utils.messages.whichExtensionHtml() ) new Error( window.utils.messages.whichExtensionHtml() )
); );
} }
@ -116,181 +85,20 @@
}; };
const clarifyErrorThen = function clarifyFetchErrorThen(message, cb) {
return (err, ...args) => cb( clarify(err || {}, message), ...args );
};
const clarifyFetchErrorThen = (cb) =>
clarifyErrorThen('Что-то не так с сетью, проверьте соединение.', cb);
const ifModifiedSince = function ifModifiedSince(
url = mandatory(),
lastModified = mandatory(),
cb = mandatory()
) {
const wasModified = new Date(0).toUTCString();
const notModifiedCode = 304;
fetch(url, {
method: 'HEAD',
headers: new Headers({
'If-Modified-Since': lastModified,
}),
}).then(
(res) => {
cb(
null,
res.status === notModifiedCode ?
false :
(res.headers.get('Last-Modified') || wasModified)
);
},
clarifyFetchErrorThen((err) => cb(err, wasModified))
);
};
const httpGet = function httpGet(url, cb = mandatory()) {
const start = Date.now();
fetch(url, {cache: 'no-store'}).then(
(res) => {
const textCb =
(err) => res.text().then( (text) => cb(err, text), cb );
const status = res.status;
if ( !( status >= 200 && status < 300 || status === 304 ) ) {
return textCb(
clarify(
res,
'Получен ответ с неудачным HTTP-кодом ' + status + '.'
)
);
}
console.log('GETed with success:', url, Date.now() - start);
textCb();
},
clarifyFetchErrorThen(cb)
);
};
const getIpsFor = function getIpsFor(host = mandatory(), cb = mandatory()) {
const types = [1, 28];
const promises = types.map(
(type) => new Promise((resolve) =>
httpGet(
'https://dns.google.com/resolve?type=' + type + '&name=' + host,
(err, res) => {
if (res) {
try {
res = JSON.parse(res);
console.log('Json parsed.');
if (err || res.Status) {
const msg = ['Answer', 'Comment', 'Status']
.filter( (prop) => res[prop] )
.map( (prop) => prop + ': ' + JSON.stringify( res[prop] ) )
.join(', \n');
clarify(err, 'Сервер (json): ' + msg, {data: res});
} else {
res = res.Answer || [];
res = res.filter(
(record) => types.includes(record.type)
);
}
} catch(e) {
err = clarify(
e,
'Сервер (текст): ' + res, err ? {data: err} : null
);
}
}
resolve([err, res]);
}
)
)
);
Promise.all(promises).then(
([[v4err, v4res], [v6err, v6res]]) => {
if(v4err) {
return cb(v4err, v4res);
}
const ips = v4res;
let warns = [];
if (!v6err) {
ips.push(...v6res);
} else {
warns = [v6err];
}
cb(null, ips, ...warns);
}
);
};
const updatePacProxyIps = function updatePacProxyIps( const updatePacProxyIps = function updatePacProxyIps(
provider,
cb = throwIfError cb = throwIfError
) { ) {
cb = asyncLogGroup( cb = asyncLogGroup(
'Getting IP for '+ provider.proxyHosts.join(', ') + '...', 'Getting IPs for PAC hosts...',
cb cb
); );
let failure = { window.apis.ipToHost.updateAllAsync(cb);
errors: {},
};
let hostsProcessed = 0;
provider.proxyHosts.forEach(
(proxyHost) => getIpsFor(
proxyHost,
(err, ips, ...warns) => {
if (!err) {
provider.proxyIps = provider.proxyIps || {};
ips.forEach(
(ip) => provider.proxyIps[ip] = proxyHost
);
}
if (err || warns.length) {
failure.errors[proxyHost] = err || warns;
}
if ( ++hostsProcessed < provider.proxyHosts.length ) {
return;
}
// All hosts were processed.
const errorsCount = Object.keys(failure.errors).length;
if (!errorsCount) {
return cb();
}
clarify(
failure,
'Не удалось получить один или несколько IP адресов для' +
' прокси-серверов. Иконка для уведомления об обходе' +
' блокировок может не отображаться.'
);
errorsCount === hostsProcessed
? cb(failure)
: cb(null, null, failure);
}
)
);
}; };
const setPacScriptFromProviderAsync = function setPacScriptFromProviderAsync( const setPacScriptFromProviderAsync = function setPacScriptFromProviderAsync(
provider = mandatory(), lastModified = mandatory(), cb = throwIfError provider, lastModified = mandatory(), cb = throwIfError
) { ) {
const pacUrl = provider.pacUrls[0]; const pacUrl = provider.pacUrls[0];
@ -299,7 +107,7 @@
cb cb
); );
ifModifiedSince(pacUrl, lastModified, (err, newLastModified) => { httpLib.ifModifiedSince(pacUrl, lastModified, (err, newLastModified) => {
if (!newLastModified) { if (!newLastModified) {
return cb( return cb(
@ -318,7 +126,7 @@
pacDataPromise = pacDataPromise.catch( pacDataPromise = pacDataPromise.catch(
(err) => new Promise( (err) => new Promise(
(resolve, reject) => httpGet( (resolve, reject) => httpLib.get(
url, url,
(newErr, pacData) => newErr ? reject(newErr) : resolve(pacData) (newErr, pacData) => newErr ? reject(newErr) : resolve(pacData)
) )
@ -339,7 +147,7 @@
); );
}, },
clarifyErrorThen( clarifyThen(
'Не удалось скачать PAC-скрипт с адресов: [ ' 'Не удалось скачать PAC-скрипт с адресов: [ '
+ provider.pacUrls.join(' , ') + ' ].', + provider.pacUrls.join(' , ') + ' ].',
cb cb
@ -363,14 +171,6 @@
' <br/> <a href="https://antizapret.prostovpn.org">Страница проекта</a>.', ' <br/> <a href="https://antizapret.prostovpn.org">Страница проекта</a>.',
pacUrls: ['https://antizapret.prostovpn.org/proxy.pac'], pacUrls: ['https://antizapret.prostovpn.org/proxy.pac'],
proxyHosts: ['proxy.antizapret.prostovpn.org'],
proxyIps: {
'195.123.209.38': 'proxy.antizapret.prostovpn.org',
'137.74.171.91': 'proxy.antizapret.prostovpn.org',
'51.15.39.201': 'proxy.antizapret.prostovpn.org',
'2001:bc8:4700:2300::1:d07': 'proxy.antizapret.prostovpn.org',
'2a02:27ac::10': 'proxy.antizapret.prostovpn.org',
},
}, },
Антицензорити: { Антицензорити: {
label: 'Антицензорити', label: 'Антицензорити',
@ -393,14 +193,6 @@
'\x68\x74\x74\x70\x73\x3a\x2f\x2f\x72\x61\x77\x2e\x67\x69\x74\x68\x75\x62\x75\x73\x65\x72\x63\x6f\x6e\x74\x65\x6e\x74\x2e\x63\x6f\x6d\x2f\x61\x6e\x74\x69\x63\x65\x6e\x73\x6f\x72\x73\x68\x69\x70\x2d\x72\x75\x73\x73\x69\x61\x2f\x67\x65\x6e\x65\x72\x61\x74\x65\x64\x2d\x70\x61\x63\x2d\x73\x63\x72\x69\x70\x74\x73\x2f\x6d\x61\x73\x74\x65\x72\x2f\x61\x6e\x74\x69\x63\x65\x6e\x73\x6f\x72\x69\x74\x79\x2e\x70\x61\x63', // eslint-disable-line max-len '\x68\x74\x74\x70\x73\x3a\x2f\x2f\x72\x61\x77\x2e\x67\x69\x74\x68\x75\x62\x75\x73\x65\x72\x63\x6f\x6e\x74\x65\x6e\x74\x2e\x63\x6f\x6d\x2f\x61\x6e\x74\x69\x63\x65\x6e\x73\x6f\x72\x73\x68\x69\x70\x2d\x72\x75\x73\x73\x69\x61\x2f\x67\x65\x6e\x65\x72\x61\x74\x65\x64\x2d\x70\x61\x63\x2d\x73\x63\x72\x69\x70\x74\x73\x2f\x6d\x61\x73\x74\x65\x72\x2f\x61\x6e\x74\x69\x63\x65\x6e\x73\x6f\x72\x69\x74\x79\x2e\x70\x61\x63', // eslint-disable-line max-len
// Google Drive (0.17): // Google Drive (0.17):
'\x68\x74\x74\x70\x73\x3a\x2f\x2f\x64\x72\x69\x76\x65\x2e\x67\x6f\x6f\x67\x6c\x65\x2e\x63\x6f\x6d\x2f\x75\x63\x3f\x65\x78\x70\x6f\x72\x74\x3d\x64\x6f\x77\x6e\x6c\x6f\x61\x64\x26\x69\x64\x3d\x30\x42\x2d\x5a\x43\x56\x53\x76\x75\x4e\x57\x66\x30\x54\x44\x46\x52\x4f\x47\x35\x46\x62\x55\x39\x4f\x64\x44\x67'], // eslint-disable-line max-len '\x68\x74\x74\x70\x73\x3a\x2f\x2f\x64\x72\x69\x76\x65\x2e\x67\x6f\x6f\x67\x6c\x65\x2e\x63\x6f\x6d\x2f\x75\x63\x3f\x65\x78\x70\x6f\x72\x74\x3d\x64\x6f\x77\x6e\x6c\x6f\x61\x64\x26\x69\x64\x3d\x30\x42\x2d\x5a\x43\x56\x53\x76\x75\x4e\x57\x66\x30\x54\x44\x46\x52\x4f\x47\x35\x46\x62\x55\x39\x4f\x64\x44\x67'], // eslint-disable-line max-len
proxyHosts: ['proxy.antizapret.prostovpn.org'],
proxyIps: {
'195.123.209.38': 'proxy.antizapret.prostovpn.org',
'137.74.171.91': 'proxy.antizapret.prostovpn.org',
'51.15.39.201': 'proxy.antizapret.prostovpn.org',
'2001:bc8:4700:2300::1:d07': 'proxy.antizapret.prostovpn.org',
'2a02:27ac::10': 'proxy.antizapret.prostovpn.org',
},
}, },
}, },
@ -422,22 +214,13 @@
return new Date(0).toUTCString(); return new Date(0).toUTCString();
}, },
setLastModified(newValue = mandatory()) { setLastModified(newValue = mandatory()) {
this._currentPacProviderLastModified = newValue; this._currentPacProviderLastModified = newValue;
}, },
isProxied(ip) {
// Executed on each request with ip. Make it as fast as possible.
// Property lookups are cheap (I tested).
return this._currentPacProviderKey
&& this.pacProviders[this._currentPacProviderKey]
.proxyIps.hasOwnProperty(ip);
},
mustBeKey(key = mandatory()) { mustBeKey(key = mandatory()) {
if ( !(key === null || this.pacProviders[key]) ) { if ( !(key === null || this.pacProviders[key]) ) {
@ -451,6 +234,7 @@
return this._currentPacProviderKey; return this._currentPacProviderKey;
}, },
setCurrentPacProviderKey( setCurrentPacProviderKey(
newKey = mandatory(), newKey = mandatory(),
lastModified = new Date().toUTCString() lastModified = new Date().toUTCString()
@ -510,7 +294,7 @@
if (key === null) { if (key === null) {
// No pac provider set. // No pac provider set.
return clarifyErrorThen('Сперва выберите PAC-провайдера.', cb); return clarifyThen('Сперва выберите PAC-провайдера.', cb);
} }
const pacProvider = this.getPacProvider(key); const pacProvider = this.getPacProvider(key);
@ -536,7 +320,6 @@
const ipsErrorPromise = new Promise( const ipsErrorPromise = new Promise(
(resolve, reject) => updatePacProxyIps( (resolve, reject) => updatePacProxyIps(
pacProvider,
resolve resolve
) )
); );

View File

@ -43,17 +43,13 @@
chrome.tabs.onUpdated.addListener( onTabUpdate ); chrome.tabs.onUpdated.addListener( onTabUpdate );
const antiCensorRu = window.apis.antiCensorRu; const updateTitle = function updateTitle(requestDetails, proxyHost, cb) {
const updateTitle = function updateTitle(requestDetails, cb) {
chrome.browserAction.getTitle( chrome.browserAction.getTitle(
{tabId: requestDetails.tabId}, {tabId: requestDetails.tabId},
(title) => { (title) => {
const ifTitleSetAlready = /\n/.test(title); const ifTitleSetAlready = /\n/.test(title);
const proxyHost = antiCensorRu.getPacProvider()
.proxyIps[requestDetails.ip];
const hostname = new URL( requestDetails.url ).hostname; const hostname = new URL( requestDetails.url ).hostname;
@ -131,8 +127,8 @@
const isProxiedAndInformed = function isProxiedAndInformed(requestDetails) { const isProxiedAndInformed = function isProxiedAndInformed(requestDetails) {
if ( !(requestDetails.ip const host = window.apis.ipToHost.get( requestDetails.ip );
&& antiCensorRu.isProxied( requestDetails.ip )) ) { if (!host) {
return false; return false;
} }
@ -141,7 +137,7 @@
previousUpdateTitleFinished = previousUpdateTitleFinished.then( previousUpdateTitleFinished = previousUpdateTitleFinished.then(
() => new Promise( () => new Promise(
(resolve) => { (resolve) => {
const cb = () => updateTitle( requestDetails, resolve ); const cb = () => updateTitle( requestDetails, host, resolve );
return ifMainFrame return ifMainFrame
? afterTabUpdated(requestDetails.tabId, cb) : cb(); ? afterTabUpdated(requestDetails.tabId, cb) : cb();
} }

View File

@ -25,9 +25,12 @@
"background": { "background": {
"scripts": [ "scripts": [
"00-init-apis.js", "00-init-apis.js",
"11-api-error-handlers.js", "11-error-handlers-api.js",
"13-pac-kitchen.js", "12-errors-lib.js",
"15-api-sync-pac-script-with-pac-provider.js", "13-http-lib.js",
"14-ip-to-host-api.js",
"15-pac-kitchen-api.js",
"17-sync-pac-script-with-pac-provider-api.js",
"20-api-fixes.js", "20-api-fixes.js",
"30-block-informer.js", "30-block-informer.js",
"40-context-menus.js" "40-context-menus.js"

View File

@ -6,14 +6,29 @@
:root { :root {
--ribbon-color: #4169e1; /* #1a6cc8 */ --ribbon-color: #4169e1; /* #1a6cc8 */
} }
div { div, section {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
header {
margin: 0 0 0.4em;
}
section header {
display: block;
border-bottom: 1px solid #558abb;
border-left: 5px solid #1048ac;
margin: 0;
position: relative;
height: 2em;
background-color: DodgerBlue;
color: white;
}
label { label {
user-select: none; user-select: none;
} }
ul { ul, ol {
list-style-type: none; list-style-type: none;
padding: 0; padding: 0;
margin: 0; margin: 0;
@ -53,9 +68,6 @@
#none:checked + label { #none:checked + label {
color: red; color: red;
} }
#configs-panel > header {
margin: 0 0 0.4em;
}
.if-not-controlled { .if-not-controlled {
display: none; display: none;
color: red; color: red;
@ -147,6 +159,39 @@
margin-left: 1em; margin-left: 1em;
text-align: right; text-align: right;
} }
select#exceptions-select {
color: black;
background: transparent;
border-radius: 0;
box-shadow: none;
text-shadow: none;
padding: 0;
}
#flex-right {
flex-grow: 99;
}
#flex-right > * {
width: 100%;
}
#except-editor {
border-radius: 0 !important;
border-bottom: 0;
height: 1.6em !important;
min-height: 1.6em !important;
}
#flex-container {
position: relative;
padding: 0;
margin: 0;
display: flex;
}
#flex-container > * {
flex-grow: 1;
padding: 0;
margin: 0;
}
</style> </style>
</head> </head>
<body> <body>
@ -154,27 +199,61 @@
<span id="which-extension"></span> <span id="which-extension"></span>
<hr style="border-color: red; border-style: solid;"/> <hr style="border-color: red; border-style: solid;"/>
</span> </span>
<section>
<header>PAC-скрипт</header>
<ul id="list-of-providers"> <ul id="list-of-providers">
<li><input type="radio" name="pacProvider" id="none" checked> <label for="none">Отключить</label></li> <li><input type="radio" name="pacProvider" id="none" checked> <label for="none">Отключить</label></li>
</ul> </ul>
<div style="white-space: nowrap"> <div style="white-space: nowrap">
Обновлялись: <span class="update-date">...</span> Обновлялись: <span class="update-date">...</span>
</div> </div>
<span id="status" style="will-change: contents">Загрузка...</span> </section>
<section id="status" style="will-change: contents">Загрузка...</section>
<hr/>
<div id="exceptions">
<header>Проксировать этот сайт?</header>
<div id="flex-container">
<ul style="padding-right: 1em">
<li><input id="this-auto" type="radio" checked name="if-proxy-this-site"/> <label for="this-auto">🔄&#xFE0E; авто</label></li>
<li><input id="this-yes" type="radio" name="if-proxy-this-site"/> <label for="this-yes">&nbsp;да</label></li>
<li><input id="this-no" type="radio" name="if-proxy-this-site"/> <label for="this-no">&nbsp;нет</label></li>
<li><a href>Весь список</a></li>
</ul>
<div id="flex-right">
<input type="text" value="google.com" id="except-editor"/>
<select multiple id="exceptions-select">
<option class="sel">google.com</option>
<option class="sel">yandex.ru</option>
<option>yandex.ru</option>
<option>yandex.ru</option>
<option>yandex.ru</option>
<option>yandex.ru</option>
<option>yandex.ru</option>
<option>yandex.ru</option>
<option>yandex.ru</option>
<option>yandex.ru</option>
<option>yandex.ru</option>
<option>yandex.ru</option>
<option>yandex.ru</option>
<option>yandex.ru</option>
<option>yandex.ru</option>
<option>yandex.ru</option>
<option>yandex.ru</option>
</select>
</div>
</div>
</div>
<hr/> <hr/>
<div>
<ul id="pac-mods"> <ul id="pac-mods">
<li class="control-row"> <li class="control-row">
<input type="button" value="Применить" id="apply-mods" disabled/> <input type="button" value="Применить" id="apply-mods" disabled/>
<a href id="reset-mods" class="link-button">К изначальным!</a> <a href id="reset-mods" class="link-button">К изначальным!</a>
</li> </li>
</ul> </ul>
</div>
<hr/> <hr/>
<div id="configs-panel"> <div id="configs-panel">
<header>Я ❤️ уведомления:</header> <header>Я ❤️ уведомления:</header>
<ul id="list-of-handlers"> <ul id="list-of-handlers"></ul>
</ul>
</div> </div>
<footer class="control-row"> <footer class="control-row">
<input type="button" value="Готово" class="close-button"> <input type="button" value="Готово" class="close-button">

View File

@ -80,8 +80,7 @@ chrome.runtime.getBackgroundPage( (backgroundPage) =>
const warning = warns const warning = warns
.map( .map(
(w) => (w) => w && w.message || ''
(w && (w.clarification && w.clarification.message || w.message) || '')
) )
.filter( (m) => m ) .filter( (m) => m )
.map( (m) => '✘ ' + m ) .map( (m) => '✘ ' + m )
@ -89,18 +88,20 @@ chrome.runtime.getBackgroundPage( (backgroundPage) =>
let message = ''; let message = '';
if (err) { if (err) {
let clarification = err.clarification; let wrapped = err.wrapped;
message = err.message || ''; message = err.message || '';
while( clarification ) { while( wrapped ) {
message = (clarification && (clarification.message + ' ')) + const deeperMsg = wrapped && wrapped.message;
message; if (deeperMsg) {
clarification = clarification.prev; message = message + ' &gt; ' + deeperMsg;
}
wrapped = wrapped.wrapped;
} }
} }
message = message.trim(); message = message.trim();
if (warning) { if (warning) {
message += ' ' + warning; message = message ? message + '<br/>' + warning : warning;
} }
setStatusTo( setStatusTo(
`<span style="color:red"> `<span style="color:red">
@ -121,11 +122,11 @@ chrome.runtime.getBackgroundPage( (backgroundPage) =>
}; };
const enableDisableInputs = function() { const switchInputs = function(val) {
const inputs = document.querySelectorAll('input'); const inputs = document.querySelectorAll('input');
for ( let i = 0; i < inputs.length; i++ ) { for ( let i = 0; i < inputs.length; i++ ) {
inputs[i].disabled = !inputs[i].disabled; inputs[i].disabled = val === 'on' ? false : true;
} }
}; };
@ -133,14 +134,17 @@ chrome.runtime.getBackgroundPage( (backgroundPage) =>
const conduct = (beforeStatus, operation, afterStatus, onSuccess) => { const conduct = (beforeStatus, operation, afterStatus, onSuccess) => {
setStatusTo(beforeStatus); setStatusTo(beforeStatus);
enableDisableInputs(); switchInputs('off');
operation((err, res, ...warns) => { operation((err, res, ...warns) => {
warns = warns.filter( (w) => w );
if (err || warns.length) { if (err || warns.length) {
backgroundPage.console.log('ERR', err, 'W', warns.length, 'w', warns);
showErrors(err, ...warns); showErrors(err, ...warns);
} else { } else {
setStatusTo(afterStatus); setStatusTo(afterStatus);
} }
enableDisableInputs(); switchInputs('on');
if (!err) { if (!err) {
onSuccess && onSuccess(res); onSuccess && onSuccess(res);
} }
@ -148,6 +152,15 @@ chrome.runtime.getBackgroundPage( (backgroundPage) =>
}; };
const infoSign = function infoSign(tooltip) {
return `<div class="desc">
<span class="info-sign">🛈</span>
<div class="tooltip">${tooltip}</div>
</div>`;
};
{ {
const ul = document.querySelector('#list-of-providers'); const ul = document.querySelector('#list-of-providers');
const _firstChild = ul.firstChild; const _firstChild = ul.firstChild;
@ -161,11 +174,8 @@ chrome.runtime.getBackgroundPage( (backgroundPage) =>
<input type="radio" name="pacProvider" id="${providerKey}"> <input type="radio" name="pacProvider" id="${providerKey}">
<label for="${providerKey}"> ${provider.label}</label> <label for="${providerKey}"> ${provider.label}</label>
&nbsp;<a href class="link-button checked-radio-panel" &nbsp;<a href class="link-button checked-radio-panel"
id="update-${providerKey}">[обновить]</a> id="update-${providerKey}">[обновить]</a> ` +
<div class="desc"> infoSign(provider.desc);
<span class="info-sign">🛈</span>
<div class="tooltip">${provider.desc}</div>
</div>`;
li.querySelector('.link-button').onclick = li.querySelector('.link-button').onclick =
() => { () => {
conduct( conduct(
@ -212,6 +222,26 @@ chrome.runtime.getBackgroundPage( (backgroundPage) =>
}; };
} }
// EXCEPTIONS PANEL
chrome.tabs.query({ active: true, currentWindow: true }, ([tab]) => {
/*
console.log(tab);
const opt = document.createElement('option');
opt.text =
opt.selected = true;
opt.style.backgroundColor = 'green !important';
opt.style.background = 'green !important';
const sl = document.getElementById('exceptions-select');
sl.insertBefore( opt, sl.firstChild );
*/
document.getElementById('except-editor').value = new URL(tab.url).hostname;
});
// PAC MODS PANEL // PAC MODS PANEL
{ {
@ -222,6 +252,7 @@ chrome.runtime.getBackgroundPage( (backgroundPage) =>
const _firstChild = modPanel.firstChild; const _firstChild = modPanel.firstChild;
const keyToLi = {}; const keyToLi = {};
const customProxyStringKey = 'customProxyStringRaw'; const customProxyStringKey = 'customProxyStringRaw';
const uiRaw = 'ui-proxy-string-raw';
pacKitchen.getConfigs().forEach( (conf) => { pacKitchen.getConfigs().forEach( (conf) => {
const key = conf.key; const key = conf.key;
@ -229,16 +260,12 @@ chrome.runtime.getBackgroundPage( (backgroundPage) =>
const li = document.createElement('li'); const li = document.createElement('li');
li.className = 'info-row'; li.className = 'info-row';
keyToLi[key] = li; keyToLi[key] = li;
console.log(key, conf.value);
li.innerHTML = ` li.innerHTML = `
<input type="checkbox" id="${iddy}" ${ conf.value ? 'checked' : '' }/> <input type="checkbox" id="${iddy}" ${ conf.value ? 'checked' : '' }/>
<label for="${iddy}"> ${ conf.label }</label>`; <label for="${iddy}"> ${ conf.label }</label>`;
if (key !== customProxyStringKey) { if (key !== customProxyStringKey) {
li.innerHTML += `<div class="desc"> li.innerHTML += infoSign(conf.desc);
<span class="info-sign">🛈</span>
<div class="tooltip">${conf.desc}</div>
</div>`;
} 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
@ -246,7 +273,7 @@ chrome.runtime.getBackgroundPage( (backgroundPage) =>
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;
HTTPS 11.22.33.44:8080;">${conf.value || ''}</textarea>`; HTTPS 11.22.33.44:8080;">${conf.value || localStorage.getItem(uiRaw) || ''}</textarea>`;
li.querySelector('textarea').onkeyup = function() { li.querySelector('textarea').onkeyup = function() {
this.dispatchEvent(new Event('change', { 'bubbles': true })); this.dispatchEvent(new Event('change', { 'bubbles': true }));
@ -270,12 +297,28 @@ HTTPS 11.22.33.44:8080;">${conf.value || ''}</textarea>`;
return configs; return configs;
}, {}); }, {});
const taVal = keyToLi[customProxyStringKey].querySelector('textarea').value;
if (configs[customProxyStringKey]) { if (configs[customProxyStringKey]) {
configs[customProxyStringKey] = keyToLi[customProxyStringKey].querySelector('textarea').value; const ifValid = taVal
.replace(/#.*$/mg)
.split(/\s*[;\n\r]+\s*/g)
.every(
(str) =>
/^(?:DIRECT|(?:(?:HTTPS?|PROXY|SOCKS(?:4|5))\s+\S+))$/g
.test(str)
)
if (!ifValid) {
return showErrors(new TypeError(
'Неверный формат своих прокси. Свертесь с <a href="https://rebrand.ly/ac-own-proxy" data-in-bg="true">документацией</a>.'
))
}
configs[customProxyStringKey] = taVal;
} else {
localStorage.setItem(uiRaw, taVal);
} }
conduct( conduct(
'Применяем настройки...', 'Применяем настройки...',
(cb) => pacKitchen.keepCookedNow(configs, cb), (cb) => pacKitchen.keepCookedNowAsync(configs, cb),
'Настройки применены.', 'Настройки применены.',
() => { document.getElementById('apply-mods').disabled = true; } () => { document.getElementById('apply-mods').disabled = true; }
); );
@ -284,7 +327,12 @@ HTTPS 11.22.33.44:8080;">${conf.value || ''}</textarea>`;
document.getElementById('reset-mods').onclick = () => { document.getElementById('reset-mods').onclick = () => {
const ifSure = backgroundPage.confirm('Сбросить все модификации PAC-скрипта?');
if (!ifSure) {
return;
}
pacKitchen.resetToDefaultsVoid(); pacKitchen.resetToDefaultsVoid();
backgroundPage.apis.ipToHost.resetToDefaultsVoid();
window.close(); window.close();
}; };