Handle update errors in popup, add ip lookup method, fix rollbacks (change API)

This commit is contained in:
Ilya Ig. Petrov 2016-11-24 08:40:57 -08:00
parent aa20f220ee
commit 9ef8939127
4 changed files with 228 additions and 180 deletions

View File

@ -49,15 +49,41 @@
_currentPacProviderKey: 'Оба_и_на_свитчах',
isProxied(ip) {
// Executed on each request with ip. Make it as fast as possible.
return this._currentPacProviderKey && this.pacProviders[this._currentPacProviderKey].proxyIps.hasOwnProperty(ip);
// The benefit of removing lookups is little, e.g. this._currentProxyIps && this._currentProxyIps.hasOwnProperty(ip);
},
mustBeKey(key) {
if ( !(key === null || this.pacProviders[key]) ) {
throw new IllegalArgumentException('No provider for key:' + key);
}
},
get currentPacProviderKey() { return this._currentPacProviderKey },
set currentPacProviderKey(newKey) {
if (newKey && !this.pacProviders[newKey])
throw new IllegalArgumentException('No provider for key:' + newKey);
this.mustBeKey(newKey);
this._currentPacProviderKey = newKey;
},
get pacProvider() { return this.pacProviders[this.currentPacProviderKey] },
getPacProvider(key) {
if(key) {
this.mustBeKey(key);
}
else {
key = this.currentPacProviderKey;
}
return this.pacProviders[key];
},
/*
Is it the first time extension installed? Do something, e.g. initiate PAC sync.
@ -106,19 +132,28 @@
});
},
syncWithPacProvider(cb) {
syncWithPacProvider(key, cb) {
cb = asyncLogGroup('Syncing with PAC provider...', cb);
if (!this.pacProvider) {
if( !key || typeof(key) === 'function' ) {
cb = key;
key = this.currentPacProviderKey;
}
cb = asyncLogGroup('Syncing with PAC provider ' + key + '...', cb);
if (key === null) {
// No pac provider set.
return cb({clarification:{message:'Сперва выберите PAC-провайдера.'}});
}
const pacProvider = this.getPacProvider(key);
const pacSetPromise = new Promise(
(resolve, reject) => setPacScriptFromProvider(
this.pacProvider,
pacProvider,
(err, res) => {
if (!err) {
this.currentPacProviderKey = key;
this.lastPacUpdateStamp = Date.now();
this.ifFirstInstall = false;
this.setAlarms();
@ -132,7 +167,7 @@
const ipsPromise = new Promise(
(resolve, reject) => updatePacProxyIps(
this.pacProvider,
pacProvider,
(ipsError) => {
if (ipsError && ipsError.clarification) {
@ -186,28 +221,15 @@
installPac(key, cb) {
console.log('Installing PAC');
if(typeof(key) === 'function') {
cb = key;
key = undefined;
console.log('Installing PAC...');
if (!key) {
throw new Error('Key must be defined.');
}
const oldKey = this.currentPacProviderKey;
if(key || key !== oldKey) {
this.currentPacProviderKey = key;
const _cb = cb;
cb = (err, res) => {
if (err && !(err.clarification && err.clarification.ifNotCritical)) {
console.log('Rollback privider key.');
this.currentPacProviderKey = oldKey;
}
_cb(err, res);
};
if (this.currentProviderKey !== key) {
return this.syncWithPacProvider(key, cb);
}
this.syncWithPacProvider(cb);
console.log(key + ' already installed.');
cb();
},
@ -223,7 +245,7 @@
if (err) {
return cb(err);
}
this.currentPacProviderKey = undefined;
this.currentPacProviderKey = null;
this.pushToStorage(cb);
}
@ -236,16 +258,12 @@
// ON EACH LAUNCH, STARTUP, RELOAD, UPDATE, ENABLE
chrome.storage.local.get(null, (oldStorage) => {
console.log('Init on storage:', oldStorage);
antiCensorRu.ifFirstInstall = Object.keys(oldStorage).length === 0;
if (!antiCensorRu.ifFirstInstall) {
// LAUNCH, RELOAD, UPDATE
antiCensorRu._currentPacProviderKey = oldStorage._currentPacProviderKey;
antiCensorRu.lastPacUpdateStamp = oldStorage.lastPacUpdateStamp || antiCensorRu.lastPacUpdateStamp;
console.log( 'Last PAC update was on', new Date(antiCensorRu.lastPacUpdateStamp).toLocaleString('ru-RU') );
}
checkChromeError();
/*
Event handlers that ALWAYS work (even if installation is not done or failed).
E.g. install window may fail to open or be closed by user accidentally.
In such case extension _should_ try to work on default parameters.
*/
chrome.alarms.onAlarm.addListener(
(alarm) => {
@ -265,19 +283,28 @@
});
if (antiCensorRu.ifFirstInstall) {
console.log('Storage on init:', oldStorage);
antiCensorRu.ifFirstInstall = Object.keys(oldStorage).length === 0;
if (!antiCensorRu.ifFirstInstall) {
// LAUNCH, RELOAD, UPDATE
antiCensorRu._currentPacProviderKey = oldStorage._currentPacProviderKey || null; // Old or migrate.
antiCensorRu.lastPacUpdateStamp = oldStorage.lastPacUpdateStamp || antiCensorRu.lastPacUpdateStamp; // Old or migrate to default.
console.log( 'Last PAC update was on', new Date(antiCensorRu.lastPacUpdateStamp).toLocaleString('ru-RU') );
}
else {
// INSTALL
console.log('Installing...');
chrome.runtime.openOptionsPage();
return chrome.runtime.openOptionsPage();
}
if (!antiCensorRu.pacProvider) {
return console.log('No PAC provider set. Do nothing.');
if (!antiCensorRu.getPacProvider()) {
/*
In case of UPDATE:
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.');
}
/*

View File

@ -52,13 +52,81 @@ window.tabWithError2ip = {}; // For errors only: Error? -> Check this IP!
chrome.tabs.onRemoved.addListener( (tabId) => { onTabUpdate(tabId); delete window.tabWithError2ip[tabId] } );
function updateTitle(requestDetails, cb) {
chrome.browserAction.getTitle(
{ tabId: requestDetails.tabId },
(title) => {
const ifTitleSetAlready = /\n/.test(title);
const proxyHost = window.antiCensorRu.getPacProvider().proxyIps[ requestDetails.ip ];
const hostname = new URL( requestDetails.url ).hostname;
let ifShouldUpdateTitle = false;
const indent = ' ';
const proxyTitle = 'Прокси:';
if (!ifTitleSetAlready) {
title = 'Разблокированы:\n'+ indent + hostname +'\n'+ proxyTitle +'\n'+ indent + proxyHost;
ifShouldUpdateTitle = true;
chrome.browserAction.setBadgeText({
tabId: requestDetails.tabId,
text: requestDetails.type === 'main_frame' ? '1' : '%1'
});
}
else {
const hostsProxiesPair = title.split(proxyTitle);
if (hostsProxiesPair[1].indexOf(proxyHost) === -1) {
title = title.replace(hostsProxiesPair[1], hostsProxiesPair[1] +'\n'+ indent + proxyHost);
ifShouldUpdateTitle = true;
}
if (hostsProxiesPair[0].indexOf(hostname) === -1) {
title = title.replace(proxyTitle, indent + hostname +'\n'+ proxyTitle);
ifShouldUpdateTitle = true;
const _cb = cb;
cb = () => chrome.browserAction.getBadgeText(
{tabId: requestDetails.tabId},
(result) => {
chrome.browserAction.setBadgeText(
{
tabId: requestDetails.tabId,
text: ( isNaN( result.charAt(0) ) && result.charAt(0) || '' ) + (hostsProxiesPair[0].split('\n').length - 1)
}
);
return _cb();
}
);
}
}
if (ifShouldUpdateTitle) {
chrome.browserAction.setTitle({
title: title,
tabId: requestDetails.tabId
});
}
return cb();
}
);
}
let previousUpdateTitleFinished = Promise.resolve();
function isProxiedAndInformed(requestDetails) {
if (
!( window.antiCensorRu.pacProvider && window.antiCensorRu.pacProvider.proxyIps && window.antiCensorRu.pacProvider.proxyIps[ requestDetails.ip ] )
) {
if ( !(requestDetails.ip && antiCensorRu.isProxied( requestDetails.ip )) ) {
return false;
}
@ -74,75 +142,6 @@ window.tabWithError2ip = {}; // For errors only: Error? -> Check this IP!
);
return true;
function updateTitle(requestDetails, cb) {
chrome.browserAction.getTitle(
{ tabId: requestDetails.tabId },
(title) => {
const ifTitleSetAlready = /\n/.test(title);
const proxyHost = window.antiCensorRu.pacProvider.proxyIps[ requestDetails.ip ];
const hostname = new URL( requestDetails.url ).hostname;
let ifShouldUpdateTitle = false;
const indent = ' ';
const proxyTitle = 'Прокси:';
if (!ifTitleSetAlready) {
title = 'Разблокированы:\n'+ indent + hostname +'\n'+ proxyTitle +'\n'+ indent + proxyHost;
ifShouldUpdateTitle = true;
chrome.browserAction.setBadgeText({
tabId: requestDetails.tabId,
text: ifMainFrame ? '1' : '%1'
});
}
else {
const hostsProxiesPair = title.split(proxyTitle);
if (hostsProxiesPair[1].indexOf(proxyHost) === -1) {
title = title.replace(hostsProxiesPair[1], hostsProxiesPair[1] +'\n'+ indent + proxyHost);
ifShouldUpdateTitle = true;
}
if (hostsProxiesPair[0].indexOf(hostname) === -1) {
title = title.replace(proxyTitle, indent + hostname +'\n'+ proxyTitle);
ifShouldUpdateTitle = true;
const _cb = cb;
cb = () => chrome.browserAction.getBadgeText(
{tabId: requestDetails.tabId},
(result) => {
chrome.browserAction.setBadgeText(
{
tabId: requestDetails.tabId,
text: ( isNaN( result.charAt(0) ) && result.charAt(0) || '' ) + (hostsProxiesPair[0].split('\n').length - 1)
}
);
return _cb();
}
);
}
}
if (ifShouldUpdateTitle) {
chrome.browserAction.setTitle({
title: title,
tabId: requestDetails.tabId
});
}
return cb();
}
);
}
}
chrome.webRequest.onResponseStarted.addListener(

View File

@ -9,9 +9,10 @@
margin: 0;
margin-bottom: 1em;
}
li {
li, footer {
display: block;
white-space: nowrap;
word-break: keep-all;
}
li > * {
vertical-align: middle;
@ -49,8 +50,7 @@
</div>
<div id="status">Загрузка...</div>
<footer>
<input type="button" value="Готово" class="close-button">
<a href="../debug/index.html" style="float: right; text-decoration: none">Отладка</a>
<input type="button" value="Готово" class="close-button">&nbsp;<a href="../debug/index.html" style="text-decoration: none; margin-left: 1em;">Отладка</a>
</footer>
<script src="./index.js"></script>
<script src="./keep-links-clickable.js"></script>

View File

@ -61,92 +61,114 @@ chrome.runtime.getBackgroundPage( (backgroundPage) => {
// RADIOS
const currentRadio = () => {
const currentProviderRadio = () => {
const id = antiCensorRu.currentPacProviderKey || 'none';
return document.querySelector('#'+id);
}
const checkChosenProvider = () => currentRadio().checked = true;
const triggerChosenProvider = () => currentRadio().click();
const checkChosenProvider = () => currentProviderRadio().checked = true;
const ul = document.querySelector('#list-of-providers');
const _firstChild = ul.firstChild;
for( const providerKey of Object.keys(antiCensorRu.pacProviders).sort() ) {
const li = document.createElement('li');
li.innerHTML = '<input type="radio" name="pacProvider" id="' + providerKey + '"> <label for="' + providerKey + '">'+providerKey + '</label> <a href class="link-button checked-radio-panel">[обновить]</a>';
li.querySelector('.link-button').onclick = () => { triggerChosenProvider(); return false; };
ul.insertBefore( li, _firstChild );
}
const showError = (err) => {
const radios = [].slice.apply( document.querySelectorAll('[name=pacProvider]') );
for(const radio of radios) {
radio.onclick = function(event) {
let clarification = err.clarification;
const ifNotCritical = clarification && clarification.ifNotCritical;
let message = err.message || '';
const pacKey = event.target.id;
if (pacKey === 'none') {
return antiCensorRu.clearPac();
}
const enableDisableInputs = function () {
const inputs = document.querySelectorAll('input');
for ( let i = 0; i < inputs.length; i++ ) {
inputs[i].disabled = !inputs[i].disabled;
}
}
enableDisableInputs();
setStatusTo('Установка...');
antiCensorRu.installPac(pacKey, (err) => {
backgroundPage.console.log('Popup callback...');
if (!err) {
setStatusTo('PAC-скрипт установлен.');
checkChosenProvider();
}
else {
const ifNotCritical = err.clarification && err.clarification.ifNotCritical;
let message = '';
let clarification = err.clarification;
do {
message = message +' '+ (clarification && clarification.message || err.message || '');
clarification = clarification && clarification.prev;
} while( clarification );
message = message.trim();
setStatusTo(
while( clarification ) {
message = (clarification && (clarification.message + ' ')) + message;
clarification = clarification.prev;
}
message = message.trim();
setStatusTo(
`<span style="color:red">${ifNotCritical ? 'Некритичная ошибка.' : 'Ошибка!'}</span>
<br/>
<span style="font-size: 0.9em; color: darkred">${message}</span>
<button>Сообщить автору</button><br/>
<a href class="link-button">[Ещё&nbsp;подробнее]</a>`
);
getStatus().querySelector('.link-button').onclick = function() {
);
getStatus().querySelector('.link-button').onclick = function() {
const div = document.createElement('div');
div.innerHTML = `
const div = document.createElement('div');
div.innerHTML = `
Более подробную информацию можно узнать из логов фоновой страницы:<br/>
<a href="chrome://extensions?id=${chrome.runtime.id}" data-in-bg="true">chrome://extensions</a> Это расширение Отладка страниц: фоновая страница Console (DevTools)
<br>
Ещё: ${JSON.stringify({err: err, stack: err.stack})}
`;
getStatus().replaceChild(div, this);
return false;
};
}
enableDisableInputs();
});
getStatus().replaceChild(div, this);
return false;
};
};
const enableDisableInputs = function () {
const inputs = document.querySelectorAll('input');
for ( let i = 0; i < inputs.length; i++ ) {
inputs[i].disabled = !inputs[i].disabled;
}
};
const conduct = (beforeStatus, operation, afterStatus, onSuccess) => {
setStatusTo(beforeStatus);
enableDisableInputs();
operation((err) => {
if (err) {
showError(err);
}
else {
setStatusTo(afterStatus);
onSuccess && onSuccess();
}
enableDisableInputs();
})
};
const ul = document.querySelector('#list-of-providers');
const _firstChild = ul.firstChild;
for( const providerKey of Object.keys(antiCensorRu.pacProviders).sort() ) {
const li = document.createElement('li');
li.innerHTML = `<input type="radio" name="pacProvider" id="${providerKey}"> <label for="${providerKey}">${providerKey}</label> <a href class="link-button checked-radio-panel">[обновить]</a>`;
li.querySelector('.link-button').onclick = () => { conduct( 'Обновляем...', (cb) => antiCensorRu.syncWithPacProvider(cb), 'Обновлено.' ); return false; };
ul.insertBefore( li, _firstChild );
}
checkChosenProvider();
const radios = [].slice.apply( document.querySelectorAll('[name=pacProvider]') );
for(const radio of radios) {
radio.onclick = function(event) {
if (event.target.id === (antiCensorRu.currentPacProviderKey || 'none')) {
return false;
}
const pacKey = event.target.id;
if (pacKey === 'none') {
conduct(
'Отключение...',
(cb) => antiCensorRu.clearPac(cb),
'Отключено.',
checkChosenProvider
);
}
else {
conduct(
'Установка...',
(cb) => antiCensorRu.installPac(pacKey, cb),
'PAC-скрипт установлен.',
checkChosenProvider
);
}
return false;
};
}
setStatusTo('');
checkChosenProvider();
if (antiCensorRu.ifFirstInstall) {
triggerChosenProvider();
currentProviderRadio().click();
}
});