Merge remote-tracking branch 'origin/production' into development

This commit is contained in:
Ilya Ig. Petrov 2017-08-26 20:45:56 +05:00
commit 64971bb098
13 changed files with 732 additions and 63 deletions

View File

@ -10,19 +10,19 @@ const PluginName = 'Template literals';
const templatePlugin = (context) => through.obj(function(file, encoding, cb) { const templatePlugin = (context) => through.obj(function(file, encoding, cb) {
const tjson = '.tmpl.json'; const suffixes = ['.tmpl.json', 'tmpl.js'];
if (file.path.endsWith(tjson)) { if ( suffixes.some( (suff) => file.path.endsWith(suff) ) ) {
const originalPath = file.path; const originalPath = file.path;
file.path = file.path.replace(new RegExp(`${tjson}$`), '.json'); file.path = file.path.replace(new RegExp(`tmpl.([^.]+)$`), '$1');
if (file.isStream()) { if (file.isStream()) {
return cb(new PluginError(PluginName, 'Streams not supported!')); return cb(new PluginError(PluginName, 'Streams are not supported!'));
} else if (file.isBuffer()) { } else if (file.isBuffer()) {
const {keys, values} = Object.keys(context).reduce( (acc, key) => { const {keys, values} = Object.keys(context).reduce( (acc, key) => {
const value = context[key]; const value = context[key];
acc.keys.push(key); acc.keys.push(key);
acc.values.push(value); acc.values.push(value);
return acc; return acc;
@ -60,6 +60,7 @@ const commonWoTests = ['./src/extension-common/**/*', ...excluded];
const miniDst = './build/extension-mini'; const miniDst = './build/extension-mini';
const fullDst = './build/extension-full'; const fullDst = './build/extension-full';
const betaDst = './build/extension-beta';
gulp.task('_cp-common', ['clean'], function(cb) { gulp.task('_cp-common', ['clean'], function(cb) {
@ -71,23 +72,28 @@ gulp.task('_cp-common', ['clean'], function(cb) {
}; };
gulp.src(commonWoTests) gulp.src(commonWoTests)
.pipe(changed(miniDst)) //.pipe(changed(miniDst))
.pipe(templatePlugin(contexts.mini)) .pipe(templatePlugin(contexts.mini))
.pipe(gulp.dest(miniDst)) .pipe(gulp.dest(miniDst))
.on('end', intheend); .on('end', intheend);
gulp.src(commonWoTests) gulp.src(commonWoTests)
.pipe(changed(fullDst)) //.pipe(changed(fullDst))
.pipe(templatePlugin(contexts.full)) .pipe(templatePlugin(contexts.full))
.pipe(gulp.dest(fullDst)) .pipe(gulp.dest(fullDst))
.on('end', intheend); .on('end', intheend);
gulp.src(commonWoTests)
//.pipe(changed(fullDst))
.pipe(templatePlugin(contexts.beta))
.pipe(gulp.dest(betaDst))
.on('end', intheend);
}); });
gulp.task('_cp-mini', ['_cp-common'], function(cb) { gulp.task('_cp-mini', ['_cp-common'], function(cb) {
gulp.src(['./src/extension-mini/**/*', ...excluded]) gulp.src(['./src/extension-mini/**/*', ...excluded])
.pipe(changed(miniDst)) //.pipe(changed(miniDst))
.pipe(templatePlugin(contexts.mini)) .pipe(templatePlugin(contexts.mini))
.pipe(gulp.dest(miniDst)) .pipe(gulp.dest(miniDst))
.on('end', cb); .on('end', cb);
@ -96,11 +102,22 @@ gulp.task('_cp-mini', ['_cp-common'], function(cb) {
gulp.task('_cp-full', ['_cp-common'], function(cb) { gulp.task('_cp-full', ['_cp-common'], function(cb) {
gulp.src(['./src/extension-full/**/*', ...excluded]) gulp.src(['./src/extension-full/**/*', ...excluded])
.pipe(changed(fullDst)) //.pipe(changed(fullDst))
.pipe(templatePlugin(contexts.full)) .pipe(templatePlugin(contexts.full))
.pipe(gulp.dest(fullDst)) .pipe(gulp.dest(fullDst))
.on('end', cb); .on('end', cb);
}); });
gulp.task('build', ['_cp-mini', '_cp-full']); gulp.task('_cp-beta', ['_cp-common'], function(cb) {
gulp.src(['./src/extension-full/**/*', ...excluded])
//.pipe(changed(fullDst))
.pipe(templatePlugin(contexts.beta))
.pipe(gulp.dest(betaDst))
.on('end', cb);
});
gulp.task('build:all', ['_cp-mini', '_cp-full', '_cp-beta']);
gulp.task('build', ['_cp-full']);

View File

@ -7,7 +7,9 @@
"lint": "eslint ./src/**/*.js --ignore-pattern vendor", "lint": "eslint ./src/**/*.js --ignore-pattern vendor",
"gulp": "gulp", "gulp": "gulp",
"test": "mocha --recursive ./src/**/test/*", "test": "mocha --recursive ./src/**/test/*",
"start": "cd ./src/extension-common/pages/options/ && npm run build && cd - && npm run gulp" "subpages": "cd ./src/extension-common/pages/options/ && npm run build && cd -",
"start": "npm run subpages && npm run gulp",
"beta": "npm run subpages && npm run gulp build:all"
}, },
"author": "Ilya Ig. Petrov", "author": "Ilya Ig. Petrov",
"license": "GPLv3", "license": "GPLv3",

View File

@ -0,0 +1,581 @@
'use strict';
/*
Task 1. Gets IPs for proxies of antizapret/anticenz via dns over https.
These IPs are used in block-informer to inform user when proxy is ON.
Task 2. Downloads PAC proxy script from antizapret/anticenz/
my Google Drive and sets it in Chromium settings.
Task 3. Schedules tasks 1 & 2 for every 4 hours.
*/
/*
In background scripts use window.apis.antiCensorRu public variables.
In pages window.apis.antiCensorRu is not accessible,
use chrome.runtime.getBackgroundPage(..),
extension.getBackgroundPage is deprecated
If you want to catch errors, then call api from setTimeout!
See errorHandlers api for more.
*/
{ // Private namespace starts.
const mandatory = window.utils.mandatory;
const throwIfError = window.utils.throwIfError;
const chromified = window.utils.chromified;
const timeouted = window.utils.timeouted;
const clarifyThen = window.apis.errorsLib.clarifyThen;
const Warning = window.apis.errorsLib.Warning;
const httpLib = window.apis.httpLib;
const handlers = window.apis.errorHandlers;
const asyncLogGroup = function asyncLogGroup(...args) {
const cb = args.pop();
if(!(cb && typeof(cb) === 'function')) {
throw new TypeError('cb must be a function, but got: ' + cb);
}
console.group(...args);
return function(...cbArgs) {
console.groupEnd();
console.log('Group finished.');
cb(...cbArgs);
};
};
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}, chromified((err) => {
if (err) {
return cb(err);
}
handlers.updateControlState( () => {
if ( !handlers.ifControlled ) {
console.warn('Failed, other extension is in control.');
return cb(
new Error( window.utils.messages.whichExtensionHtml() )
);
}
console.log('Successfuly set PAC in proxy settings..');
cb();
});
}));
};
const updatePacProxyIps = function updatePacProxyIps(
cb = throwIfError
) {
cb = asyncLogGroup(
'Getting IPs for PAC hosts...',
cb
);
window.utils.fireRequest('ip-to-host-update-all', cb);
};
const setPacScriptFromProviderAsync = function setPacScriptFromProviderAsync(
provider, lastModifiedStr = mandatory(), cb = throwIfError
) {
const pacUrl = provider.pacUrls[0];
cb = asyncLogGroup(
'Getting PAC script from provider...', pacUrl,
cb
);
httpLib.ifModifiedSince(pacUrl, lastModifiedStr, (err, newLastModifiedStr) => {
if (!newLastModifiedStr) {
const res = {lastModified: lastModifiedStr};
const ifWasEverModified = lastModifiedStr !== new Date(0).toUTCString();
if (ifWasEverModified) {
return cb(
null, res,
new Warning(
'Ваш PAC-скрипт не нуждается в обновлении. Его дата: ' +
lastModifiedStr
)
);
}
}
// Employ all urls, the latter are fallbacks for the former.
const pacDataPromise = provider.pacUrls.reduce(
(promise, url) => promise.catch(
() => new Promise(
(resolve, reject) => httpLib.get(
url,
(newErr, pacData) => newErr ? reject(newErr) : resolve(pacData)
)
)
),
Promise.reject()
);
pacDataPromise.then(
(pacData) => {
setPacAsync(
pacData,
(err, res) => cb(
err,
Object.assign(res || {}, {lastModified: newLastModifiedStr})
)
);
},
clarifyThen(
'Не удалось скачать PAC-скрипт с адресов: [ '
+ provider.pacUrls.join(' , ') + ' ].',
cb
)
);
});
};
window.apis.antiCensorRu = {
version: chrome.runtime.getManifest().version,
pacProviders: {
Антизапрет: {
label: 'Антизапрет',
desc: \`Альтернативный PAC-скрипт от стороннего разработчика.
Работает быстрее, но охватывает меньше сайтов.
Блокировка определяется по доменному имени,
<br/> <a href="https://antizapret.prostovpn.org">Страница проекта</a>.\`,
order: 0,
pacUrls: ['https://antizapret.prostovpn.org/proxy.pac'],
},
Антицензорити: {
label: 'Антицензорити',
desc: \`Основной PAC-скрипт от автора расширения.
Работает медленней, но охватывает больше сайтов.
Блокировка определятся по доменному имени или IP адресу.<br/>
<a href="https://rebrand.ly/ac-anticensority">Страница проекта</a>.\`,
order: 1,
/*
Don't use in system configs! Because Windows does poor caching.
Some urls are encoded to counter abuse.
Version: 0.17
*/
pacUrls: ${JSON.stringify(anticensorityPacUrls, null, 2)},
/*[
// First official, shortened:
'https://rebrand.ly/ac-chrome-anticensority-pac',
// Second official, Cloud Flare with caching:
'https://anticensority.tk/generated-pac-scripts/anticensority.pac',
// GitHub.io (anticensority):
'\x68\x74\x74\x70\x73\x3a\x2f\x2f\x61\x6e\x74\x69\x63\x65\x6e\x73\x6f\x72\x69\x74\x79\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 (anticensority):
'\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\x69\x74\x79\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',
// Old, deprecated:
'https://anticensorship-russia.tk/generated-pac-scripts/anticensority.pac',
// Google Drive (0.17, anticensority):
'\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\x32\x6d\x68\x42\x67\x46\x6e\x66\x34\x70\x45\x4c\x56\x6c\x47\x4e\x54\x42\x45\x4d\x58\x4e\x6d\x52\x58\x63',
],*/
},
onlyOwnSites: {
label: 'Только свои сайты и свои прокси',
desc: 'Проксируются только добавленные вручную сайты через СВОИ вручную добавленные прокси или через локальный Tor.',
order: 99,
pacUrls: [
'data:application/x-ns-proxy-autoconfig,' + escape('function FindProxyForURL(){ return "DIRECT"; }'),
]
}
},
getSortedEntriesForProviders() {
return Object.entries(this.pacProviders).sort((entryA, entryB) => entryA[1].order - entryB[1].order).map(([key, prov]) => Object.assign({key: key}, prov));
},
_currentPacProviderKey: 'Антизапрет',
/* Is it the first time extension installed?
Do something, e.g. initiate PAC sync.
*/
ifFirstInstall: false,
lastPacUpdateStamp: 0,
setTitle() {
const upDate = new Date(this.lastPacUpdateStamp).toLocaleString('ru-RU')
.replace(/:\\d+$/, '').replace(/\\.\\d{4}/, '');
chrome.browserAction.setTitle({
title: \`Обновлялись \${upDate} | Версия \${window.apis.version.build}\`,
});
},
_currentPacProviderLastModified: 0, // Not initialized.
getLastModifiedForKey(key = mandatory()) {
if (this._currentPacProviderKey === key) {
return new Date(this._currentPacProviderLastModified).toUTCString();
}
return new Date(0).toUTCString();
},
setLastModified(newValue = mandatory()) {
this._currentPacProviderLastModified = newValue;
},
mustBeKey(key = mandatory()) {
if ( !(key === null || this.pacProviders[key]) ) {
throw new TypeError('No provider for key:' + key);
}
},
getCurrentPacProviderKey() {
return this._currentPacProviderKey;
},
setCurrentPacProviderKey(
newKey = mandatory(),
lastModified = new Date().toUTCString()
) {
this.mustBeKey(newKey);
this._currentPacProviderKey = newKey;
this._currentPacProviderLastModified = lastModified;
},
getPacProvider(key) {
if(key) {
this.mustBeKey(key);
} else {
key = this.getCurrentPacProviderKey();
}
return this.pacProviders[key];
},
_periodicUpdateAlarmReason: 'Периодичное обновление PAC-скрипта',
pushToStorageAsync(cb = throwIfError) {
console.log('Pushing to storage...');
// Copy only settable properties (except functions).
const onlySettable = {};
for(const key of Object.keys(this)) {
if (
Object.getOwnPropertyDescriptor(this, key).writable
&& typeof(this[key]) !== 'function'
) {
onlySettable[key] = this[key];
}
}
chrome.storage.local.clear(
() => chrome.storage.local.set(
onlySettable,
chromified(cb)
)
);
},
syncWithPacProviderAsync(
key = this.currentPacProvierKey, cb = throwIfError) {
if( typeof(key) === 'function' ) {
cb = key;
key = this.getCurrentPacProviderKey();
}
cb = asyncLogGroup('Syncing with PAC provider ' + key + '...', cb);
if (key === null) {
// No pac provider set.
return clarifyThen('Сперва выберите PAC-провайдера.', cb);
}
const pacProvider = this.getPacProvider(key);
const pacSetPromise = new Promise(
(resolve, reject) => setPacScriptFromProviderAsync(
pacProvider,
this.getLastModifiedForKey(key),
(err, res, ...warns) => {
if (!err) {
this.setCurrentPacProviderKey(key, res.lastModified);
this.lastPacUpdateStamp = Date.now();
this.ifFirstInstall = false;
this.setAlarms();
this.setTitle();
}
resolve([err, null, ...warns]);
}
)
);
const ipsErrorPromise = new Promise(
(resolve, reject) => updatePacProxyIps(
resolve
)
);
Promise.all([pacSetPromise, ipsErrorPromise]).then(
([[pacErr, pacRes, ...pacWarns], ipsErr]) => {
if (pacErr && ipsErr) {
return cb(pacErr, pacRes);
}
const warns = pacWarns;
if (ipsErr) {
warns.push(ipsErr);
}
this.pushToStorageAsync(
(pushErr) => cb(pacErr || pushErr, null, ...warns)
);
},
cb
);
},
_pacUpdatePeriodInMinutes: 12*60,
get pacUpdatePeriodInMinutes() {
return this._pacUpdatePeriodInMinutes;
},
setAlarms() {
let nextUpdateMoment = this.lastPacUpdateStamp
+ this._pacUpdatePeriodInMinutes*60*1000;
const now = Date.now();
if (nextUpdateMoment < now) {
nextUpdateMoment = now;
}
console.log(
'Next PAC update is scheduled on',
new Date(nextUpdateMoment).toLocaleString('ru-RU')
);
chrome.alarms.create(
this._periodicUpdateAlarmReason,
{
when: nextUpdateMoment,
periodInMinutes: this._pacUpdatePeriodInMinutes,
}
);
// ifAlarmTriggered. May be changed in the future.
return nextUpdateMoment === now;
},
installPacAsync(key, cb = throwIfError) {
console.log('Installing PAC...');
if (!key) {
throw new Error('Key must be defined.');
}
if (this.currentProviderKey !== key) {
return this.syncWithPacProviderAsync(key, cb);
}
console.log(key + ' already installed.');
cb();
},
clearPacAsync(cb = throwIfError) {
cb = asyncLogGroup('Cearing alarms and PAC...', cb);
chrome.alarms.clearAll(
() => chrome.proxy.settings.clear(
{},
chromified((err) => {
if (err) {
return cb(err);
}
this.setCurrentPacProviderKey(null);
this.pushToStorageAsync(
() => handlers.updateControlState(cb)
);
})
)
);
},
};
// ON EACH LAUNCH, STARTUP, RELOAD, UPDATE, ENABLE
chrome.storage.local.get(null, chromified( async (err, oldStorage) => {
if (err) {
throw err;
}
/*
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.
*/
const antiCensorRu = window.apis.antiCensorRu;
chrome.alarms.onAlarm.addListener(
timeouted( (alarm) => {
if (alarm.name === antiCensorRu._periodicUpdateAlarmReason) {
console.log(
'Periodic PAC update triggered:',
new Date().toLocaleString('ru-RU')
);
antiCensorRu.syncWithPacProviderAsync(() => {/* swallow */});
}
})
);
console.log('Alarm listener installed. We won\\'t miss any PAC update.');
window.addEventListener('online', () => {
console.log('We are online, checking periodic updates...');
antiCensorRu.setAlarms();
});
console.log('Keep cooked...');
await new Promise((resolve) => window.apis.pacKitchen.keepCookedNowAsync(resolve));
console.log('Storage on init:', oldStorage);
antiCensorRu.ifFirstInstall = Object.keys(oldStorage).length === 0;
if (antiCensorRu.ifFirstInstall) {
// INSTALL
console.log('Installing...');
handlers.switch('on', 'ext-error');
return chrome.runtime.openOptionsPage();
}
// LAUNCH, RELOAD, UPDATE
// Use old or migrate to default.
antiCensorRu._currentPacProviderKey =
oldStorage._currentPacProviderKey || null;
antiCensorRu.lastPacUpdateStamp =
oldStorage.lastPacUpdateStamp || antiCensorRu.lastPacUpdateStamp;
antiCensorRu._currentPacProviderLastModified =
oldStorage._currentPacProviderLastModified
|| antiCensorRu._currentPacProviderLastModified;
console.log(
'Last PAC update was on',
new Date(antiCensorRu.lastPacUpdateStamp).toLocaleString('ru-RU')
);
/*
1. There is no way to check that chrome.runtime.onInstalled wasn't fired
except timeout.
Otherwise we could put storage migration code only there.
2. We have to check storage for migration before using it.
Better on each launch then on each pull.
*/
await new Promise((resolve) => {
const ifUpdating = antiCensorRu.version !== oldStorage.version;
if (!ifUpdating) {
// LAUNCH, RELOAD, ENABLE
antiCensorRu.pacProviders = oldStorage.pacProviders;
console.log('Extension launched, reloaded or enabled.');
return resolve();
}
// UPDATE & MIGRATION
console.log('Updating from ', oldStorage.version, 'to', antiCensorRu.version);
const key = antiCensorRu._currentPacProviderKey;
if (key !== null) {
const ifVeryOld = !Object.keys(antiCensorRu.pacProviders).includes(key);
if (ifVeryOld) {
antiCensorRu._currentPacProviderKey = 'Антизапрет';
}
}
antiCensorRu.pushToStorageAsync(() => {
console.log('Extension updated.');
resolve();
});
});
if (antiCensorRu.getPacProvider()) {
antiCensorRu.setAlarms();
}
antiCensorRu.setTitle();
/*
History of Changes to Storage (Migration Guide)
-----------------------------------------------
Version 0.0.0.17:
* Remove "Антиценз".
* Rename "Оба_и_на_свитчах" to "Антицензорити"
* Add provider.label and provider.desc.
Version 0.0.0.10:
* Add this.version.
* Change PacProvider.proxyIps from {ip -> Boolean} to {ip -> hostname}.
Version 0.0.0.8-9:
* Change storage.ifNotInstalled to storage.ifFirstInstall.
* Add storage.lastPacUpdateStamp.
**/
}));
}

View File

@ -168,10 +168,10 @@ textarea {
min-height: 2em; min-height: 2em;
padding: 3px; padding: 3px;
outline: none; outline: none;
<if expr="is_win or is_macosx or is_ios"> /*<if expr="is_win or is_macosx or is_ios">*
/* For better alignment between adjacent buttons and inputs. */ /* For better alignment between adjacent buttons and inputs. */
padding-bottom: 4px; padding-bottom: 4px;
</if> /*</if>*/
} }
input[type='search'] { input[type='search'] {

View File

@ -36,7 +36,7 @@ export default function getApp(theState) {
this.setState( this.setState(
{ {
status: msg, status: msg || 'Хорошего настроения Вам!',
}, },
cb cb
); );
@ -79,7 +79,8 @@ export default function getApp(theState) {
headers: new Headers(headers), headers: new Headers(headers),
}; };
const ghUrl = `https://api.github.com/repos/anticensority/chromium-extension/issues/10/comments${query}`; //const ghUrl = `https://api.github.com/repos/anticensority/chromium-extension/issues/10/comments${query}`;
const ghUrl = `https://api.github.com/repos/anticensority/for-testing/issues/1/comments${query}`;
const [error, comments, etag] = await fetch( const [error, comments, etag] = await fetch(
ghUrl, ghUrl,
@ -156,7 +157,7 @@ export default function getApp(theState) {
})(); })();
if (!ifNewsWasSet) { if (!ifNewsWasSet) {
this.setStatusTo('Хорошего настроения Вам!'); this.setStatusTo();
} }
} }

View File

@ -38,7 +38,8 @@ export default function getMain(theState) {
super(props); super(props);
this.state = { this.state = {
ifModsChangesStashed: false, ifModsChangesAreStashed: false,
ifModsChangesAreValid: true,
catToOrderedMods: { catToOrderedMods: {
'general': props.apis.pacKitchen.getOrderedConfigs('general'), 'general': props.apis.pacKitchen.getOrderedConfigs('general'),
'ownProxies': props.apis.pacKitchen.getOrderedConfigs('ownProxies'), 'ownProxies': props.apis.pacKitchen.getOrderedConfigs('ownProxies'),
@ -58,24 +59,46 @@ export default function getMain(theState) {
handleModApply(that) { handleModApply(that) {
if (!that.state.ifModsChangesAreValid) {
// Error message must be already set by a config validator.
return;
}
const modsMutated = that.props.apis.pacKitchen.getPacMods(); const modsMutated = that.props.apis.pacKitchen.getPacMods();
const newMods = that.getAllMods().reduce((_, conf) => { const newMods = that.getAllMods().reduce((_, conf) => {
modsMutated[conf.key] = conf.value; modsMutated[conf.key] = conf.value;
return modsMutated; return modsMutated;
}, modsMutated/*< Needed for index 0*/); }, modsMutated/* Needed for index 0*/);
that.props.funs.conduct( that.props.funs.conduct(
'Применяем настройки...', 'Применяем настройки...',
(cb) => that.props.apis.pacKitchen.keepCookedNowAsync(newMods, cb), (cb) => that.props.apis.pacKitchen.keepCookedNowAsync(newMods, cb),
'Настройки применены.', 'Настройки применены.',
() => that.setState({ifModsChangesStashed: false}) () => that.setState({
ifModsChangesAreStashed: false,
ifModsChangesAreValid: true,
})
); );
} }
handleModChange({targetConf, targetIndex, newValue}) { handleModChange({ifValid, targetConf, targetIndex, newValue}) {
if (ifValid === undefined) {
// User input some data, but not validated yet.
this.setState({
// Make apply button clickable when user only starts writing.
ifModsChangesAreStashed: true,
});
return;
}
if (ifValid === false) {
this.setState({
ifModsChangesAreValid: false,
ifModsChangesAreStashed: true,
})
return;
}
const oldCats = this.state.catToOrderedMods; const oldCats = this.state.catToOrderedMods;
const newCats = Object.keys(this.state.catToOrderedMods).reduce((acc, cat) => { const newCats = Object.keys(this.state.catToOrderedMods).reduce((acc, cat) => {
@ -99,7 +122,8 @@ export default function getMain(theState) {
this.setState({ this.setState({
catToOrderedMods: newCats, catToOrderedMods: newCats,
ifModsChangesStashed: true, ifModsChangesAreStashed: true,
ifModsChangesAreValid: true,
}); });
} }
@ -108,7 +132,7 @@ export default function getMain(theState) {
const applyModsEl = createElement(ApplyMods, Object.assign({}, props, const applyModsEl = createElement(ApplyMods, Object.assign({}, props,
{ {
ifInputsDisabled: !this.state.ifModsChangesStashed || props.ifInputsDisabled, ifInputsDisabled: !this.state.ifModsChangesAreStashed || props.ifInputsDisabled,
onClick: linkEvent(this, this.handleModApply), onClick: linkEvent(this, this.handleModApply),
} }
)); ));

View File

@ -26,17 +26,18 @@ export default function getModList(theState) {
) )
}); });
if (ifChecked === false || !confMeta.ifChild) { if (ifChecked === false || !confMeta.ifChild) {
this.handleNewValue(confMeta, ifChecked); this.handleNewValue(true, confMeta, ifChecked);
} }
} }
handleNewValue({ conf, index }, newValue) { handleNewValue(ifValid, { conf, index }, newValue) {
this.props.onConfChanged({ this.props.onConfChanged({
targetConf: conf, targetConf: conf,
targetIndex: index, targetIndex: index,
newValue: newValue, newValue: newValue,
ifValid,
}); });
} }
@ -54,7 +55,7 @@ export default function getModList(theState) {
const child = ifMayHaveChild && this.state.checks[index] const child = ifMayHaveChild && this.state.checks[index]
&& createElement( && createElement(
props.childrenOfMod[conf.key], props.childrenOfMod[conf.key],
Object.assign({}, props, {conf, onNewValue: (newValue) => this.handleNewValue(confMeta, newValue)}) Object.assign({}, props, {conf, onNewValue: (ifValid, newValue) => this.handleNewValue(ifValid, confMeta, newValue)})
); );
return (<InfoLi return (<InfoLi

View File

@ -160,7 +160,7 @@ export default function getProxyEditor(theState) {
}; };
const splitBySemi = (proxyString) => proxyString.replace(/#.*$/mg, '').trim().split(/\s*;\s*/g).filter((s) => s); const splitBySemi = (proxyString) => proxyString.replace(/#.*$/mg, '').trim().split(/\s*;\s*/g).filter((s) => s);
const joinBySemi = (strs) => strs.join(';\n') + ';'; const joinBySemi = (strs) => strs.join(';\n') + ';';
const normilizeProxyString = (str) => joinBySemi(splitBySemi(str)); const normalizeProxyString = (str) => joinBySemi(splitBySemi(str));
const PROXY_TYPE_LABEL_PAIRS = [['PROXY', 'PROXY/HTTP'],['HTTPS'],['SOCKS4'],['SOCKS5'],['SOCKS']]; const PROXY_TYPE_LABEL_PAIRS = [['PROXY', 'PROXY/HTTP'],['HTTPS'],['SOCKS4'],['SOCKS5'],['SOCKS']];
@ -222,7 +222,7 @@ export default function getProxyEditor(theState) {
const newValue = `${that.props.proxyStringRaw}; ${type} ${hostname}:${port}` const newValue = `${that.props.proxyStringRaw}; ${type} ${hostname}:${port}`
.trim().replace(/(\s*;\s*)+/, '; '); .trim().replace(/(\s*;\s*)+/, '; ');
that.props.setProxyStringRaw(newValue); that.props.setProxyStringRaw(true, newValue);
} }
@ -232,7 +232,7 @@ export default function getProxyEditor(theState) {
const proxyStrings = splitBySemi(that.props.proxyStringRaw); const proxyStrings = splitBySemi(that.props.proxyStringRaw);
proxyStrings.splice(index, 1); proxyStrings.splice(index, 1);
that.props.setProxyStringRaw( joinBySemi(proxyStrings) ); that.props.setProxyStringRaw(true, joinBySemi(proxyStrings) );
} }
@ -245,7 +245,7 @@ export default function getProxyEditor(theState) {
const proxyStrings = splitBySemi(that.props.proxyStringRaw); const proxyStrings = splitBySemi(that.props.proxyStringRaw);
proxyStrings.splice(index - 1, 2, proxyStrings[index], proxyStrings[index-1]); proxyStrings.splice(index - 1, 2, proxyStrings[index], proxyStrings[index-1]);
that.props.setProxyStringRaw( joinBySemi(proxyStrings) ); that.props.setProxyStringRaw(true, joinBySemi(proxyStrings) );
} }
@ -366,11 +366,17 @@ export default function getProxyEditor(theState) {
super(props); super(props);
this.state = getInitState(); this.state = getInitState();
this.resetState = linkEvent(this, this.resetState);
this.showApply = linkEvent(undefined, props.setProxyStringRaw);
} }
resetState(that, event) { resetState(that, event) {
that.props.setProxyStringRaw(true, that.props.proxyStringRaw);
if (that.state.ifHasErrors) {
that.props.funs.setStatusTo(''); // Clear errors
}
that.setState(getInitState()); that.setState(getInitState());
event.preventDefault(); event.preventDefault();
@ -417,19 +423,9 @@ export default function getProxyEditor(theState) {
handleModeSwitch(that, event) { handleModeSwitch(that, event) {
if (that.state.stashedExports !== false) { if (that.state.ifHasErrors) {
const errors = that.getErrorsInStashedExports(); return;
if (errors) {
that.setState({ifHasErrors: true});
that.props.funs.showErrors(...errors);
return;
}
that.props.setProxyStringRaw(that.state.stashedExports);
} }
that.setState({
stashedExports: false,
ifHasErrors: false,
});
that.props.onSwitch(); that.props.onSwitch();
} }
@ -437,7 +433,20 @@ export default function getProxyEditor(theState) {
handleTextareaChange(that, event) { handleTextareaChange(that, event) {
that.setState({ that.setState({
stashedExports: normilizeProxyString(event.target.value), stashedExports: normalizeProxyString(event.target.value),
});
const errors = that.getErrorsInStashedExports();
if (errors) {
that.props.setProxyStringRaw(false);
that.setState({ifHasErrors: true});
that.props.funs.showErrors(...errors);
return;
}
// No errors.
that.props.setProxyStringRaw(true, that.state.stashedExports);
that.setState({
stashedExports: false,
ifHasErrors: false,
}); });
} }
@ -451,8 +460,6 @@ export default function getProxyEditor(theState) {
render(props) { render(props) {
const reset = linkEvent(this, this.resetState);
return ( return (
<form onSubmit={linkEvent(this, this.handleSubmit)}> <form onSubmit={linkEvent(this, this.handleSubmit)}>
<table class={scopedCss.editor + ' ' + scopedCss.exportsEditor}> <table class={scopedCss.editor + ' ' + scopedCss.exportsEditor}>
@ -463,8 +470,8 @@ export default function getProxyEditor(theState) {
this.state.stashedExports === false this.state.stashedExports === false
? 'Комментарии вырезаются!' ? 'Комментарии вырезаются!'
: (this.state.ifHasErrors : (this.state.ifHasErrors
? (<span><a href="" onClick={reset}>Сбросьте изменения</a> или поправьте</span>) ? (<span><a href="" onClick={this.resetState} style="color: red">Сбросьте изменения</a> или поправьте</span>)
: (<a href="" onClick={reset}>Сбросить изменения</a>) : (<a href="" onClick={this.resetState}>Сбросить изменения</a>)
) )
} }
</th> </th>
@ -484,6 +491,7 @@ SOCKS5 localhost:9150; # Tor Browser
HTTPS 11.22.33.44:3143; HTTPS 11.22.33.44:3143;
PROXY foobar.com:8080; # Not HTTP!`.trim()} PROXY foobar.com:8080; # Not HTTP!`.trim()}
onChange={linkEvent(this, this.handleTextareaChange)} onChange={linkEvent(this, this.handleTextareaChange)}
onFocus={this.showApply}
value={ value={
this.state.stashedExports !== false this.state.stashedExports !== false
? this.state.stashedExports ? this.state.stashedExports
@ -525,6 +533,7 @@ PROXY foobar.com:8080; # Not HTTP!`.trim()}
this.state = { this.state = {
proxyStringRaw: newValue, proxyStringRaw: newValue,
ifExportsMode: false, ifExportsMode: false,
ifValid: true,
}; };
this.handleSwitch = () => this.setState({ifExportsMode: !this.state.ifExportsMode}); this.handleSwitch = () => this.setState({ifExportsMode: !this.state.ifExportsMode});
waitingTillMount.push(newValue); // Wait till mount or eat bugs. waitingTillMount.push(newValue); // Wait till mount or eat bugs.
@ -546,12 +555,12 @@ PROXY foobar.com:8080; # Not HTTP!`.trim()}
} }
mayEmitNewValue(oldValue, newValue) { mayEmitNewValue(oldValue, newValue, ifValidityChanged) {
if ( // Reject: 1) both `false` OR 2) both `===`. if ( // Reject: 1) both `false` OR 2) both `===`.
( Boolean(oldValue) || Boolean(newValue) ) && oldValue !== newValue ifValidityChanged || ( Boolean(oldValue) || Boolean(newValue) ) && oldValue !== newValue
) { ) {
this.props.onNewValue(newValue); this.props.onNewValue(this.state.ifValid, newValue);
} }
} }
@ -561,12 +570,21 @@ PROXY foobar.com:8080; # Not HTTP!`.trim()}
const props = Object.assign({ const props = Object.assign({
proxyStringRaw: this.state.proxyStringRaw, proxyStringRaw: this.state.proxyStringRaw,
onSwitch: this.handleSwitch, onSwitch: this.handleSwitch,
setProxyStringRaw: (newValue) => { setProxyStringRaw: (ifValid, newValue) => {
const ifValidityChanged = this.state.ifValid !== ifValid;
if (!ifValid) {
if (ifValidityChanged || ifValid === undefined) {
this.props.onNewValue(ifValid);
this.setState({ ifValid });
}
return;
}
const oldValue = this.state.proxyStringRaw; const oldValue = this.state.proxyStringRaw;
localStorage.setItem(UI_RAW, newValue); localStorage.setItem(UI_RAW, newValue);
this.setState({proxyStringRaw: newValue}); this.setState({proxyStringRaw: newValue, ifValid});
this.mayEmitNewValue(oldValue, newValue); this.mayEmitNewValue(oldValue, newValue, ifValidityChanged);
}, },
}, originalProps); }, originalProps);

View File

@ -2,6 +2,20 @@
const commonContext = { const commonContext = {
version: '1.3', version: '1.3',
anticensorityPacUrls: [
// First official, shortened:
'https://rebrand.ly/ac-chrome-anticensority-pac',
// Second official, Cloud Flare with caching:
'https://anticensority.tk/generated-pac-scripts/anticensority.pac',
// GitHub.io (anticensority):
'\x68\x74\x74\x70\x73\x3a\x2f\x2f\x61\x6e\x74\x69\x63\x65\x6e\x73\x6f\x72\x69\x74\x79\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 (anticensority):
'\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\x69\x74\x79\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',
// Old, deprecated:
'https://anticensorship-russia.tk/generated-pac-scripts/anticensority.pac',
// Google Drive (0.17, anticensority):
'\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\x32\x6d\x68\x42\x67\x46\x6e\x66\x34\x70\x45\x4c\x56\x6c\x47\x4e\x54\x42\x45\x4d\x58\x4e\x6d\x52\x58\x63',
]
}; };
exports.contexts = {}; exports.contexts = {};
@ -26,3 +40,14 @@ exports.contexts.mini = Object.assign({}, commonContext, {
scripts_8x: '', scripts_8x: '',
}); });
exports.contexts.beta = Object.assign({}, commonContext, {
anticensorityPacUrls: ['https://rebrand.ly/ac-beta-pac'],
version: '1.5',
versionSuffix: '',
nameSuffixEn: ' FOR TESTING',
nameSuffixRu: ' ДЛЯ ТЕСТОВ',
extra_permissions: ', "webRequest", "webNavigation"',
persistent: '',
scripts_2x: ', "20-ip-to-host-api.js"',
scripts_8x: ', "80-error-menu.js", "83-last-errors.js", "85-block-informer.js"',
});