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: 'Оба_и_на_свитчах', _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 }, get currentPacProviderKey() { return this._currentPacProviderKey },
set currentPacProviderKey(newKey) { set currentPacProviderKey(newKey) {
if (newKey && !this.pacProviders[newKey]) this.mustBeKey(newKey);
throw new IllegalArgumentException('No provider for key:' + newKey);
this._currentPacProviderKey = 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. 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( !key || typeof(key) === 'function' ) {
if (!this.pacProvider) { 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-провайдера.'}}); return cb({clarification:{message:'Сперва выберите PAC-провайдера.'}});
} }
const pacProvider = this.getPacProvider(key);
const pacSetPromise = new Promise( const pacSetPromise = new Promise(
(resolve, reject) => setPacScriptFromProvider( (resolve, reject) => setPacScriptFromProvider(
this.pacProvider, pacProvider,
(err, res) => { (err, res) => {
if (!err) { if (!err) {
this.currentPacProviderKey = key;
this.lastPacUpdateStamp = Date.now(); this.lastPacUpdateStamp = Date.now();
this.ifFirstInstall = false; this.ifFirstInstall = false;
this.setAlarms(); this.setAlarms();
@ -132,7 +167,7 @@
const ipsPromise = new Promise( const ipsPromise = new Promise(
(resolve, reject) => updatePacProxyIps( (resolve, reject) => updatePacProxyIps(
this.pacProvider, pacProvider,
(ipsError) => { (ipsError) => {
if (ipsError && ipsError.clarification) { if (ipsError && ipsError.clarification) {
@ -186,28 +221,15 @@
installPac(key, cb) { installPac(key, cb) {
console.log('Installing PAC'); console.log('Installing PAC...');
if(typeof(key) === 'function') { if (!key) {
cb = key; throw new Error('Key must be defined.');
key = undefined;
} }
if (this.currentProviderKey !== key) {
const oldKey = this.currentPacProviderKey; return this.syncWithPacProvider(key, cb);
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);
};
} }
console.log(key + ' already installed.');
this.syncWithPacProvider(cb); cb();
}, },
@ -223,7 +245,7 @@
if (err) { if (err) {
return cb(err); return cb(err);
} }
this.currentPacProviderKey = undefined; this.currentPacProviderKey = null;
this.pushToStorage(cb); this.pushToStorage(cb);
} }
@ -236,16 +258,12 @@
// ON EACH LAUNCH, STARTUP, RELOAD, UPDATE, ENABLE // ON EACH LAUNCH, STARTUP, RELOAD, UPDATE, ENABLE
chrome.storage.local.get(null, (oldStorage) => { chrome.storage.local.get(null, (oldStorage) => {
console.log('Init on storage:', oldStorage); checkChromeError();
antiCensorRu.ifFirstInstall = Object.keys(oldStorage).length === 0; /*
Event handlers that ALWAYS work (even if installation is not done or failed).
if (!antiCensorRu.ifFirstInstall) { E.g. install window may fail to open or be closed by user accidentally.
// LAUNCH, RELOAD, UPDATE In such case extension _should_ try to work on default parameters.
antiCensorRu._currentPacProviderKey = oldStorage._currentPacProviderKey; */
antiCensorRu.lastPacUpdateStamp = oldStorage.lastPacUpdateStamp || antiCensorRu.lastPacUpdateStamp;
console.log( 'Last PAC update was on', new Date(antiCensorRu.lastPacUpdateStamp).toLocaleString('ru-RU') );
}
chrome.alarms.onAlarm.addListener( chrome.alarms.onAlarm.addListener(
(alarm) => { (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 // INSTALL
console.log('Installing...'); console.log('Installing...');
chrome.runtime.openOptionsPage(); return chrome.runtime.openOptionsPage();
} }
if (!antiCensorRu.pacProvider) { if (!antiCensorRu.getPacProvider()) {
return console.log('No PAC provider set. Do nothing.');
/* /*
In case of UPDATE: In case of UPDATE:
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.');
} }
/* /*

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] } ); 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(); let previousUpdateTitleFinished = Promise.resolve();
function isProxiedAndInformed(requestDetails) { function isProxiedAndInformed(requestDetails) {
if ( if ( !(requestDetails.ip && antiCensorRu.isProxied( requestDetails.ip )) ) {
!( window.antiCensorRu.pacProvider && window.antiCensorRu.pacProvider.proxyIps && window.antiCensorRu.pacProvider.proxyIps[ requestDetails.ip ] )
) {
return false; return false;
} }
@ -74,75 +142,6 @@ window.tabWithError2ip = {}; // For errors only: Error? -> Check this IP!
); );
return true; 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( chrome.webRequest.onResponseStarted.addListener(

View File

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

View File

@ -61,92 +61,114 @@ chrome.runtime.getBackgroundPage( (backgroundPage) => {
// RADIOS // RADIOS
const currentRadio = () => { const currentProviderRadio = () => {
const id = antiCensorRu.currentPacProviderKey || 'none'; const id = antiCensorRu.currentPacProviderKey || 'none';
return document.querySelector('#'+id); return document.querySelector('#'+id);
} }
const checkChosenProvider = () => currentRadio().checked = true; const checkChosenProvider = () => currentProviderRadio().checked = true;
const triggerChosenProvider = () => currentRadio().click();
const ul = document.querySelector('#list-of-providers'); const showError = (err) => {
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 radios = [].slice.apply( document.querySelectorAll('[name=pacProvider]') ); let clarification = err.clarification;
for(const radio of radios) { const ifNotCritical = clarification && clarification.ifNotCritical;
radio.onclick = function(event) { let message = err.message || '';
const pacKey = event.target.id; while( clarification ) {
if (pacKey === 'none') { message = (clarification && (clarification.message + ' ')) + message;
return antiCensorRu.clearPac(); clarification = clarification.prev;
} }
message = message.trim();
const enableDisableInputs = function () { setStatusTo(
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(
`<span style="color:red">${ifNotCritical ? 'Некритичная ошибка.' : 'Ошибка!'}</span> `<span style="color:red">${ifNotCritical ? 'Некритичная ошибка.' : 'Ошибка!'}</span>
<br/> <br/>
<span style="font-size: 0.9em; color: darkred">${message}</span> <span style="font-size: 0.9em; color: darkred">${message}</span>
<button>Сообщить автору</button><br/>
<a href class="link-button">[Ещё&nbsp;подробнее]</a>` <a href class="link-button">[Ещё&nbsp;подробнее]</a>`
); );
getStatus().querySelector('.link-button').onclick = function() { getStatus().querySelector('.link-button').onclick = function() {
const div = document.createElement('div'); const div = document.createElement('div');
div.innerHTML = ` div.innerHTML = `
Более подробную информацию можно узнать из логов фоновой страницы:<br/> Более подробную информацию можно узнать из логов фоновой страницы:<br/>
<a href="chrome://extensions?id=${chrome.runtime.id}" data-in-bg="true">chrome://extensions</a> Это расширение Отладка страниц: фоновая страница Console (DevTools) <a href="chrome://extensions?id=${chrome.runtime.id}" data-in-bg="true">chrome://extensions</a> Это расширение Отладка страниц: фоновая страница Console (DevTools)
<br> <br>
Ещё: ${JSON.stringify({err: err, stack: err.stack})} Ещё: ${JSON.stringify({err: err, stack: err.stack})}
`; `;
getStatus().replaceChild(div, this); getStatus().replaceChild(div, this);
return false;
};
}
enableDisableInputs();
});
return false; 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(''); setStatusTo('');
checkChosenProvider();
if (antiCensorRu.ifFirstInstall) { if (antiCensorRu.ifFirstInstall) {
triggerChosenProvider(); currentProviderRadio().click();
} }
}); });