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
pull/73/head
dessant 6 years ago
parent 61b1f97af2
commit 3398166e83

@ -1,17 +1,8 @@
{
"presets": [
[
"@babel/env",
{
"exclude": ["transform-regenerator"],
"modules": false
}
]
],
"presets": ["@babel/env"],
"env": {
"production": {
"plugins": ["lodash"]
}
}
}

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

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

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

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

@ -0,0 +1,8 @@
{
"presets": ["@babel/env"],
"env": {
"production": {
"presets": [["minify", {"deadcode": false}]]
}
}
}

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

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

@ -1,3 +1,4 @@
<!-- prettier-ignore -->
<template>
<div id="app">
<v-contribute :extName="extName" :extSlug="extSlug" :notice="notice">

@ -23,11 +23,11 @@
"notifications",
"webRequest",
"webRequestBlocking",
"webNavigation",
"<all_urls>"
],
"content_security_policy":
"default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src *; object-src 'none';",
"content_security_policy": "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src *; object-src 'none';",
"icons": {
"16": "src/icons/app/icon-16.png",
@ -60,5 +60,7 @@
"background": {
"page": "src/background/index.html"
}
},
"web_accessible_resources": ["src/content/reset.js"]
}

@ -1,3 +1,4 @@
<!-- prettier-ignore -->
<template>
<div id="app" v-if="dataLoaded">
<div class="section">

@ -17,7 +17,7 @@ let solverWorking = false;
function setSolverState({working = true} = {}) {
solverWorking = working;
const button = document.querySelector('#buster-button');
const button = document.querySelector('#solver-button');
if (button) {
if (working) {
button.classList.add('working');
@ -27,10 +27,39 @@ function setSolverState({working = true} = {}) {
}
}
function setButton() {
const infoButton = document.body.querySelector(
'button#recaptcha-help-button'
);
function resetCaptcha() {
return browser.runtime.sendMessage({
id: 'resetCaptcha',
challengeUrl: window.location.href
});
}
function syncUI() {
if (isBlocked()) {
if (!document.querySelector('.solver-controls')) {
const div = document.createElement('div');
div.classList.add('solver-controls');
const button = document.createElement('button');
button.classList.add('rc-button');
button.setAttribute('tabindex', '0');
button.setAttribute('title', getText('buttonText_reset'));
button.id = 'reset-button';
button.addEventListener('click', resetCaptcha);
button.addEventListener('keydown', e => {
if (['Enter', ' '].includes(e.key)) {
resetCaptcha();
}
});
div.appendChild(button);
document.querySelector('.rc-footer').appendChild(div);
}
return;
}
const infoButton = document.querySelector('#recaptcha-help-button');
if (infoButton) {
infoButton.remove();
@ -41,7 +70,7 @@ function setButton() {
button.classList.add('rc-button', 'goog-inline-block');
button.setAttribute('tabindex', '0');
button.setAttribute('title', getText('buttonText_solve'));
button.id = 'buster-button';
button.id = 'solver-button';
if (solverWorking) {
button.classList.add('working');
}
@ -58,10 +87,14 @@ function setButton() {
}
}
async function isBlocked({timeout = 0} = {}) {
function isBlocked({timeout = 0} = {}) {
const selector = '.rc-doscaptcha-body';
if (timeout) {
return Boolean(await waitForElement(selector, {timeout}));
return new Promise(resolve => {
waitForElement(selector, {timeout}).then(result =>
resolve(Boolean(result))
);
});
}
return Boolean(document.querySelector(selector));
@ -194,7 +227,7 @@ async function solve() {
let audioUrl;
let solution;
if (await isBlocked()) {
if (isBlocked()) {
return;
}
@ -419,13 +452,13 @@ function start(e) {
}
function init() {
const observer = new MutationObserver(setButton);
const observer = new MutationObserver(syncUI);
observer.observe(document.body, {
childList: true,
subtree: true
});
setButton();
syncUI();
}
init();

@ -1,15 +1,51 @@
button#buster-button {
#solver-button {
background-image: url('data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%20192%20192%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M71.182%20138.872l27.077%2027.077H8.002v-18.051c0-19.947%2032.312-36.103%2072.205-36.103l17.058.993-26.083%2026.084m9.025-117.333c19.939%200%2036.103%2016.164%2036.103%2036.103s-16.164%2036.103-36.103%2036.103S44.105%2077.58%2044.105%2057.641s16.163-36.102%2036.102-36.102z%22%20fill%3D%22%23ff9f43%22%2F%3E%3Cpath%20fill%3D%22%2327ae60%22%20d%3D%22M171.362%2098.256l12.636%2012.726-58.938%2059.479-31.319-31.589%2012.636-12.727%2018.683%2018.774%2046.302-46.663%22%2F%3E%3C%2Fsvg%3E') !important;
background-color: transparent !important;
opacity: 0.8 !important;
}
button#buster-button:focus,
button#buster-button:hover {
#solver-button:focus,
#solver-button:hover {
opacity: 1 !important;
}
button#buster-button.working {
#solver-button.working {
background-image: url('data:image/svg+xml,%3Csvg%20width%3D%2244%22%20height%3D%2244%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20stroke%3D%22%23727272%22%3E%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%20stroke-width%3D%225%22%3E%3Ccircle%20cx%3D%2222%22%20cy%3D%2222%22%20r%3D%221%22%3E%3Canimate%20attributeName%3D%22r%22%20begin%3D%220s%22%20dur%3D%221.8s%22%20values%3D%221%3B%2020%22%20calcMode%3D%22spline%22%20keyTimes%3D%220%3B%201%22%20keySplines%3D%220.165%2C%200.84%2C%200.44%2C%201%22%20repeatCount%3D%22indefinite%22%2F%3E%3Canimate%20attributeName%3D%22stroke-opacity%22%20begin%3D%220s%22%20dur%3D%221.8s%22%20values%3D%221%3B%200%22%20calcMode%3D%22spline%22%20keyTimes%3D%220%3B%201%22%20keySplines%3D%220.3%2C%200.61%2C%200.355%2C%201%22%20repeatCount%3D%22indefinite%22%2F%3E%3C%2Fcircle%3E%3Ccircle%20cx%3D%2222%22%20cy%3D%2222%22%20r%3D%221%22%3E%3Canimate%20attributeName%3D%22r%22%20begin%3D%22-0.9s%22%20dur%3D%221.8s%22%20values%3D%221%3B%2020%22%20calcMode%3D%22spline%22%20keyTimes%3D%220%3B%201%22%20keySplines%3D%220.165%2C%200.84%2C%200.44%2C%201%22%20repeatCount%3D%22indefinite%22%2F%3E%3Canimate%20attributeName%3D%22stroke-opacity%22%20begin%3D%22-0.9s%22%20dur%3D%221.8s%22%20values%3D%221%3B%200%22%20calcMode%3D%22spline%22%20keyTimes%3D%220%3B%201%22%20keySplines%3D%220.3%2C%200.61%2C%200.355%2C%201%22%20repeatCount%3D%22indefinite%22%2F%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E') !important;
opacity: 1 !important;
}
#reset-button {
background-image: url('data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M17.65%206.35C16.2%204.9%2014.21%204%2012%204c-4.42%200-7.99%203.58-7.99%208s3.57%208%207.99%208c3.73%200%206.84-2.55%207.73-6h-2.08c-.82%202.33-3.04%204-5.65%204-3.31%200-6-2.69-6-6s2.69-6%206-6c1.66%200%203.14.69%204.22%201.78L13%2011h7V4l-2.35%202.35z%22%2F%3E%3C%2Fsvg%3E') !important;
background-color: transparent !important;
}
.solver-controls {
display: flex !important;
align-items: center !important;
justify-content: center !important;
height: 58px !important;
}
.hidden {
display: none !important;
}
.rc-doscaptcha-header {
display: flex !important;
align-items: center !important;
justify-content: center !important;
height: 30px !important;
}
.rc-doscaptcha-body {
height: 120px !important;
}
.rc-doscaptcha-footer {
height: 60px !important;
pointer-events: auto !important;
}
.rc-doscaptcha-footer .rc-controls {
display: none !important;
}

@ -71,11 +71,57 @@ function arrayBufferToBase64(buffer) {
return window.btoa(binary);
}
function executeCode(string, tabId, frameId = 0, runAt = 'document_start') {
return browser.tabs.executeScript(tabId, {
frameId: frameId,
runAt: runAt,
code: string
});
}
function executeFile(file, tabId, frameId = 0, runAt = 'document_start') {
return browser.tabs.executeScript(tabId, {
frameId: frameId,
runAt: runAt,
file: file
});
}
async function scriptsAllowed(tabId, frameId = 0) {
try {
await browser.tabs.executeScript(tabId, {
frameId: frameId,
runAt: 'document_start',
code: 'true;'
});
return true;
} catch (e) {}
}
async function functionInContext(
functionName,
tabId,
frameId = 0,
runAt = 'document_start'
) {
const [isFunction] = await executeCode(
`typeof ${functionName} === "function"`,
tabId,
frameId,
runAt
);
return isFunction;
}
export {
getText,
createTab,
isAndroid,
getActiveTab,
waitForElement,
arrayBufferToBase64
arrayBufferToBase64,
executeCode,
executeFile,
scriptsAllowed,
functionInContext
};

@ -10170,6 +10170,11 @@ prettier@1.13.7:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.13.7.tgz#850f3b8af784a49a6ea2d2eaa7ed1428a34b7281"
integrity sha512-KIU72UmYPGk4MujZGYMFwinB7lOf2LsDNGSOC8ufevsrPLISrZbNJlWstRi3m0AMuszbH+EFSQ/r6w56RSPK6w==
prettier@^1.15.3:
version "1.15.3"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.15.3.tgz#1feaac5bdd181237b54dbe65d874e02a1472786a"
integrity sha512-gAU9AGAPMaKb3NNSUUuhhFAS7SCO4ALTN4nRIn6PJ075Qd28Yn2Ig2ahEJWdJwJmlEBTUfC7mMUSFy8MwsOCfg==
pretty-bytes@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-4.0.2.tgz#b2bf82e7350d65c6c33aa95aaa5a4f6327f61cd9"

Loading…
Cancel
Save