Add help hints, warnings, restyle

This commit is contained in:
Ilya Ig. Petrov 2017-01-10 19:33:21 +00:00
parent 797ef68397
commit fc1b0fcae2
14 changed files with 3243 additions and 398 deletions

View File

@ -1,5 +1,18 @@
'use strict';
const IF_DEBUG = true;
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 = {
areSettingsNotControlledFor(details) {

View File

@ -82,8 +82,7 @@
const json = JSON.stringify(err, errorJsonReplacer, 0);
openAndFocus(
//'https://rebrand.ly/ac-error/?' + btoa(encodeURIComponent(json))
'https://anticensorship-russia.tk/error/?' + json
'http://rebrand.ly/ac-error/?json=' + encodeURIComponent(json) + '&version=' + chrome.runtime.getManifest().version
);
},
@ -137,8 +136,11 @@
mayNotifyVoid(
id, title, errOrMessage,
{
icon = 'default-128.png',
context = extName
context = extName,
ifSticky = true
}
) {
if ( !this.isOn(id) ) {
@ -152,7 +154,7 @@
title: title,
message: message,
contextMessage: context,
requireInteraction: true,
requireInteraction: ifSticky,
type: 'basic',
iconUrl: './icons/' + icon,
appIconMaskUrl: './icons/default-mask-128.png',
@ -168,7 +170,7 @@
console.warn(name + ':GLOBAL ERROR', errEvent);
this.mayNotifyVoid('ext-error', 'Ошибка расширения', errEvent,
'ext-error-128.png');
{icon: 'ext-error-128.png'});
});
@ -230,7 +232,7 @@
// TOOD: add "view pac script at this line" button.
handlers.mayNotifyVoid('pac-error', 'Ошибка PAC!',
details.error + '\n' + details.details,
'pac-error-128.png'
{icon: 'pac-error-128.png'}
);
});
@ -244,7 +246,7 @@
noCon,
chrome.i18n.getMessage('noControl'),
chrome.i18n.getMessage('which'),
'no-control-128.png'
{icon:'no-control-128.png', ifSticky: false}
);
} else {
chrome.notifications.clear( noCon );

View File

@ -21,22 +21,22 @@
{ // Private namespace starts.
function mandatory() {
const mandatory = function mandatory() {
throw new TypeError('Missing required argument. ' +
'Be explicit if you swallow errors.');
}
};
function throwIfError(err) {
const throwIfError = function throwIfError(err) {
if(err) {
throw err;
}
}
};
function asyncLogGroup(...args) {
const asyncLogGroup = function asyncLogGroup(...args) {
const cb = args.pop();
if(!(cb && typeof(cb) === 'function')) {
@ -51,13 +51,13 @@
};
}
};
function checkChromeError(betterStack) {
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 || null;
const err = chrome.runtime.lastError || chrome.extension.lastError;
if (err) {
const args = ['API returned error:', err];
if (betterStack) {
@ -67,9 +67,9 @@
}
return err;
}
};
function chromified(cb = mandatory(), ...replaceArgs) {
const chromified = function chromified(cb = mandatory(), ...replaceArgs) {
const stack = (new Error()).stack;
// Take error first callback and convert it to chrome api callback.
@ -84,57 +84,355 @@
};
};
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(
pacData = mandatory(), cb = throwIfError
) {
const config = {
mode: 'pac_script',
pacScript: {
mandatory: false,
data: pacData,
},
};
console.log('Setting chrome proxy settings...');
chrome.proxy.settings.set( {value: config}, () => {
const err = checkChromeError();
if (err) {
return cb(err);
}
chrome.proxy.settings.get({}, (details) => {
if ( window.utils.areSettingsNotControlledFor( details ) ) {
console.warn('Failed, other extension is in control.');
return cb(
null, null,
[new Warning( window.utils.messages.whichExtensionHtml() )]
);
}
console.log('Successfuly set PAC in proxy settings..');
cb();
});
});
};
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 = null;
if (!v6err) {
ips.push(...v6res);
} else {
warns = [v6err];
}
cb(null, ips, warns);
}
);
};
const updatePacProxyIps = function updatePacProxyIps(provider, cb = throwIfError) {
cb = asyncLogGroup(
'Getting IP for '+ provider.proxyHosts.join(', ') + '...',
cb
);
let failure = {
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) {
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(
provider = mandatory(), lastModified = mandatory(), cb = throwIfError
) {
const pacUrl = provider.pacUrls[0];
cb = asyncLogGroup(
'Getting PAC script from provider...', pacUrl,
cb
);
ifModifiedSince(pacUrl, lastModified, (err, newLastModified) => {
if (!newLastModified) {
return cb(
null,
{lastModified},
[new Warning('Ваш PAC-скрипт не нуждается в обновлении. Его дата: ' + lastModified)]
);
}
// Employ all urls, the latter are fallbacks for the former.
let pacDataPromise = Promise.reject();
for(const url of provider.pacUrls) {
pacDataPromise = pacDataPromise.catch(
(err) => new Promise(
(resolve, reject) => httpGet(
url,
(newErr, pacData) => newErr ? reject(newErr) : resolve(pacData)
)
)
);
}
pacDataPromise.then(
(pacData) => {
setPacAsync(
pacData,
(err, res) => cb(
err,
Object.assign(res || {}, {lastModified: newLastModified})
)
);
},
clarifyErrorThen(
'Не удалось скачать PAC-скрипт с адресов: [ '
+ provider.pacUrls.join(' , ') + ' ].',
cb
)
);
});
};
window.apis.antiCensorRu = {
version: chrome.runtime.getManifest().version,
throwAsync() { throw new Error('ABC') }, // TODO: delete
pacProviders: {
Антизапрет: {
label: 'Антизапрет',
desc: 'Альтернативный PAC-скрипт от стороннего разработчика.' +
' Блокировка определяется по доменному имени,' +
' для некоторых провайдеров есть автоопредление.' +
' <br/> <a href="https://antizapret.prostovpn.org">Страница проекта</a>.',
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',
'2a02:27ac::10': 'proxy.antizapret.prostovpn.org',
'2001:bc8:4700:2300::1:d07': 'proxy.antizapret.prostovpn.org',
'2a02:27ac::10': 'proxy.antizapret.prostovpn.org',
},
},
Антиценз: {
pacUrls: ['https://config.anticenz.org/proxy.pac'],
proxyHosts: ['gw2.anticenz.org'],
proxyIps: {
'5.196.220.114': 'gw2.anticenz.org',
},
},
Обаа_свитчах: {
Антицензорити: {
label: 'Антицензорити',
desc: 'Основной PAC-скрипт от автора расширения.' +
' Блокировка определятся по доменному имени или IP адресу. Работает на switch-ах.' +
' <br/><a href="https://rebrand.ly/ac-wiki">Страница проекта</a>.',
/*
Don't use in system configs! Because Win does poor caching.
Url is encoded to counter abuse.
Don't use in system configs! Because Windows does poor caching.
Some urls are encoded to counter abuse.
Version: 0.17
*/
pacUrls: [
// Cloud Flare
'\x68\x74\x74\x70\x73\x3a\x2f\x2f\x61\x6e\x74\x69\x63\x65\x6e\x73\x6f\x72\x73\x68\x69\x70\x2d\x72\x75\x73\x73\x69\x61\x2e\x74\x6b\x2f\x67\x65\x6e\x65\x72\x61\x74\x65\x64\x2d\x70\x61\x63\x2d\x73\x63\x72\x69\x70\x74\x73\x2f\x6f\x6e\x2d\x73\x77\x69\x74\x63\x68\x65\x73\x2d\x30\x2e\x31\x37\x2e\x70\x61\x63',
// GitHub
'\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\x6f\x6e\x2d\x73\x77\x69\x74\x63\x68\x65\x73\x2d\x30\x2e\x31\x37\x2e\x70\x61\x63',
// Google Drive
// Official, Cloud Flare with caching:
'https://anticensorship-russia.tk/generated-pac-scripts/anticensority.pac',
// GitHub.io:
'\x68\x74\x74\x70\x73\x3a\x2f\x2f\x61\x6e\x74\x69\x63\x65\x6e\x73\x6f\x72\x73\x68\x69\x70\x2d\x72\x75\x73\x73\x69\x61\x2e\x67\x69\x74\x68\x75\x62\x2e\x69\x6f\x2f\x67\x65\x6e\x65\x72\x61\x74\x65\x64\x2d\x70\x61\x63\x2d\x73\x63\x72\x69\x70\x74\x73\x2f\x61\x6e\x74\x69\x63\x65\x6e\x73\x6f\x72\x69\x74\x79\x2e\x70\x61\x63',
// GitHub repo:
'\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',
// 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'],
proxyHosts: ['proxy.antizapret.prostovpn.org', 'gw2.anticenz.org'],
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',
'2a02:27ac::10': 'proxy.antizapret.prostovpn.org',
'2001:bc8:4700:2300::1:d07': 'proxy.antizapret.prostovpn.org',
'5.196.220.114': 'gw2.anticenz.org',
'2a02:27ac::10': 'proxy.antizapret.prostovpn.org',
},
},
},
_currentPacProviderKey: 'Оба_и_на_свитчах',
_currentPacProviderKey: 'Антицензорити',
/* Is it the first time extension installed?
Do something, e.g. initiate PAC sync.
@ -181,7 +479,7 @@
return this._currentPacProviderKey;
},
setCurrentPacProviderKey(newKey, lastModified = new Date().toUTCString()) {
setCurrentPacProviderKey(newKey = mandatory(), lastModified = new Date().toUTCString()) {
this.mustBeKey(newKey);
this._currentPacProviderKey = newKey;
@ -237,11 +535,7 @@
if (key === null) {
// No pac provider set.
return cb({
clarification: {
message: 'Сперва выберите PAC-провайдера.',
},
});
return clarifyErrorThen('Сперва выберите PAC-провайдера.', cb);
}
const pacProvider = this.getPacProvider(key);
@ -250,43 +544,40 @@
(resolve, reject) => setPacScriptFromProviderAsync(
pacProvider,
this.getLastModifiedForKey(key),
(err, res) => {
(err, res, warns) => {
if (res && res.ifPacSet) {
if (!err) {
this.setCurrentPacProviderKey(key, res.lastModified);
this.lastPacUpdateStamp = Date.now();
this.ifFirstInstall = false;
this.setAlarms();
}
resolve([err, res]);
resolve([err, null, warns]);
}
)
);
const ipsPromise = new Promise(
const ipsErrorPromise = new Promise(
(resolve, reject) => updatePacProxyIps(
pacProvider,
(ipsError) => {
if (ipsError && ipsError.clarification) {
ipsError.clarification.ifNotCritical = true;
}
resolve([ipsError]);
}
resolve
)
);
Promise.all([pacSetPromise, ipsPromise]).then(
([[pacErr, pacRes], [ipsErr]]) => {
Promise.all([pacSetPromise, ipsErrorPromise]).then(
([[pacErr, pacRes, pacWarns], ipsErr]) => {
if (pacErr && ipsErr) {
return cb(pacErr, pacRes);
}
let warns = [...(pacWarns || []), ipsErr].filter( (_) => _ );
if (!warns.length) {
warns = null;
}
this.pushToStorageAsync(
(pushErr) => cb(pacErr || ipsErr || pushErr, pacRes)
(pushErr) => cb(pacErr || pushErr, null, warns)
);
},
@ -407,7 +698,12 @@
console.log('Storage on init:', oldStorage);
antiCensorRu.ifFirstInstall = Object.keys(oldStorage).length === 0;
if (!antiCensorRu.ifFirstInstall) {
if (antiCensorRu.ifFirstInstall) {
// INSTALL
console.log('Installing...');
return chrome.runtime.openOptionsPage();
}
// LAUNCH, RELOAD, UPDATE
// Use old or migrate to default.
antiCensorRu._currentPacProviderKey =
@ -421,10 +717,26 @@
'Last PAC update was on',
new Date(antiCensorRu.lastPacUpdateStamp).toLocaleString('ru-RU')
);
const ifUpdating = antiCensorRu.version !== oldStorage.version;
console.log('IF_UPD?', ifUpdating, antiCensorRu.version, 'vs', oldStorage.version);
const pushOnUpdate = () => ifUpdating ? antiCensorRu.pushToStorageAsync() : null;
if (!ifUpdating) {
// LAUNCH, RELOAD, ENABLE
antiCensorRu.pacProviders = oldStorage.pacProviders;
console.log('Extension launched, reloaded or enabled.');
} else {
// INSTALL
console.log('Installing...');
return chrome.runtime.openOptionsPage();
// UPDATE & MIGRATION
const key = antiCensorRu._currentPacProviderKey;
if (
key !== null &&
!Object.keys(antiCensorRu.pacProviders).includes(key)
) {
antiCensorRu._currentPacProviderKey = 'Антицензорити'
}
console.log('Extension updated.');
}
if (!antiCensorRu.getPacProvider()) {
@ -433,7 +745,8 @@
1. new providers will still be shown.
2. new version won't be pushed to storage
*/
return console.log('No PAC provider set. Do nothing.');
console.log('No PAC provider set. Do nothing.');
return pushOnUpdate();
}
/*
@ -446,306 +759,24 @@
const ifAlarmTriggered = antiCensorRu.setAlarms();
if (antiCensorRu.version === oldStorage.version) {
// LAUNCH, RELOAD, ENABLE
antiCensorRu.pacProviders = oldStorage.pacProviders;
return console.log('Extension launched, reloaded or enabled.');
}
// UPDATE & MIGRATION
console.log('Extension updated.');
if (!ifAlarmTriggered) {
antiCensorRu.pushToStorageAsync();
return pushOnUpdate();
}
/*
History of Changes to Storage (Migration Guide)
-----------------------------------------------
Version 0.0.0.10
* Added this.version
* PacProvider.proxyIps changed from {ip -> Boolean} to {ip -> hostname}
Version 0.0.0.8-9
* Changed storage.ifNotInstalled to storage.ifFirstInstall
* Added storage.lastPacUpdateStamp
Version 0.0.0.17:
* "Антиценз" removed.
* "Оба_и_на_свитчах" renamed to "Антицензорити".
Version 0.0.0.10:
* Added this.version.
* PacProvider.proxyIps changed from {ip -> Boolean} to {ip -> hostname}.
Version 0.0.0.8-9:
* Changed storage.ifNotInstalled to storage.ifFirstInstall.
* Added storage.lastPacUpdateStamp.
**/
});
/*
* result.ifPacSet is true if PAC was set (maybe with non-critical errors).
* */
function setPacAsync(
{pacData = mandatory(), pacUrl = mandatory()},
cb = throwIfError
) {
const config = {
mode: 'pac_script',
pacScript: {
mandatory: false,
data: pacData,
},
};
console.log('Setting chrome proxy settings...');
chrome.proxy.settings.set( {value: config}, async () => {
let err = checkChromeError();
let asciiErr;
if (err) {
if (err.message.startsWith('\'pacScript.data\' supports only ASCII')) {
asciiErr = err;
asciiErr.clarification = {ifNotCritical: true};
err = await new Promise((resolve) => {
chrome.proxy.settings.set({
value: {
mode: 'pac_script',
pacScript: {
url: pacUrl,
},
},
},
() => resolve( checkChromeError() )
);
});
}
if (err) {
return cb(err);
}
}
chrome.proxy.settings.get({}, (details) => {
if ( window.utils.areSettingsNotControlledFor( details ) ) {
console.warn('Failed, other extension is in control.');
return cb({clarification: {
message: window.utils.messages.whichExtensionHtml(),
}});
}
console.log('Successfuly set PAC in proxy settings..');
cb(asciiErr, {ifPacSet: true});
});
});
}
function clarifyFetchErrorThen(cb) {
return (err) => {
err.clarification = {
message: 'Что-то не так с сетью, проверьте соединение.',
};
return cb(err);
}
}
function ifModifiedSince(url, lastModified = mandatory(), cb = mandatory()) {
const nowModified = new Date(0).toUTCString();
fetch(url, {
method: 'HEAD',
headers: new Headers({
'If-Modified-Since': lastModified
})
}).then(
(res) => {
cb(null, res.status === 304 ? false : (res.headers.get('Last-Modified') || nowModified) );
},
clarifyFetchErrorThen((err) => cb(err, nowModified))
);
}
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 ) ) {
res.clarification = {
message: 'Получен ответ с неудачным HTTP-кодом ' + status + '.',
};
return textCb(res);
}
console.log('GETed with success:', url, Date.now() - start);
textCb();
},
clarifyFetchErrorThen(cb)
);
}
function getIpsFor(host, 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');
err.clarification.message += ' Сервер (json): ' + msg;
err.data = err.data || res;
} else {
res = res.Answer || [];
res = res.filter(
(record) => types.includes(record.type)
);
}
} catch(e) {
err = e || err || {clarification: {message: ''}};
err.clarification = err.clarification || {message: ''};
err.clarification.message = (
err.clarification.message
+ ' Сервер (текст): '+ res
).trim();
err.data = err.data || res;
}
}
resolve([err, res]);
}
)
)
);
Promise.all(promises).then(
([[v4err, v4res], [v6err, v6res]]) => {
if(v4err) {
return cb(v4err, v4res);
}
const ips = v4res;
if (!v6err) {
ips.push(...v6res);
} else {
v6err.clarification.ifNotCritical = true;
console.warn(v6err);
}
cb(v6err, ips);
}
);
}
function updatePacProxyIps(provider, cb = throwIfError) {
cb = asyncLogGroup(
'Getting IP for '+ provider.proxyHosts.join(', ') + '...',
cb
);
let failure = {
clarification: {
message: 'Не удалось получить один или несколько IP адресов для' +
' прокси-серверов. Иконка для уведомления об обходе блокировок ' +
'может не отображаться.',
},
errors: {},
};
let i = 0;
provider.proxyHosts.forEach(
(proxyHost) => getIpsFor(
proxyHost,
(err, ips) => {
if (!err || err.clarification.ifNotCritical) {
provider.proxyIps = provider.proxyIps || {};
ips.forEach(
(ip) => provider.proxyIps[ip] = proxyHost
);
} else {
failure.errors[proxyHost] = err;
}
if ( ++i === provider.proxyHosts.length ) {
failure = Object.keys(failure.errors).length ? failure : null;
cb(failure, provider.proxyIps);
}
}
)
);
}
/*
* result.ifPacSet is true if PAC was set.
**/
function setPacScriptFromProviderAsync(provider = mandatory(), lastModified = mandatory(), cb = throwIfError) {
const pacUrl = provider.pacUrls[0];
cb = asyncLogGroup(
'Getting PAC script from provider...', pacUrl,
cb
);
ifModifiedSince(pacUrl, lastModified, (err, newLastModified) => {
if (!newLastModified) {
return cb(
{clarification: {
message: 'Ваш PAC-скрипт не нуждается в обновлении. Его дата: ' + lastModified,
ifNotCritical: true,
}}
);
}
// Employ all urls, the latter are fallbacks for the former.
let pacDataPromise = Promise.reject();
for(const url of provider.pacUrls) {
pacDataPromise = pacDataPromise.catch(
(err) => new Promise(
(resolve, reject) => httpGet(
url,
(newErr, pacData) => newErr ? reject(newErr) : resolve(pacData)
)
)
);
}
pacDataPromise.then(
(pacData) => {
setPacAsync(
{pacData, pacUrl},
(err, res) => cb( err, Object.assign(res || {}, {lastModified: newLastModified}) )
);
},
(err) => {
err.clarification = {
message: 'Не удалось скачать PAC-скрипт с адресов: [ '
+ provider.pacUrls.join(' , ') + ' ].',
prev: err.clarification,
};
return cb(err);
}
);
});
}
}

View File

@ -11,7 +11,7 @@
});
createMenuLinkEntry(
'Сайт доступен из-за границы? Is up?',
'Сайт доступен из-за границы?',
(tab) => `data:text/html;charset=utf8,<title>Запрашиваю...</title>
<form class='tracker-form' method='POST'
action='https://www.host-tracker.com/ru/InstantCheck/Create'>
@ -42,8 +42,8 @@
);
createMenuLinkEntry(
'У меня проблемы с расширением!',
(tab) => 'https://rebrand.ly/ac-support'
'Руководство / Помощь / Ссылки',
(tab) => 'https://rebrand.ly/ac-wiki'
);
}

View File

@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "__MSG_extName__ 0.17",
"name": "__MSG_extName__ 0.16",
"default_locale": "ru",
"description": "__MSG_extDesc__",
"version": "0.0.0.17",

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 434 KiB

View File

@ -2,7 +2,11 @@
<html style="display: none">
<head>
<title>Выбор провайдера PAC</title>
<link rel="stylesheet" href="./font-awesome/css/font-awesome.min.css">
<style>
:root {
--ribbon-color: #4169e1; /* #1a6cc8 */
}
div {
margin: 0;
padding: 0;
@ -27,9 +31,6 @@
input[type="radio"], label {
cursor: pointer;
}
.off {
display: none;
}
.link-button, .link-button:visited {
color: #0000EE;
text-decoration: none;
@ -58,6 +59,112 @@
display: none;
color: red;
}
li.provider {
display: table;
width: 100%;
}
.desc i {
vertical-align: bottom;
font-size: 1.1em;
}
/* Source: https://jsfiddle.net/greypants/zgCb7/ */
.tooltip {
display: none;
white-space: initial;
padding: 1em;
}
.desc {
display: table-cell;
text-align: right;
color: var(--ribbon-color);
cursor: help;
}
.desc:hover br .tooltip {
display: block;
text-align: left;
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 2.6em;
background-color: var(--ribbon-color);
color: white;
z-index: 1;
}
.tooltip a {
color: white;
}
/* CSS Triangles - courtesy of Trevor */
.desc .tooltip:after {
border-top: solid transparent 7px;
border-bottom: solid transparent 7px;
border-left: solid var(--ribbon-color) 7px;
right: -7px;
content: "";
width: 0;
top: 7px;
margin-top: -13px;
position: absolute;
height: 0;
}
/* This bridges the gap so you can mouse into the tooltip without it disappearing
.desc .tooltip:before {
position: absolute;
right: -14px;
content: " ";
display: block;
width: 14px;
top: 0;
height: 100%;
}
*/
li.provider {
position: relative;
}
.xyz {
display: none;
position: absolute;
white-space: initial;
word-break: initial;
top: 100%;
left: 0;
right: 1em;
z-index: 1;
background-color: var(--ribbon-color);
padding: 1em;
color: white;
text-align: initial;
}
.desc:hover .xyz {
display: block;
}
.xyz a {
color: white;
}
.desc .xyz:after {
border-left: solid transparent 7px;
border-bottom: solid var(--ribbon-color) 7px;
position: absolute;
top: -7px;
content: "";
width: 0;
right: 0;
height: 0;
}
/* This bridges the gap so you can mouse into the tooltip without it disappearing */
.desc .xyz:before {
position: absolute;
top: -14px;
content: "";
display: block;
height: 22px;
left: 0;
width: calc(100% + 0.6em);
}
</style>
</head>
<body>

View File

@ -68,33 +68,43 @@ chrome.runtime.getBackgroundPage( (backgroundPage) =>
};
const checkChosenProvider = () => currentProviderRadio().checked = true;
const showError = (err) => {
const showErrors = (err, warns) => {
warns = warns || [];
const warning = warns
.map( (w) => '✘ ' + (w.clarification && w.clarification.message || w.message || '') )
.join('<br/>');
let message = '';
if (err) {
let clarification = err.clarification;
const ifNotCritical = clarification && clarification.ifNotCritical;
let message = err.message || '';
message = err.message || '';
while( clarification ) {
message = (clarification && (clarification.message + ' ')) + message;
clarification = clarification.prev;
}
}
message = message.trim();
if (warning) {
message += ' ' + warning;
}
setStatusTo(
`<span style="color:red">
${ifNotCritical ? 'Некритичная ошибка.' : 'Ошибка!'}
${err ? '🔥 Ошибка!' : 'Некритичная ошибка.'}
</span>
<br/>
<span style="font-size: 0.9em; color: darkred">${message}</span>
<a href class="link-button">
[Ещё&nbsp;подробнее]
</a>`
${err ? '<a href class="link-button">[Ещё&nbsp;подробнее]</a>' : ''}`
);
if (err) {
getStatus().querySelector('.link-button').onclick = function() {
errorHandlers.viewErrorVoid(err);
return false;
};
}
};
@ -111,14 +121,13 @@ chrome.runtime.getBackgroundPage( (backgroundPage) =>
setStatusTo(beforeStatus);
enableDisableInputs();
operation((err) => {
if (err) {
showError(err);
if (err.clarification && err.clarification.ifNotCritical) {
onSuccess && onSuccess();
}
operation((err, res, warns) => {
if (err || warns) {
showErrors(err, warns);
} else {
setStatusTo(afterStatus);
}
if (!err) {
onSuccess && onSuccess();
}
enableDisableInputs();
@ -131,12 +140,20 @@ chrome.runtime.getBackgroundPage( (backgroundPage) =>
for(
const providerKey of Object.keys(antiCensorRu.pacProviders).sort()
) {
const provider = antiCensorRu.getPacProvider(providerKey);
const li = document.createElement('li');
li.className = 'provider';
li.innerHTML = `
<input type="radio" name="pacProvider" id="${providerKey}">
<label for="${providerKey}">${providerKey}</label>
<label for="${providerKey}"> ${provider.label}</label>
<a href class="link-button checked-radio-panel"
id="update-${providerKey}">[обновить]</a>`;
id="update-${providerKey}"> [обновить]</a>
<div class="desc">
&nbsp;<i class="fa fa-question-circle" aria-hidden="true"></i>
<div class="tooltip">${provider.desc}</div>
<div class="xyz">${provider.desc}</div>
</div>
`
li.querySelector('.link-button').onclick =
() => {
conduct(