From e17107f751b617cef62dcecd9e237c819b2550d1 Mon Sep 17 00:00:00 2001 From: dessant Date: Sat, 2 Mar 2019 11:23:03 +0200 Subject: [PATCH] feat: add option for automatically updating client app --- src/_locales/en/messages.json | 33 +++++++-- src/background/main.js | 22 ++++-- src/content/{install.js => setup.js} | 6 +- src/manifest.json | 6 +- src/options/App.vue | 15 +++- src/{install => setup}/App.vue | 48 +++++++++++-- src/{install => setup}/index.html | 0 src/{install => setup}/main.js | 0 src/solve/main.js | 89 +++++++++++++++++++----- src/storage/versions/local/X3djS8vZC.js | 27 +++++++ src/storage/versions/local/versions.json | 3 +- src/storage/versions/sync/X3djS8vZC.js | 27 +++++++ src/storage/versions/sync/versions.json | 3 +- src/utils/app.js | 26 +++++-- src/utils/config.js | 4 +- src/utils/data.js | 3 +- webpack.config.js | 4 +- 17 files changed, 259 insertions(+), 57 deletions(-) rename src/content/{install.js => setup.js} (71%) rename src/{install => setup}/App.vue (81%) rename src/{install => setup}/index.html (100%) rename src/{install => setup}/main.js (100%) create mode 100644 src/storage/versions/local/X3djS8vZC.js create mode 100644 src/storage/versions/sync/X3djS8vZC.js diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index 8eb12a8..99681ee 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -144,6 +144,11 @@ "description": "Title of the option." }, + "optionTitle_autoUpdateClientApp": { + "message": "Automatically update client app", + "description": "Title of the option." + }, + "optionTitle_witSpeechApiLang": { "message": "API language", "description": "Title of the option." @@ -466,7 +471,7 @@ }, "pageContent_optionClientAppDownloadDesc": { - "message": "Download and install the client app for user input simulation.", + "message": "Download and install the client app to enable user input simulation.", "description": "Page content." }, @@ -481,7 +486,7 @@ }, "pageContent_installDesc": { - "message": "The client app enables Buster to simulate user input and helps lower the occurrence of difficult challenges and temporary blocks.", + "message": "The client app enables Buster to simulate user input and helps improve the success rate of the extension and lower the occurrence of temporary blocks.", "description": "Page content." }, @@ -505,6 +510,11 @@ "description": "Page content." }, + "pageContent_manifestLocationDesc": { + "message": "The manifest location is browser-dependent, edit the path only if the installation does not succeed.", + "description": "Page content." + }, + "pageTitle": { "message": "$PAGETITLE$ - $EXTENSIONNAME$", "description": "Title of the page.", @@ -530,6 +540,11 @@ "description": "Title of the page." }, + "info_updatingClientApp": { + "message": "Updating client app. This will take a moment.", + "description": "Info message." + }, + "error_captchaNotSolved": { "message": "Captcha could not be solved. Try again after requesting a new challenge.", "description": "Error message." @@ -545,8 +560,18 @@ "description": "Error message." }, - "error_missingNativeApp": { - "message": "Cannot connect to native app. Finish setting up the application or turn off user input simulation from the options page.", + "error_missingClientApp": { + "message": "Cannot connect to client app. Finish setting up the app or turn off user input simulation from the extension's options page.", + "description": "Error message." + }, + + "error_outdatedClientApp": { + "message": "The client app is outdated. Download and install the latest version from the extension's options page.", + "description": "Error message." + }, + + "error_clientAppUpdateFailed": { + "message": "The client app cannot be updated. Download and install the latest version from the extension's options page.", "description": "Error message." }, diff --git a/src/background/main.js b/src/background/main.js index fd24879..2b6a737 100644 --- a/src/background/main.js +++ b/src/background/main.js @@ -11,9 +11,11 @@ import { executeCode, executeFile, scriptsAllowed, - functionInContext + functionInContext, + getBrowser, + getPlatform } from 'utils/common'; -import {clientAppApiVersion} from 'utils/config'; +import {clientAppVersion} from 'utils/config'; let nativePort; @@ -151,18 +153,24 @@ async function onMessage(request, sender) { return getFramePos(sender.tab.id, sender.frameId, request.index); } else if (request.id === 'getTabZoom') { return browser.tabs.getZoom(sender.tab.id); - } else if (request.id === 'startNativeApp') { + } else if (request.id === 'startClientApp') { nativePort = browser.runtime.connectNative('org.buster.client'); - } else if (request.id === 'stopNativeApp') { + } else if (request.id === 'stopClientApp') { if (nativePort) { nativePort.disconnect(); } - } else if (request.id === 'sendNativeMessage') { + } else if (request.id === 'messageClientApp') { const message = { - apiVersion: clientAppApiVersion, + apiVersion: clientAppVersion, ...request.message }; - return await sendNativeMessage(nativePort, message); + return sendNativeMessage(nativePort, message); + } else if (request.id === 'openOptions') { + browser.runtime.openOptionsPage(); + } else if (request.id === 'getPlatform') { + return getPlatform(); + } else if (request.id === 'getBrowser') { + return getBrowser(); } } diff --git a/src/content/install.js b/src/content/setup.js similarity index 71% rename from src/content/install.js rename to src/content/setup.js index 0d8ad94..ccc28bd 100644 --- a/src/content/install.js +++ b/src/content/setup.js @@ -1,5 +1,5 @@ -function install() { - const url = new URL(chrome.extension.getURL('/src/install/index.html')); +function setup() { + const url = new URL(chrome.extension.getURL('/src/setup/index.html')); url.searchParams.set( 'session', new URL(window.location.href).searchParams.get('session') @@ -11,4 +11,4 @@ function install() { document.body.appendChild(frame); } -install(); +setup(); diff --git a/src/manifest.json b/src/manifest.json index 0b58d55..9a6d24f 100755 --- a/src/manifest.json +++ b/src/manifest.json @@ -52,9 +52,9 @@ "js": ["src/manifest.js", "src/solve/script.js"] }, { - "matches": ["http://127.0.0.1/buster/install?session=*"], + "matches": ["http://127.0.0.1/buster/setup?session=*"], "run_at": "document_idle", - "js": ["src/content/install.js"] + "js": ["src/content/setup.js"] } ], @@ -69,5 +69,5 @@ "page": "src/background/index.html" }, - "web_accessible_resources": ["src/install/index.html", "src/content/reset.js"] + "web_accessible_resources": ["src/setup/index.html", "src/content/reset.js"] } diff --git a/src/options/App.vue b/src/options/App.vue index ebf0165..9bab6b0 100644 --- a/src/options/App.vue +++ b/src/options/App.vue @@ -100,6 +100,15 @@ + +
+ + + +
+
{{ getText('pageContent_optionClientAppDownloadDesc') }} @@ -131,6 +140,7 @@ import {Button, Select, Switch, FormField, TextField} from 'ext-components'; import storage from 'storage/storage'; import {getOptionLabels, pingClientApp} from 'utils/app'; import {getText, getPlatform} from 'utils/common'; +import {clientAppVersion} from 'utils/config'; import { optionKeys, clientAppPlatforms, @@ -199,7 +209,8 @@ export default { witSpeechApiKeys: {}, loadEnglishChallenge: false, tryEnglishSpeechModel: false, - simulateUserInput: false + simulateUserInput: false, + autoUpdateClientApp: false } }; }, @@ -213,7 +224,7 @@ export default { if (!this.clientAppDownloadUrl) { const {os, arch} = await getPlatform(); if (clientAppPlatforms.includes(`${os}/${arch}`)) { - this.clientAppDownloadUrl = `https://github.com/dessant/buster-client/releases/download/v0.1.0/buster-client-v0.1.0-${os}-${arch}`; + this.clientAppDownloadUrl = `https://github.com/dessant/buster-client/releases/download/v${clientAppVersion}/buster-client-setup-v${clientAppVersion}-${os}-${arch}`; if (os === 'windows') { this.clientAppDownloadUrl += '.exe'; } diff --git a/src/install/App.vue b/src/setup/App.vue similarity index 81% rename from src/install/App.vue rename to src/setup/App.vue index 0356799..1cdb18d 100644 --- a/src/install/App.vue +++ b/src/setup/App.vue @@ -3,7 +3,7 @@
- {{ getText('buttonText_installApp') }} + {{ getText('pageContent_installTitle') }}
{{ getText('pageContent_installDesc') }} @@ -13,7 +13,13 @@ v-model.trim="appDir" :label="getText('inputLabel_appLocation')"> + +
+ {{ getText('pageContent_manifestLocationDesc') }} +
+ @@ -52,7 +58,7 @@ import {Button, TextField} from 'ext-components'; import storage from 'storage/storage'; import {pingClientApp} from 'utils/app'; -import {getText, getBrowser} from 'utils/common'; +import {getText} from 'utils/common'; import {targetEnv} from 'utils/config'; export default { @@ -73,6 +79,7 @@ export default { session: urlParams.get('session'), appDir: '', manifestDir: '', + manifestDirEditable: false, isInstalling: false, isInstallSuccess: false, @@ -113,15 +120,30 @@ export default { } finally { this.isInstalling = false; } + + if (this.isInstallSuccess) { + const data = new FormData(); + data.append('session', this.session); + + await fetch(`${this.apiUrl}/setup/close`, { + referrer: '', + mode: 'cors', + method: 'POST', + body: data + }); + } }, location: async function() { const data = new FormData(); data.append('session', this.session); - data.append('browser', (await getBrowser()).name); + data.append( + 'browser', + (await browser.runtime.sendMessage({id: 'getBrowser'})).name + ); data.append('targetEnv', targetEnv); - const rsp = await fetch(`${this.apiUrl}/install/location`, { + const rsp = await fetch(`${this.apiUrl}/setup/location`, { referrer: '', mode: 'cors', method: 'POST', @@ -146,7 +168,7 @@ export default { data.append('targetEnv', targetEnv); data.append('extension', this.getExtensionId()); - const rsp = await fetch(`${this.apiUrl}/install/run`, { + const rsp = await fetch(`${this.apiUrl}/setup/install`, { referrer: '', mode: 'cors', method: 'POST', @@ -167,6 +189,11 @@ export default { created: async function() { await this.setLocation(); + const {os} = await browser.runtime.sendMessage({id: 'getPlatform'}); + if (os !== 'windows') { + this.manifestDirEditable = true; + } + this.dataLoaded = true; } }; @@ -199,7 +226,8 @@ body { } .title, -.desc { +.desc, +.manifest-desc { @include mdc-theme-prop('color', 'text-primary-on-light'); } @@ -218,6 +246,12 @@ body { margin-bottom: 24px; } +.manifest-desc { + @include mdc-typography('caption'); + margin-top: 12px; + margin-bottom: 4px; +} + .button { @include mdc-button-ink-color(#fff); width: 200px; @@ -225,7 +259,7 @@ body { } .install-button { - margin-top: 24px; + margin-top: 36px; } .error-button { diff --git a/src/install/index.html b/src/setup/index.html similarity index 100% rename from src/install/index.html rename to src/setup/index.html diff --git a/src/install/main.js b/src/setup/main.js similarity index 100% rename from src/install/main.js rename to src/setup/main.js diff --git a/src/solve/main.js b/src/solve/main.js index 528c1ac..5322fed 100644 --- a/src/solve/main.js +++ b/src/solve/main.js @@ -2,7 +2,7 @@ import browser from 'webextension-polyfill'; import audioBufferToWav from 'audiobuffer-to-wav'; import storage from 'storage/storage'; -import {meanSleep} from 'utils/app'; +import {meanSleep, pingClientApp} from 'utils/app'; import { getText, waitForElement, @@ -17,7 +17,7 @@ import { ibmSpeechApiUrls, microsoftSpeechApiUrls } from 'utils/data'; -import {witApiKeys} from 'utils/config'; +import {clientAppVersion, witApiKeys} from 'utils/config'; let solverWorking = false; @@ -140,17 +140,17 @@ async function navigateToElement(node, {forward = true} = {}) { } if (!forward) { - await sendNativeMessage({command: 'pressKey', data: 'shift'}); + await messageClientApp({command: 'pressKey', data: 'shift'}); await meanSleep(300); } while (document.activeElement !== node) { - await sendNativeMessage({command: 'tapKey', data: 'tab'}); + await messageClientApp({command: 'tapKey', data: 'tab'}); await meanSleep(300); } if (!forward) { - await sendNativeMessage({command: 'releaseKey', data: 'shift'}); + await messageClientApp({command: 'releaseKey', data: 'shift'}); await meanSleep(300); } } @@ -158,24 +158,24 @@ async function navigateToElement(node, {forward = true} = {}) { async function tapEnter(node, {navigateForward = true} = {}) { await navigateToElement(node, {forward: navigateForward}); await meanSleep(200); - await sendNativeMessage({command: 'tapKey', data: 'enter'}); + await messageClientApp({command: 'tapKey', data: 'enter'}); } async function clickElement(node, browserBorder) { const targetPos = await getClickPos(node, browserBorder); - await sendNativeMessage({command: 'moveMouse', ...targetPos}); + await messageClientApp({command: 'moveMouse', ...targetPos}); await meanSleep(100); - await sendNativeMessage({command: 'clickMouse'}); + await messageClientApp({command: 'clickMouse'}); } -async function sendNativeMessage(message) { +async function messageClientApp(message) { const rsp = await browser.runtime.sendMessage({ - id: 'sendNativeMessage', + id: 'messageClientApp', message }); if (!rsp.success) { - throw new Error(`Native response: ${rsp.text}`); + throw new Error(`Client app response: ${rsp.text}`); } return rsp; @@ -570,7 +570,7 @@ async function solve(simulateUserInput, clickEvent) { } await meanSleep(200); - await sendNativeMessage({command: 'typeText', data: solution}); + await messageClientApp({command: 'typeText', data: solution}); } else { input.value = solution; } @@ -613,16 +613,67 @@ function solveChallenge(ev) { } async function runSolver(ev) { - const {simulateUserInput} = await storage.get('simulateUserInput', 'sync'); + const {simulateUserInput, autoUpdateClientApp} = await storage.get( + ['simulateUserInput', 'autoUpdateClientApp'], + 'sync' + ); if (simulateUserInput) { try { - await browser.runtime.sendMessage({id: 'startNativeApp'}); + let pingRsp; + + try { + pingRsp = await pingClientApp({stop: false, checkResponse: false}); + } catch (err) { + browser.runtime.sendMessage({ + id: 'notification', + messageId: 'error_missingClientApp' + }); + browser.runtime.sendMessage({id: 'openOptions'}); + throw err; + } + + if (!pingRsp.success) { + if (!pingRsp.apiVersion !== clientAppVersion) { + if (!autoUpdateClientApp || pingRsp.apiVersion === '1') { + browser.runtime.sendMessage({ + id: 'notification', + messageId: 'error_outdatedClientApp' + }); + browser.runtime.sendMessage({id: 'openOptions'}); + throw new Error('Client app outdated'); + } else { + try { + browser.runtime.sendMessage({ + id: 'notification', + messageId: 'info_updatingClientApp' + }); + const rsp = await browser.runtime.sendMessage({ + id: 'messageClientApp', + message: {command: 'installClient', data: clientAppVersion} + }); + + if (rsp.success) { + await browser.runtime.sendMessage({id: 'stopClientApp'}); + + await pingClientApp({stop: false}); + } else { + throw new Error(`Client app update failed: ${rsp.data}`); + } + } catch (err) { + browser.runtime.sendMessage({ + id: 'notification', + messageId: 'error_clientAppUpdateFailed' + }); + browser.runtime.sendMessage({id: 'openOptions'}); + throw err; + } + } + } + } } catch (err) { - browser.runtime.sendMessage({ - id: 'notification', - messageId: 'error_missingNativeApp' - }); + console.log(err.toString()); + await browser.runtime.sendMessage({id: 'stopClientApp'}); return; } } @@ -631,7 +682,7 @@ async function runSolver(ev) { await solve(simulateUserInput, ev); } finally { if (simulateUserInput) { - await browser.runtime.sendMessage({id: 'stopNativeApp'}); + await browser.runtime.sendMessage({id: 'stopClientApp'}); } } } diff --git a/src/storage/versions/local/X3djS8vZC.js b/src/storage/versions/local/X3djS8vZC.js new file mode 100644 index 0000000..e3d18bf --- /dev/null +++ b/src/storage/versions/local/X3djS8vZC.js @@ -0,0 +1,27 @@ +import browser from 'webextension-polyfill'; + +const message = 'Add autoUpdateClientApp option'; + +const revision = 'X3djS8vZC'; +const downRevision = 't335iRDhZ8'; + +const storage = browser.storage.local; + +async function upgrade() { + const changes = { + autoUpdateClientApp: true + }; + + changes.storageVersion = revision; + return storage.set(changes); +} + +async function downgrade() { + const changes = {}; + await storage.remove(['autoUpdateClientApp']); + + changes.storageVersion = downRevision; + return storage.set(changes); +} + +export {message, revision, upgrade, downgrade}; diff --git a/src/storage/versions/local/versions.json b/src/storage/versions/local/versions.json index 7b0f423..01906d9 100644 --- a/src/storage/versions/local/versions.json +++ b/src/storage/versions/local/versions.json @@ -5,6 +5,7 @@ "UidMDYaYA", "nOedd0Txqd", "ZtLMLoh1ag", - "t335iRDhZ8" + "t335iRDhZ8", + "X3djS8vZC" ] } diff --git a/src/storage/versions/sync/X3djS8vZC.js b/src/storage/versions/sync/X3djS8vZC.js new file mode 100644 index 0000000..35784f7 --- /dev/null +++ b/src/storage/versions/sync/X3djS8vZC.js @@ -0,0 +1,27 @@ +import browser from 'webextension-polyfill'; + +const message = 'Add autoUpdateClientApp option'; + +const revision = 'X3djS8vZC'; +const downRevision = 't335iRDhZ8'; + +const storage = browser.storage.sync; + +async function upgrade() { + const changes = { + autoUpdateClientApp: true + }; + + changes.storageVersion = revision; + return storage.set(changes); +} + +async function downgrade() { + const changes = {}; + await storage.remove(['autoUpdateClientApp']); + + changes.storageVersion = downRevision; + return storage.set(changes); +} + +export {message, revision, upgrade, downgrade}; diff --git a/src/storage/versions/sync/versions.json b/src/storage/versions/sync/versions.json index 7b0f423..01906d9 100644 --- a/src/storage/versions/sync/versions.json +++ b/src/storage/versions/sync/versions.json @@ -5,6 +5,7 @@ "UidMDYaYA", "nOedd0Txqd", "ZtLMLoh1ag", - "t335iRDhZ8" + "t335iRDhZ8", + "X3djS8vZC" ] } diff --git a/src/utils/app.js b/src/utils/app.js index 4b223d8..bfd411d 100755 --- a/src/utils/app.js +++ b/src/utils/app.js @@ -85,13 +85,29 @@ function sendNativeMessage(port, message, {timeout = 10000} = {}) { }); } -async function pingClientApp() { - await browser.runtime.sendMessage({id: 'startNativeApp'}); - await browser.runtime.sendMessage({ - id: 'sendNativeMessage', +async function pingClientApp({ + start = true, + stop = true, + checkResponse = true +} = {}) { + if (start) { + await browser.runtime.sendMessage({id: 'startClientApp'}); + } + + const rsp = await browser.runtime.sendMessage({ + id: 'messageClientApp', message: {command: 'ping'} }); - await browser.runtime.sendMessage({id: 'stopNativeApp'}); + + if (checkResponse && (!rsp.success || rsp.data !== 'pong')) { + throw new Error(`Client app response: ${rsp.data}`); + } + + if (stop) { + await browser.runtime.sendMessage({id: 'stopClientApp'}); + } + + return rsp; } export { diff --git a/src/utils/config.js b/src/utils/config.js index 5a2ed78..38fe52b 100644 --- a/src/utils/config.js +++ b/src/utils/config.js @@ -1,6 +1,6 @@ const targetEnv = process.env.TARGET_ENV; -const clientAppApiVersion = '1'; +const clientAppVersion = '0.1.0'; const witApiKeys = { afrikaans: 'T3T7A2WS3TQJVBB4L4CTK2EEUI6N7YGZ', @@ -57,4 +57,4 @@ const witApiKeys = { zulu: 'B6OMGRZUYIJ5WLDQZODKCFCXCTH7PHB3' }; -export {targetEnv, clientAppApiVersion, witApiKeys}; +export {targetEnv, clientAppVersion, witApiKeys}; diff --git a/src/utils/data.js b/src/utils/data.js index 3761737..3559c8d 100755 --- a/src/utils/data.js +++ b/src/utils/data.js @@ -8,7 +8,8 @@ const optionKeys = [ 'witSpeechApiKeys', 'loadEnglishChallenge', 'tryEnglishSpeechModel', - 'simulateUserInput' + 'simulateUserInput', + 'autoUpdateClientApp' ]; const clientAppPlatforms = ['windows/amd64', 'linux/amd64', 'macos/amd64']; diff --git a/webpack.config.js b/webpack.config.js index 12c36b3..d39c12b 100755 --- a/webpack.config.js +++ b/webpack.config.js @@ -30,7 +30,7 @@ module.exports = { options: './src/options/main.js', contribute: './src/contribute/main.js', solve: './src/solve/main.js', - install: './src/install/main.js' + setup: './src/setup/main.js' }, output: { path: path.resolve(__dirname, 'dist', targetEnv, 'src'), @@ -46,7 +46,7 @@ module.exports = { commonsUi: { name: 'commons-ui', chunks: chunk => { - return ['options', 'contribute', 'install'].includes(chunk.name); + return ['options', 'contribute', 'setup'].includes(chunk.name); }, minChunks: 2 }