From 3398166e834ec7617fc95c5df66c9c31b8a8d0e1 Mon Sep 17 00:00:00 2001 From: dessant Date: Thu, 27 Dec 2018 00:16:59 +0200 Subject: [PATCH] feat: show reset button when the challenge is blocked The challenge can be reloaded during a temporary block, without reloading the entire page. Requires new permission: webNavigation --- .babelrc | 11 +------ gulpfile.js | 12 +++++++- package.json | 1 + src/_locales/en/messages.json | 19 ++++++++---- src/background/main.js | 33 ++++++++++++++++++++- src/content/.babelrc | 8 +++++ src/content/initReset.js | 28 ++++++++++++++++++ src/content/reset.js | 24 +++++++++++++++ src/contribute/App.vue | 1 + src/manifest.json | 8 +++-- src/options/App.vue | 1 + src/solve/main.js | 55 ++++++++++++++++++++++++++++------- src/solve/style.css | 44 +++++++++++++++++++++++++--- src/utils/common.js | 48 +++++++++++++++++++++++++++++- yarn.lock | 5 ++++ 15 files changed, 261 insertions(+), 37 deletions(-) create mode 100644 src/content/.babelrc create mode 100644 src/content/initReset.js create mode 100644 src/content/reset.js diff --git a/.babelrc b/.babelrc index f859143..5f03543 100644 --- a/.babelrc +++ b/.babelrc @@ -1,17 +1,8 @@ { - "presets": [ - [ - "@babel/env", - { - "exclude": ["transform-regenerator"], - "modules": false - } - ] - ], + "presets": ["@babel/env"], "env": { "production": { "plugins": ["lodash"] } } } - diff --git a/gulpfile.js b/gulpfile.js index 236d38e..3bfe379 100755 --- a/gulpfile.js +++ b/gulpfile.js @@ -7,6 +7,7 @@ const gulp = require('gulp'); const gulpSeq = require('gulp-sequence'); const htmlmin = require('gulp-htmlmin'); const svgmin = require('gulp-svgmin'); +const babel = require('gulp-babel'); const postcss = require('gulp-postcss'); const gulpif = require('gulp-if'); const del = require('del'); @@ -24,7 +25,7 @@ gulp.task('clean', function() { return del([distDir]); }); -gulp.task('js', function(done) { +gulp.task('js:webpack', function(done) { exec('webpack-cli --display-error-details --bail --colors', function( err, stdout, @@ -36,6 +37,15 @@ gulp.task('js', function(done) { }); }); +gulp.task('js:babel', function() { + return gulp + .src(['src/content/**/*.js'], {base: '.'}) + .pipe(babel()) + .pipe(gulp.dest(distDir)); +}); + +gulp.task('js', ['js:webpack', 'js:babel']); + gulp.task('html', function() { return gulp .src('src/**/*.html', {base: '.'}) diff --git a/package.json b/package.json index da5565c..7e8a0ed 100755 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "npm-check-updates": "^2.14.2", "npm-run-all": "^4.1.5", "postcss-loader": "^3.0.0", + "prettier": "^1.15.3", "recursive-readdir": "^2.2.2", "sass-loader": "^7.1.0", "sharp": "^0.20.5", diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index ebbde8b..a07092c 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -430,6 +430,11 @@ "description": "Text of the button." }, + "buttonText_reset": { + "message": "Reset the challenge", + "description": "Text of the button." + }, + "pageTitle": { "message": "$PAGETITLE$ - $EXTENSIONNAME$", "description": "Title of the page.", @@ -456,20 +461,22 @@ }, "error_captchaNotSolved": { - "message": - "Captcha could not be solved. Try again after requesting a new challenge.", + "message": "Captcha could not be solved. Try again after requesting a new challenge.", "description": "Error message." }, "error_missingApiKey": { - "message": - "API key missing. Visit the options page to configure the service.", + "message": "API key missing. Visit the options page to configure the service.", + "description": "Error message." + }, + + "error_scriptsNotAllowed": { + "message": "Content scripts are not allowed on this page.", "description": "Error message." }, "error_internalError": { - "message": - "Something went wrong. Open the browser console for more details.", + "message": "Something went wrong. Open the browser console for more details.", "description": "Error message." } } diff --git a/src/background/main.js b/src/background/main.js index cfeb974..2902faf 100644 --- a/src/background/main.js +++ b/src/background/main.js @@ -3,6 +3,12 @@ import browser from 'webextension-polyfill'; import {initStorage} from 'storage/init'; import storage from 'storage/storage'; import {showNotification, showContributePage} from 'utils/app'; +import { + executeCode, + executeFile, + scriptsAllowed, + functionInContext +} from 'utils/common'; function challengeRequestCallback(details) { const url = new URL(details.url); @@ -41,7 +47,7 @@ async function setChallengeLocale() { } } -async function onMessage(request, sender, sendResponse) { +async function onMessage(request, sender) { if (request.id === 'notification') { showNotification({ message: request.message, @@ -56,6 +62,31 @@ async function onMessage(request, sender, sendResponse) { if ([30, 100].includes(useCount)) { await showContributePage('use'); } + } else if (request.id === 'resetCaptcha') { + const tabId = sender.tab.id; + const frameId = (await browser.webNavigation.getFrame({ + tabId, + frameId: sender.frameId + })).parentFrameId; + + if (!(await scriptsAllowed(tabId, frameId))) { + await showNotification({messageId: 'error_scriptsNotAllowed'}); + return; + } + + if (!(await functionInContext('addListener', tabId, frameId))) { + await executeFile('/src/content/initReset.js', tabId, frameId); + } + await executeCode('addListener()', tabId, frameId); + + await browser.tabs.sendMessage( + tabId, + { + id: 'resetCaptcha', + challengeUrl: request.challengeUrl + }, + {frameId} + ); } } diff --git a/src/content/.babelrc b/src/content/.babelrc new file mode 100644 index 0000000..de2aa1a --- /dev/null +++ b/src/content/.babelrc @@ -0,0 +1,8 @@ +{ + "presets": ["@babel/env"], + "env": { + "production": { + "presets": [["minify", {"deadcode": false}]] + } + } +} diff --git a/src/content/initReset.js b/src/content/initReset.js new file mode 100644 index 0000000..d51a7d9 --- /dev/null +++ b/src/content/initReset.js @@ -0,0 +1,28 @@ +function initReset(challengeUrl) { + const script = document.createElement('script'); + script.onload = function(e) { + e.target.remove(); + document.dispatchEvent( + new CustomEvent('___resetCaptcha', {detail: challengeUrl}) + ); + }; + script.src = chrome.extension.getURL('/src/content/reset.js'); + document.documentElement.appendChild(script); +} + +function addListener() { + const onMessage = function(request) { + if (request.id === 'resetCaptcha') { + removeCallbacks(); + initReset(request.challengeUrl); + } + }; + + const removeCallbacks = function() { + window.clearTimeout(timeoutId); + chrome.runtime.onMessage.removeListener(onMessage); + }; + const timeoutId = window.setTimeout(removeCallbacks, 10000); // 10 seconds + + chrome.runtime.onMessage.addListener(onMessage); +} diff --git a/src/content/reset.js b/src/content/reset.js new file mode 100644 index 0000000..c70e66a --- /dev/null +++ b/src/content/reset.js @@ -0,0 +1,24 @@ +(function() { + const onMessage = function(e) { + window.clearTimeout(timeoutId); + const challengeUrl = e.detail; + for (const [k, v] of Object.entries(___grecaptcha_cfg.clients)) { + if (v['O'].D.src === challengeUrl) { + grecaptcha.reset(k); + break; + } + } + }; + + const timeoutId = window.setTimeout(function() { + document.removeEventListener('___resetCaptcha', onMessage, { + capture: true, + once: true + }); + }, 10000); // 10 seconds + + document.addEventListener('___resetCaptcha', onMessage, { + capture: true, + once: true + }); +})(); diff --git a/src/contribute/App.vue b/src/contribute/App.vue index 654ccff..62b9d5d 100644 --- a/src/contribute/App.vue +++ b/src/contribute/App.vue @@ -1,3 +1,4 @@ +