From 77a88e577855735413eb0e9058c896e977e6f6d5 Mon Sep 17 00:00:00 2001 From: "Ilya Ig. Petrov" Date: Tue, 29 Nov 2016 09:27:15 -0800 Subject: [PATCH] Add eslint w/ Google styles, restyle --- .gitignore | 1 + .../minimalistic-pac-setter/.eslintrc.js | 27 + .../1-sync-pac-script-with-pac-provider.js | 1289 +++++++++-------- .../extension/2-block-informer.js | 321 ++-- .../extension/3-context-menus.js | 66 +- .../pages/choose-pac-provider/index.js | 363 ++--- .../keep-links-clickable.js | 11 +- .../extension/pages/debug/index.js | 14 +- .../minimalistic-pac-setter/package.json | 21 + 9 files changed, 1138 insertions(+), 975 deletions(-) create mode 100644 extensions/chromium/minimalistic-pac-setter/.eslintrc.js create mode 100644 extensions/chromium/minimalistic-pac-setter/package.json diff --git a/.gitignore b/.gitignore index 5b2ff92..1414f5f 100755 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules +npm-debug.log .swp diff --git a/extensions/chromium/minimalistic-pac-setter/.eslintrc.js b/extensions/chromium/minimalistic-pac-setter/.eslintrc.js new file mode 100644 index 0000000..bb4f67e --- /dev/null +++ b/extensions/chromium/minimalistic-pac-setter/.eslintrc.js @@ -0,0 +1,27 @@ +module.exports = { + "extends": ["eslint:recommended", "google"], + "plugins": [ + //"hapi" + ], + "env": { + "browser": true, + "webextensions": true, + "es6": true + }, + "globals": { + "chrome": true + }, + "parserOptions": { + "sourceType": "script", + "ecmaFeatures": { + "impliedStrict": false + } + }, + "rules": { + "strict": ["error", "global"], + "no-console": "off", + "padded-blocks": "off", + "require-jsdoc": "off" + //"hapi/hapi-scope-start": ["warn"] + } +}; diff --git a/extensions/chromium/minimalistic-pac-setter/extension/1-sync-pac-script-with-pac-provider.js b/extensions/chromium/minimalistic-pac-setter/extension/1-sync-pac-script-with-pac-provider.js index 15a1916..6a80f59 100755 --- a/extensions/chromium/minimalistic-pac-setter/extension/1-sync-pac-script-with-pac-provider.js +++ b/extensions/chromium/minimalistic-pac-setter/extension/1-sync-pac-script-with-pac-provider.js @@ -1,617 +1,672 @@ -'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.antiCensorRu public variables. - In pages window.antiCensorRu is not accessible, - use chrome.runtime.getBackgroundPage(..), - extension.getBackgroundPage is deprecated -*/ -{ // Private namespace starts. - - window.antiCensorRu = { - - version: chrome.runtime.getManifest().version, - - pacProviders: { - Антизапрет: { - pacUrl: 'https://antizapret.prostovpn.org/proxy.pac', - proxyHosts: ['proxy.antizapret.prostovpn.org'], - proxyIps: { - '195.123.209.38': 'proxy.antizapret.prostovpn.org', - '2a02:27ac::10': 'proxy.antizapret.prostovpn.org' - } - }, - Антиценз: { - pacUrl: 'https://config.anticenz.org/proxy.pac', - proxyHosts: ['gw2.anticenz.org'], - proxyIps: { - '5.196.220.114': 'gw2.anticenz.org' - } - }, - Оба_и_на_свитчах: { - //pacUrl: 'https://drive.google.com/uc?export=download&id=0B-ZCVSvuNWf0akpCOURNS2VCTmc', // 0.14 - pacUrl: 'https://drive.google.com/uc?export=download&id=0B-ZCVSvuNWf0bzNUR2F4RF8wOU0', // 0.15 - proxyHosts: ['proxy.antizapret.prostovpn.org', 'gw2.anticenz.org'], - proxyIps: { - '195.123.209.38': 'proxy.antizapret.prostovpn.org', - '2a02:27ac::10': 'proxy.antizapret.prostovpn.org', - '5.196.220.114': 'gw2.anticenz.org' - } - } - }, - - _currentPacProviderKey: 'Оба_и_на_свитчах', - - isProxied(ip) { - - // Executed on each request with ip. Make it as fast as possible. - return this._currentPacProviderKey && this.pacProviders[this._currentPacProviderKey].proxyIps.hasOwnProperty(ip); - // The benefit of removing lookups is little, e.g. this._currentProxyIps && this._currentProxyIps.hasOwnProperty(ip); - - }, - - mustBeKey(key) { - - if ( !(key === null || this.pacProviders[key]) ) { - throw new IllegalArgumentException('No provider for key:' + key); - } - - }, - - get currentPacProviderKey() { return this._currentPacProviderKey }, - set currentPacProviderKey(newKey) { - - this.mustBeKey(newKey); - this._currentPacProviderKey = newKey; - - }, - - getPacProvider(key) { - - if(key) { - this.mustBeKey(key); - } - else { - key = this.currentPacProviderKey; - } - return this.pacProviders[key]; - - }, - - /* - Is it the first time extension installed? Do something, e.g. initiate PAC sync. - */ - ifFirstInstall: false, - lastPacUpdateStamp: 0, - - _periodicUpdateAlarmReason: 'Периодичное обновление PAC-скрипта Антизапрет', - - pushToStorage(cb) { - - 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, onlySettable) - ) - ); - - }, - - pullFromStorage(cb) { - - chrome.storage.local.get(null, (storage) => { - - const err = checkChromeError(); - if (!err) { - console.log('Pulled from storage:', storage); - for(const key of Object.keys(storage)) { - this[key] = storage[key]; - } - } - - console.log('Synced with storage, any callback?', !!cb); - cb && cb(err, storage); - - }); - }, - - syncWithPacProvider(key, cb) { - - if( !key || typeof(key) === 'function' ) { - cb = key; - key = this.currentPacProviderKey; - } - cb = asyncLogGroup('Syncing with PAC provider ' + key + '...', cb); - - if (key === null) { - // No pac provider set. - return cb({clarification:{message:'Сперва выберите PAC-провайдера.'}}); - } - - const pacProvider = this.getPacProvider(key); - - const pacSetPromise = new Promise( - (resolve, reject) => setPacScriptFromProvider( - pacProvider, - (err, res) => { - - if (!err) { - this.currentPacProviderKey = key; - this.lastPacUpdateStamp = Date.now(); - this.ifFirstInstall = false; - this.setAlarms(); - } - - resolve([err, res]); - - } - ) - ); - - const ipsPromise = new Promise( - (resolve, reject) => updatePacProxyIps( - pacProvider, - (ipsError) => { - - if (ipsError && ipsError.clarification) { - ipsError.clarification.ifNotCritical = true; - } - resolve([ipsError]); - - } - ) - ); - - Promise.all([pacSetPromise, ipsPromise]).then( - ([[pacErr, pacRes], [ipsErr]]) => { - - if (pacErr && ipsErr) { - return cb(pacErr, pacRes); - } - this.pushToStorage( - (pushErr) => cb(pacErr || ipsErr || pushErr, pacRes) - ); - - }, - cb - ); - - }, - - _pacUpdatePeriodInMinutes: 4*60, - - 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 - } - ); - - return nextUpdateMoment === now; // ifAlarmTriggered. May be changed in the future. - - }, - - installPac(key, cb) { - - console.log('Installing PAC...'); - if (!key) { - throw new Error('Key must be defined.'); - } - if (this.currentProviderKey !== key) { - return this.syncWithPacProvider(key, cb); - } - console.log(key + ' already installed.'); - cb(); - - }, - - clearPac(cb) { - - cb = asyncLogGroup('Cearing alarms and PAC...', cb); - chrome.alarms.clearAll( - () => chrome.proxy.settings.clear( - {}, - () => { - - const err = checkChromeError(); - if (err) { - return cb(err); - } - this.currentPacProviderKey = null; - this.pushToStorage(cb); - - } - ) - ); - } - - }; - - // ON EACH LAUNCH, STARTUP, RELOAD, UPDATE, ENABLE - chrome.storage.local.get(null, (oldStorage) => { - - checkChromeError(); - /* - Event handlers that ALWAYS work (even if installation is not done or failed). - E.g. install window may fail to open or be closed by user accidentally. - In such case extension _should_ try to work on default parameters. - */ - chrome.alarms.onAlarm.addListener( - (alarm) => { - - if (alarm.name === antiCensorRu._periodicUpdateAlarmReason) { - console.log('Periodic PAC update triggered:', new Date().toLocaleString('ru-RU')); - antiCensorRu.syncWithPacProvider(/* Swallows errors. */); - } - - } - ); - 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('Storage on init:', oldStorage); - antiCensorRu.ifFirstInstall = Object.keys(oldStorage).length === 0; - - if (!antiCensorRu.ifFirstInstall) { - // LAUNCH, RELOAD, UPDATE - antiCensorRu._currentPacProviderKey = oldStorage._currentPacProviderKey || null; // Old or migrate. - antiCensorRu.lastPacUpdateStamp = oldStorage.lastPacUpdateStamp || antiCensorRu.lastPacUpdateStamp; // Old or migrate to default. - console.log( 'Last PAC update was on', new Date(antiCensorRu.lastPacUpdateStamp).toLocaleString('ru-RU') ); - } - else { - // INSTALL - console.log('Installing...'); - return chrome.runtime.openOptionsPage(); - } - - if (!antiCensorRu.getPacProvider()) { - /* - In case of UPDATE: - 1. new providers will still be shown. - 2. new version won't be pushed to storage - */ - return console.log('No PAC provider set. Do nothing.'); - } - - /* - 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. - */ - - const ifAlarmTriggered = antiCensorRu.setAlarms(); - - if (antiCensorRu.version === oldStorage.version) { - // LAUNCH, RELOAD, ENABLE - antiCensorRu.pacProviders = oldStorage.pacProviders; - return console.log('Extension launched, reloaded or enabled.'); - } - - // UPDATE & MIGRATION - console.log('Extension updated.'); - if (!ifAlarmTriggered) { - antiCensorRu.pushToStorage(/* Swallows errors. */); - } - - /* - History of Changes to Storage (Migration Guide) - ----------------------------------------------- - Version 0.0.0.10 - * Added this.version - * PacProvider.proxyIps changed from {ip -> Boolean} to {ip -> hostname} - Version 0.0.0.8-9 - * Changed storage.ifNotInstalled to storage.ifFirstInstall - * Added storage.lastPacUpdateStamp - **/ - }); - - function asyncLogGroup(...args) { - - const cb = args.pop() || (() => {}); - console.group.apply(console, args); - return function(...cbArgs) { - - console.groupEnd(); - console.log('Group finished.'); - cb.apply(this, cbArgs); - - } - } - - function checkChromeError(betterStack) { - - // Chrome API calls your cb in a context different from the point of API method invokation. - const err = chrome.runtime.lastError || chrome.extension.lastError || null; - if (err) { - const args = ['API returned error:', err]; - if (betterStack) { - args.push('\n' + betterStack); - } - console.warn.apply(console, args); - } - return err; - - } - - function chromified(cb, ...replaceArgs) { - - const stack = (new Error()).stack; - // Take error first callback and covert it to chrome api callback. - return function(...args) { - - if (replaceArgs.length) { - args = replaceArgs; - } - const err = checkChromeError(stack); - cb && cb.call(this, err, ...args); - - }; - - } - - function setPac(pacData, cb) { - - const config = { - mode: 'pac_script', - pacScript: { - mandatory: false, - data: pacData - } - }; - console.log('Setting chrome proxy settings...'); - chrome.proxy.settings.set( {value: config}, () => { - - const err = checkChromeError(); - if (err) { - return cb(err); - } - chrome.proxy.settings.get({}, (details) => { - - const ifThis = details.levelOfControl.startsWith('controlled_by_this'); - if (!ifThis) { - console.warn('Failed, other extension is in control.'); - return cb({clarification: {message:'Настройки прокси контролирует другое расширение. Какое?'}}); - } - console.log('Successfuly set PAC in proxy settings..'); - cb(); - }); - - }); - - } - - function httpGet(url, cb) { - - const start = Date.now(); - fetch(url).then( - (res) => { - - const textCb = - (err) => cb && res.text() - .then( (text) => cb(err, text), cb ); - const status = res.status; - if ( !( status >= 200 && status < 300 || status === 304 ) ) { - res.clarification = {message: 'Получен ответ с неудачным HTTP-кодом ' + status + '.'}; - return textCb(res); - } - console.log('GETed with success:', url, Date.now() - start); - textCb(); - - }, - (err) => { - - err.clarification = {message: 'Что-то не так с сетью, проверьте соединение.'}; - cb && cb(err); - - } - ); - } - - function getOneDnsRecord(args, cb) { - - // args: { host:..., type: 'AAAA', filter: ['AAAA'] } - if (!(args.host && args.type && cb)) { - throw new Error('Wrong args:' + host + ',' + type); - } - - const type2str = { - // https://en.wikipedia.org/wiki/List_of_DNS_record_types - // A, AAAA may be localized (github, e.g.), but you may use ANY - 1: 'A', // IPv4 - 28: 'AAAA', // IPv6 - 2: 'NS', - 5: 'CNAME', // Synonyms, returned by server together with A/AAAA. - 255: 'ANY' // Deprecated on some servers, not recommended - }; - - httpGet( - 'https://dns.google.com/resolve?type=' + args.type + '&name=' + args.host, - (err, res) => { - if (res) { - try { - res = JSON.parse(res); - console.log('Json parsed.'); - if (err || res.Status) { - const msg = ['Answer', 'Comment', 'Status'] - .filter( (prop) => res[ prop ] ) - .map( (prop) => prop + ': ' + JSON.stringify( res[ prop ] ) ) - .join(', \n'); - err.clarification.message += ' Сервер (json): ' + msg; - err.data = err.data || res; - } - else { - res = res.Answer || []; - for (const record of res) { - record.type = type2str[ record.type ]; - } - if ( args.filter ) { - res = res.filter( (record) => args.filter.indexOf( record.type ) > -1 ); - } - } - } - catch(e) { - err = e || err || {clarification:{message:''}}; - err.clarification = err.clarification || { message: '' }; - err.clarification.message += ' Сервер (текст): '+ res; - err.clarification.message.trim(); - err.data = err.data || res; - } - } - cb( err, res ); - } - ); - }; - - function getDnsRecords(args, cb) { - - /* - Example of input: - { - // Required - host: 'proxy.navalny.cia.gov', - // Optional - types: { - string: ['A', 'AAAA'], // <- Default. Makes one request per each type. - filter: ['A', 'AAAA'], // <- Default. E.g., you want to get rid of CNAME type from response. - } - } - Exmple of answer from google: - "Answer": - [ - { - "name": "apple.com.", // Always matches name in the Question section - "type": 1, // A - Standard DNS RR type - "TTL": 3599, // Record's time-to-live in seconds - "data": "17.178.96.59" // Data for A - IP address as text - }, - ... - Exmple of output: - The same as google, but types _may be_ canonical strings ('AAAA', 'A') - **/ - - if ( !args.host.length ) { - throw new Error('args.host is required: ' + args.host); - } - args.types = Object.assign({ - string: ['A', 'AAAA'], - filter: ['A', 'AAAA'] - }, args.types); - - const promises = args.types.string.map( - (type) => new Promise( (resolve, reject) => - getOneDnsRecord({ host: args.host, type: type, filter: args.types.filter }, (err, res) => err ? reject(err) : resolve(res) ) - ) - ); - Promise.all(promises).then( (answers) => cb( null, [].concat.apply([], answers) ), cb ); - } - - const getIpDnsRecords = (host, cb) => getDnsRecords({ host: host }, cb); - - function updatePacProxyIps(provider, cb) { - - cb = asyncLogGroup('Getting IP for '+ provider.proxyHosts.join(', ') +'...', cb); - let failure = { - clarification: {message:'Не удалось получить один или несколько IP адресов для прокси-серверов. Иконка для уведомления об обходе блокировок может не отображаться.'}, - errors: {} - }; - let i = 0; - provider.proxyHosts.map( - (proxyHost) => getIpDnsRecords( - proxyHost, - (err, records) => { - - if (!err) { - provider.proxyIps = provider.proxyIps || {}; - records.forEach( (ans) => provider.proxyIps[ ans.data ] = proxyHost ); - } - else { - failure.errors[proxyHost] = err; - } - - if ( ++i === provider.proxyHosts.length ) { - failure = Object.keys(failure.errors).length ? failure : null; - cb(failure, provider.proxyIps); - } - } - ) - ); - } - - function setPacScriptFromProvider(provider, cb) { - - cb = asyncLogGroup('Getting pac script from provider...', provider.pacUrl, cb); - - httpGet( - provider.pacUrl, - (err, pacData) => { - - if (err) { - err.clarification = { - message: 'Не удалось скачать PAC-скрипт с адреса: ' + provider.pacUrl + '.', - prev: err.clarification - }; - return cb(err); - } - setPac(pacData, cb); - - } - ); - } - -} - -window.addEventListener('error', (err) => { - - console.error('Global error'); - -}); - -window.addEventListener('unhandledrejection', (event) => { - - console.warn('Unhandled rejection. Throwing error.'); - event.preventDefault(); - throw event.reason; - -}); - -chrome.proxy.settings.onChange.addListener((details) => { - - console.log('Proxy settings changed.', details); - const ifOther = details.levelOfControl.startsWith('controlled_by_other'); - -}); +'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.antiCensorRu public variables. + In pages window.antiCensorRu is not accessible, + use chrome.runtime.getBackgroundPage(..), + extension.getBackgroundPage is deprecated +*/ +{ // Private namespace starts. + + window.antiCensorRu = { + + version: chrome.runtime.getManifest().version, + + pacProviders: { + Антизапрет: { + pacUrl: 'https://antizapret.prostovpn.org/proxy.pac', + proxyHosts: ['proxy.antizapret.prostovpn.org'], + proxyIps: { + '195.123.209.38': 'proxy.antizapret.prostovpn.org', + '2a02:27ac::10': 'proxy.antizapret.prostovpn.org', + }, + }, + Антиценз: { + pacUrl: 'https://config.anticenz.org/proxy.pac', + proxyHosts: ['gw2.anticenz.org'], + proxyIps: { + '5.196.220.114': 'gw2.anticenz.org', + }, + }, + Оба_и_на_свитчах: { + // pacUrl: 'https://drive.google.com/uc?export=download&id=0B-ZCVSvuNWf0akpCOURNS2VCTmc', // 0.14 + pacUrl: 'https://drive.google.com/uc?export=download&id=0B-ZCVSvuNWf0bzNUR2F4RF8wOU0', // 0.15 + proxyHosts: ['proxy.antizapret.prostovpn.org', 'gw2.anticenz.org'], + proxyIps: { + '195.123.209.38': 'proxy.antizapret.prostovpn.org', + '2a02:27ac::10': 'proxy.antizapret.prostovpn.org', + '5.196.220.114': 'gw2.anticenz.org', + }, + }, + }, + + _currentPacProviderKey: 'Оба_и_на_свитчах', + + isProxied(ip) { + + // Executed on each request with ip. Make it as fast as possible. + // Property lookups are cheap (I tested). + return this._currentPacProviderKey + && this.pacProviders[this._currentPacProviderKey] + .proxyIps.hasOwnProperty(ip); + + }, + + mustBeKey(key) { + + if ( !(key === null || this.pacProviders[key]) ) { + throw new TypeError('No provider for key:' + key); + } + + }, + + get currentPacProviderKey() { + + return this._currentPacProviderKey; + + }, + set currentPacProviderKey(newKey) { + + this.mustBeKey(newKey); + this._currentPacProviderKey = newKey; + + }, + + getPacProvider(key) { + + if(key) { + this.mustBeKey(key); + } else { + key = this.currentPacProviderKey; + } + return this.pacProviders[key]; + + }, + + /* Is it the first time extension installed? + Do something, e.g. initiate PAC sync. + */ + ifFirstInstall: false, + lastPacUpdateStamp: 0, + + _periodicUpdateAlarmReason: 'Периодичное обновление PAC-скрипта Антизапрет', + + pushToStorage(cb) { + + 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, onlySettable) + ) + ); + + }, + + pullFromStorage(cb) { + + chrome.storage.local.get(null, (storage) => { + + const err = checkChromeError(); + if (!err) { + console.log('Pulled from storage:', storage); + for(const key of Object.keys(storage)) { + this[key] = storage[key]; + } + } + + console.log('Synced with storage, any callback?', !!cb); + cb && cb(err, storage); + + }); + }, + + syncWithPacProvider(key, cb) { + + if( !key || typeof(key) === 'function' ) { + cb = key; + key = this.currentPacProviderKey; + } + cb = asyncLogGroup('Syncing with PAC provider ' + key + '...', cb); + + if (key === null) { + // No pac provider set. + return cb({ + clarification: { + message: 'Сперва выберите PAC-провайдера.', + }, + }); + } + + const pacProvider = this.getPacProvider(key); + + const pacSetPromise = new Promise( + (resolve, reject) => setPacScriptFromProvider( + pacProvider, + (err, res) => { + + if (!err) { + this.currentPacProviderKey = key; + this.lastPacUpdateStamp = Date.now(); + this.ifFirstInstall = false; + this.setAlarms(); + } + + resolve([err, res]); + + } + ) + ); + + const ipsPromise = new Promise( + (resolve, reject) => updatePacProxyIps( + pacProvider, + (ipsError) => { + + if (ipsError && ipsError.clarification) { + ipsError.clarification.ifNotCritical = true; + } + resolve([ipsError]); + + } + ) + ); + + Promise.all([pacSetPromise, ipsPromise]).then( + ([[pacErr, pacRes], [ipsErr]]) => { + + if (pacErr && ipsErr) { + return cb(pacErr, pacRes); + } + this.pushToStorage( + (pushErr) => cb(pacErr || ipsErr || pushErr, pacRes) + ); + + }, + cb + ); + + }, + + _pacUpdatePeriodInMinutes: 4*60, + + 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; + + }, + + installPac(key, cb) { + + console.log('Installing PAC...'); + if (!key) { + throw new Error('Key must be defined.'); + } + if (this.currentProviderKey !== key) { + return this.syncWithPacProvider(key, cb); + } + console.log(key + ' already installed.'); + cb(); + + }, + + clearPac(cb) { + + cb = asyncLogGroup('Cearing alarms and PAC...', cb); + chrome.alarms.clearAll( + () => chrome.proxy.settings.clear( + {}, + () => { + + const err = checkChromeError(); + if (err) { + return cb(err); + } + this.currentPacProviderKey = null; + this.pushToStorage(cb); + + } + ) + ); + }, + + }; + + // ON EACH LAUNCH, STARTUP, RELOAD, UPDATE, ENABLE + chrome.storage.local.get(null, (oldStorage) => { + + checkChromeError(); + /* + Event handlers that ALWAYS work (even if installation is not done + or failed). + E.g. install window may fail to open or be closed by user accidentally. + In such case extension _should_ try to work on default parameters. + */ + const antiCensorRu = window.antiCensorRu; + chrome.alarms.onAlarm.addListener( + (alarm) => { + + if (alarm.name === antiCensorRu._periodicUpdateAlarmReason) { + console.log( + 'Periodic PAC update triggered:', + new Date().toLocaleString('ru-RU') + ); + antiCensorRu.syncWithPacProvider(/* Swallows errors. */); + } + + } + ); + 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('Storage on init:', oldStorage); + antiCensorRu.ifFirstInstall = Object.keys(oldStorage).length === 0; + + if (!antiCensorRu.ifFirstInstall) { + // LAUNCH, RELOAD, UPDATE + // Use old or migrate to default. + antiCensorRu._currentPacProviderKey = + oldStorage._currentPacProviderKey || null; + antiCensorRu.lastPacUpdateStamp = + oldStorage.lastPacUpdateStamp || antiCensorRu.lastPacUpdateStamp; + console.log( + 'Last PAC update was on', + new Date(antiCensorRu.lastPacUpdateStamp).toLocaleString('ru-RU') + ); + } else { + // INSTALL + console.log('Installing...'); + return chrome.runtime.openOptionsPage(); + } + + if (!antiCensorRu.getPacProvider()) { + /* + In case of UPDATE: + 1. new providers will still be shown. + 2. new version won't be pushed to storage + */ + return console.log('No PAC provider set. Do nothing.'); + } + + /* + 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. + */ + + const ifAlarmTriggered = antiCensorRu.setAlarms(); + + if (antiCensorRu.version === oldStorage.version) { + // LAUNCH, RELOAD, ENABLE + antiCensorRu.pacProviders = oldStorage.pacProviders; + return console.log('Extension launched, reloaded or enabled.'); + } + + // UPDATE & MIGRATION + console.log('Extension updated.'); + if (!ifAlarmTriggered) { + antiCensorRu.pushToStorage(/* Swallows errors. */); + } + + /* + History of Changes to Storage (Migration Guide) + ----------------------------------------------- + Version 0.0.0.10 + * Added this.version + * PacProvider.proxyIps changed from {ip -> Boolean} to {ip -> hostname} + Version 0.0.0.8-9 + * Changed storage.ifNotInstalled to storage.ifFirstInstall + * Added storage.lastPacUpdateStamp + **/ + }); + + function asyncLogGroup(...args) { + + const cb = args.pop() || (() => {}); + console.group(...args); + return function(...cbArgs) { + + console.groupEnd(); + console.log('Group finished.'); + cb(...cbArgs); + + }; + } + + function checkChromeError(betterStack) { + + // Chrome API calls your cb in a context different from the point of API + // method invokation. + const err = chrome.runtime.lastError || chrome.extension.lastError || null; + if (err) { + const args = ['API returned error:', err]; + if (betterStack) { + args.push('\n' + betterStack); + } + console.warn(...args); + } + return err; + + } + + function chromified(cb, ...replaceArgs) { + + const stack = (new Error()).stack; + // Take error first callback and convert it to chrome api callback. + return function(...args) { + + if (replaceArgs.length) { + args = replaceArgs; + } + const err = checkChromeError(stack); + cb && cb.call(null, err, ...args); + + }; + + } + + function setPac(pacData, cb) { + + const config = { + mode: 'pac_script', + pacScript: { + mandatory: false, + data: pacData, + }, + }; + console.log('Setting chrome proxy settings...'); + chrome.proxy.settings.set( {value: config}, () => { + + const err = checkChromeError(); + if (err) { + return cb(err); + } + chrome.proxy.settings.get({}, (details) => { + + const ifThis = details.levelOfControl.startsWith('controlled_by_this'); + if (!ifThis) { + console.warn('Failed, other extension is in control.'); + return cb({clarification: {message: 'Настройки прокси контролирует другое расширение. Какое?'}}); + } + console.log('Successfuly set PAC in proxy settings..'); + cb(); + }); + + }); + + } + + function httpGet(url, cb) { + + const start = Date.now(); + fetch(url).then( + (res) => { + + const textCb = + (err) => cb && res.text() + .then( (text) => cb(err, text), cb ); + const status = res.status; + if ( !( status >= 200 && status < 300 || status === 304 ) ) { + res.clarification = { + message: 'Получен ответ с неудачным HTTP-кодом ' + status + '.', + }; + return textCb(res); + } + console.log('GETed with success:', url, Date.now() - start); + textCb(); + + }, + (err) => { + + err.clarification = { + message: 'Что-то не так с сетью, проверьте соединение.', + }; + cb && cb(err); + + } + ); + } + + function getOneDnsRecord(args, cb) { + + // args: { host:..., type: 'AAAA', filter: ['AAAA'] } + if (!(args.host && args.type && cb)) { + throw new Error('Wrong args:' + args.host + ',' + args.type); + } + + const type2str = { + // https://en.wikipedia.org/wiki/List_of_DNS_record_types + // A, AAAA may be localized (github, e.g.), but you may use ANY + 1: 'A', // IPv4 + 28: 'AAAA', // IPv6 + 2: 'NS', + 5: 'CNAME', // Synonyms, returned by server together with A/AAAA. + 255: 'ANY', // Deprecated on some servers, not recommended + }; + + httpGet( + 'https://dns.google.com/resolve?type=' + args.type + '&name=' + args.host, + (err, res) => { + if (res) { + try { + res = JSON.parse(res); + console.log('Json parsed.'); + if (err || res.Status) { + const msg = ['Answer', 'Comment', 'Status'] + .filter( (prop) => res[prop] ) + .map( (prop) => prop + ': ' + JSON.stringify( res[prop] ) ) + .join(', \n'); + err.clarification.message += ' Сервер (json): ' + msg; + err.data = err.data || res; + } else { + res = res.Answer || []; + for (const record of res) { + record.type = type2str[record.type]; + } + if ( args.filter ) { + res = res.filter( + (record) => args.filter.indexOf( record.type ) > -1 + ); + } + } + } catch(e) { + err = e || err || {clarification: {message: ''}}; + err.clarification = err.clarification || {message: ''}; + err.clarification.message += ' Сервер (текст): '+ res; + err.clarification.message.trim(); + err.data = err.data || res; + } + } + cb( err, res ); + } + ); + } + + function getDnsRecords(args, cb) { + + /* + Example of input: + { + // Required + host: 'proxy.navalny.cia.gov', + // Optional + types: { + string: ['A', 'AAAA'], + // ^ Default. Makes one request per each type. + filter: ['A', 'AAAA'], + // ^ Default. E.g., you want to get rid of CNAME type from + // response. + } + } + Exmple of answer from google: + "Answer": + [ + { + "name": "apple.com.", // Always matches name in the Question + // section. + "type": 1, // A - Standard DNS RR type. + "TTL": 3599, // Record's time-to-live in seconds. + "data": "17.178.96.59" // Data for A - IP address as text. + }, + ... + Exmple of output: + The same as google, but types _may be_ canonical strings ('AAAA', 'A') + **/ + + if ( !args.host.length ) { + throw new Error('args.host is required: ' + args.host); + } + args.types = Object.assign({ + string: ['A', 'AAAA'], + filter: ['A', 'AAAA'], + }, args.types); + + const promises = args.types.string.map( + (type) => new Promise( (resolve, reject) => + getOneDnsRecord( + {host: args.host, type: type, filter: args.types.filter}, + (err, res) => err ? reject(err) : resolve(res) ) + ) + ); + Promise.all(promises).then( + (answers) => cb( null, [].concat(...answers) ), + cb + ); + } + + const getIpDnsRecords = (host, cb) => getDnsRecords({host: host}, cb); + + function updatePacProxyIps(provider, cb) { + + cb = asyncLogGroup( + 'Getting IP for '+ provider.proxyHosts.join(', ') + '...', + cb + ); + let failure = { + clarification: { + message: 'Не удалось получить один или несколько IP адресов для' + + ' прокси-серверов. Иконка для уведомления об обходе блокировок ' + + 'может не отображаться.', + }, + errors: {}, + }; + let i = 0; + provider.proxyHosts.map( + (proxyHost) => getIpDnsRecords( + proxyHost, + (err, records) => { + + if (!err) { + provider.proxyIps = provider.proxyIps || {}; + records.forEach( + (ans) => provider.proxyIps[ans.data] = proxyHost + ); + } else { + failure.errors[proxyHost] = err; + } + + if ( ++i === provider.proxyHosts.length ) { + failure = Object.keys(failure.errors).length ? failure : null; + cb(failure, provider.proxyIps); + } + } + ) + ); + } + + function setPacScriptFromProvider(provider, cb) { + + cb = asyncLogGroup( + 'Getting pac script from provider...', provider.pacUrl, + cb + ); + + httpGet( + provider.pacUrl, + (err, pacData) => { + + if (err) { + err.clarification = { + message: 'Не удалось скачать PAC-скрипт с адреса: ' + + provider.pacUrl + '.', + prev: err.clarification, + }; + return cb(err); + } + setPac(pacData, cb); + + } + ); + } + +} + +window.addEventListener('error', (err) => { + + console.error('Global error'); + +}); + +window.addEventListener('unhandledrejection', (event) => { + + console.warn('Unhandled rejection. Throwing error.'); + event.preventDefault(); + throw event.reason; + +}); + +chrome.proxy.settings.onChange.addListener((details) => { + + console.log('Proxy settings changed.', details); + // const ifOther = details.levelOfControl.startsWith('controlled_by_other'); + +}); diff --git a/extensions/chromium/minimalistic-pac-setter/extension/2-block-informer.js b/extensions/chromium/minimalistic-pac-setter/extension/2-block-informer.js index 965cd03..3522816 100755 --- a/extensions/chromium/minimalistic-pac-setter/extension/2-block-informer.js +++ b/extensions/chromium/minimalistic-pac-setter/extension/2-block-informer.js @@ -1,152 +1,169 @@ -'use strict'; - -// Shows user browserAction icon if any part of the current site is being blocked and proxied. - -/* - In what moment the title of the previous icon is cleared? - By my observations it usually takes place near tabs.onUpdate of tab status to "loading". - So if you set a title earlier it may be cleared by browser. - It pertains not only to page refesh but to newly opened pages too. - Also on loosing title see: - https://github.com/ilyaigpetrov/repository-for-chrome-bugs/blob/master/browserAction-title-lost-after-setting/background.js - Crazy parallel Chrome. -**/ - -window.chrome.browserAction.setBadgeBackgroundColor({ - color: '#db4b2f' -}); - -window.tabWithError2ip = {}; // For errors only: Error? -> Check this IP! - -{ - - const _tabCallbacks = {}; - - function afterTabUpdated(tabId, cb) { - if (_tabCallbacks[tabId]) - _tabCallbacks[tabId].push(cb); - else _tabCallbacks[tabId] = [cb]; - } - - function onTabUpdate(tabId) { - if (_tabCallbacks[tabId]) { - _tabCallbacks[tabId].map( f => f() ); - delete _tabCallbacks[tabId]; - } - } - - chrome.tabs.onUpdated.addListener( onTabUpdate ); - - function isInsideTabWithIp(requestDetails) { - return requestDetails.tabId !== -1 && requestDetails.ip - } - - chrome.webRequest.onErrorOccurred.addListener( - (requestDetails) => - isInsideTabWithIp(requestDetails) && - ( - isProxiedAndInformed(requestDetails) || requestDetails.type === 'main_frame' && ( window.tabWithError2ip[requestDetails.tabId] = requestDetails.ip ) - ), - { urls: [''] } - ); - - chrome.tabs.onRemoved.addListener( (tabId) => { onTabUpdate(tabId); delete window.tabWithError2ip[tabId] } ); - - function updateTitle(requestDetails, cb) { - - chrome.browserAction.getTitle( - { tabId: requestDetails.tabId }, - (title) => { - - const ifTitleSetAlready = /\n/.test(title); - const proxyHost = window.antiCensorRu.getPacProvider().proxyIps[ requestDetails.ip ]; - - const hostname = new URL( requestDetails.url ).hostname; - - let ifShouldUpdateTitle = false; - const indent = ' '; - const proxyTitle = 'Прокси:'; - - if (!ifTitleSetAlready) { - title = 'Разблокированы:\n'+ indent + hostname +'\n'+ proxyTitle +'\n'+ indent + proxyHost; - ifShouldUpdateTitle = true; - - chrome.browserAction.setBadgeText({ - tabId: requestDetails.tabId, - text: requestDetails.type === 'main_frame' ? '1' : '%1' - }); - - } - else { - const hostsProxiesPair = title.split(proxyTitle); - - if (hostsProxiesPair[1].indexOf(proxyHost) === -1) { - title = title.replace(hostsProxiesPair[1], hostsProxiesPair[1] +'\n'+ indent + proxyHost); - ifShouldUpdateTitle = true; - } - - if (hostsProxiesPair[0].indexOf(hostname) === -1) { - title = title.replace(proxyTitle, indent + hostname +'\n'+ proxyTitle); - ifShouldUpdateTitle = true; - - const _cb = cb; - cb = () => chrome.browserAction.getBadgeText( - {tabId: requestDetails.tabId}, - (result) => { - - chrome.browserAction.setBadgeText( - { - tabId: requestDetails.tabId, - text: ( isNaN( result.charAt(0) ) && result.charAt(0) || '' ) + (hostsProxiesPair[0].split('\n').length - 1) - } - ); - return _cb(); - - } - ); - - } - } - - if (ifShouldUpdateTitle) { - chrome.browserAction.setTitle({ - title: title, - tabId: requestDetails.tabId - }); - } - - return cb(); - - } - ); - } - - - let previousUpdateTitleFinished = Promise.resolve(); - - function isProxiedAndInformed(requestDetails) { - - if ( !(requestDetails.ip && antiCensorRu.isProxied( requestDetails.ip )) ) { - return false; - } - - const ifMainFrame = requestDetails.type === 'main_frame'; - - previousUpdateTitleFinished = previousUpdateTitleFinished.then( - () => new Promise( - (resolve) => { - const cb = () => updateTitle( requestDetails, resolve ); - return ifMainFrame ? afterTabUpdated(requestDetails.tabId, cb) : cb(); - } - ) - ); - - return true; - } - - chrome.webRequest.onResponseStarted.addListener( - (requestDetails) => isInsideTabWithIp(requestDetails) && isProxiedAndInformed(requestDetails), - { urls: [''] } - ); - -} +'use strict'; + +// Shows user browserAction icon if any part of the current site is being +// blocked and proxied. + +/* + In what moment the title of the previous icon is cleared? + By my observations it usually takes place near tabs.onUpdate of tab status + to "loading". + So if you set a title earlier it may be cleared by browser. + It pertains not only to page refesh but to newly opened pages too. + Also on loosing title see: + https://github.com/ilyaigpetrov/repository-for-chrome-bugs/blob/master/browserAction-title-lost-after-setting/background.js + Crazy parallel Chrome. +**/ + +window.chrome.browserAction.setBadgeBackgroundColor({ + color: '#db4b2f', +}); + +window.tabWithError2ip = {}; // For errors only: Error? -> Check this IP! + +{ + + const _tabCallbacks = {}; + + function afterTabUpdated(tabId, cb) { + if (_tabCallbacks[tabId]) + _tabCallbacks[tabId].push(cb); + else _tabCallbacks[tabId] = [cb]; + } + + function onTabUpdate(tabId) { + if (_tabCallbacks[tabId]) { + _tabCallbacks[tabId].map( (f) => f() ); + delete _tabCallbacks[tabId]; + } + } + + chrome.tabs.onUpdated.addListener( onTabUpdate ); + + function isInsideTabWithIp(requestDetails) { + return requestDetails.tabId !== -1 && requestDetails.ip; + } + + chrome.webRequest.onErrorOccurred.addListener( + (requestDetails) => + isInsideTabWithIp(requestDetails) && + ( + isProxiedAndInformed(requestDetails) + || requestDetails.type === 'main_frame' + && (window.tabWithError2ip[requestDetails.tabId] = requestDetails.ip) + ), + {urls: ['']} + ); + + chrome.tabs.onRemoved.addListener( (tabId) => { + onTabUpdate(tabId); + delete window.tabWithError2ip[tabId]; + }); + + function updateTitle(requestDetails, cb) { + + chrome.browserAction.getTitle( + {tabId: requestDetails.tabId}, + (title) => { + + const ifTitleSetAlready = /\n/.test(title); + const proxyHost = window.antiCensorRu.getPacProvider() + .proxyIps[requestDetails.ip]; + + const hostname = new URL( requestDetails.url ).hostname; + + let ifShouldUpdateTitle = false; + const indent = ' '; + const proxyTitle = 'Прокси:'; + + if (!ifTitleSetAlready) { + title = 'Разблокированы:\n' + indent + hostname + '\n' + + proxyTitle + '\n' + indent + proxyHost; + ifShouldUpdateTitle = true; + + chrome.browserAction.setBadgeText({ + tabId: requestDetails.tabId, + text: requestDetails.type === 'main_frame' ? '1' : '%1', + }); + + } else { + const hostsProxiesPair = title.split(proxyTitle); + + if (hostsProxiesPair[1].indexOf(proxyHost) === -1) { + title = title.replace( + hostsProxiesPair[1], + hostsProxiesPair[1] + '\n' + indent + proxyHost + ); + ifShouldUpdateTitle = true; + } + + if (hostsProxiesPair[0].indexOf(hostname) === -1) { + title = title.replace( + proxyTitle, + indent + hostname + '\n' + proxyTitle + ); + ifShouldUpdateTitle = true; + + const _cb = cb; + cb = () => chrome.browserAction.getBadgeText( + {tabId: requestDetails.tabId}, + (result) => { + + chrome.browserAction.setBadgeText( + { + tabId: requestDetails.tabId, + text: (isNaN( result.charAt(0)) && result.charAt(0) || '') + + (hostsProxiesPair[0].split('\n').length - 1), + } + ); + return _cb(); + + } + ); + + } + } + + if (ifShouldUpdateTitle) { + chrome.browserAction.setTitle({ + title: title, + tabId: requestDetails.tabId, + }); + } + + return cb(); + + } + ); + } + + let previousUpdateTitleFinished = Promise.resolve(); + + function isProxiedAndInformed(requestDetails) { + + if ( !(requestDetails.ip + && window.antiCensorRu.isProxied( requestDetails.ip )) ) { + return false; + } + + const ifMainFrame = requestDetails.type === 'main_frame'; + + previousUpdateTitleFinished = previousUpdateTitleFinished.then( + () => new Promise( + (resolve) => { + const cb = () => updateTitle( requestDetails, resolve ); + return ifMainFrame + ? afterTabUpdated(requestDetails.tabId, cb) : cb(); + } + ) + ); + + return true; + } + + chrome.webRequest.onResponseStarted.addListener( + (requestDetails) => isInsideTabWithIp(requestDetails) + && isProxiedAndInformed(requestDetails), + {urls: ['']} + ); + +} diff --git a/extensions/chromium/minimalistic-pac-setter/extension/3-context-menus.js b/extensions/chromium/minimalistic-pac-setter/extension/3-context-menus.js index 5ccbea3..d7e1fd2 100755 --- a/extensions/chromium/minimalistic-pac-setter/extension/3-context-menus.js +++ b/extensions/chromium/minimalistic-pac-setter/extension/3-context-menus.js @@ -1,23 +1,43 @@ -'use strict'; - -{ - - const createMenuLinkEntry = (title, tab2url) => chrome.contextMenus.create({ - title: title, - contexts: ['browser_action'], - onclick: (menuInfo, tab) => Promise.resolve( tab2url( tab ) ).then( (url) => chrome.tabs.create({url: url}) ) - }); - - createMenuLinkEntry( 'Сайт доступен из-за границы? Is up?', (tab) => 'http://isup.me/'+ new URL(tab.url).hostname ); - - createMenuLinkEntry( 'Сайт в реестре блокировок?', (tab) => 'https://antizapret.info/index.php?search=' + tab.url ); - - createMenuLinkEntry( 'Из архива archive.org', (tab) => 'https://web.archive.org/web/*/' + tab.url ); - - createMenuLinkEntry( 'Открыть веб прокси (не наш)', (tab) => 'https://kproxy.com' ); - - createMenuLinkEntry( 'Другие варианты разблокировки', (tab) => 'https://rebrand.ly/unblock#' + tab.url ); - - createMenuLinkEntry( 'У меня проблемы с расширением!', (tab) => 'https://rebrand.ly/ac-support'); - -}; +'use strict'; + +{ + + const createMenuLinkEntry = (title, tab2url) => chrome.contextMenus.create({ + title: title, + contexts: ['browser_action'], + onclick: + (menuInfo, tab) => Promise.resolve( tab2url( tab ) ) + .then( (url) => chrome.tabs.create({url: url}) ), + }); + + createMenuLinkEntry( + 'Сайт доступен из-за границы? Is up?', + (tab) => 'http://isup.me/' + new URL(tab.url).hostname + ); + + createMenuLinkEntry( + 'Сайт в реестре блокировок?', + (tab) => 'https://antizapret.info/index.php?search=' + tab.url + ); + + createMenuLinkEntry( + 'Из архива archive.org', + (tab) => 'https://web.archive.org/web/*/' + tab.url + ); + + createMenuLinkEntry( + 'Открыть веб прокси (не наш)', + (tab) => 'https://kproxy.com' + ); + + createMenuLinkEntry( + 'Другие варианты разблокировки', + (tab) => 'https://rebrand.ly/unblock#' + tab.url + ); + + createMenuLinkEntry( + 'У меня проблемы с расширением!', + (tab) => 'https://rebrand.ly/ac-support' + ); + +} diff --git a/extensions/chromium/minimalistic-pac-setter/extension/pages/choose-pac-provider/index.js b/extensions/chromium/minimalistic-pac-setter/extension/pages/choose-pac-provider/index.js index 4e99409..3033214 100755 --- a/extensions/chromium/minimalistic-pac-setter/extension/pages/choose-pac-provider/index.js +++ b/extensions/chromium/minimalistic-pac-setter/extension/pages/choose-pac-provider/index.js @@ -1,174 +1,189 @@ -'use strict'; - -chrome.runtime.getBackgroundPage( (backgroundPage) => { - - const getStatus = () => document.querySelector('#status'); - - const setStatusTo = (msg) => { - - const status = getStatus(); - if (msg) { - status.classList.remove('off'); - status.innerHTML = msg; - } - else { - status.classList.add('off'); - } - - }; - - const antiCensorRu = backgroundPage.antiCensorRu; - - // SET DATE - - const setDate = () => { - - let dateForUser = 'никогда'; - if( antiCensorRu.lastPacUpdateStamp ) { - let diff = Date.now() - antiCensorRu.lastPacUpdateStamp; - let units = ' мс'; - const gauges = [ - [1000, ' с'], - [60, ' мин'], - [60, ' ч'], - [24, ' дн'], - [7, ' недель'], - [4, ' месяцев'], - [12, ' г'] - ]; - for(const g of gauges) { - const diffy = Math.floor(diff / g[0]); - if (!diffy) - break; - diff = diffy; - units = g[1]; - } - dateForUser = diff + units + ' назад'; - } - - const dateElement = document.querySelector('.update-date'); - dateElement.innerText = dateForUser; - dateElement.title = new Date(antiCensorRu.lastPacUpdateStamp).toLocaleString('ru-RU'); - - }; - - setDate(); - chrome.storage.onChanged.addListener( (changes) => changes.lastPacUpdateStamp.newValue && setDate() ); - - // CLOSE BUTTON - - document.querySelector('.close-button').onclick = () => window.close(); - - // RADIOS - - const currentProviderRadio = () => { - - const id = antiCensorRu.currentPacProviderKey || 'none'; - return document.querySelector('#'+id); - - } - const checkChosenProvider = () => currentProviderRadio().checked = true; - - const showError = (err) => { - - let clarification = err.clarification; - const ifNotCritical = clarification && clarification.ifNotCritical; - let message = err.message || ''; - - while( clarification ) { - message = (clarification && (clarification.message + ' ')) + message; - clarification = clarification.prev; - } - message = message.trim(); - setStatusTo( -`${ifNotCritical ? 'Некритичная ошибка.' : 'Ошибка!'} -
-${message} -[Ещё подробнее]` - ); - getStatus().querySelector('.link-button').onclick = function() { - - const div = document.createElement('div'); - div.innerHTML = ` -Более подробную информацию можно узнать из логов фоновой страницы:
-chrome://extensions › Это расширение › Отладка страниц: фоновая страница › Console (DevTools) -
-Ещё: ${JSON.stringify({err: err, stack: err.stack})} -`; - getStatus().replaceChild(div, this); - return false; - - }; - - }; - - const enableDisableInputs = function () { - - const inputs = document.querySelectorAll('input'); - for ( let i = 0; i < inputs.length; i++ ) { - inputs[i].disabled = !inputs[i].disabled; - } - - }; - - const conduct = (beforeStatus, operation, afterStatus, onSuccess) => { - - setStatusTo(beforeStatus); - enableDisableInputs(); - operation((err) => { - if (err) { - showError(err); - } - else { - setStatusTo(afterStatus); - onSuccess && onSuccess(); - } - enableDisableInputs(); - }) - - }; - - const ul = document.querySelector('#list-of-providers'); - const _firstChild = ul.firstChild; - for( const providerKey of Object.keys(antiCensorRu.pacProviders).sort() ) { - const li = document.createElement('li'); - li.innerHTML = ` [обновить]`; - li.querySelector('.link-button').onclick = () => { conduct( 'Обновляем...', (cb) => antiCensorRu.syncWithPacProvider(cb), 'Обновлено.' ); return false; }; - ul.insertBefore( li, _firstChild ); - } - checkChosenProvider(); - - const radios = [].slice.apply( document.querySelectorAll('[name=pacProvider]') ); - for(const radio of radios) { - radio.onclick = function(event) { - - if (event.target.id === (antiCensorRu.currentPacProviderKey || 'none')) { - return false; - } - const pacKey = event.target.id; - if (pacKey === 'none') { - conduct( - 'Отключение...', - (cb) => antiCensorRu.clearPac(cb), - 'Отключено.', - checkChosenProvider - ); - } - else { - conduct( - 'Установка...', - (cb) => antiCensorRu.installPac(pacKey, cb), - 'PAC-скрипт установлен.', - checkChosenProvider - ); - } - return false; - }; - } - - setStatusTo(''); - if (antiCensorRu.ifFirstInstall) { - currentProviderRadio().click(); - } - -}); +'use strict'; + +chrome.runtime.getBackgroundPage( (backgroundPage) => { + + const getStatus = () => document.querySelector('#status'); + + const setStatusTo = (msg) => { + + const status = getStatus(); + if (msg) { + status.classList.remove('off'); + status.innerHTML = msg; + } else { + status.classList.add('off'); + } + + }; + + const antiCensorRu = backgroundPage.antiCensorRu; + + // SET DATE + + const setDate = () => { + + let dateForUser = 'никогда'; + if( antiCensorRu.lastPacUpdateStamp ) { + let diff = Date.now() - antiCensorRu.lastPacUpdateStamp; + let units = ' мс'; + const gauges = [ + [1000, ' с'], + [60, ' мин'], + [60, ' ч'], + [24, ' дн'], + [7, ' недель'], + [4, ' месяцев'], + [12, ' г'], + ]; + for(const g of gauges) { + const diffy = Math.floor(diff / g[0]); + if (!diffy) + break; + diff = diffy; + units = g[1]; + } + dateForUser = diff + units + ' назад'; + } + + const dateElement = document.querySelector('.update-date'); + dateElement.innerText = dateForUser; + dateElement.title = new Date(antiCensorRu.lastPacUpdateStamp) + .toLocaleString('ru-RU'); + + }; + + setDate(); + chrome.storage.onChanged.addListener( + (changes) => changes.lastPacUpdateStamp.newValue && setDate() + ); + + // CLOSE BUTTON + + document.querySelector('.close-button').onclick = () => window.close(); + + // RADIOS + + const currentProviderRadio = () => { + + const id = antiCensorRu.currentPacProviderKey || 'none'; + return document.querySelector('#'+id); + + }; + const checkChosenProvider = () => currentProviderRadio().checked = true; + + const showError = (err) => { + + let clarification = err.clarification; + const ifNotCritical = clarification && clarification.ifNotCritical; + let message = err.message || ''; + + while( clarification ) { + message = (clarification && (clarification.message + ' ')) + message; + clarification = clarification.prev; + } + message = message.trim(); + setStatusTo( +` +${ifNotCritical ? 'Некритичная ошибка.' : 'Ошибка!'} + +
+${message} +[Ещё подробнее]` + ); + getStatus().querySelector('.link-button').onclick = function() { + + const div = document.createElement('div'); + div.innerHTML = ` +Более подробную информацию можно узнать из логов фоновой страницы:
+ +chrome://extensions › +Это расширение › Отладка страниц: фоновая страница › Console (DevTools) +
+Ещё: ${JSON.stringify({err: err, stack: err.stack})} +`; + getStatus().replaceChild(div, this); + return false; + + }; + + }; + + const enableDisableInputs = function() { + + const inputs = document.querySelectorAll('input'); + for ( let i = 0; i < inputs.length; i++ ) { + inputs[i].disabled = !inputs[i].disabled; + } + + }; + + const conduct = (beforeStatus, operation, afterStatus, onSuccess) => { + + setStatusTo(beforeStatus); + enableDisableInputs(); + operation((err) => { + if (err) { + showError(err); + } else { + setStatusTo(afterStatus); + onSuccess && onSuccess(); + } + enableDisableInputs(); + }); + + }; + + const ul = document.querySelector('#list-of-providers'); + const _firstChild = ul.firstChild; + for( const providerKey of Object.keys(antiCensorRu.pacProviders).sort() ) { + const li = document.createElement('li'); + li.innerHTML = ` + + [обновить]`; + li.querySelector('.link-button').onclick = + () => { + conduct( + 'Обновляем...', (cb) => antiCensorRu.syncWithPacProvider(cb), + 'Обновлено.' + ); + return false; + }; + ul.insertBefore( li, _firstChild ); + } + checkChosenProvider(); + + const radios = [].slice.apply( + document.querySelectorAll('[name=pacProvider]') + ); + for(const radio of radios) { + radio.onclick = function(event) { + + if (event.target.id === (antiCensorRu.currentPacProviderKey || 'none')) { + return false; + } + const pacKey = event.target.id; + if (pacKey === 'none') { + conduct( + 'Отключение...', + (cb) => antiCensorRu.clearPac(cb), + 'Отключено.', + checkChosenProvider + ); + } else { + conduct( + 'Установка...', + (cb) => antiCensorRu.installPac(pacKey, cb), + 'PAC-скрипт установлен.', + checkChosenProvider + ); + } + return false; + }; + } + + setStatusTo(''); + if (antiCensorRu.ifFirstInstall) { + currentProviderRadio().click(); + } + +}); diff --git a/extensions/chromium/minimalistic-pac-setter/extension/pages/choose-pac-provider/keep-links-clickable.js b/extensions/chromium/minimalistic-pac-setter/extension/pages/choose-pac-provider/keep-links-clickable.js index 8f12e4b..31ce3c5 100644 --- a/extensions/chromium/minimalistic-pac-setter/extension/pages/choose-pac-provider/keep-links-clickable.js +++ b/extensions/chromium/minimalistic-pac-setter/extension/pages/choose-pac-provider/keep-links-clickable.js @@ -13,9 +13,9 @@ const updateLinks = () => { for (let i = 0; i < links.length; i++) { const ln = links[i]; const location = ln.href; - ln.onclick = function () { + ln.onclick = function() { - chrome.tabs.create({ active: !this.dataset.inBg, url: location }); + chrome.tabs.create({active: !this.dataset.inBg, url: location}); return false; }; @@ -24,6 +24,11 @@ const updateLinks = () => { }; new MutationObserver( updateLinks ) - .observe(target, { attributes: false, subtree: true, childList: true, characterData: false }); + .observe(target, { + attributes: false, + subtree: true, + childList: true, + characterData: false, + }); document.addEventListener('DOMContentLoaded', updateLinks); diff --git a/extensions/chromium/minimalistic-pac-setter/extension/pages/debug/index.js b/extensions/chromium/minimalistic-pac-setter/extension/pages/debug/index.js index 7e6c8d7..d096dcd 100644 --- a/extensions/chromium/minimalistic-pac-setter/extension/pages/debug/index.js +++ b/extensions/chromium/minimalistic-pac-setter/extension/pages/debug/index.js @@ -4,13 +4,15 @@ const setStatusTo = (msg) => document.getElementById('status').innerHTML = msg; const red = (text) => '' + text + ''; -const editor = ace.edit('editor'); +const editor = window.ace.edit('editor'); editor.getSession().setOptions({ - mode: "ace/mode/javascript", - useSoftTabs: true + mode: 'ace/mode/javascript', + useSoftTabs: true, }); -chrome.proxy.settings.onChange.addListener( (details) => setStatusTo(red( details.levelOfControl + '!') ) ); +chrome.proxy.settings.onChange.addListener( + (details) => setStatusTo(red( details.levelOfControl + '!') ) +); document.querySelector('#read-button').onclick = () => { @@ -36,8 +38,8 @@ document.querySelector('#save-button').onclick = () => { mode: 'pac_script', pacScript: { mandatory: false, - data: editor.getValue() - } + data: editor.getValue(), + }, }; chrome.proxy.settings.set( {value: config}, () => alert('Saved!') ); diff --git a/extensions/chromium/minimalistic-pac-setter/package.json b/extensions/chromium/minimalistic-pac-setter/package.json new file mode 100644 index 0000000..7e06e92 --- /dev/null +++ b/extensions/chromium/minimalistic-pac-setter/package.json @@ -0,0 +1,21 @@ +{ + "name": "russian-censorship-bypass", + "version": "0.0.15", + "description": "Development tools for chromium extension", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "lint": "./node_modules/.bin/eslint ./extension/**/*.js --ignore-pattern vendor" + }, + "author": "Ilya Ig. Petrov", + "license": "GPLv3", + "devDependencies": { + "eslint": "^3.11.1", + "eslint-config-airbnb": "^13.0.0", + "eslint-config-google": "^0.7.1", + "eslint-plugin-hapi": "^4.0.0", + "eslint-plugin-import": "^2.2.0", + "eslint-plugin-jsx-a11y": "^2.2.3", + "eslint-plugin-react": "^6.7.1" + } +}