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 (
@@ -463,8 +470,8 @@ export default function getProxyEditor(theState) { this.state.stashedExports === false ? 'Комментарии вырезаются!' : (this.state.ifHasErrors - ? (Сбросьте изменения или поправьте) - : (Сбросить изменения) + ? (Сбросьте изменения или поправьте) + : (Сбросить изменения) ) } @@ -484,6 +491,7 @@ SOCKS5 localhost:9150; # Tor Browser HTTPS 11.22.33.44:3143; PROXY foobar.com:8080; # Not HTTP!`.trim()} onChange={linkEvent(this, this.handleTextareaChange)} + onFocus={this.showApply} value={ this.state.stashedExports !== false ? this.state.stashedExports @@ -525,10 +533,11 @@ PROXY foobar.com:8080; # Not HTTP!`.trim()} this.state = { proxyStringRaw: newValue, ifExportsMode: false, + ifValid: true, }; this.handleSwitch = () => this.setState({ifExportsMode: !this.state.ifExportsMode}); waitingTillMount.push(newValue); // Wait till mount or eat bugs. - + } componentDidMount() { @@ -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 `===`. - ( 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,16 +570,25 @@ PROXY foobar.com:8080; # Not HTTP!`.trim()} const props = Object.assign({ proxyStringRaw: this.state.proxyStringRaw, 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; localStorage.setItem(UI_RAW, newValue); - this.setState({proxyStringRaw: newValue}); - this.mayEmitNewValue(oldValue, newValue); + this.setState({proxyStringRaw: newValue, ifValid}); + this.mayEmitNewValue(oldValue, newValue, ifValidityChanged); }, }, originalProps); - + return this.state.ifExportsMode ? createElement(ExportsEditor, props) : createElement(TabledEditor, props); diff --git a/extensions/chromium/runet-censorship-bypass/src/templates-data.js b/extensions/chromium/runet-censorship-bypass/src/templates-data.js index 36022f5..eac4931 100644 --- a/extensions/chromium/runet-censorship-bypass/src/templates-data.js +++ b/extensions/chromium/runet-censorship-bypass/src/templates-data.js @@ -2,6 +2,20 @@ const commonContext = { 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 = {}; @@ -26,3 +40,14 @@ exports.contexts.mini = Object.assign({}, commonContext, { 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"', +});