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'; '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 = { window.utils = {
areSettingsNotControlledFor(details) { areSettingsNotControlledFor(details) {

View File

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

View File

@ -21,22 +21,22 @@
{ // Private namespace starts. { // Private namespace starts.
function mandatory() { const mandatory = function 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.');
} };
function throwIfError(err) { const throwIfError = function throwIfError(err) {
if(err) { if(err) {
throw err; throw err;
} }
} };
function asyncLogGroup(...args) { const asyncLogGroup = function asyncLogGroup(...args) {
const cb = args.pop(); const cb = args.pop();
if(!(cb && typeof(cb) === 'function')) { 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 // Chrome API calls your cb in a context different from the point of API
// method invokation. // method invokation.
const err = chrome.runtime.lastError || chrome.extension.lastError || null; const err = chrome.runtime.lastError || chrome.extension.lastError;
if (err) { if (err) {
const args = ['API returned error:', err]; const args = ['API returned error:', err];
if (betterStack) { if (betterStack) {
@ -67,9 +67,9 @@
} }
return err; return err;
} };
function chromified(cb = mandatory(), ...replaceArgs) { const chromified = function chromified(cb = mandatory(), ...replaceArgs) {
const stack = (new Error()).stack; const stack = (new Error()).stack;
// Take error first callback and convert it to chrome api callback. // 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 = { window.apis.antiCensorRu = {
version: chrome.runtime.getManifest().version, version: chrome.runtime.getManifest().version,
throwAsync() { throw new Error('ABC') }, // TODO: delete
pacProviders: { pacProviders: {
Антизапрет: { Антизапрет: {
label: 'Антизапрет',
desc: 'Альтернативный PAC-скрипт от стороннего разработчика.' +
' Блокировка определяется по доменному имени,' +
' для некоторых провайдеров есть автоопредление.' +
' <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'], proxyHosts: ['proxy.antizapret.prostovpn.org'],
proxyIps: { proxyIps: {
'195.123.209.38': 'proxy.antizapret.prostovpn.org', '195.123.209.38': 'proxy.antizapret.prostovpn.org',
'137.74.171.91': 'proxy.antizapret.prostovpn.org', '137.74.171.91': 'proxy.antizapret.prostovpn.org',
'51.15.39.201': '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', '2001:bc8:4700:2300::1:d07': 'proxy.antizapret.prostovpn.org',
'2a02:27ac::10': 'proxy.antizapret.prostovpn.org',
}, },
}, },
Антиценз: { Антицензорити: {
pacUrls: ['https://config.anticenz.org/proxy.pac'], label: 'Антицензорити',
proxyHosts: ['gw2.anticenz.org'], desc: 'Основной PAC-скрипт от автора расширения.' +
proxyIps: { ' Блокировка определятся по доменному имени или IP адресу. Работает на switch-ах.' +
'5.196.220.114': 'gw2.anticenz.org', ' <br/><a href="https://rebrand.ly/ac-wiki">Страница проекта</a>.',
},
},
Обаа_свитчах: {
/* /*
Don't use in system configs! Because Win does poor caching. Don't use in system configs! Because Windows does poor caching.
Url is encoded to counter abuse. Some urls are encoded to counter abuse.
Version: 0.17 Version: 0.17
*/ */
pacUrls: [ pacUrls: [
// Cloud Flare // Official, Cloud Flare with caching:
'\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', 'https://anticensorship-russia.tk/generated-pac-scripts/anticensority.pac',
// GitHub // GitHub.io:
'\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', '\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',
// Google Drive // 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'], '\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: { proxyIps: {
'195.123.209.38': 'proxy.antizapret.prostovpn.org', '195.123.209.38': 'proxy.antizapret.prostovpn.org',
'137.74.171.91': 'proxy.antizapret.prostovpn.org', '137.74.171.91': 'proxy.antizapret.prostovpn.org',
'51.15.39.201': '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', '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? /* Is it the first time extension installed?
Do something, e.g. initiate PAC sync. Do something, e.g. initiate PAC sync.
@ -181,7 +479,7 @@
return this._currentPacProviderKey; return this._currentPacProviderKey;
}, },
setCurrentPacProviderKey(newKey, lastModified = new Date().toUTCString()) { setCurrentPacProviderKey(newKey = mandatory(), lastModified = new Date().toUTCString()) {
this.mustBeKey(newKey); this.mustBeKey(newKey);
this._currentPacProviderKey = newKey; this._currentPacProviderKey = newKey;
@ -237,11 +535,7 @@
if (key === null) { if (key === null) {
// No pac provider set. // No pac provider set.
return cb({ return clarifyErrorThen('Сперва выберите PAC-провайдера.', cb);
clarification: {
message: 'Сперва выберите PAC-провайдера.',
},
});
} }
const pacProvider = this.getPacProvider(key); const pacProvider = this.getPacProvider(key);
@ -250,43 +544,40 @@
(resolve, reject) => setPacScriptFromProviderAsync( (resolve, reject) => setPacScriptFromProviderAsync(
pacProvider, pacProvider,
this.getLastModifiedForKey(key), this.getLastModifiedForKey(key),
(err, res) => { (err, res, warns) => {
if (res && res.ifPacSet) { if (!err) {
this.setCurrentPacProviderKey(key, res.lastModified); this.setCurrentPacProviderKey(key, res.lastModified);
this.lastPacUpdateStamp = Date.now(); this.lastPacUpdateStamp = Date.now();
this.ifFirstInstall = false; this.ifFirstInstall = false;
this.setAlarms(); this.setAlarms();
} }
resolve([err, res]); resolve([err, null, warns]);
} }
) )
); );
const ipsPromise = new Promise( const ipsErrorPromise = new Promise(
(resolve, reject) => updatePacProxyIps( (resolve, reject) => updatePacProxyIps(
pacProvider, pacProvider,
(ipsError) => { resolve
if (ipsError && ipsError.clarification) {
ipsError.clarification.ifNotCritical = true;
}
resolve([ipsError]);
}
) )
); );
Promise.all([pacSetPromise, ipsPromise]).then( Promise.all([pacSetPromise, ipsErrorPromise]).then(
([[pacErr, pacRes], [ipsErr]]) => { ([[pacErr, pacRes, pacWarns], ipsErr]) => {
if (pacErr && ipsErr) { if (pacErr && ipsErr) {
return cb(pacErr, pacRes); return cb(pacErr, pacRes);
} }
let warns = [...(pacWarns || []), ipsErr].filter( (_) => _ );
if (!warns.length) {
warns = null;
}
this.pushToStorageAsync( this.pushToStorageAsync(
(pushErr) => cb(pacErr || ipsErr || pushErr, pacRes) (pushErr) => cb(pacErr || pushErr, null, warns)
); );
}, },
@ -407,7 +698,12 @@
console.log('Storage on init:', oldStorage); console.log('Storage on init:', oldStorage);
antiCensorRu.ifFirstInstall = Object.keys(oldStorage).length === 0; antiCensorRu.ifFirstInstall = Object.keys(oldStorage).length === 0;
if (!antiCensorRu.ifFirstInstall) { if (antiCensorRu.ifFirstInstall) {
// INSTALL
console.log('Installing...');
return chrome.runtime.openOptionsPage();
}
// LAUNCH, RELOAD, UPDATE // LAUNCH, RELOAD, UPDATE
// Use old or migrate to default. // Use old or migrate to default.
antiCensorRu._currentPacProviderKey = antiCensorRu._currentPacProviderKey =
@ -421,10 +717,26 @@
'Last PAC update was on', 'Last PAC update was on',
new Date(antiCensorRu.lastPacUpdateStamp).toLocaleString('ru-RU') 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 { } else {
// INSTALL // UPDATE & MIGRATION
console.log('Installing...'); const key = antiCensorRu._currentPacProviderKey;
return chrome.runtime.openOptionsPage(); if (
key !== null &&
!Object.keys(antiCensorRu.pacProviders).includes(key)
) {
antiCensorRu._currentPacProviderKey = 'Антицензорити'
}
console.log('Extension updated.');
} }
if (!antiCensorRu.getPacProvider()) { if (!antiCensorRu.getPacProvider()) {
@ -433,7 +745,8 @@
1. new providers will still be shown. 1. new providers will still be shown.
2. new version won't be pushed to storage 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(); 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) { if (!ifAlarmTriggered) {
antiCensorRu.pushToStorageAsync(); return pushOnUpdate();
} }
/* /*
History of Changes to Storage (Migration Guide) History of Changes to Storage (Migration Guide)
----------------------------------------------- -----------------------------------------------
Version 0.0.0.10 Version 0.0.0.17:
* Added this.version * "Антиценз" removed.
* PacProvider.proxyIps changed from {ip -> Boolean} to {ip -> hostname} * "Оба_и_на_свитчах" renamed to "Антицензорити".
Version 0.0.0.8-9 Version 0.0.0.10:
* Changed storage.ifNotInstalled to storage.ifFirstInstall * Added this.version.
* Added storage.lastPacUpdateStamp * 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( createMenuLinkEntry(
'Сайт доступен из-за границы? Is up?', 'Сайт доступен из-за границы?',
(tab) => `data:text/html;charset=utf8,<title>Запрашиваю...</title> (tab) => `data:text/html;charset=utf8,<title>Запрашиваю...</title>
<form class='tracker-form' method='POST' <form class='tracker-form' method='POST'
action='https://www.host-tracker.com/ru/InstantCheck/Create'> action='https://www.host-tracker.com/ru/InstantCheck/Create'>
@ -42,8 +42,8 @@
); );
createMenuLinkEntry( createMenuLinkEntry(
'У меня проблемы с расширением!', 'Руководство / Помощь / Ссылки',
(tab) => 'https://rebrand.ly/ac-support' (tab) => 'https://rebrand.ly/ac-wiki'
); );
} }

View File

@ -1,7 +1,7 @@
{ {
"manifest_version": 2, "manifest_version": 2,
"name": "__MSG_extName__ 0.17", "name": "__MSG_extName__ 0.16",
"default_locale": "ru", "default_locale": "ru",
"description": "__MSG_extDesc__", "description": "__MSG_extDesc__",
"version": "0.0.0.17", "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"> <html style="display: none">
<head> <head>
<title>Выбор провайдера PAC</title> <title>Выбор провайдера PAC</title>
<link rel="stylesheet" href="./font-awesome/css/font-awesome.min.css">
<style> <style>
:root {
--ribbon-color: #4169e1; /* #1a6cc8 */
}
div { div {
margin: 0; margin: 0;
padding: 0; padding: 0;
@ -27,9 +31,6 @@
input[type="radio"], label { input[type="radio"], label {
cursor: pointer; cursor: pointer;
} }
.off {
display: none;
}
.link-button, .link-button:visited { .link-button, .link-button:visited {
color: #0000EE; color: #0000EE;
text-decoration: none; text-decoration: none;
@ -58,6 +59,112 @@
display: none; display: none;
color: red; 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> </style>
</head> </head>
<body> <body>

View File

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