diff --git a/README.md b/README.md
index 3cdf0ac..9ce1eb6 100644
--- a/README.md
+++ b/README.md
@@ -9,14 +9,15 @@ A simple, configurable, easy-to-start component for handling reCAPTCHA v2 and v3
## Table of contents
1. [Installation](#installation)
-2. [Basic Usage](#example-basic)
+1. [Basic Usage](#example-basic)
+ * [reCAPTCHA v3 Usage](#example-basic-v3)
* [Playground](#playground)
-3. [Working with `@angular/forms`](#forms-ready)
-4. [API](#api)
+1. [Working with `@angular/forms`](#forms-ready)
+1. [API](#api)
* [Input Options](#api-options)
* [Events](#api-events)
* [Methods](#api-methods)
-5. [Examples](#examples)
+. [Examples](#examples)
* [Configuring the component globally](#example-global-config)
* [Specifying a different language](#example-language)
* [Loading the reCAPTCHA API by yourself](#example-preload-api)
@@ -137,6 +138,8 @@ export class RecaptchaV3DemoComponent {
As always with subscriptions, please don't forget to **unsubscribe**.
+❗️ **Important note**: If your site uses both v2 and v3, then you should _always_ provide `RECAPTCHA_V3_SITE_KEY` (even in modules that only rely on v2 functionality). This will prevent bugs in your code by allowing `ng-recaptcha` to follow reCAPTCHA development guidelines properly ([this one](https://developers.google.com/recaptcha/docs/faq#can-i-run-recaptcha-v2-and-v3-on-the-same-page) in particular).
+
A more advanced v3 usage scenario includes listening to all actions and their respectively emitted tokens. This is covered [later on this page](#example-v3-all-actions).
### Playground
diff --git a/index.ts b/index.ts
index 601ae4a..14e255d 100644
--- a/index.ts
+++ b/index.ts
@@ -1,20 +1,24 @@
export { RecaptchaComponent } from './recaptcha/recaptcha.component';
export {
RecaptchaLoaderService,
- RECAPTCHA_LANGUAGE,
- RECAPTCHA_BASE_URL,
- RECAPTCHA_NONCE,
} from './recaptcha/recaptcha-loader.service';
export { RecaptchaModule } from './recaptcha/recaptcha.module';
-export { RECAPTCHA_SETTINGS, RecaptchaSettings } from './recaptcha/recaptcha-settings';
+export { RecaptchaSettings } from './recaptcha/recaptcha-settings';
export { RecaptchaV3Module } from './recaptcha/recaptcha-v3.module';
export {
OnExecuteData,
OnExecuteErrorData,
ReCaptchaV3Service,
- RECAPTCHA_V3_SITE_KEY,
} from './recaptcha/recaptcha-v3.service';
export { RecaptchaFormsModule } from './recaptcha/recaptcha-forms.module';
export { RecaptchaValueAccessorDirective } from './recaptcha/recaptcha-value-accessor.directive';
+
+export {
+ RECAPTCHA_LANGUAGE,
+ RECAPTCHA_BASE_URL,
+ RECAPTCHA_NONCE,
+ RECAPTCHA_SETTINGS,
+ RECAPTCHA_V3_SITE_KEY,
+} from './recaptcha/tokens';
diff --git a/recaptcha/load-script.ts b/recaptcha/load-script.ts
new file mode 100644
index 0000000..f8c8bc3
--- /dev/null
+++ b/recaptcha/load-script.ts
@@ -0,0 +1,29 @@
+declare global {
+ interface Window {
+ ng2recaptchaloaded: () => void;
+ }
+}
+
+export function loadScript(
+ renderMode: 'explicit' | string,
+ onLoaded: (grecaptcha: ReCaptchaV2.ReCaptcha) => void,
+ urlParams: string,
+ url?: string,
+ nonce?: string,
+) {
+ window.ng2recaptchaloaded = () => {
+ onLoaded(grecaptcha);
+ };
+ const script = document.createElement('script');
+ script.innerHTML = '';
+ const baseUrl = url || 'https://www.google.com/recaptcha/api.js';
+
+ script.src = `${baseUrl}?render=${renderMode}&onload=ng2recaptchaloaded${urlParams}`;
+ if (nonce) {
+ // tslint:disable-next-line:no-any Remove "any" cast once we upgrade Angular to 7 and TypeScript along with it
+ (script as any).nonce = nonce;
+ }
+ script.async = true;
+ script.defer = true;
+ document.head.appendChild(script);
+}
diff --git a/recaptcha/recaptcha-loader.service.ts b/recaptcha/recaptcha-loader.service.ts
index af2c631..5920613 100644
--- a/recaptcha/recaptcha-loader.service.ts
+++ b/recaptcha/recaptcha-loader.service.ts
@@ -2,45 +2,18 @@ import { isPlatformBrowser } from '@angular/common';
import {
Inject,
Injectable,
- InjectionToken,
Optional,
PLATFORM_ID,
} from '@angular/core';
import { BehaviorSubject, Observable, of } from 'rxjs';
-export const RECAPTCHA_LANGUAGE = new InjectionToken('recaptcha-language');
-export const RECAPTCHA_BASE_URL = new InjectionToken('recaptcha-base-url');
-export const RECAPTCHA_NONCE = new InjectionToken('recaptcha-nonce-tag');
-
-declare global {
- interface Window {
- ng2recaptchaloaded: () => void;
- }
-}
-
-export function loadScript(
- renderMode: 'explicit' | string,
- onLoaded: (grecaptcha: ReCaptchaV2.ReCaptcha) => void,
- urlParams: string,
- url?: string,
- nonce?: string,
-) {
- window.ng2recaptchaloaded = () => {
- onLoaded(grecaptcha);
- };
- const script = document.createElement('script');
- script.innerHTML = '';
- const baseUrl = url || 'https://www.google.com/recaptcha/api.js';
-
- script.src = `${baseUrl}?render=${renderMode}&onload=ng2recaptchaloaded${urlParams}`;
- if (nonce) {
- // tslint:disable-next-line:no-any Remove "any" cast once we upgrade Angular to 7 and TypeScript along with it
- (script as any).nonce = nonce;
- }
- script.async = true;
- script.defer = true;
- document.head.appendChild(script);
-}
+import { loadScript } from './load-script';
+import {
+ RECAPTCHA_BASE_URL,
+ RECAPTCHA_LANGUAGE,
+ RECAPTCHA_NONCE,
+ RECAPTCHA_V3_SITE_KEY,
+} from './tokens';
@Injectable()
export class RecaptchaLoaderService {
@@ -58,6 +31,8 @@ export class RecaptchaLoaderService {
private baseUrl: string;
/** @internal */
private nonce: string;
+ /** @internal */
+ private v3SiteKey: string;
constructor(
// tslint:disable-next-line:no-any
@@ -65,10 +40,12 @@ export class RecaptchaLoaderService {
@Optional() @Inject(RECAPTCHA_LANGUAGE) language?: string,
@Optional() @Inject(RECAPTCHA_BASE_URL) baseUrl?: string,
@Optional() @Inject(RECAPTCHA_NONCE) nonce?: string,
+ @Optional() @Inject(RECAPTCHA_V3_SITE_KEY) v3SiteKey?: string,
) {
this.language = language;
this.baseUrl = baseUrl;
this.nonce = nonce;
+ this.v3SiteKey = v3SiteKey;
this.init();
this.ready = isPlatformBrowser(this.platformId) ? RecaptchaLoaderService.ready.asObservable() : of();
}
@@ -82,7 +59,9 @@ export class RecaptchaLoaderService {
const subject = new BehaviorSubject(null);
RecaptchaLoaderService.ready = subject;
const langParam = this.language ? '&hl=' + this.language : '';
- loadScript('explicit', (grecaptcha) => subject.next(grecaptcha), langParam, this.baseUrl, this.nonce);
+
+ const renderMode = this.v3SiteKey || 'explicit';
+ loadScript(renderMode, (grecaptcha) => subject.next(grecaptcha), langParam, this.baseUrl, this.nonce);
}
}
}
diff --git a/recaptcha/recaptcha-settings.ts b/recaptcha/recaptcha-settings.ts
index b87e592..d4286d4 100644
--- a/recaptcha/recaptcha-settings.ts
+++ b/recaptcha/recaptcha-settings.ts
@@ -1,7 +1,3 @@
-import { InjectionToken } from '@angular/core';
-
-export const RECAPTCHA_SETTINGS = new InjectionToken('recaptcha-settings');
-
export interface RecaptchaSettings {
siteKey?: string;
theme?: ReCaptchaV2.Theme;
diff --git a/recaptcha/recaptcha-v3.service.ts b/recaptcha/recaptcha-v3.service.ts
index 209ae73..b16fb8a 100644
--- a/recaptcha/recaptcha-v3.service.ts
+++ b/recaptcha/recaptcha-v3.service.ts
@@ -1,10 +1,9 @@
import { isPlatformBrowser } from '@angular/common';
-import { Inject, Injectable, InjectionToken, NgZone, Optional, PLATFORM_ID } from '@angular/core';
+import { Inject, Injectable, NgZone, Optional, PLATFORM_ID } from '@angular/core';
import { Observable, Subject } from 'rxjs';
-import { loadScript, RECAPTCHA_BASE_URL, RECAPTCHA_LANGUAGE, RECAPTCHA_NONCE } from './recaptcha-loader.service';
-
-export const RECAPTCHA_V3_SITE_KEY = new InjectionToken('recaptcha-v3-site-key');
+import { loadScript } from './load-script';
+import { RECAPTCHA_BASE_URL, RECAPTCHA_LANGUAGE, RECAPTCHA_NONCE, RECAPTCHA_V3_SITE_KEY } from './tokens';
export interface OnExecuteData {
/**
diff --git a/recaptcha/recaptcha.component.ts b/recaptcha/recaptcha.component.ts
index b7666fb..56330d1 100644
--- a/recaptcha/recaptcha.component.ts
+++ b/recaptcha/recaptcha.component.ts
@@ -14,7 +14,8 @@ import {
import { Subscription } from 'rxjs';
import { RecaptchaLoaderService } from './recaptcha-loader.service';
-import { RECAPTCHA_SETTINGS, RecaptchaSettings } from './recaptcha-settings';
+import { RecaptchaSettings } from './recaptcha-settings';
+import { RECAPTCHA_SETTINGS } from './tokens';
let nextId = 0;
diff --git a/recaptcha/tokens.ts b/recaptcha/tokens.ts
new file mode 100644
index 0000000..219c67f
--- /dev/null
+++ b/recaptcha/tokens.ts
@@ -0,0 +1,11 @@
+import {
+ InjectionToken,
+} from '@angular/core';
+
+import { RecaptchaSettings } from './recaptcha-settings';
+
+export const RECAPTCHA_LANGUAGE = new InjectionToken('recaptcha-language');
+export const RECAPTCHA_BASE_URL = new InjectionToken('recaptcha-base-url');
+export const RECAPTCHA_NONCE = new InjectionToken('recaptcha-nonce-tag');
+export const RECAPTCHA_SETTINGS = new InjectionToken('recaptcha-settings');
+export const RECAPTCHA_V3_SITE_KEY = new InjectionToken('recaptcha-v3-site-key');