diff --git a/extensions/chromium/runet-censorship-bypass/src/extension-common/37-sync-pac-script-with-pac-provider-api.js b/extensions/chromium/runet-censorship-bypass/37-sync-pac-script-with-pac-provider-api.js
similarity index 100%
rename from extensions/chromium/runet-censorship-bypass/src/extension-common/37-sync-pac-script-with-pac-provider-api.js
rename to extensions/chromium/runet-censorship-bypass/37-sync-pac-script-with-pac-provider-api.js
diff --git a/extensions/chromium/runet-censorship-bypass/gulpfile.js b/extensions/chromium/runet-censorship-bypass/gulpfile.js
index 0f2ea15..034dfcd 100644
--- a/extensions/chromium/runet-censorship-bypass/gulpfile.js
+++ b/extensions/chromium/runet-censorship-bypass/gulpfile.js
@@ -10,19 +10,19 @@ const PluginName = 'Template literals';
const templatePlugin = (context) => through.obj(function(file, encoding, cb) {
- const tjson = '.tmpl.json';
- if (file.path.endsWith(tjson)) {
+ const suffixes = ['.tmpl.json', 'tmpl.js'];
+ if ( suffixes.some( (suff) => file.path.endsWith(suff) ) ) {
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()) {
- return cb(new PluginError(PluginName, 'Streams not supported!'));
+ return cb(new PluginError(PluginName, 'Streams are not supported!'));
} else if (file.isBuffer()) {
const {keys, values} = Object.keys(context).reduce( (acc, key) => {
- const value = context[key];
+ const value = context[key];
acc.keys.push(key);
acc.values.push(value);
return acc;
@@ -60,6 +60,7 @@ const commonWoTests = ['./src/extension-common/**/*', ...excluded];
const miniDst = './build/extension-mini';
const fullDst = './build/extension-full';
+const betaDst = './build/extension-beta';
gulp.task('_cp-common', ['clean'], function(cb) {
@@ -71,23 +72,28 @@ gulp.task('_cp-common', ['clean'], function(cb) {
};
gulp.src(commonWoTests)
- .pipe(changed(miniDst))
+ //.pipe(changed(miniDst))
.pipe(templatePlugin(contexts.mini))
.pipe(gulp.dest(miniDst))
.on('end', intheend);
gulp.src(commonWoTests)
- .pipe(changed(fullDst))
+ //.pipe(changed(fullDst))
.pipe(templatePlugin(contexts.full))
.pipe(gulp.dest(fullDst))
.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.src(['./src/extension-mini/**/*', ...excluded])
- .pipe(changed(miniDst))
+ //.pipe(changed(miniDst))
.pipe(templatePlugin(contexts.mini))
.pipe(gulp.dest(miniDst))
.on('end', cb);
@@ -96,11 +102,22 @@ gulp.task('_cp-mini', ['_cp-common'], function(cb) {
gulp.task('_cp-full', ['_cp-common'], function(cb) {
gulp.src(['./src/extension-full/**/*', ...excluded])
- .pipe(changed(fullDst))
+ //.pipe(changed(fullDst))
.pipe(templatePlugin(contexts.full))
.pipe(gulp.dest(fullDst))
.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']);
diff --git a/extensions/chromium/runet-censorship-bypass/package.json b/extensions/chromium/runet-censorship-bypass/package.json
index 5fdf7a3..88854d6 100644
--- a/extensions/chromium/runet-censorship-bypass/package.json
+++ b/extensions/chromium/runet-censorship-bypass/package.json
@@ -7,7 +7,9 @@
"lint": "eslint ./src/**/*.js --ignore-pattern vendor",
"gulp": "gulp",
"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",
"license": "GPLv3",
diff --git a/extensions/chromium/runet-censorship-bypass/src/extension-common/37-sync-pac-script-with-pac-provider-api.tmpl.js b/extensions/chromium/runet-censorship-bypass/src/extension-common/37-sync-pac-script-with-pac-provider-api.tmpl.js
new file mode 100644
index 0000000..e2184dc
--- /dev/null
+++ b/extensions/chromium/runet-censorship-bypass/src/extension-common/37-sync-pac-script-with-pac-provider-api.tmpl.js
@@ -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-скрипт от стороннего разработчика.
+ Работает быстрее, но охватывает меньше сайтов.
+ Блокировка определяется по доменному имени,
+
Страница проекта.\`,
+ order: 0,
+ pacUrls: ['https://antizapret.prostovpn.org/proxy.pac'],
+ },
+ Антицензорити: {
+ label: 'Антицензорити',
+ desc: \`Основной PAC-скрипт от автора расширения.
+ Работает медленней, но охватывает больше сайтов.
+ Блокировка определятся по доменному имени или IP адресу.
+ Страница проекта.\`,
+ 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.
+ **/
+
+ }));
+
+}
diff --git a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/lib/chrome-style/index.css b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/lib/chrome-style/index.css
index a3cc382..497fdd0 100644
--- a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/lib/chrome-style/index.css
+++ b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/lib/chrome-style/index.css
@@ -168,10 +168,10 @@ textarea {
min-height: 2em;
padding: 3px;
outline: none;
-
+/**
/* For better alignment between adjacent buttons and inputs. */
padding-bottom: 4px;
-
+/**/
}
input[type='search'] {
diff --git a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/App.js b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/App.js
index 4bfd555..32aeeb9 100644
--- a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/App.js
+++ b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/App.js
@@ -36,7 +36,7 @@ export default function getApp(theState) {
this.setState(
{
- status: msg,
+ status: msg || 'Хорошего настроения Вам!',
},
cb
);
@@ -79,7 +79,8 @@ export default function getApp(theState) {
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(
ghUrl,
@@ -156,7 +157,7 @@ export default function getApp(theState) {
})();
if (!ifNewsWasSet) {
- this.setStatusTo('Хорошего настроения Вам!');
+ this.setStatusTo();
}
}
@@ -206,7 +207,7 @@ export default function getApp(theState) {
};
let messageHtml = err ? errToHtmlMessage(err) : '';
-
+
const warningHtml = warns
.filter((w) => w)
.map(
diff --git a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ExcEditor.js b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ExcEditor.js
index 378e92b..4bf2dbe 100644
--- a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ExcEditor.js
+++ b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ExcEditor.js
@@ -65,7 +65,7 @@ export default function getExcEditor(theState) {
return class ExcEditor extends Component {
modsToOpts(pacMods) {
-
+
return Object.keys(pacMods.exceptions || {}).sort().map(
(excHost) => [excHost, pacMods.exceptions[excHost]]
);
@@ -100,7 +100,7 @@ export default function getExcEditor(theState) {
},
{}),
});
-
+
}
isHostValid(host) {
diff --git a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/InfoLi.js b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/InfoLi.js
index 131ff77..d1f1c53 100644
--- a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/InfoLi.js
+++ b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/InfoLi.js
@@ -41,7 +41,7 @@ export default function getInfoLi() {
.desc {
text-align: right;
color: var(--ribbon-color);
- cursor: help;
+ cursor: help;
padding-left: 0.3em;
}
.tooltip {
diff --git a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/Main.js b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/Main.js
index 8ae6249..ca68cdf 100644
--- a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/Main.js
+++ b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/Main.js
@@ -38,7 +38,8 @@ export default function getMain(theState) {
super(props);
this.state = {
- ifModsChangesStashed: false,
+ ifModsChangesAreStashed: false,
+ ifModsChangesAreValid: true,
catToOrderedMods: {
'general': props.apis.pacKitchen.getOrderedConfigs('general'),
'ownProxies': props.apis.pacKitchen.getOrderedConfigs('ownProxies'),
@@ -58,24 +59,46 @@ export default function getMain(theState) {
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 newMods = that.getAllMods().reduce((_, conf) => {
modsMutated[conf.key] = conf.value;
return modsMutated;
- }, modsMutated/*< Needed for index 0*/);
+ }, modsMutated/* Needed for index 0*/);
that.props.funs.conduct(
'Применяем настройки...',
(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 newCats = Object.keys(this.state.catToOrderedMods).reduce((acc, cat) => {
@@ -96,10 +119,11 @@ export default function getMain(theState) {
return acc;
}, {});
-
+
this.setState({
catToOrderedMods: newCats,
- ifModsChangesStashed: true,
+ ifModsChangesAreStashed: true,
+ ifModsChangesAreValid: true,
});
}
@@ -108,7 +132,7 @@ export default function getMain(theState) {
const applyModsEl = createElement(ApplyMods, Object.assign({}, props,
{
- ifInputsDisabled: !this.state.ifModsChangesStashed || props.ifInputsDisabled,
+ ifInputsDisabled: !this.state.ifModsChangesAreStashed || props.ifInputsDisabled,
onClick: linkEvent(this, this.handleModApply),
}
));
diff --git a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ModList.js b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ModList.js
index d43c1c1..3c3dab8 100644
--- a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ModList.js
+++ b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ModList.js
@@ -7,7 +7,7 @@ export default function getModList(theState) {
const InfoLi = getInfoLi(theState);
- return class ModList extends Component {
+ return class ModList extends Component {
constructor(props) {
@@ -26,17 +26,18 @@ export default function getModList(theState) {
)
});
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({
targetConf: conf,
targetIndex: index,
newValue: newValue,
+ ifValid,
});
}
@@ -54,7 +55,7 @@ export default function getModList(theState) {
const child = ifMayHaveChild && this.state.checks[index]
&& createElement(
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 (
+ const checkChosenProvider = () =>
this.setState({ chosenPacName: this.getCurrentProviderId() });
const pacKey = event.target.id;
diff --git a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ProxyEditor.js b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ProxyEditor.js
index bbca265..85c31da 100644
--- a/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ProxyEditor.js
+++ b/extensions/chromium/runet-censorship-bypass/src/extension-common/pages/options/src/components/ProxyEditor.js
@@ -103,7 +103,7 @@ export default function getProxyEditor(theState) {
right: 0;
}
table.editor .add {
- font-weight: 900;
+ font-weight: 900;
}
table.editor .export {
/*padding-right: 2px;*/
@@ -160,7 +160,7 @@ export default function getProxyEditor(theState) {
};
const splitBySemi = (proxyString) => proxyString.replace(/#.*$/mg, '').trim().split(/\s*;\s*/g).filter((s) => s);
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']];
@@ -222,7 +222,7 @@ export default function getProxyEditor(theState) {
const newValue = `${that.props.proxyStringRaw}; ${type} ${hostname}:${port}`
.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);
proxyStrings.splice(index, 1);
- that.props.setProxyStringRaw( joinBySemi(proxyStrings) );
+ that.props.setProxyStringRaw(true, joinBySemi(proxyStrings) );
}
@@ -245,8 +245,8 @@ export default function getProxyEditor(theState) {
const proxyStrings = splitBySemi(that.props.proxyStringRaw);
proxyStrings.splice(index - 1, 2, proxyStrings[index], proxyStrings[index-1]);
- that.props.setProxyStringRaw( joinBySemi(proxyStrings) );
-
+ that.props.setProxyStringRaw(true, joinBySemi(proxyStrings) );
+
}
handleSubmit(that, event) {
@@ -366,11 +366,17 @@ export default function getProxyEditor(theState) {
super(props);
this.state = getInitState();
+ this.resetState = linkEvent(this, this.resetState);
+ this.showApply = linkEvent(undefined, props.setProxyStringRaw);
}
resetState(that, event) {
+ that.props.setProxyStringRaw(true, that.props.proxyStringRaw);
+ if (that.state.ifHasErrors) {
+ that.props.funs.setStatusTo(''); // Clear errors
+ }
that.setState(getInitState());
event.preventDefault();
@@ -417,19 +423,9 @@ export default function getProxyEditor(theState) {
handleModeSwitch(that, event) {
- if (that.state.stashedExports !== false) {
- const errors = that.getErrorsInStashedExports();
- if (errors) {
- that.setState({ifHasErrors: true});
- that.props.funs.showErrors(...errors);
- return;
- }
- that.props.setProxyStringRaw(that.state.stashedExports);
+ if (that.state.ifHasErrors) {
+ return;
}
- that.setState({
- stashedExports: false,
- ifHasErrors: false,
- });
that.props.onSwitch();
}
@@ -437,7 +433,20 @@ export default function getProxyEditor(theState) {
handleTextareaChange(that, event) {
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) {
- const reset = linkEvent(this, this.resetState);
-
return (