@hcaptcha/react-hcaptcha 1.3.1 → 1.4.2

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.
@@ -0,0 +1,336 @@
1
+ import _assertThisInitialized from "@babel/runtime/helpers/assertThisInitialized";
2
+ import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose";
3
+ import * as React from 'react';
4
+ import { generateQuery } from "./utils.js";
5
+ var SCRIPT_ID = 'hcaptcha-api-script-id';
6
+ var HCAPTCHA_LOAD_FN_NAME = 'hcaptchaOnLoad'; // Prevent loading API script multiple times
7
+
8
+ var resolveFn;
9
+ var rejectFn;
10
+ var mountPromise = new Promise(function (resolve, reject) {
11
+ resolveFn = resolve;
12
+ rejectFn = reject;
13
+ }); // Generate hCaptcha API script
14
+
15
+ var mountCaptchaScript = function mountCaptchaScript(params) {
16
+ if (params === void 0) {
17
+ params = {};
18
+ }
19
+
20
+ if (document.getElementById(SCRIPT_ID)) {
21
+ // API was already requested
22
+ return mountPromise;
23
+ } // Create global onload callback
24
+
25
+
26
+ window[HCAPTCHA_LOAD_FN_NAME] = resolveFn;
27
+ var domain = params.apihost || "https://js.hcaptcha.com";
28
+ delete params.apihost;
29
+ var script = document.createElement("script");
30
+ script.id = SCRIPT_ID;
31
+ script.src = domain + "/1/api.js?render=explicit&onload=" + HCAPTCHA_LOAD_FN_NAME;
32
+ script.async = true;
33
+
34
+ script.onerror = function (event) {
35
+ return rejectFn('script-error');
36
+ };
37
+
38
+ var query = generateQuery(params);
39
+ script.src += query !== "" ? "&" + query : "";
40
+ document.head.appendChild(script);
41
+ return mountPromise;
42
+ };
43
+
44
+ var HCaptcha = /*#__PURE__*/function (_React$Component) {
45
+ _inheritsLoose(HCaptcha, _React$Component);
46
+
47
+ function HCaptcha(props) {
48
+ var _this;
49
+
50
+ _this = _React$Component.call(this, props) || this; // API Methods
51
+
52
+ _this.renderCaptcha = _this.renderCaptcha.bind(_assertThisInitialized(_this));
53
+ _this.resetCaptcha = _this.resetCaptcha.bind(_assertThisInitialized(_this));
54
+ _this.removeCaptcha = _this.removeCaptcha.bind(_assertThisInitialized(_this));
55
+ _this.isReady = _this.isReady.bind(_assertThisInitialized(_this)); // Event Handlers
56
+
57
+ _this.handleOnLoad = _this.handleOnLoad.bind(_assertThisInitialized(_this));
58
+ _this.handleSubmit = _this.handleSubmit.bind(_assertThisInitialized(_this));
59
+ _this.handleExpire = _this.handleExpire.bind(_assertThisInitialized(_this));
60
+ _this.handleError = _this.handleError.bind(_assertThisInitialized(_this));
61
+ _this.handleOpen = _this.handleOpen.bind(_assertThisInitialized(_this));
62
+ _this.handleClose = _this.handleClose.bind(_assertThisInitialized(_this));
63
+ _this.handleChallengeExpired = _this.handleChallengeExpired.bind(_assertThisInitialized(_this));
64
+ var isApiReady = typeof hcaptcha !== 'undefined';
65
+ _this.ref = /*#__PURE__*/React.createRef();
66
+ _this.state = {
67
+ isApiReady: isApiReady,
68
+ isRemoved: false,
69
+ elementId: props.id,
70
+ captchaId: ''
71
+ };
72
+ return _this;
73
+ }
74
+
75
+ var _proto = HCaptcha.prototype;
76
+
77
+ _proto.componentDidMount = function componentDidMount() {
78
+ // Once captcha is mounted intialize hCaptcha - hCaptcha
79
+ var _this$props = this.props,
80
+ apihost = _this$props.apihost,
81
+ assethost = _this$props.assethost,
82
+ endpoint = _this$props.endpoint,
83
+ host = _this$props.host,
84
+ imghost = _this$props.imghost,
85
+ hl = _this$props.languageOverride,
86
+ reCaptchaCompat = _this$props.reCaptchaCompat,
87
+ reportapi = _this$props.reportapi,
88
+ sentry = _this$props.sentry,
89
+ custom = _this$props.custom;
90
+ var isApiReady = this.state.isApiReady;
91
+
92
+ if (!isApiReady) {
93
+ // Check if hCaptcha has already been loaded, if not create script tag and wait to render captcha
94
+ var mountParams = {
95
+ apihost: apihost,
96
+ assethost: assethost,
97
+ endpoint: endpoint,
98
+ hl: hl,
99
+ host: host,
100
+ imghost: imghost,
101
+ recaptchacompat: reCaptchaCompat === false ? "off" : null,
102
+ reportapi: reportapi,
103
+ sentry: sentry,
104
+ custom: custom
105
+ }; // Only create the script tag once, use a global promise to track
106
+
107
+ mountCaptchaScript(mountParams).then(this.handleOnLoad)["catch"](this.handleError);
108
+ } else {
109
+ this.renderCaptcha();
110
+ }
111
+ };
112
+
113
+ _proto.componentWillUnmount = function componentWillUnmount() {
114
+ var captchaId = this.state.captchaId;
115
+
116
+ if (!this.isReady()) {
117
+ return;
118
+ } // Reset any stored variables / timers when unmounting
119
+
120
+
121
+ hcaptcha.reset(captchaId);
122
+ hcaptcha.remove(captchaId);
123
+ };
124
+
125
+ _proto.shouldComponentUpdate = function shouldComponentUpdate(nextProps, nextState) {
126
+ // Prevent component re-rendering when these internal state variables are updated
127
+ if (this.state.isApiReady !== nextState.isApiReady || this.state.isRemoved !== nextState.isRemoved) {
128
+ return false;
129
+ }
130
+
131
+ return true;
132
+ };
133
+
134
+ _proto.componentDidUpdate = function componentDidUpdate(prevProps) {
135
+ var _this2 = this;
136
+
137
+ // Prop Keys that could change
138
+ var keys = ['sitekey', 'size', 'theme', 'tabindex', 'languageOverride', 'endpoint']; // See if any props changed during component update
139
+
140
+ var match = keys.every(function (key) {
141
+ return prevProps[key] === _this2.props[key];
142
+ }); // If they have changed, remove current captcha and render a new one
143
+
144
+ if (!match) {
145
+ this.removeCaptcha(function () {
146
+ _this2.renderCaptcha();
147
+ });
148
+ }
149
+ };
150
+
151
+ _proto.renderCaptcha = function renderCaptcha(onReady) {
152
+ var isApiReady = this.state.isApiReady;
153
+ if (!isApiReady) return;
154
+ var renderParams = Object.assign({
155
+ "open-callback": this.handleOpen,
156
+ "close-callback": this.handleClose,
157
+ "error-callback": this.handleError,
158
+ "chalexpired-callback": this.handleChallengeExpired,
159
+ "expired-callback": this.handleExpire,
160
+ "callback": this.handleSubmit
161
+ }, this.props, {
162
+ hl: this.props.hl || this.props.languageOverride,
163
+ languageOverride: undefined
164
+ }); //Render hCaptcha widget and provide necessary callbacks - hCaptcha
165
+
166
+ var captchaId = hcaptcha.render(this.ref.current, renderParams);
167
+ this.setState({
168
+ isRemoved: false,
169
+ captchaId: captchaId
170
+ }, function () {
171
+ onReady && onReady();
172
+ });
173
+ };
174
+
175
+ _proto.resetCaptcha = function resetCaptcha() {
176
+ var captchaId = this.state.captchaId;
177
+
178
+ if (!this.isReady()) {
179
+ return;
180
+ } // Reset captcha state, removes stored token and unticks checkbox
181
+
182
+
183
+ hcaptcha.reset(captchaId);
184
+ };
185
+
186
+ _proto.removeCaptcha = function removeCaptcha(callback) {
187
+ var captchaId = this.state.captchaId;
188
+
189
+ if (!this.isReady()) {
190
+ return;
191
+ }
192
+
193
+ this.setState({
194
+ isRemoved: true
195
+ }, function () {
196
+ hcaptcha.remove(captchaId);
197
+ callback && callback();
198
+ });
199
+ };
200
+
201
+ _proto.handleOnLoad = function handleOnLoad() {
202
+ var _this3 = this;
203
+
204
+ this.setState({
205
+ isApiReady: true
206
+ }, function () {
207
+ // render captcha and wait for captcha id
208
+ _this3.renderCaptcha(function () {
209
+ // trigger onLoad if it exists
210
+ var onLoad = _this3.props.onLoad;
211
+ if (onLoad) onLoad();
212
+ });
213
+ });
214
+ };
215
+
216
+ _proto.handleSubmit = function handleSubmit(event) {
217
+ var onVerify = this.props.onVerify;
218
+ var _this$state = this.state,
219
+ isRemoved = _this$state.isRemoved,
220
+ captchaId = _this$state.captchaId;
221
+ if (typeof hcaptcha === 'undefined' || isRemoved) return;
222
+ var token = hcaptcha.getResponse(captchaId); //Get response token from hCaptcha widget
223
+
224
+ var ekey = hcaptcha.getRespKey(captchaId); //Get current challenge session id from hCaptcha widget
225
+
226
+ onVerify(token, ekey); //Dispatch event to verify user response
227
+ };
228
+
229
+ _proto.handleExpire = function handleExpire() {
230
+ var onExpire = this.props.onExpire;
231
+ var captchaId = this.state.captchaId;
232
+
233
+ if (!this.isReady()) {
234
+ return;
235
+ }
236
+
237
+ hcaptcha.reset(captchaId); // If hCaptcha runs into error, reset captcha - hCaptcha
238
+
239
+ if (onExpire) onExpire();
240
+ };
241
+
242
+ _proto.handleError = function handleError(event) {
243
+ var onError = this.props.onError;
244
+ var captchaId = this.state.captchaId;
245
+
246
+ if (this.isReady()) {
247
+ // If hCaptcha runs into error, reset captcha - hCaptcha
248
+ hcaptcha.reset(captchaId);
249
+ }
250
+
251
+ if (onError) onError(event);
252
+ };
253
+
254
+ _proto.isReady = function isReady() {
255
+ var _this$state2 = this.state,
256
+ isApiReady = _this$state2.isApiReady,
257
+ isRemoved = _this$state2.isRemoved;
258
+ return isApiReady && !isRemoved;
259
+ };
260
+
261
+ _proto.handleOpen = function handleOpen() {
262
+ if (!this.isReady() || !this.props.onOpen) {
263
+ return;
264
+ }
265
+
266
+ this.props.onOpen();
267
+ };
268
+
269
+ _proto.handleClose = function handleClose() {
270
+ if (!this.isReady() || !this.props.onClose) {
271
+ return;
272
+ }
273
+
274
+ this.props.onClose();
275
+ };
276
+
277
+ _proto.handleChallengeExpired = function handleChallengeExpired() {
278
+ if (!this.isReady() || !this.props.onChalExpired) {
279
+ return;
280
+ }
281
+
282
+ this.props.onChalExpired();
283
+ };
284
+
285
+ _proto.execute = function execute(opts) {
286
+ if (opts === void 0) {
287
+ opts = null;
288
+ }
289
+
290
+ var captchaId = this.state.captchaId;
291
+
292
+ if (!this.isReady()) {
293
+ return;
294
+ }
295
+
296
+ if (opts && typeof opts !== "object") {
297
+ opts = null;
298
+ }
299
+
300
+ return hcaptcha.execute(captchaId, opts);
301
+ };
302
+
303
+ _proto.setData = function setData(data) {
304
+ var captchaId = this.state.captchaId;
305
+
306
+ if (!this.isReady()) {
307
+ return;
308
+ }
309
+
310
+ if (data && typeof data !== "object") {
311
+ data = null;
312
+ }
313
+
314
+ hcaptcha.setData(captchaId, data);
315
+ };
316
+
317
+ _proto.getResponse = function getResponse() {
318
+ return hcaptcha.getResponse(this.state.captchaId);
319
+ };
320
+
321
+ _proto.getRespKey = function getRespKey() {
322
+ return hcaptcha.getRespKey(this.state.captchaId);
323
+ };
324
+
325
+ _proto.render = function render() {
326
+ var elementId = this.state.elementId;
327
+ return /*#__PURE__*/React.createElement("div", {
328
+ ref: this.ref,
329
+ id: elementId
330
+ });
331
+ };
332
+
333
+ return HCaptcha;
334
+ }(React.Component);
335
+
336
+ export default HCaptcha;
@@ -0,0 +1,14 @@
1
+ function generateQuery(params) {
2
+ return Object.entries(params).filter(function (_ref) {
3
+ var key = _ref[0],
4
+ value = _ref[1];
5
+ return value || value === false;
6
+ }).map(function (_ref2) {
7
+ var key = _ref2[0],
8
+ value = _ref2[1];
9
+ return encodeURIComponent(key) + "=" + encodeURIComponent(value);
10
+ }).join("&");
11
+ }
12
+
13
+ ;
14
+ export { generateQuery };
package/dist/index.js CHANGED
@@ -1,7 +1,14 @@
1
1
  "use strict";
2
2
 
3
+ var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
4
+
3
5
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
6
 
7
+ Object.defineProperty(exports, "__esModule", {
8
+ value: true
9
+ });
10
+ exports["default"] = void 0;
11
+
5
12
  var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
6
13
 
7
14
  var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
@@ -16,39 +23,49 @@ var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime
16
23
 
17
24
  var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));
18
25
 
19
- function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0, _getPrototypeOf2["default"])(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2["default"])(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2["default"])(this, result); }; }
26
+ var React = _interopRequireWildcard(require("react"));
20
27
 
21
- function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }
28
+ var _utils = require("./utils.js");
22
29
 
23
- var React = require('react');
30
+ function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0, _getPrototypeOf2["default"])(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2["default"])(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2["default"])(this, result); }; }
24
31
 
25
- var _require = require("./utils.js"),
26
- generateQuery = _require.generateQuery; // Create script to init hCaptcha
32
+ function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }
27
33
 
34
+ var SCRIPT_ID = 'hcaptcha-api-script-id';
35
+ var HCAPTCHA_LOAD_FN_NAME = 'hcaptchaOnLoad'; // Prevent loading API script multiple times
28
36
 
29
- var onLoadListeners = [];
30
- var apiScriptRequested = false; // Generate hCaptcha API Script
37
+ var resolveFn;
38
+ var rejectFn;
39
+ var mountPromise = new Promise(function (resolve, reject) {
40
+ resolveFn = resolve;
41
+ rejectFn = reject;
42
+ }); // Generate hCaptcha API script
31
43
 
32
44
  var mountCaptchaScript = function mountCaptchaScript() {
33
45
  var params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
34
- apiScriptRequested = true; // Create global onload callback
35
-
36
- window.hcaptchaOnLoad = function () {
37
- // Iterate over onload listeners, call each listener
38
- onLoadListeners = onLoadListeners.filter(function (listener) {
39
- listener();
40
- return false;
41
- });
42
- };
43
46
 
47
+ if (document.getElementById(SCRIPT_ID)) {
48
+ // API was already requested
49
+ return mountPromise;
50
+ } // Create global onload callback
51
+
52
+
53
+ window[HCAPTCHA_LOAD_FN_NAME] = resolveFn;
44
54
  var domain = params.apihost || "https://js.hcaptcha.com";
45
55
  delete params.apihost;
46
56
  var script = document.createElement("script");
47
- script.src = "".concat(domain, "/1/api.js?render=explicit&onload=hcaptchaOnLoad");
57
+ script.id = SCRIPT_ID;
58
+ script.src = "".concat(domain, "/1/api.js?render=explicit&onload=").concat(HCAPTCHA_LOAD_FN_NAME);
48
59
  script.async = true;
49
- var query = generateQuery(params);
60
+
61
+ script.onerror = function (event) {
62
+ return rejectFn('script-error');
63
+ };
64
+
65
+ var query = (0, _utils.generateQuery)(params);
50
66
  script.src += query !== "" ? "&".concat(query) : "";
51
67
  document.head.appendChild(script);
68
+ return mountPromise;
52
69
  };
53
70
 
54
71
  var HCaptcha = /*#__PURE__*/function (_React$Component) {
@@ -75,7 +92,7 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
75
92
  _this.handleClose = _this.handleClose.bind((0, _assertThisInitialized2["default"])(_this));
76
93
  _this.handleChallengeExpired = _this.handleChallengeExpired.bind((0, _assertThisInitialized2["default"])(_this));
77
94
  var isApiReady = typeof hcaptcha !== 'undefined';
78
- _this.ref = React.createRef();
95
+ _this.ref = /*#__PURE__*/React.createRef();
79
96
  _this.state = {
80
97
  isApiReady: isApiReady,
81
98
  isRemoved: false,
@@ -88,7 +105,7 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
88
105
  (0, _createClass2["default"])(HCaptcha, [{
89
106
  key: "componentDidMount",
90
107
  value: function componentDidMount() {
91
- //Once captcha is mounted intialize hCaptcha - hCaptcha
108
+ // Once captcha is mounted intialize hCaptcha - hCaptcha
92
109
  var _this$props = this.props,
93
110
  apihost = _this$props.apihost,
94
111
  assethost = _this$props.assethost,
@@ -103,13 +120,8 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
103
120
  var isApiReady = this.state.isApiReady;
104
121
 
105
122
  if (!isApiReady) {
106
- //Check if hCaptcha has already been loaded, if not create script tag and wait to render captcha
107
- if (apiScriptRequested) {
108
- return;
109
- } // Only create the script tag once, use a global variable to track
110
-
111
-
112
- mountCaptchaScript({
123
+ // Check if hCaptcha has already been loaded, if not create script tag and wait to render captcha
124
+ var mountParams = {
113
125
  apihost: apihost,
114
126
  assethost: assethost,
115
127
  endpoint: endpoint,
@@ -120,9 +132,9 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
120
132
  reportapi: reportapi,
121
133
  sentry: sentry,
122
134
  custom: custom
123
- }); // Add onload callback to global onload listeners
135
+ }; // Only create the script tag once, use a global promise to track
124
136
 
125
- onLoadListeners.push(this.handleOnLoad);
137
+ mountCaptchaScript(mountParams).then(this.handleOnLoad)["catch"](this.handleError);
126
138
  } else {
127
139
  this.renderCaptcha();
128
140
  }
@@ -271,12 +283,11 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
271
283
  var onError = this.props.onError;
272
284
  var captchaId = this.state.captchaId;
273
285
 
274
- if (!this.isReady()) {
275
- return;
286
+ if (this.isReady()) {
287
+ // If hCaptcha runs into error, reset captcha - hCaptcha
288
+ hcaptcha.reset(captchaId);
276
289
  }
277
290
 
278
- hcaptcha.reset(captchaId); // If hCaptcha runs into error, reset captcha - hCaptcha
279
-
280
291
  if (onError) onError(event);
281
292
  }
282
293
  }, {
@@ -368,4 +379,6 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
368
379
  return HCaptcha;
369
380
  }(React.Component);
370
381
 
371
- module.exports = HCaptcha;
382
+ var _default = HCaptcha;
383
+ exports["default"] = _default;
384
+ module.exports = exports.default;
package/dist/utils.js CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
4
 
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ exports.generateQuery = generateQuery;
9
+
5
10
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
6
11
 
7
12
  function generateQuery(params) {
@@ -20,7 +25,4 @@ function generateQuery(params) {
20
25
  }).join("&");
21
26
  }
22
27
 
23
- ;
24
- module.exports = {
25
- generateQuery: generateQuery
26
- };
28
+ ;
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@hcaptcha/react-hcaptcha",
3
- "version": "1.3.1",
3
+ "version": "1.4.2",
4
4
  "types": "types/index.d.ts",
5
5
  "main": "dist/index.js",
6
+ "module": "dist/esm/index.js",
6
7
  "files": [
7
8
  "src",
8
9
  "dist",
9
- "types/index.d.ts"
10
+ "types"
10
11
  ],
11
12
  "description": "A React library for hCaptcha",
12
13
  "scripts": {
@@ -14,8 +15,10 @@
14
15
  "test": "jest",
15
16
  "watch": "babel src -d dist --copy-files --watch",
16
17
  "transpile": "babel src -d dist --copy-files",
17
- "build": "npm run transpile",
18
- "prepublishOnly": "npm run transpile"
18
+ "prebuild": "rimraf dist",
19
+ "build": "npm run transpile && npm run build:esm",
20
+ "build:esm": "cross-env BABEL_ENV=esm babel src -d dist/esm --copy-files",
21
+ "prepublishOnly": "npm run build"
19
22
  },
20
23
  "peerDependencies": {
21
24
  "react": ">= 16.3.0",
@@ -36,15 +39,19 @@
36
39
  "@babel/preset-env": "^7.12.11",
37
40
  "@babel/preset-react": "^7.12.10",
38
41
  "babel-loader": "^8.2.2",
42
+ "babel-plugin-add-module-exports": "^1.0.4",
43
+ "cross-env": "^7.0.3",
39
44
  "html-webpack-plugin": "^3.2.0",
40
45
  "jest": "^26.6.3",
41
46
  "react": "^16.14.0",
42
47
  "react-dom": "^16.14.0",
48
+ "rimraf": "^3.0.2",
43
49
  "webpack": "^4.44.2",
44
50
  "webpack-cli": "^3.3.12",
45
51
  "webpack-dev-server": "^3.11.0"
46
52
  },
47
53
  "dependencies": {
48
- "@babel/runtime": "^7.17.9"
54
+ "@babel/runtime": "^7.17.9",
55
+ "@types/react": "^18.0.0"
49
56
  }
50
57
  }
package/src/index.js CHANGED
@@ -1,34 +1,42 @@
1
- const React = require('react');
2
- const { generateQuery } = require("./utils.js");
1
+ import * as React from 'react';
2
+ import { generateQuery } from "./utils.js";
3
3
 
4
- // Create script to init hCaptcha
5
- let onLoadListeners = [];
6
- let apiScriptRequested = false;
4
+ const SCRIPT_ID = 'hcaptcha-api-script-id';
5
+ const HCAPTCHA_LOAD_FN_NAME = 'hcaptchaOnLoad';
7
6
 
8
- // Generate hCaptcha API Script
7
+ // Prevent loading API script multiple times
8
+ let resolveFn;
9
+ let rejectFn;
10
+ const mountPromise = new Promise((resolve, reject) => {
11
+ resolveFn = resolve;
12
+ rejectFn = reject;
13
+ });
14
+
15
+ // Generate hCaptcha API script
9
16
  const mountCaptchaScript = (params={}) => {
10
- apiScriptRequested = true;
17
+ if (document.getElementById(SCRIPT_ID)) {
18
+ // API was already requested
19
+ return mountPromise;
20
+ }
21
+
11
22
  // Create global onload callback
12
- window.hcaptchaOnLoad = () => {
13
- // Iterate over onload listeners, call each listener
14
- onLoadListeners = onLoadListeners.filter(listener => {
15
- listener();
16
- return false;
17
- });
18
- };
23
+ window[HCAPTCHA_LOAD_FN_NAME] = resolveFn;
19
24
 
20
25
  const domain = params.apihost || "https://js.hcaptcha.com";
21
26
  delete params.apihost;
22
27
 
23
28
  const script = document.createElement("script");
24
- script.src = `${domain}/1/api.js?render=explicit&onload=hcaptchaOnLoad`;
29
+ script.id = SCRIPT_ID;
30
+ script.src = `${domain}/1/api.js?render=explicit&onload=${HCAPTCHA_LOAD_FN_NAME}`;
25
31
  script.async = true;
32
+ script.onerror = (event) => rejectFn('script-error');
26
33
 
27
34
  const query = generateQuery(params);
28
35
  script.src += query !== ""? `&${query}` : "";
29
36
 
30
37
  document.head.appendChild(script);
31
- }
38
+ return mountPromise;
39
+ };
32
40
 
33
41
 
34
42
  class HCaptcha extends React.Component {
@@ -62,17 +70,12 @@ class HCaptcha extends React.Component {
62
70
  }
63
71
  }
64
72
 
65
- componentDidMount () { //Once captcha is mounted intialize hCaptcha - hCaptcha
73
+ componentDidMount () { // Once captcha is mounted intialize hCaptcha - hCaptcha
66
74
  const { apihost, assethost, endpoint, host, imghost, languageOverride:hl, reCaptchaCompat, reportapi, sentry, custom } = this.props;
67
75
  const { isApiReady } = this.state;
68
76
 
69
- if (!isApiReady) { //Check if hCaptcha has already been loaded, if not create script tag and wait to render captcha
70
- if (apiScriptRequested) {
71
- return;
72
- }
73
-
74
- // Only create the script tag once, use a global variable to track
75
- mountCaptchaScript({
77
+ if (!isApiReady) { // Check if hCaptcha has already been loaded, if not create script tag and wait to render captcha
78
+ const mountParams = {
76
79
  apihost,
77
80
  assethost,
78
81
  endpoint,
@@ -83,10 +86,12 @@ class HCaptcha extends React.Component {
83
86
  reportapi,
84
87
  sentry,
85
88
  custom
86
- });
89
+ };
87
90
 
88
- // Add onload callback to global onload listeners
89
- onLoadListeners.push(this.handleOnLoad);
91
+ // Only create the script tag once, use a global promise to track
92
+ mountCaptchaScript(mountParams)
93
+ .then(this.handleOnLoad)
94
+ .catch(this.handleError);
90
95
  } else {
91
96
  this.renderCaptcha();
92
97
  }
@@ -214,11 +219,11 @@ class HCaptcha extends React.Component {
214
219
  const { onError } = this.props;
215
220
  const { captchaId } = this.state;
216
221
 
217
- if (!this.isReady()) {
218
- return;
222
+ if (this.isReady()) {
223
+ // If hCaptcha runs into error, reset captcha - hCaptcha
224
+ hcaptcha.reset(captchaId);
219
225
  }
220
226
 
221
- hcaptcha.reset(captchaId) // If hCaptcha runs into error, reset captcha - hCaptcha
222
227
  if (onError) onError(event);
223
228
  }
224
229
 
@@ -294,4 +299,4 @@ class HCaptcha extends React.Component {
294
299
  }
295
300
  }
296
301
 
297
- module.exports = HCaptcha;
302
+ export default HCaptcha;
package/src/utils.js CHANGED
@@ -6,6 +6,4 @@ function generateQuery(params) {
6
6
  }).join("&");
7
7
  };
8
8
 
9
- module.exports = {
10
- generateQuery
11
- };
9
+ export { generateQuery };