@adobe-commerce/recaptcha 1.0.0-alpha1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +5 -0
- package/README.md +55 -0
- package/package.json +21 -0
- package/src/configs/index.ts +3 -0
- package/src/configs/message.config.ts +7 -0
- package/src/configs/recaptchaBadgeSelector.config.ts +1 -0
- package/src/configs/typeForms.config.ts +12 -0
- package/src/graphql/recaptchaConfig.graphql.ts +12 -0
- package/src/index.ts +222 -0
- package/src/lib/_checkRecaptchaBadge.ts +38 -0
- package/src/lib/_convertKeysToCamelCase.ts +13 -0
- package/src/lib/_extendConfig.ts +20 -0
- package/src/lib/_storageConfig.ts +37 -0
- package/src/lib/index.ts +4 -0
- package/src/services/recaptcha.service.ts +80 -0
- package/src/types/grecaptcha.d.ts +24 -0
- package/src/types/recaptcha.types.ts +50 -0
package/.eslintrc.js
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# ReCaptcha Module
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
This functionality is designed to prevent CAPTCHAs. It provides methods for detecting and bypassing CAPTCHAs, improving user experience and automating interactions with web services.
|
|
6
|
+
`
|
|
7
|
+
|
|
8
|
+
## Methods
|
|
9
|
+
|
|
10
|
+
### Private Methods
|
|
11
|
+
|
|
12
|
+
Private methods are used within the class and are not accessible externally.
|
|
13
|
+
|
|
14
|
+
- `_updateBadgePosition(currentForm, config);`
|
|
15
|
+
- Responsible for changing the widget's position if it needs to be placed inline.
|
|
16
|
+
- `_addRecaptchaScript();`
|
|
17
|
+
- Adds a script to the page.
|
|
18
|
+
- `_fetchStoreConfig();`
|
|
19
|
+
- Requests configuration from the backend.
|
|
20
|
+
- `_loadConfig();`
|
|
21
|
+
- Responsible for loading the config from Session Storage.
|
|
22
|
+
|
|
23
|
+
### Public Methods
|
|
24
|
+
|
|
25
|
+
`import {setEndpoint, setConfig, initReCaptcha, verifyReCaptcha } from "@adobe/recaptcha"`
|
|
26
|
+
|
|
27
|
+
Public methods are available for use when interacting with the functionality.
|
|
28
|
+
|
|
29
|
+
- `setEndpoint(url : string);`
|
|
30
|
+
- It sets the URL from which the reCAPTCHA settings will be fetched.
|
|
31
|
+
- `setConfig(configList : [{ badgeId: 'badgeId'}]);`
|
|
32
|
+
- Initializes the configuration, accepting a URL and a set of parameters. The set of parameters is necessary for customizing form settings. Init on top lvl application.
|
|
33
|
+
- `initReCaptcha();`
|
|
34
|
+
- Initializes reCAPTCHA and adds a script to the website.
|
|
35
|
+
- `verifyReCaptcha();`
|
|
36
|
+
- If the method is present, it returns a token.
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
To install this functionality, follow these steps:
|
|
41
|
+
|
|
42
|
+
1. npm i: `@adobe/recaptcha`
|
|
43
|
+
|
|
44
|
+
2. [ setEndpoint ] - Use this function at the top level to pass the backend URL.
|
|
45
|
+
|
|
46
|
+
3. [ setConfig ] - Also use this function at the top level to pass your custom configurations if you plan to use your custom form.
|
|
47
|
+
|
|
48
|
+
4. [ initReCaptcha ] - Call the function on the page where Dropins is integrated, or immediately after setEndpoint or setConfig. Adds a script to the website.
|
|
49
|
+
|
|
50
|
+
5. [ verifyReCaptcha ] This function serves as an example in either the API method or your form submission handler. It returns a token, which can then be initialized in the headers upon receipt.
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
## Summary
|
|
54
|
+
|
|
55
|
+
This functionality provides methods for preventing and solving CAPTCHAs, enhancing automation and interaction with websites. Using it will help simplify processes related to CAPTCHA.
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@adobe-commerce/recaptcha",
|
|
3
|
+
"version": "1.0.0-alpha1",
|
|
4
|
+
"license": "SEE LICENSE IN LICENSE.md",
|
|
5
|
+
"description": "Module allows to efficiently verify that users are humans, not bots or spammers",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=16"
|
|
8
|
+
},
|
|
9
|
+
"main": "src/index.ts",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"lint": "eslint",
|
|
12
|
+
"test": "jest",
|
|
13
|
+
"test:ci": "jest --config jest.config.js --passWithNoTests --coverage",
|
|
14
|
+
"build": " "
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@adobe-commerce/fetch-graphql": "~1.0.0",
|
|
18
|
+
"@adobe/recaptcha": "file:../../packages/recaptcha"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {}
|
|
21
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const recaptchaMessage = {
|
|
2
|
+
failedFetch: 'Failed to fetch config from backend with status:',
|
|
3
|
+
failedSetStorageConfig: 'Failed to set storage config',
|
|
4
|
+
failedGetStorageConfig: 'Configuration could not be loaded.',
|
|
5
|
+
failedExecutionRecaptcha: 'Recaptcha execution failed',
|
|
6
|
+
failedInitializing: 'An error occurred while initializing ReCaptcha:',
|
|
7
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const recaptchaBadgeSelector = '.grecaptcha-badge iframe';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const typeDefaultForm: Record<string, string> = {
|
|
2
|
+
PLACE_ORDER: 'placeOrder',
|
|
3
|
+
CONTACT: 'contactUs',
|
|
4
|
+
CUSTOMER_LOGIN: 'generateCustomerToken',
|
|
5
|
+
CUSTOMER_FORGOT_PASSWORD: 'requestPasswordResetEmail',
|
|
6
|
+
CUSTOMER_CREATE: 'createCustomerV2',
|
|
7
|
+
CUSTOMER_EDIT: 'updateCustomerV2',
|
|
8
|
+
NEWSLETTER: 'subscribeEmailToNewsletter',
|
|
9
|
+
PRODUCT_REVIEW: 'createProductReview',
|
|
10
|
+
SENDFRIEND: 'SENDFRIEND',
|
|
11
|
+
BRAINTREE: 'BRAINTREE',
|
|
12
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ReCaptchaV3Response,
|
|
3
|
+
PropsFormTypes,
|
|
4
|
+
ReCaptchaV3Model,
|
|
5
|
+
} from './types/recaptcha.types';
|
|
6
|
+
import { recaptchaMessage, recaptchaBadgeSelector } from './configs';
|
|
7
|
+
import {
|
|
8
|
+
extendConfig,
|
|
9
|
+
setConfigStorage,
|
|
10
|
+
getConfigStorage,
|
|
11
|
+
checkRecaptchaBadge,
|
|
12
|
+
convertKeysToCamelCase,
|
|
13
|
+
} from './lib';
|
|
14
|
+
import {
|
|
15
|
+
getRecaptchaToken,
|
|
16
|
+
verifyReCaptchaLoad,
|
|
17
|
+
} from './services/recaptcha.service';
|
|
18
|
+
import { RECAPTCHA_CONFIGURATION_V3 } from './graphql/recaptchaConfig.graphql';
|
|
19
|
+
|
|
20
|
+
import { FetchGraphQL } from '@adobe/fetch-graphql';
|
|
21
|
+
|
|
22
|
+
export const recaptchaFetchApi = new FetchGraphQL().getMethods();
|
|
23
|
+
|
|
24
|
+
export class RecaptchaModule {
|
|
25
|
+
_enableReCAPTCHA: boolean = false;
|
|
26
|
+
_recaptchaBackendEndpoint: string =
|
|
27
|
+
recaptchaFetchApi.getConfig()?.endpoint || '';
|
|
28
|
+
_recaptchaScriptUrl: string = 'https://www.google.com/recaptcha/api.js';
|
|
29
|
+
_configStorageKey: string = 'recaptchaConfig';
|
|
30
|
+
_logger: boolean = false;
|
|
31
|
+
|
|
32
|
+
async _updateBadgePosition(
|
|
33
|
+
badgeId: string,
|
|
34
|
+
config: ReCaptchaV3Model
|
|
35
|
+
): Promise<void | null> {
|
|
36
|
+
if (!config) return;
|
|
37
|
+
|
|
38
|
+
if (config?.badgePosition === 'inline') {
|
|
39
|
+
await verifyReCaptchaLoad(badgeId, config, this._logger);
|
|
40
|
+
} else {
|
|
41
|
+
const isBadgeLoaded = await checkRecaptchaBadge();
|
|
42
|
+
|
|
43
|
+
if (!isBadgeLoaded) return;
|
|
44
|
+
|
|
45
|
+
const recaptchaBadge = document.querySelector(
|
|
46
|
+
recaptchaBadgeSelector
|
|
47
|
+
) as HTMLIFrameElement;
|
|
48
|
+
|
|
49
|
+
const shouldUpdateSrc =
|
|
50
|
+
config.theme &&
|
|
51
|
+
recaptchaBadge &&
|
|
52
|
+
!recaptchaBadge.src.includes('theme=dark') &&
|
|
53
|
+
!recaptchaBadge.src.includes('theme=light');
|
|
54
|
+
|
|
55
|
+
if (shouldUpdateSrc) {
|
|
56
|
+
recaptchaBadge.setAttribute(
|
|
57
|
+
'src',
|
|
58
|
+
`${recaptchaBadge.src}&theme=${config.theme}`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async _addRecaptchaScript(): Promise<void> {
|
|
65
|
+
const config = await this._loadConfig();
|
|
66
|
+
|
|
67
|
+
if (!document.getElementById('recaptchaId') && config) {
|
|
68
|
+
const webApiKey = config.websiteKey;
|
|
69
|
+
const isBadgeGlobal = config.badgePosition === 'inline';
|
|
70
|
+
const languageCode = config.languageCode;
|
|
71
|
+
|
|
72
|
+
if (!webApiKey) return;
|
|
73
|
+
|
|
74
|
+
const script = document.createElement('script');
|
|
75
|
+
script.setAttribute('id', 'recaptchaId');
|
|
76
|
+
script.defer = true;
|
|
77
|
+
script.src = isBadgeGlobal
|
|
78
|
+
? `${this._recaptchaScriptUrl}?render=${webApiKey}&badge=none&hl=${languageCode}`
|
|
79
|
+
: `${this._recaptchaScriptUrl}?render=${webApiKey}&badge=${config.badgePosition}&hl=${languageCode}`;
|
|
80
|
+
|
|
81
|
+
document.head.appendChild(script);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async _fetchStoreConfig(): Promise<ReCaptchaV3Response | undefined> {
|
|
86
|
+
try {
|
|
87
|
+
const response = await recaptchaFetchApi.fetchGraphQl(
|
|
88
|
+
RECAPTCHA_CONFIGURATION_V3,
|
|
89
|
+
{
|
|
90
|
+
method: 'GET',
|
|
91
|
+
cache: 'force-cache',
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
if (response?.errors?.length) {
|
|
96
|
+
this._logger && console.error(response.errors[0].message);
|
|
97
|
+
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return response;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
this._logger && console.error(`${recaptchaMessage.failedFetch}:`, error);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async _loadConfig(): Promise<ReCaptchaV3Model | null> {
|
|
108
|
+
const config = await getConfigStorage(this._configStorageKey);
|
|
109
|
+
|
|
110
|
+
if (!config) {
|
|
111
|
+
this._logger && console.error(recaptchaMessage.failedGetStorageConfig);
|
|
112
|
+
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
this._enableReCAPTCHA = !!config.isEnabled;
|
|
117
|
+
|
|
118
|
+
return config;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
setEndpoint(url: string) {
|
|
122
|
+
if (!url) return;
|
|
123
|
+
|
|
124
|
+
this._recaptchaBackendEndpoint = url;
|
|
125
|
+
recaptchaFetchApi.setEndpoint(url);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async setConfig(configList: PropsFormTypes[]) {
|
|
129
|
+
try {
|
|
130
|
+
const config = await this._fetchStoreConfig();
|
|
131
|
+
|
|
132
|
+
if (!config?.data?.recaptchaV3Config) {
|
|
133
|
+
sessionStorage.removeItem(this._configStorageKey);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const transformConfig: ReCaptchaV3Model = convertKeysToCamelCase(
|
|
138
|
+
config?.data?.recaptchaV3Config
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const extendedRecaptchaConfig = extendConfig(transformConfig, configList);
|
|
142
|
+
|
|
143
|
+
if (extendedRecaptchaConfig) {
|
|
144
|
+
setConfigStorage(
|
|
145
|
+
this._configStorageKey,
|
|
146
|
+
extendedRecaptchaConfig,
|
|
147
|
+
this._logger
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
} catch (error) {
|
|
151
|
+
this._logger &&
|
|
152
|
+
console.error(recaptchaMessage.failedSetStorageConfig, error);
|
|
153
|
+
|
|
154
|
+
sessionStorage.removeItem(this._configStorageKey);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async initReCaptcha(lazyLoadTimeout = 3000) {
|
|
159
|
+
// IIFE added to fix SonarQube error "Promise returned in function argument where a void return was expected"
|
|
160
|
+
setTimeout(() => {
|
|
161
|
+
(async () => {
|
|
162
|
+
try {
|
|
163
|
+
const config = await this._loadConfig();
|
|
164
|
+
|
|
165
|
+
if (!config?.forms || !config.isEnabled) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
await this._addRecaptchaScript();
|
|
170
|
+
|
|
171
|
+
if (config.badgePosition === 'inline') {
|
|
172
|
+
await Promise.all(
|
|
173
|
+
(config.forms as PropsFormTypes[]).map((element) =>
|
|
174
|
+
this._updateBadgePosition(element.badgeId, config)
|
|
175
|
+
)
|
|
176
|
+
);
|
|
177
|
+
} else {
|
|
178
|
+
await this._updateBadgePosition('', config);
|
|
179
|
+
}
|
|
180
|
+
} catch (error) {
|
|
181
|
+
this._logger &&
|
|
182
|
+
console.error(recaptchaMessage.failedInitializing, error);
|
|
183
|
+
}
|
|
184
|
+
})();
|
|
185
|
+
}, lazyLoadTimeout);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async verifyReCaptcha(): Promise<string | undefined> {
|
|
189
|
+
try {
|
|
190
|
+
const config = await this._loadConfig();
|
|
191
|
+
|
|
192
|
+
if (!config?.forms || !config.websiteKey || !config.isEnabled) {
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return await getRecaptchaToken(config.websiteKey);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
this._logger && console.error(error);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
enableLogger(logger: boolean) {
|
|
203
|
+
this._logger = logger;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
getMethods() {
|
|
207
|
+
return {
|
|
208
|
+
enableLogger: this.enableLogger.bind(this),
|
|
209
|
+
setEndpoint: this.setEndpoint.bind(this),
|
|
210
|
+
setConfig: this.setConfig.bind(this),
|
|
211
|
+
initReCaptcha: this.initReCaptcha.bind(this),
|
|
212
|
+
verifyReCaptcha: this.verifyReCaptcha.bind(this),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const recaptcha = new RecaptchaModule();
|
|
218
|
+
|
|
219
|
+
const { initReCaptcha, verifyReCaptcha, setEndpoint, setConfig, enableLogger } =
|
|
220
|
+
recaptcha.getMethods();
|
|
221
|
+
|
|
222
|
+
export { setEndpoint, setConfig, initReCaptcha, verifyReCaptcha, enableLogger };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { recaptchaBadgeSelector } from '../configs';
|
|
2
|
+
|
|
3
|
+
const waitForElement = (selector: string): Promise<void> => {
|
|
4
|
+
return new Promise((resolve, reject) => {
|
|
5
|
+
try {
|
|
6
|
+
// Check if the element is already in the DOM
|
|
7
|
+
if (document.querySelector(selector)) {
|
|
8
|
+
resolve();
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Create an observer to watch for changes
|
|
13
|
+
const observer = new MutationObserver(() => {
|
|
14
|
+
if (document.querySelector(selector)) {
|
|
15
|
+
resolve();
|
|
16
|
+
observer.disconnect();
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// Start observing the body for child changes only
|
|
21
|
+
observer.observe(document.body, {
|
|
22
|
+
childList: true,
|
|
23
|
+
subtree: false,
|
|
24
|
+
});
|
|
25
|
+
} catch (error) {
|
|
26
|
+
reject(error);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export const checkRecaptchaBadge = async (): Promise<boolean> => {
|
|
32
|
+
try {
|
|
33
|
+
await waitForElement(recaptchaBadgeSelector);
|
|
34
|
+
return true;
|
|
35
|
+
} catch (error) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const convertKeysToCamelCase = (obj: {
|
|
2
|
+
[key: string]: any;
|
|
3
|
+
}): { [key: string]: string | number | boolean } => {
|
|
4
|
+
const camelCaseKey = (key: string): string => {
|
|
5
|
+
return key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
return Object.keys(obj).reduce((result, key) => {
|
|
9
|
+
const newKey = camelCaseKey(key);
|
|
10
|
+
result[newKey] = obj[key];
|
|
11
|
+
return result;
|
|
12
|
+
}, {} as { [key: string]: any });
|
|
13
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ReCaptchaV3Model } from '../types/recaptcha.types';
|
|
2
|
+
import { typeDefaultForm } from '../configs/typeForms.config';
|
|
3
|
+
|
|
4
|
+
export const extendConfig = (
|
|
5
|
+
config: ReCaptchaV3Model,
|
|
6
|
+
modifyParams: any[]
|
|
7
|
+
): ReCaptchaV3Model | undefined => {
|
|
8
|
+
if (config && config.forms) {
|
|
9
|
+
const modifyForm = config.forms.concat(modifyParams).map((el) => {
|
|
10
|
+
if (typeof el !== 'string') return { ...el, enabledBadgePlace: false };
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
badgeId: typeDefaultForm[el],
|
|
14
|
+
enabledBadgePlace: false,
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
return { ...config, forms: [...new Set(modifyForm)] };
|
|
19
|
+
}
|
|
20
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { recaptchaMessage } from '../configs';
|
|
2
|
+
import { ReCaptchaV3Model } from '../types/recaptcha.types';
|
|
3
|
+
|
|
4
|
+
const getConfigStorage = async (
|
|
5
|
+
storageKey: string,
|
|
6
|
+
retries = 1,
|
|
7
|
+
delay = 1000
|
|
8
|
+
): Promise<ReCaptchaV3Model | null> => {
|
|
9
|
+
const storedConfig = sessionStorage.getItem(storageKey);
|
|
10
|
+
|
|
11
|
+
if (storedConfig !== null) {
|
|
12
|
+
return JSON.parse(storedConfig);
|
|
13
|
+
} else if (retries > 0) {
|
|
14
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
15
|
+
|
|
16
|
+
return getConfigStorage(storageKey, retries - 1, delay);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return null;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const setConfigStorage = (
|
|
23
|
+
storageKey: string,
|
|
24
|
+
config: ReCaptchaV3Model,
|
|
25
|
+
logger: boolean
|
|
26
|
+
) => {
|
|
27
|
+
if (!storageKey || !config.websiteKey) return null;
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
sessionStorage.setItem(storageKey, JSON.stringify(config));
|
|
31
|
+
} catch (error) {
|
|
32
|
+
logger && console.error(recaptchaMessage.failedSetStorageConfig, error);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export { getConfigStorage, setConfigStorage };
|
package/src/lib/index.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { recaptchaMessage } from '../configs';
|
|
2
|
+
import {
|
|
3
|
+
MutationObserverInit,
|
|
4
|
+
ReCaptchaV3Model,
|
|
5
|
+
} from '../types/recaptcha.types';
|
|
6
|
+
const { failedExecutionRecaptcha } = recaptchaMessage;
|
|
7
|
+
|
|
8
|
+
export const getRecaptchaToken = async (
|
|
9
|
+
websiteKey: string
|
|
10
|
+
): Promise<string> => {
|
|
11
|
+
if (!(window as any).grecaptcha) {
|
|
12
|
+
return Promise.reject(failedExecutionRecaptcha);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const token = await window.grecaptcha.execute(websiteKey, {
|
|
17
|
+
action: 'click',
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
return token;
|
|
21
|
+
} catch (error) {
|
|
22
|
+
return Promise.reject(`${failedExecutionRecaptcha} : ${error}`);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const waitForReCaptcha = () => {
|
|
27
|
+
return new Promise((resolve) => {
|
|
28
|
+
const observer = new MutationObserver((_, obs) => {
|
|
29
|
+
if (window.grecaptcha) {
|
|
30
|
+
obs.disconnect();
|
|
31
|
+
resolve(true);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const observerOptions: MutationObserverInit = {
|
|
36
|
+
childList: true,
|
|
37
|
+
subtree: true,
|
|
38
|
+
attributes: true,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
observer.observe(document.body, observerOptions);
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const verifyReCaptchaLoad = async (
|
|
46
|
+
badgeId: string,
|
|
47
|
+
config: ReCaptchaV3Model,
|
|
48
|
+
logger: boolean
|
|
49
|
+
): Promise<void> => {
|
|
50
|
+
if (!window.grecaptcha) {
|
|
51
|
+
await waitForReCaptcha();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return grecaptcha.ready(() => {
|
|
55
|
+
const badgeContainers = document.querySelectorAll(`#${badgeId}`);
|
|
56
|
+
|
|
57
|
+
if (!badgeContainers.length) return;
|
|
58
|
+
|
|
59
|
+
// Handle the case when multiple instances of the drop-in container rendered on the same page
|
|
60
|
+
|
|
61
|
+
badgeContainers.forEach(
|
|
62
|
+
(element) => (element.id = `${element.id}_${Math.random().toString(36)}`) // NOSONAR
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
badgeContainers.forEach((element) => {
|
|
66
|
+
if (element.innerHTML === '') {
|
|
67
|
+
try {
|
|
68
|
+
grecaptcha.render(element.id, {
|
|
69
|
+
sitekey: config.websiteKey as string,
|
|
70
|
+
badge: config.badgePosition,
|
|
71
|
+
size: 'invisible',
|
|
72
|
+
theme: config.theme ?? 'light',
|
|
73
|
+
});
|
|
74
|
+
} catch (error) {
|
|
75
|
+
logger && console.error(error);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
declare namespace grecaptcha {
|
|
2
|
+
interface RenderParameters {
|
|
3
|
+
sitekey: string;
|
|
4
|
+
theme?: string;
|
|
5
|
+
size?: string;
|
|
6
|
+
tabindex?: number;
|
|
7
|
+
callback?: (response: string) => void;
|
|
8
|
+
'expired-callback'?: () => void;
|
|
9
|
+
'error-callback'?: () => void;
|
|
10
|
+
badge?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function ready(callback: () => void): void;
|
|
14
|
+
function execute(
|
|
15
|
+
siteKey: string,
|
|
16
|
+
options: { action: string }
|
|
17
|
+
): Promise<string>;
|
|
18
|
+
function render(
|
|
19
|
+
container: string | HTMLElement,
|
|
20
|
+
parameters: RenderParameters
|
|
21
|
+
): string;
|
|
22
|
+
function reset(opt_widget_id?: string): void;
|
|
23
|
+
function getResponse(opt_widget_id?: string): string;
|
|
24
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export interface ReCaptchaV3InitProps {
|
|
2
|
+
is_enabled?: boolean;
|
|
3
|
+
website_key?: string;
|
|
4
|
+
minimum_score?: number;
|
|
5
|
+
badge_position?: string;
|
|
6
|
+
language_code?: string;
|
|
7
|
+
failure_message?: string;
|
|
8
|
+
theme: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ReCaptchaV3Props extends ReCaptchaV3InitProps {
|
|
12
|
+
forms?: string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface PropsFormTypes {
|
|
16
|
+
badgeId: string;
|
|
17
|
+
enabledBadgePlace?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ReCaptchaV3ModifyProps extends ReCaptchaV3InitProps {
|
|
21
|
+
forms?: PropsFormTypes[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ReCaptchaV3Response {
|
|
25
|
+
data?: {
|
|
26
|
+
recaptchaV3Config?: ReCaptchaV3Props | ReCaptchaV3ModifyProps;
|
|
27
|
+
};
|
|
28
|
+
errors?: { message: string }[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ReCaptchaV3Model {
|
|
32
|
+
isEnabled?: boolean;
|
|
33
|
+
websiteKey?: string;
|
|
34
|
+
minimumScore?: number;
|
|
35
|
+
badgePosition?: string;
|
|
36
|
+
languageCode?: string;
|
|
37
|
+
failureMessage?: string;
|
|
38
|
+
theme?: string;
|
|
39
|
+
forms?: PropsFormTypes[] | string[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface MutationObserverInit {
|
|
43
|
+
childList?: boolean;
|
|
44
|
+
attributes?: boolean;
|
|
45
|
+
characterData?: boolean;
|
|
46
|
+
subtree?: boolean;
|
|
47
|
+
attributeOldValue?: boolean;
|
|
48
|
+
characterDataOldValue?: boolean;
|
|
49
|
+
attributeFilter?: string[];
|
|
50
|
+
}
|