feat: add option for automatically updating client app

pull/73/head
dessant 5 years ago
parent 0fcc1db276
commit e17107f751

@ -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."
},

@ -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();
}
}

@ -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();

@ -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"]
}

@ -100,6 +100,15 @@
<v-switch id="si" v-model="options.simulateUserInput"></v-switch>
</v-form-field>
</div>
<div class="option">
<v-form-field input-id="auc"
v-if="options.simulateUserInput"
:label="getText('optionTitle_autoUpdateClientApp')">
<v-switch id="auc" v-model="options.autoUpdateClientApp"></v-switch>
</v-form-field>
</div>
<div class="client-bownload" v-if="showClientAppNotice">
<div class="download-desc">
{{ 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';
}

@ -3,7 +3,7 @@
<div id="app" v-if="dataLoaded">
<div class="wrap" v-if="!isInstallSuccess && !isInstallError">
<div class="title">
{{ getText('buttonText_installApp') }}
{{ getText('pageContent_installTitle') }}
</div>
<div class="desc">
{{ getText('pageContent_installDesc') }}
@ -13,7 +13,13 @@
v-model.trim="appDir"
:label="getText('inputLabel_appLocation')">
</v-textfield>
<div class="manifest-desc" v-if="manifestDirEditable">
{{ getText('pageContent_manifestLocationDesc') }}
</div>
<v-textfield
v-if="manifestDirEditable"
v-model.trim="manifestDir"
:label="getText('inputLabel_manifestLocation')">
</v-textfield>
@ -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 {

@ -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'});
}
}
}

@ -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};

@ -5,6 +5,7 @@
"UidMDYaYA",
"nOedd0Txqd",
"ZtLMLoh1ag",
"t335iRDhZ8"
"t335iRDhZ8",
"X3djS8vZC"
]
}

@ -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};

@ -5,6 +5,7 @@
"UidMDYaYA",
"nOedd0Txqd",
"ZtLMLoh1ag",
"t335iRDhZ8"
"t335iRDhZ8",
"X3djS8vZC"
]
}

@ -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 {

@ -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};

@ -8,7 +8,8 @@ const optionKeys = [
'witSpeechApiKeys',
'loadEnglishChallenge',
'tryEnglishSpeechModel',
'simulateUserInput'
'simulateUserInput',
'autoUpdateClientApp'
];
const clientAppPlatforms = ['windows/amd64', 'linux/amd64', 'macos/amd64'];

@ -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
}

Loading…
Cancel
Save