Skip to content

Commit

Permalink
✨ Add global support for amp-recaptcha-input component. (#27450)
Browse files Browse the repository at this point in the history
* Add global support for amp-recaptcha-input component

* Update integration test for amp-recaptcha

* Update global attribute to data-global and update unit tests for amp-recaptcha

* Update naming of global property to global attribute
  • Loading branch information
krdwan committed Apr 3, 2020
1 parent e22492b commit cbefbbf
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 9 deletions.
14 changes: 13 additions & 1 deletion 3p/recaptcha.js
Expand Up @@ -27,7 +27,7 @@ import {
setReportError,
user,
} from '../src/log';
import {dict} from '../src/utils/object';
import {dict, hasOwn} from '../src/utils/object';
import {isProxyOrigin, parseUrlDeprecated} from '../src/url';
import {loadScript} from './3p';
import {parseJson} from '../src/json';
Expand Down Expand Up @@ -56,6 +56,10 @@ const TAG = 'RECAPTCHA';
/** @const {string} */
const RECAPTCHA_API_URL = 'https://www.google.com/recaptcha/api.js?render=';

/** @const {string} */
const GLOBAL_RECAPTCHA_API_URL =
'https://www.recaptcha.net/recaptcha/api.js?render=';

/** {?IframeMessaginClient} */
let iframeMessagingClient = null;

Expand Down Expand Up @@ -99,6 +103,14 @@ export function initRecaptcha(recaptchaApiBaseUrl = RECAPTCHA_API_URL) {
'The sitekey is required for the <amp-recaptcha-input> iframe'
);
sitekey = dataObject.sitekey;
// Determine the recaptcha api URL based on global property
devAssert(
hasOwn(dataObject, 'global'),
'The global property is required for the <amp-recaptcha-input> iframe'
);
if (dataObject.global) {
recaptchaApiBaseUrl = GLOBAL_RECAPTCHA_API_URL;
}
const recaptchaApiUrl = recaptchaApiBaseUrl + sitekey;

loadScript(
Expand Down
10 changes: 9 additions & 1 deletion extensions/amp-recaptcha-input/0.1/amp-recaptcha-input.js
Expand Up @@ -53,6 +53,9 @@ export class AmpRecaptchaInput extends AMP.BaseElement {

/** @private {?Promise} */
this.registerPromise_ = null;

/** @private {boolean} */
this.global_ = false;
}

/** @override */
Expand All @@ -76,6 +79,8 @@ export class AmpRecaptchaInput extends AMP.BaseElement {
this.element
);

this.global_ = this.element.hasAttribute('data-global');

return recaptchaServiceForDoc(this.element).then((service) => {
this.recaptchaService_ = service;

Expand Down Expand Up @@ -108,7 +113,10 @@ export class AmpRecaptchaInput extends AMP.BaseElement {
/** @override */
layoutCallback() {
if (!this.registerPromise_ && this.sitekey_) {
this.registerPromise_ = this.recaptchaService_.register(this.sitekey_);
this.registerPromise_ = this.recaptchaService_.register(
this.sitekey_,
this.global_
);
}

return /** @type {!Promise} */ (this.registerPromise_);
Expand Down
17 changes: 16 additions & 1 deletion extensions/amp-recaptcha-input/0.1/amp-recaptcha-service.js
Expand Up @@ -88,15 +88,19 @@ export class AmpRecaptchaService {

/** @private {Object} */
this.executeMap_ = {};

/** @private {boolean} */
this.global_;
}

/**
* Function to register as a dependant of the AmpRecaptcha serivce.
* Used to create/destroy recaptcha boostrap iframe.
* @param {string} sitekey
* @param {boolean} global
* @return {Promise}
*/
register(sitekey) {
register(sitekey, global = false) {
if (!this.sitekey_) {
this.sitekey_ = sitekey;
} else if (this.sitekey_ !== sitekey) {
Expand All @@ -107,6 +111,16 @@ export class AmpRecaptchaService {
)
);
}
if (this.global_ === undefined) {
this.global_ = global;
} else if (this.global_ !== global) {
return Promise.reject(
new Error(
'You must supply the data-global attribute ' +
'to all or none of the amp-recaptcha-input elements.'
)
);
}

this.registeredElementCount_++;
if (!this.iframeLoadPromise_) {
Expand Down Expand Up @@ -230,6 +244,7 @@ export class AmpRecaptchaService {
dict({
'sitekey': this.sitekey_,
'sentinel': 'amp-recaptcha',
'global': this.global_,
})
)
);
Expand Down
Expand Up @@ -106,7 +106,7 @@ describes.realWin(
});
});

it('Should apply styles aftyer build', () => {
it('Should apply styles after build', () => {
return getRecaptchaInput().then((ampRecaptchaInput) => {
expect(win.getComputedStyle(ampRecaptchaInput).position).to.equal(
'absolute'
Expand Down
Expand Up @@ -109,16 +109,40 @@ describes.realWin(
' if they pass a different sitekey',
() => {
expect(recaptchaService.registeredElementCount_).to.be.equal(0);
return recaptchaService.register(fakeSitekey).then(() => {
expect(recaptchaService.registeredElementCount_).to.be.equal(1);
expect(recaptchaService.iframe_).to.be.ok;
return recaptchaService
.register(fakeSitekey)
.then(() => {
expect(recaptchaService.registeredElementCount_).to.be.equal(1);
expect(recaptchaService.iframe_).to.be.ok;
return recaptchaService.register(anotherFakeSitekey);
})
.catch((err) => {
expect(err).to.be.ok;
expect(err.message).to.have.string('sitekey');
expect(recaptchaService.registeredElementCount_).to.be.equal(1);
expect(recaptchaService.iframe_).to.be.ok;
});
}
);

return recaptchaService.register(anotherFakeSitekey).catch((err) => {
it(
'should not allow elements to register,' +
' if they do not all or none pass in the global attribute',
() => {
expect(recaptchaService.registeredElementCount_).to.be.equal(0);
return recaptchaService
.register(fakeSitekey, true)
.then(() => {
expect(recaptchaService.registeredElementCount_).to.be.equal(1);
expect(recaptchaService.iframe_).to.be.ok;
return recaptchaService.register(fakeSitekey, false);
})
.catch((err) => {
expect(err).to.be.ok;
expect(err.message).to.have.string('data-global');
expect(recaptchaService.registeredElementCount_).to.be.equal(1);
expect(recaptchaService.iframe_).to.be.ok;
});
});
}
);

Expand Down
4 changes: 4 additions & 0 deletions extensions/amp-recaptcha-input/amp-recaptcha-input.md
Expand Up @@ -78,6 +78,10 @@ grecaptcha.execute('reCAPTCHA_site_key', {action: 'reCAPTCHA_example_action'});
<td width="40%"><strong>data-action (required)</strong></td>
<td><a href="https://developers.google.com/recaptcha/docs/v3">reCAPTCHA v3</a> action to be executed on form submission.</td>
</tr>
<tr>
<td width="40%"><strong>data-global [optional]</strong></td>
<td>By default, the iframe loads the recaptcha api script using the <code>www.google.com</code> endpoint. There are some situations when this is not accessible. When the <code>data-global</code> attribute is included, the component will load the script from the <code>www.recaptcha.net</code> endpoint instead. More information can be found in the <a href="https://developers.google.com/recaptcha/docs/faq#can-i-use-recaptcha-globally">reCAPTCHA FAQ</a>.</td>
</tr>
</table>
## Validation
Expand Down
1 change: 1 addition & 0 deletions test/integration/test-amp-recaptcha-input.js
Expand Up @@ -136,6 +136,7 @@ describe
JSON.stringify({
'sitekey': '6LebBGoUAAAAAHbj1oeZMBU_rze_CutlbyzpH8VE',
'sentinel': 'amp-recaptcha',
'global': false,
})
);
});
Expand Down
8 changes: 8 additions & 0 deletions test/unit/3p/test-recaptcha.js
Expand Up @@ -35,6 +35,14 @@ describe('3p recaptcha.js', () => {
});
});

it('should require a global property in the window.name dataObject', () => {
allowConsoleError(() => {
window.name = '{"sitekey":"sitekey"}';
expect(initRecaptcha).to.throw('global');
window.name = undefined;
});
});

describe('doesOriginDomainMatchIframeSrc()', () => {
const getMockIframeWindowWithLocation = (url) => {
// NOTE: The thirdPartyUrl returned by the config
Expand Down

0 comments on commit cbefbbf

Please sign in to comment.