@matt-block/react-recaptcha-v2
Advanced tools
Comparing version 1.0.9 to 2.0.0-rc.0
@@ -1,2 +0,2 @@ | ||
import React, { Component } from 'react'; | ||
import React, { useState, useEffect } from 'react'; | ||
@@ -17,15 +17,13 @@ /*! ***************************************************************************** | ||
***************************************************************************** */ | ||
/* global Reflect, Promise */ | ||
var extendStatics = function(d, b) { | ||
extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; | ||
return extendStatics(d, b); | ||
}; | ||
function __extends(d, b) { | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
function __rest(s, e) { | ||
var t = {}; | ||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) | ||
t[p] = s[p]; | ||
if (s != null && typeof Object.getOwnPropertySymbols === "function") | ||
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { | ||
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) | ||
t[p[i]] = s[p[i]]; | ||
} | ||
return t; | ||
} | ||
@@ -44,98 +42,121 @@ | ||
var ReCaptcha = (function (_super) { | ||
__extends(ReCaptcha, _super); | ||
function ReCaptcha() { | ||
var _this = _super !== null && _super.apply(this, arguments) || this; | ||
_this.scriptSrc = "https://www.google.com/recaptcha/api.js"; | ||
_this.testSiteKey = "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI"; | ||
_this.observer = new MutationObserver(_this.mutationCallbackGenerator()); | ||
_this.hiddenDiv = document.createElement("div"); | ||
_this.id = nanoid(); | ||
_this.successCallbackId = nanoid(); | ||
_this.expiredCallbackId = nanoid(); | ||
_this.errorCallbackId = nanoid(); | ||
return _this; | ||
const MAIN_SCRIPT_ID = "recaptcha"; | ||
const MAIN_SCRIPT_SRC = "https://www.google.com/recaptcha/api.js"; | ||
const IMPLICIT_SCRIPT_SRC_PATTERN = /https:\/\/www.gstatic.com\/recaptcha\/releases\/.*.js$/; | ||
const TEST_SITE_KEY = "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI"; | ||
const getMainScriptElement = () => { | ||
if (document.getElementById(MAIN_SCRIPT_ID) !== null) { | ||
return document.getElementById(MAIN_SCRIPT_ID); | ||
} | ||
ReCaptcha.prototype.componentDidMount = function () { | ||
this.observer.observe(document.body, { childList: true }); | ||
this.appendScript(); | ||
window[this.successCallbackId] = this.props.onSuccess; | ||
window[this.expiredCallbackId] = this.props.onExpire; | ||
window[this.errorCallbackId] = this.props.onError; | ||
}; | ||
ReCaptcha.prototype.componentWillUnmount = function () { | ||
this.cleanup(); | ||
}; | ||
ReCaptcha.prototype.appendScript = function () { | ||
if (!this.getScriptIfAvailable()) { | ||
var reCaptchaScript = this.createScriptElement(); | ||
document.body.appendChild(reCaptchaScript); | ||
} | ||
}; | ||
ReCaptcha.prototype.getScriptIfAvailable = function () { | ||
var _this = this; | ||
if (document.getElementById("recaptcha") !== null) { | ||
return document.getElementById("recaptcha"); | ||
} | ||
var availableScripts = Array.from(document.scripts); | ||
return availableScripts.find(function (script) { return script.src === _this.scriptSrc; }); | ||
}; | ||
ReCaptcha.prototype.createScriptElement = function () { | ||
var reCaptchaScript = document.createElement("script"); | ||
reCaptchaScript.id = "recaptcha"; | ||
reCaptchaScript.src = this.scriptSrc; | ||
reCaptchaScript.async = true; | ||
reCaptchaScript.defer = true; | ||
return reCaptchaScript; | ||
}; | ||
ReCaptcha.prototype.cleanup = function () { | ||
var script = this.getScriptIfAvailable(); | ||
if (script) { | ||
this.removeChild(script); | ||
} | ||
delete window[this.successCallbackId]; | ||
delete window[this.expiredCallbackId]; | ||
delete window[this.errorCallbackId]; | ||
this.removeChild(this.hiddenDiv); | ||
var allScripts = Array.from(document.scripts); | ||
var reCaptchaSrcPattern = /https:\/\/www.gstatic.com\/recaptcha\/releases\/.*.js$/; | ||
var additionalScripts = allScripts.filter(function (script) { | ||
return reCaptchaSrcPattern.test(script.src); | ||
const availableScripts = Array.from(document.scripts); | ||
return availableScripts.find((script) => script.src === MAIN_SCRIPT_SRC); | ||
}; | ||
const createMainScriptElement = () => { | ||
const scriptElement = document.createElement("script"); | ||
scriptElement.id = MAIN_SCRIPT_ID; | ||
scriptElement.src = MAIN_SCRIPT_SRC; | ||
scriptElement.async = true; | ||
scriptElement.defer = true; | ||
return scriptElement; | ||
}; | ||
const appendScript = () => { | ||
if (!getMainScriptElement()) { | ||
const reCaptchaScript = createMainScriptElement(); | ||
document.body.appendChild(reCaptchaScript); | ||
} | ||
}; | ||
const removeChildElement = (element) => { | ||
const parentNode = element.parentNode; | ||
if (parentNode !== null) { | ||
parentNode.removeChild(element); | ||
} | ||
}; | ||
const removeImplicitRecaptchaScripts = () => { | ||
const allScripts = Array.from(document.scripts); | ||
const additionalScripts = allScripts.filter((script) => IMPLICIT_SCRIPT_SRC_PATTERN.test(script.src)); | ||
additionalScripts.map(removeChildElement); | ||
}; | ||
const isNodeRecaptchaHiddenDiv = (node) => { | ||
const div = node; | ||
return (div.style && | ||
div.style.visibility === "hidden" && | ||
div.style.top === "-10000px" && | ||
div.style.position === "absolute"); | ||
}; | ||
const mutationCallbackGenerator = (onHiddenDivFound) => { | ||
return (mutations) => { | ||
mutations.forEach((mutation) => { | ||
if (mutation.type === "childList" && | ||
mutation.target === document.body && | ||
mutation.addedNodes.length === 1 && | ||
isNodeRecaptchaHiddenDiv(mutation.addedNodes[0])) { | ||
onHiddenDivFound(mutation.addedNodes[0]); | ||
} | ||
}); | ||
additionalScripts.map(this.removeChild); | ||
}; | ||
ReCaptcha.prototype.removeChild = function (element) { | ||
var parentNode = element.parentNode; | ||
if (parentNode !== null) { | ||
parentNode.removeChild(element); | ||
} | ||
}; | ||
const useWindowCallbackBinder = (callbacks) => { | ||
const [onSuccessCallbackId] = useState(nanoid()); | ||
const [onErrorCallbackId] = useState(nanoid()); | ||
const [onExpireCallbackId] = useState(nanoid()); | ||
const { onSuccess, onError, onExpire } = callbacks; | ||
useEffect(() => { | ||
window[onSuccessCallbackId] = onSuccess; | ||
window[onExpireCallbackId] = onExpire; | ||
window[onErrorCallbackId] = onError; | ||
return () => { | ||
delete window[onSuccessCallbackId]; | ||
delete window[onExpireCallbackId]; | ||
delete window[onErrorCallbackId]; | ||
}; | ||
}, [ | ||
onSuccess, | ||
onSuccessCallbackId, | ||
onError, | ||
onErrorCallbackId, | ||
onExpire, | ||
onExpireCallbackId, | ||
]); | ||
return { | ||
onSuccessCallbackId, | ||
onErrorCallbackId, | ||
onExpireCallbackId, | ||
}; | ||
ReCaptcha.prototype.mutationCallbackGenerator = function () { | ||
var _this = this; | ||
return function (mutations) { | ||
mutations.forEach(function (mutation) { | ||
if (mutation.type === "childList" && | ||
mutation.target === document.body && | ||
mutation.addedNodes.length === 1 && | ||
_this.isNodeReCaptchaHiddenDiv(mutation.addedNodes[0])) { | ||
_this.hiddenDiv = mutation.addedNodes[0]; | ||
_this.observer.disconnect(); | ||
} | ||
}); | ||
}; | ||
const useRecaptchaScripts = () => { | ||
useEffect(() => { | ||
appendScript(); | ||
return () => { | ||
const script = getMainScriptElement(); | ||
if (script) { | ||
removeChildElement(script); | ||
} | ||
removeImplicitRecaptchaScripts(); | ||
}; | ||
}; | ||
ReCaptcha.prototype.isNodeReCaptchaHiddenDiv = function (node) { | ||
var div = node; | ||
return (div.style && | ||
div.style.visibility === "hidden" && | ||
div.style.top === "-10000px" && | ||
div.style.position === "absolute"); | ||
}; | ||
ReCaptcha.prototype.render = function () { | ||
var _a = this.props, siteKey = _a.siteKey, theme = _a.theme, size = _a.size; | ||
return (React.createElement("div", { id: this.id, className: "g-recaptcha", "data-sitekey": siteKey === "test" ? this.testSiteKey : siteKey, "data-theme": theme, "data-size": size, "data-callback": this.successCallbackId, "data-expired-callback": this.expiredCallbackId, "data-error-callback": this.errorCallbackId })); | ||
}; | ||
return ReCaptcha; | ||
}(Component)); | ||
}, []); | ||
}; | ||
const useRecaptchaHiddenDivManager = () => { | ||
useEffect(() => { | ||
let hiddenDiv; | ||
const observer = new MutationObserver(mutationCallbackGenerator((div) => { | ||
hiddenDiv = div; | ||
})); | ||
observer.observe(document.body, { childList: true }); | ||
return () => { | ||
observer.disconnect(); | ||
if (hiddenDiv) { | ||
removeChildElement(hiddenDiv); | ||
} | ||
}; | ||
}, []); | ||
}; | ||
const ReCaptcha = (props) => { | ||
const { siteKey, theme, size } = props, callbacks = __rest(props, ["siteKey", "theme", "size"]); | ||
const { onSuccessCallbackId, onErrorCallbackId, onExpireCallbackId } = useWindowCallbackBinder(callbacks); | ||
useRecaptchaHiddenDivManager(); | ||
useRecaptchaScripts(); | ||
const [id] = useState(nanoid()); | ||
return (React.createElement("div", { id: id, className: "g-recaptcha", "data-sitekey": siteKey === "test" ? TEST_SITE_KEY : siteKey, "data-theme": theme, "data-size": size, "data-callback": onSuccessCallbackId, "data-error-callback": onErrorCallbackId, "data-expired-callback": onExpireCallbackId })); | ||
}; | ||
export { ReCaptcha as default }; |
{ | ||
"name": "@matt-block/react-recaptcha-v2", | ||
"version": "1.0.9", | ||
"version": "2.0.0-rc.0", | ||
"description": "Google reCAPTCHA v2 React component that does not pollute the DOM", | ||
@@ -28,3 +28,3 @@ "main": "lib/index.esm.js", | ||
"scripts": { | ||
"build": "rollup -c rollup.config.ts", | ||
"build": "rollup --config rollup.config.ts --configPlugin @rollup/plugin-typescript", | ||
"postbuild": "tsc --emitDeclarationOnly", | ||
@@ -37,4 +37,4 @@ "prepare": "husky install" | ||
"devDependencies": { | ||
"@rollup/plugin-node-resolve": "^13.3.0", | ||
"@rollup/plugin-typescript": "^8.5.0", | ||
"@rollup/plugin-node-resolve": "^15.0.1", | ||
"@rollup/plugin-typescript": "^11.0.0", | ||
"@types/react": "^18.0.28", | ||
@@ -44,3 +44,3 @@ "husky": "^7.0.4", | ||
"prettier": "^2.8.4", | ||
"rollup": "^2.79.1", | ||
"rollup": "^3.17.2", | ||
"rollup-plugin-copy": "^3.4.0", | ||
@@ -51,3 +51,3 @@ "tslib": "^1.14.1", | ||
"peerDependencies": { | ||
"react": "^16.0.0 || ^17.0.0 || ^18.0.0" | ||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0" | ||
}, | ||
@@ -54,0 +54,0 @@ "lint-staged": { |
@@ -11,6 +11,6 @@ # React reCAPTCHA v2 | ||
- [x] Does not pollute the DOM by cleaning up on unmount (see below) | ||
- [x] Can safely add multiple `<ReCaptcha>` components in the same page, they | ||
will not conflict with each other. | ||
- [x] TypeScript and Flow type declarations | ||
- Does not pollute the DOM by cleaning up on unmount (see below) | ||
- Can safely add multiple `<ReCaptcha>` components in the same page, they | ||
will not conflict with each other. | ||
- TypeScript and Flow type declarations | ||
@@ -36,9 +36,10 @@ ### DOM Pollution and Cleanup | ||
| React | Library | | ||
| :------: | :-----: | | ||
| >=16.0.0 | latest | | ||
| React | Library | Status | End-of-Life | | ||
| :---------------: | :---------------------------------------: | :-------------: | :---------: | | ||
| `>=16.8.0` | [![npm (scoped)][npm_shield]][npm] | **Active** | - | | ||
| `16.0.0 - 16.7.x` | [![npm v1 (scoped)][npm_1_shield]][npm_1] | **Maintenance** | 2023-06-01 | | ||
## Installation | ||
Install the package via Yarn or npm: | ||
Install the package via npm or Yarn: | ||
@@ -59,21 +60,19 @@ ``` | ||
```javascript | ||
import React, { Component } from "react"; | ||
import React from "react"; | ||
import ReCaptcha from "@matt-block/react-recaptcha-v2"; | ||
class MyFormComponent extends Component { | ||
// other methods and callbacks... | ||
const MyFormComponent = () => { | ||
// other logic and hooks... | ||
render() { | ||
return ( | ||
{/* other components to render... */} | ||
<ReCaptcha | ||
siteKey="6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI" | ||
theme="light" | ||
size="normal" | ||
onSuccess={(captcha) => console.log(`Successful, result is ${captcha}`)} | ||
onExpire={() => console.log("Verification has expired, re-verify.")} | ||
onError={() => console.log("Something went wrong, check your conenction")} | ||
/> | ||
); | ||
} | ||
return ( | ||
{/* other components to render... */} | ||
<ReCaptcha | ||
siteKey="6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI" | ||
theme="light" | ||
size="normal" | ||
onSuccess={(captcha) => console.log(`Successful, result is ${captcha}`)} | ||
onError={() => console.log("Something went wrong, check your conenction")} | ||
onExpire={() => console.log("Verification has expired, re-verify.")} | ||
/> | ||
); | ||
} | ||
@@ -93,4 +92,4 @@ ``` | ||
| `onSuccess` | `function` | `undefined` | Callback function, executed when the user submits a successful response. The response token is passed to your callback. | | ||
| `onExpired` | `function` | `undefined` | Callback function, executed when the reCAPTCHA response expires and the user needs to re-verify. | | ||
| `onError` | `function` | `undefined` | Callback function, executed when reCAPTCHA encounters an error (usually network connectivity) and cannot continue until connectivity is restored. If you specify a function here, you are responsible for informing the user that they should retry. | | ||
| `onExpire` | `function` | `undefined` | Callback function, executed when the reCAPTCHA response expires and the user needs to re-verify. | | ||
@@ -111,3 +110,5 @@ ## License | ||
[npm]: https://www.npmjs.com/package/@matt-block/react-recaptcha-v2 | ||
[npm_shield]: https://img.shields.io/npm/v/@matt-block/react-recaptcha-v2.svg | ||
[npm_shield]: https://img.shields.io/npm/v/@matt-block/react-recaptcha-v2/latest | ||
[npm_1]: https://www.npmjs.com/package/@matt-block/react-recaptcha-v2/v/1.0.9 | ||
[npm_1_shield]: https://img.shields.io/badge/npm-v1.0.9-blue | ||
[recaptcha_admin]: https://www.google.com/recaptcha/admin | ||
@@ -114,0 +115,0 @@ [recaptcha_docs]: https://developers.google.com/recaptcha/docs/display |
@@ -1,2 +0,2 @@ | ||
import { Component } from "react"; | ||
import { FC } from "react"; | ||
interface ReCaptchaProps { | ||
@@ -10,22 +10,3 @@ siteKey: string; | ||
} | ||
declare class ReCaptcha extends Component<ReCaptchaProps, {}> { | ||
private scriptSrc; | ||
private testSiteKey; | ||
private observer; | ||
private hiddenDiv; | ||
private id; | ||
private successCallbackId; | ||
private expiredCallbackId; | ||
private errorCallbackId; | ||
componentDidMount(): void; | ||
componentWillUnmount(): void; | ||
appendScript(): void; | ||
getScriptIfAvailable(): HTMLScriptElement | undefined; | ||
createScriptElement(): HTMLScriptElement; | ||
private cleanup; | ||
removeChild(element: HTMLElement): void; | ||
mutationCallbackGenerator(): (mutations: MutationRecord[]) => void; | ||
isNodeReCaptchaHiddenDiv(node: Node): boolean; | ||
render(): JSX.Element; | ||
} | ||
declare const ReCaptcha: FC<ReCaptchaProps>; | ||
export default ReCaptcha; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
165
0
116
15752
1
1