How would I handle Google's Recaptcha with Promises? - javascript

What I want to do is make a link that when clicked opens Google's Invisible Recaptcha. That part is working fine. But I also want to catch errors, and that's where it fails. It is failing in both the latest versions of Firefox and Chrome with an error that says, "uncaught exception: undefined" (Firefox) "uncaught (in promise) undefined" (Chrome) and traces back to the line where reject() is called. The call to alert('Error') never fires.
What am I doing wrong? Is there another way to do this?
<script>
var onloadCallback, onerrorCallback;
var promise = new Promise(function(resolve, reject) {
onerrorCallback = function() {
reject();
}
onloadCallback = function() {
var form = document.createElement('form'),
recaptcha = document.createElement('div');
form.method = 'post';
resolve(grecaptcha.render(recaptcha, {
sitekey: 'INVISIBLE RECAPTCHA SITE KEY',
size: 'invisible',
callback: function() {
form.submit()
}
}));
form.appendChild(recaptcha);
document.body.appendChild(form);
}
})
function userClick() {
promise
.then(grecaptcha.execute)
.catch(function(){alert('Error')});
}
</script>
<script async defer src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit&hl=en" onerror="onerrorCallback()"></script>
<p>Please click here to solve the recaptcha</p>

I found the problem. Here is how I fixed it:
<script>
var onloadCallback, onerrorCallback;
var promise = new Promise(function(resolve, reject) {
onerrorCallback = function() {
reject();
}
onloadCallback = function() {
var form = document.createElement('form'),
recaptcha = document.createElement('div');
form.method = 'post';
resolve(grecaptcha.render(recaptcha, {
sitekey: 'INVISIBLE RECAPTCHA SITE KEY',
size: 'invisible',
callback: function() {
form.submit()
}
}));
form.appendChild(recaptcha);
document.body.appendChild(form);
}
})
function userClick() {
promise
.then(function(id) {
grecaptcha.execute(id);
})
.catch(function(){alert('Error')});
}
</script>
<script async defer src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit&hl=en" onerror="onerrorCallback()"></script>
<p>Please click here to solve the recaptcha</p>
The problem was in the then() call. Due to grecaptcha being not defined, JavaScript halted and never made it to the catch() call. The error message printed on the console had nothing to do with that though.
I fixed it by wrapping the call to grecaptcha.execute in an anonymous function.

I wrote a class in EcmaScript 2015 which solves the outdated and unflexible Google reCAPTCHA code for me.
Usage - init and render:
const captchaAdapter = new CaptchaAdapter(recaptchaSiteKey);
captchaAdapter.renderRc(document.getElementById('newsletter-overlay-recaptcha'));
Usage - check / execute
const chekcCaptcha = captchaAdapter.triggerRcCheck();
chekcCaptcha.then((token) => {
submitNewsletter(token);
});
Class:
/**
* Invisible reCAPTCHA v2 with promises
*/
export default class CaptchaAdapter
{
constructor(recaptchaSiteKey) {
this.recaptchaSiteKey = recaptchaSiteKey;
}
/**
*
* #return {Promise<event>}
*/
loadRcScript()
{
if (window.loadingRecaptchaScript) {
return window.loadingRecaptchaScript;
}
window.loadingRecaptchaScript = new Promise((resolve) => {
const script = document.createElement('script');
script.src = 'https://www.google.com/recaptcha/api.js?render=' + this.recaptchaSiteKey;
script.addEventListener('load', (event) => {
window.grecaptcha.ready(() => {
resolve(event);
});
});
document.body.appendChild(script);
});
return window.loadingRecaptchaScript;
}
async renderRc(container)
{
this.container = container;
this.checkCallback = (token) => {
this.resolveCheck(token);
};
this.loadRcScript().then(() => {
const parameters = {
sitekey: this.recaptchaSiteKey,
callback: this.checkCallback,
size: 'invisible',
};
this.widgetId = window.grecaptcha.render(container, parameters);
});
}
fixRcPosition()
{
setTimeout(() => {
const currentPosition = document.documentElement.scrollTop || document.body.scrollTop;
document.querySelectorAll('body>div[style*="top:"]').forEach((captchaContainer) => {
captchaContainer.style.top = currentPosition + 'px';
});
}, 0.3 * 1000);
}
/**
* #return {Promise<token>}
*/
triggerRcCheck()
{
this.fixRcPosition();
return new Promise((resolve) => {
this.resolveCheck = resolve;
window.grecaptcha.ready(() => {
window.grecaptcha.execute(this.widgetId, { action: 'submit' });
});
});
}
}

Related

How to load fingerprinting js asynchronously and get visitor id

I'm using javascript scripting with a legacy version of iMacros (v8.9.7)
And I'm a bit confused about getting fingerprinting JS to work properly.
My use case :
1- load fingerprintingJS from CDN asynchronously
2- store the visitorID into variable
My code :
Components.utils.import("resource://gre/modules/Promise.jsm");
Components.utils.import("resource://gre/modules/devtools/Console.jsm");
function initFingerprintJS() {
FingerprintJS.load()
.then(fp => fp.get())
.then(result => {
const visitorId = result.visitorId;
window.console.log(visitorId);
});
}
const loadScript = (src, async = true, type = "text/javascript") => {
return new Promise((resolve, reject) => {
try {
const el = window.document.createElement("script");
const container = window.document.head || window.document.body;
el.type = type;
el.async = async;
el.src = src;
el.addEventListener("load", () => {
resolve({ status: true });
});
el.addEventListener("error", () => {
reject({
status: false,
message: `Failed to load the script ${src}`
});
});
container.appendChild(el);
} catch (err) {
reject(err);
}
});
};
function get_fingerprint() {
loadScript("https://cdn.jsdelivr.net/npm/#fingerprintjs/fingerprintjs#3/dist/fp.min.js")
.then((data) => {
window.console.log("Script loaded successfully", data);
initFingerprintJS();
})
.catch((err) => {
window.console.error(err);
});
}
<button onclick="get_fingerprint()">FINGERPRINT</button>
when I remove the two Components imports and run the bit of script in the console, everything works. But when it's from iMacros interface, it generates errors
GET
https://cdn.jsdelivr.net/npm/#fingerprintjs/fingerprintjs#3/dist/fp.min.js
[HTTP/2.0 200 OK 0 ms] Script loaded successfully Object { status:
true } ReferenceError: FingerprintJS is not defined Trace de la pile :
initFingerprintJS#resource://gre/modules/RemoteAddonsParent.jsm:1102:5
get_fingerprint/<#resource://gre/modules/RemoteAddonsParent.jsm:1142:9
Handler.prototype.process#resource://gre/modules/Promise.jsm ->
resource://gre/modules/Promise-backend.js:932:23
this.PromiseWalker.walkerLoop#resource://gre/modules/Promise.jsm ->
resource://gre/modules/Promise-backend.js:813:7
this.PromiseWalker.scheduleWalkerLoop/<#resource://gre/modules/Promise.jsm
-> resource://gre/modules/Promise-backend.js:747:11
Any helps please

Unable to retrieve object from constructor function when running Cypress

I'm trying to access my own JS module when running Cypress tests. I have defined my own module as...
cy.companies = function () {
const Company = function () {
this.id = 0;
this.name = "";
};
return {
Company: function () {
return Company;
}
};
}();
I have then modified \support\index.js to include this file...
import "../fixtures/provisioning/companies";
Then inside \support\commands.js I have added...
Cypress.Commands.add("createCompany", () => {
return new cy.companies.Company();
});
So that's the setup. I then consume it like this...
describe("...", () => {
beforeEach(function () {
const company = cy.createCompany();
console.log(company)
});
});
In the console I would expect to see...
{
id: 0,
name: ""
}
...but what I actually see is...
$Chainer {userInvocationStack: " at Context.eval (https://[...]/__cy…press\integration\[...].spec.js:54:22)", specWindow: Window, chainerId: "chainer2", firstCall: false, useInitialStack: false}
chainerId: "chainer2"
firstCall: false
...
Where have I gone wrong?
First issue is in your js module:
cy.companies = (function () {
const Company = function () {
this.id = 0;
this.name = name;
};
/* return {
Company: function () {
return Company;
}
}; */
return { Company };
})();
Second issue is due to the fact: cy commands are asynchronous and are queued to be run later, so you need to use .then
describe("...", () => {
beforeEach(function () {
// const company = cy.createCompany();
// console.log(company)
cy.createCompany().then((company) => {
cy.log(company);
console.log(company);
});
});
});

How can I get the XMLHttpRequest back after the data loads?

I have a code in which the "onloadend" runs after the "get" task, and the code would work the right way if "get" ran first. So i need a way to switch them. I must use HttpClient and JavaScript, I cannot use jQuery or anything else.
Here is my full code in CodePen: https://codepen.io/vargaadam19/pen/BaabVbZ?editors=0010
Here is the onloadend:
export class HttpClient {
constructor(url) {
this.url = url;
this.xhr = new XMLHttpRequest();
this.xhr.onloadend = (event) => {
return this.xhr.result;
};
}
I set header here:
setHeader() {
this.xhr.setRequestHeader('Content-Type','application/json');
}
and here is the get function:
get(async, header) {
return new Promise((resolve, reject) => {
this.xhr.open('GET', this.url, async);
this.xhr.setRequestHeader(header.name, header.value);
this.xhr.send();
resolve(this.xhr.response);`enter code here`
});
}
And here is the function that's what I invite the get function
getAll() {
this.httpClient.get(true, this.header).then((result) => {
const data = !!result ? result : '[]';
const items = JSON.parse(data);
console.log('result:', result);
items.forEach(item => {
const todo = new Todo(item.id, item.name, item.status);
this.items.push(todo);
});
});
}
How can I solve this problem about the order of the tasks?

How can I call another function within a Cordova function in Angular 8?

Hi when I try to use this.upload(); to call the upload function from a Cordova function I get this error
The code is
/* This is the cordova function to access the phone camera */
cameraTakePicture() {
navigator.camera.getPicture(this.onSuccess, this.onFail, {
quality: 50,
destinationType: navigator.camera.DestinationType.DATA_URL,
});
}
onSuccess(imageData) {
const image: any = document.getElementById('myImage');
image.src = 'data:image/jpeg;base64,' + imageData;
this.selected = imageData;
}
onFail(message) {
alert('Failed because: ' + message);
}
/* Upload function I am trying to call */
Upload() {
const file = new FormData();
if (this.selected != null) {
this.loading = true;
if (this.mobile === true) {
document.querySelector('#output').scrollIntoView({ behavior: 'smooth', block: 'center' });
}
file.append('file', this.selected, this.selected.name);
this.env.upload(file).subscribe(
data => {
this.data = data;
this.loading = false;
},
error => {
this.loading = false;
}
);
} else {
this.notifiy.showInfo('You need to choose a file first', 'Info');
}
}
im not sure how I can call this function any advice would be very helpful also if someone could explain why I am getting this error that would be great

How can I intercept XMLHttpRequests from a Greasemonkey script?

I would like to capture the contents of AJAX requests using Greasemonkey.
Does anybody know how to do this?
The accepted answer is almost correct, but it could use a slight improvement:
(function(open) {
XMLHttpRequest.prototype.open = function() {
this.addEventListener("readystatechange", function() {
console.log(this.readyState);
}, false);
open.apply(this, arguments);
};
})(XMLHttpRequest.prototype.open);
Prefer using apply + arguments over call because then you don't have to explicitly know all the arguments being given to open which could change!
How about modifying the XMLHttpRequest.prototype.open or send methods with replacements which set up their own callbacks and call the original methods? The callback can do its thing and then call the callback the original code specified.
In other words:
XMLHttpRequest.prototype.realOpen = XMLHttpRequest.prototype.open;
var myOpen = function(method, url, async, user, password) {
//do whatever mucking around you want here, e.g.
//changing the onload callback to your own version
//call original
this.realOpen (method, url, async, user, password);
}
//ensure all XMLHttpRequests use our custom open method
XMLHttpRequest.prototype.open = myOpen ;
Tested in Chrome 55 and Firefox 50.1.0
In my case I wanted to modify the responseText, which in Firefox was a read-only property, so I had to wrap the whole XMLHttpRequest object. I haven't implemented the whole API (particular the responseType), but it was good enough to use for all of the libraries I have.
Usage:
XHRProxy.addInterceptor(function(method, url, responseText, status) {
if (url.endsWith('.html') || url.endsWith('.htm')) {
return "<!-- HTML! -->" + responseText;
}
});
Code:
(function(window) {
var OriginalXHR = XMLHttpRequest;
var XHRProxy = function() {
this.xhr = new OriginalXHR();
function delegate(prop) {
Object.defineProperty(this, prop, {
get: function() {
return this.xhr[prop];
},
set: function(value) {
this.xhr.timeout = value;
}
});
}
delegate.call(this, 'timeout');
delegate.call(this, 'responseType');
delegate.call(this, 'withCredentials');
delegate.call(this, 'onerror');
delegate.call(this, 'onabort');
delegate.call(this, 'onloadstart');
delegate.call(this, 'onloadend');
delegate.call(this, 'onprogress');
};
XHRProxy.prototype.open = function(method, url, async, username, password) {
var ctx = this;
function applyInterceptors(src) {
ctx.responseText = ctx.xhr.responseText;
for (var i=0; i < XHRProxy.interceptors.length; i++) {
var applied = XHRProxy.interceptors[i](method, url, ctx.responseText, ctx.xhr.status);
if (applied !== undefined) {
ctx.responseText = applied;
}
}
}
function setProps() {
ctx.readyState = ctx.xhr.readyState;
ctx.responseText = ctx.xhr.responseText;
ctx.responseURL = ctx.xhr.responseURL;
ctx.responseXML = ctx.xhr.responseXML;
ctx.status = ctx.xhr.status;
ctx.statusText = ctx.xhr.statusText;
}
this.xhr.open(method, url, async, username, password);
this.xhr.onload = function(evt) {
if (ctx.onload) {
setProps();
if (ctx.xhr.readyState === 4) {
applyInterceptors();
}
return ctx.onload(evt);
}
};
this.xhr.onreadystatechange = function (evt) {
if (ctx.onreadystatechange) {
setProps();
if (ctx.xhr.readyState === 4) {
applyInterceptors();
}
return ctx.onreadystatechange(evt);
}
};
};
XHRProxy.prototype.addEventListener = function(event, fn) {
return this.xhr.addEventListener(event, fn);
};
XHRProxy.prototype.send = function(data) {
return this.xhr.send(data);
};
XHRProxy.prototype.abort = function() {
return this.xhr.abort();
};
XHRProxy.prototype.getAllResponseHeaders = function() {
return this.xhr.getAllResponseHeaders();
};
XHRProxy.prototype.getResponseHeader = function(header) {
return this.xhr.getResponseHeader(header);
};
XHRProxy.prototype.setRequestHeader = function(header, value) {
return this.xhr.setRequestHeader(header, value);
};
XHRProxy.prototype.overrideMimeType = function(mimetype) {
return this.xhr.overrideMimeType(mimetype);
};
XHRProxy.interceptors = [];
XHRProxy.addInterceptor = function(fn) {
this.interceptors.push(fn);
};
window.XMLHttpRequest = XHRProxy;
})(window);
You can replace the unsafeWindow.XMLHttpRequest object in the document with a wrapper. A little code (not tested):
var oldFunction = unsafeWindow.XMLHttpRequest;
unsafeWindow.XMLHttpRequest = function() {
alert("Hijacked! XHR was constructed.");
var xhr = oldFunction();
return {
open: function(method, url, async, user, password) {
alert("Hijacked! xhr.open().");
return xhr.open(method, url, async, user, password);
}
// TODO: include other xhr methods and properties
};
};
But this has one little problem: Greasemonkey scripts execute after a page loads, so the page can use or store the original XMLHttpRequest object during it's load sequence, so requests made before your script executes, or with the real XMLHttpRequest object wouldn't be tracked by your script. No way that I can see to work around this limitation.
I spent quite some time figuring out how to do this.
At first I was just overriding window.fetch but that stopped working for some reason - I believe it has to do with Tampermonkey trying to sandbox window (??) and I also tried unsafeWindow with the same results.
So. I started looking into overriding the requests at a lower level. The XMLHttpRequest (also that class name upper case lower case ew...)
Sean's answer was helpful to get started but didn't show how to override the responses after interception. The below does that:
let interceptors = [];
/*
* Add a interceptor.
*/
export const addInterceptor = (interceptor) => {
interceptors.push(interceptor);
};
/*
* Clear interceptors
*/
export const clearInterceptors = () => {
interceptors = [];
};
/*
* XML HTPP requests can be intercepted with interceptors.
* Takes a regex to match against requests made and a callback to process the response.
*/
const createXmlHttpOverride = (
open
) => {
return function (
method: string,
url,
async,
username,
password
) {
this.addEventListener(
"readystatechange",
function () {
if (this.readyState === 4) {
// Override `onreadystatechange` handler, there's no where else this can go.
// Basically replace the client's with our override for interception.
this.onreadystatechange = (function (
originalOnreadystatechange
) {
return function (ev) {
// Only intercept JSON requests.
const contentType = this.getResponseHeader("content-type");
if (!contentType || !contentType.includes("application/json")) {
return (
originalOnreadystatechange &&
originalOnreadystatechange.call(this, ev)
);
}
// Read data from response.
(async function () {
let success = false;
let data;
try {
data =
this.responseType === "blob"
? JSON.parse(await this.response.text())
: JSON.parse(this.responseText);
success = true;
} catch (e) {
console.error("Unable to parse response.");
}
if (!success) {
return (
originalOnreadystatechange &&
originalOnreadystatechange.call(this, ev)
);
}
for (const i in interceptors) {
const { regex, override, callback } = interceptors[i];
// Override.
const match = regex.exec(url);
if (match) {
if (override) {
try {
data = await callback(data);
} catch (e) {
logger.error(`Interceptor '${regex}' failed. ${e}`);
}
}
}
}
// Override the response text.
Object.defineProperty(this, "responseText", {
get() {
return JSON.stringify(data);
},
});
// Tell the client callback that we're done.
return (
originalOnreadystatechange &&
originalOnreadystatechange.call(this, ev)
);
}.call(this));
};
})(this.onreadystatechange);
}
},
false
);
open.call(this, method, url, async, username, password);
};
};
const main = () => {
const urlRegex = /providers/; // Match any url with "providers" in the url.
addInterceptor({
urlRegex,
callback: async (_data) => {
// Replace response data.
return JSON.parse({ hello: 'world' });
},
override: true
});
XMLHttpRequest.prototype.open = createXmlHttpOverride(
XMLHttpRequest.prototype.open
);
};
main();
Based on proposed solution I implemented 'xhr-extensions.ts' file which can be used in typescript solutions.
How to use:
Add file with code to your solution
Import like this
import { XhrSubscription, subscribToXhr } from "your-path/xhr-extensions";
Subscribe like this
const subscription = subscribeToXhr(xhr => {
if (xhr.status != 200) return;
... do something here.
});
Unsubscribe when you don't need subscription anymore
subscription.unsubscribe();
Content of 'xhr-extensions.ts' file
export class XhrSubscription {
constructor(
private callback: (xhr: XMLHttpRequest) => void
) { }
next(xhr: XMLHttpRequest): void {
return this.callback(xhr);
}
unsubscribe(): void {
subscriptions = subscriptions.filter(s => s != this);
}
}
let subscriptions: XhrSubscription[] = [];
export function subscribeToXhr(callback: (xhr: XMLHttpRequest) => void): XhrSubscription {
const subscription = new XhrSubscription(callback);
subscriptions.push(subscription);
return subscription;
}
(function (open) {
XMLHttpRequest.prototype.open = function () {
this.addEventListener("readystatechange", () => {
subscriptions.forEach(s => s.next(this));
}, false);
return open.apply(this, arguments);
};
})(XMLHttpRequest.prototype.open);
Not sure if you can do it with greasemonkey, but if you create an extension then you can use the observer service and the http-on-examine-response observer.

Categories

Resources