@hcaptcha/react-hcaptcha 1.6.1 → 1.8.0

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/README.md CHANGED
@@ -139,7 +139,8 @@ return <HCaptcha ref={captchaRef} onLoad={onLoad} sitekey={sitekey} {...props} /
139
139
  |`reportapi`|String|No|`-`|See enterprise docs.|
140
140
  |`sentry`|String|No|`-`|See enterprise docs.|
141
141
  |`custom`|Boolean|No|`-`|See enterprise docs.|
142
- |`loadAsync`|Boolean|No|`true`|Set if the script should be loaded asynchronously|
142
+ |`loadAsync`|Boolean|No|`true`|Set if the script should be loaded asynchronously.|
143
+ |`scriptLocation`|Element|No|`document.head`| Location of where to append the script tag. Make sure to add it to an area that will persist to prevent loading multiple times in the same document view. Note: If `null` is provided, the `document.head` will be used.|
143
144
 
144
145
  #### Events
145
146
 
package/dist/esm/index.js CHANGED
@@ -1,45 +1,54 @@
1
1
  import _assertThisInitialized from "@babel/runtime/helpers/assertThisInitialized";
2
2
  import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose";
3
3
  import * as React from 'react';
4
- import { generateQuery } from "./utils.js";
4
+ import { generateQuery, getFrame, getMountElement } from "./utils.js";
5
5
  var SCRIPT_ID = 'hcaptcha-api-script-id';
6
6
  var HCAPTCHA_LOAD_FN_NAME = 'hcaptchaOnLoad'; // Prevent loading API script multiple times
7
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
8
+ var scripts = []; // Generate hCaptcha API script
14
9
 
15
10
  var mountCaptchaScript = function mountCaptchaScript(params) {
16
11
  if (params === void 0) {
17
12
  params = {};
18
13
  }
19
14
 
20
- if (document.getElementById(SCRIPT_ID)) {
21
- // API was already requested
22
- return mountPromise;
23
- } // Create global onload callback
24
-
15
+ var element = getMountElement(params.scriptLocation);
16
+ delete params.scriptLocation;
17
+ var frame = getFrame(element);
18
+ var script = scripts.find(function (_ref) {
19
+ var scope = _ref.scope;
20
+ return scope === frame.window;
21
+ });
25
22
 
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 = params.loadAsync !== undefined ? params.loadAsync : true;
33
- delete params.loadAsync;
23
+ if (frame.document.getElementById(SCRIPT_ID) && script) {
24
+ // API was already requested
25
+ return script.promise;
26
+ }
34
27
 
35
- script.onerror = function (event) {
36
- return rejectFn('script-error');
37
- };
28
+ var promise = new Promise(function (resolve, reject) {
29
+ // Create global onload callback
30
+ frame.window[HCAPTCHA_LOAD_FN_NAME] = resolve;
31
+ var domain = params.apihost || "https://js.hcaptcha.com";
32
+ delete params.apihost;
33
+ var script = frame.document.createElement("script");
34
+ script.id = SCRIPT_ID;
35
+ script.src = domain + "/1/api.js?render=explicit&onload=" + HCAPTCHA_LOAD_FN_NAME;
36
+ script.async = params.loadAsync !== undefined ? params.loadAsync : true;
37
+ delete params.loadAsync;
38
+
39
+ script.onerror = function (event) {
40
+ return reject('script-error');
41
+ };
38
42
 
39
- var query = generateQuery(params);
40
- script.src += query !== "" ? "&" + query : "";
41
- document.head.appendChild(script);
42
- return mountPromise;
43
+ var query = generateQuery(params);
44
+ script.src += query !== "" ? "&" + query : "";
45
+ element.appendChild(script);
46
+ });
47
+ scripts.push({
48
+ promise: promise,
49
+ scope: frame.window
50
+ });
51
+ return promise;
43
52
  };
44
53
 
45
54
  var HCaptcha = /*#__PURE__*/function (_React$Component) {
@@ -48,7 +57,17 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
48
57
  function HCaptcha(props) {
49
58
  var _this;
50
59
 
51
- _this = _React$Component.call(this, props) || this; // API Methods
60
+ _this = _React$Component.call(this, props) || this;
61
+ var element = getMountElement(_this.props.scriptLocation);
62
+ var frame = getFrame(element);
63
+ /**
64
+ * Internal reference to track hCaptcha API
65
+ *
66
+ * Required as window is relative to initialization in application
67
+ * not where the script and iFrames have been loaded.
68
+ */
69
+
70
+ _this._hcaptcha = frame.window.hcaptcha || undefined; // API Methods
52
71
 
53
72
  _this.renderCaptcha = _this.renderCaptcha.bind(_assertThisInitialized(_this));
54
73
  _this.resetCaptcha = _this.resetCaptcha.bind(_assertThisInitialized(_this));
@@ -63,7 +82,7 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
63
82
  _this.handleOpen = _this.handleOpen.bind(_assertThisInitialized(_this));
64
83
  _this.handleClose = _this.handleClose.bind(_assertThisInitialized(_this));
65
84
  _this.handleChallengeExpired = _this.handleChallengeExpired.bind(_assertThisInitialized(_this));
66
- var isApiReady = typeof hcaptcha !== 'undefined';
85
+ var isApiReady = typeof _this._hcaptcha !== 'undefined';
67
86
  _this.ref = /*#__PURE__*/React.createRef();
68
87
  _this.apiScriptRequested = false;
69
88
  _this.state = {
@@ -96,6 +115,7 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
96
115
 
97
116
  _proto.componentWillUnmount = function componentWillUnmount() {
98
117
  var captchaId = this.state.captchaId;
118
+ var hcaptcha = this._hcaptcha;
99
119
 
100
120
  if (!this.isReady()) {
101
121
  return;
@@ -148,7 +168,8 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
148
168
  reportapi = _this$props.reportapi,
149
169
  sentry = _this$props.sentry,
150
170
  custom = _this$props.custom,
151
- loadAsync = _this$props.loadAsync;
171
+ loadAsync = _this$props.loadAsync,
172
+ scriptLocation = _this$props.scriptLocation;
152
173
  var mountParams = {
153
174
  apihost: apihost,
154
175
  assethost: assethost,
@@ -160,7 +181,8 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
160
181
  reportapi: reportapi,
161
182
  sentry: sentry,
162
183
  custom: custom,
163
- loadAsync: loadAsync
184
+ loadAsync: loadAsync,
185
+ scriptLocation: scriptLocation
164
186
  };
165
187
  mountCaptchaScript(mountParams).then(this.handleOnLoad)["catch"](this.handleError);
166
188
  this.apiScriptRequested = true;
@@ -179,7 +201,8 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
179
201
  }, this.props, {
180
202
  hl: this.props.hl || this.props.languageOverride,
181
203
  languageOverride: undefined
182
- }); //Render hCaptcha widget and provide necessary callbacks - hCaptcha
204
+ });
205
+ var hcaptcha = this._hcaptcha; //Render hCaptcha widget and provide necessary callbacks - hCaptcha
183
206
 
184
207
  var captchaId = hcaptcha.render(this.ref.current, renderParams);
185
208
  this.setState({
@@ -192,6 +215,7 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
192
215
 
193
216
  _proto.resetCaptcha = function resetCaptcha() {
194
217
  var captchaId = this.state.captchaId;
218
+ var hcaptcha = this._hcaptcha;
195
219
 
196
220
  if (!this.isReady()) {
197
221
  return;
@@ -203,6 +227,7 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
203
227
 
204
228
  _proto.removeCaptcha = function removeCaptcha(callback) {
205
229
  var captchaId = this.state.captchaId;
230
+ var hcaptcha = this._hcaptcha;
206
231
 
207
232
  if (!this.isReady()) {
208
233
  return;
@@ -222,7 +247,10 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
222
247
  this.setState({
223
248
  isApiReady: true
224
249
  }, function () {
225
- // render captcha and wait for captcha id
250
+ var element = getMountElement(_this3.props.scriptLocation);
251
+ var frame = getFrame(element);
252
+ _this3._hcaptcha = frame.window.hcaptcha; // render captcha and wait for captcha id
253
+
226
254
  _this3.renderCaptcha(function () {
227
255
  // trigger onLoad if it exists
228
256
  var onLoad = _this3.props.onLoad;
@@ -236,6 +264,7 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
236
264
  var _this$state = this.state,
237
265
  isRemoved = _this$state.isRemoved,
238
266
  captchaId = _this$state.captchaId;
267
+ var hcaptcha = this._hcaptcha;
239
268
  if (typeof hcaptcha === 'undefined' || isRemoved) return;
240
269
  var token = hcaptcha.getResponse(captchaId); //Get response token from hCaptcha widget
241
270
 
@@ -247,6 +276,7 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
247
276
  _proto.handleExpire = function handleExpire() {
248
277
  var onExpire = this.props.onExpire;
249
278
  var captchaId = this.state.captchaId;
279
+ var hcaptcha = this._hcaptcha;
250
280
 
251
281
  if (!this.isReady()) {
252
282
  return;
@@ -260,6 +290,7 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
260
290
  _proto.handleError = function handleError(event) {
261
291
  var onError = this.props.onError;
262
292
  var captchaId = this.state.captchaId;
293
+ var hcaptcha = this._hcaptcha;
263
294
 
264
295
  if (this.isReady()) {
265
296
  // If hCaptcha runs into error, reset captcha - hCaptcha
@@ -306,6 +337,7 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
306
337
  }
307
338
 
308
339
  var captchaId = this.state.captchaId;
340
+ var hcaptcha = this._hcaptcha;
309
341
 
310
342
  if (!this.isReady()) {
311
343
  return;
@@ -320,6 +352,7 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
320
352
 
321
353
  _proto.setData = function setData(data) {
322
354
  var captchaId = this.state.captchaId;
355
+ var hcaptcha = this._hcaptcha;
323
356
 
324
357
  if (!this.isReady()) {
325
358
  return;
@@ -333,10 +366,12 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
333
366
  };
334
367
 
335
368
  _proto.getResponse = function getResponse() {
369
+ var hcaptcha = this._hcaptcha;
336
370
  return hcaptcha.getResponse(this.state.captchaId);
337
371
  };
338
372
 
339
373
  _proto.getRespKey = function getRespKey() {
374
+ var hcaptcha = this._hcaptcha;
340
375
  return hcaptcha.getRespKey(this.state.captchaId);
341
376
  };
342
377
 
package/dist/esm/utils.js CHANGED
@@ -11,4 +11,18 @@ function generateQuery(params) {
11
11
  }
12
12
 
13
13
  ;
14
- export { generateQuery };
14
+
15
+ function getFrame(element) {
16
+ var doc = element && element.ownerDocument || document;
17
+ var win = doc.defaultView || doc.parentWindow;
18
+ return {
19
+ document: doc,
20
+ window: win
21
+ };
22
+ }
23
+
24
+ function getMountElement(element) {
25
+ return element || document.head;
26
+ }
27
+
28
+ export { generateQuery, getFrame, getMountElement };
package/dist/index.js CHANGED
@@ -34,39 +34,47 @@ function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Re
34
34
  var SCRIPT_ID = 'hcaptcha-api-script-id';
35
35
  var HCAPTCHA_LOAD_FN_NAME = 'hcaptchaOnLoad'; // Prevent loading API script multiple times
36
36
 
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
37
+ var scripts = []; // Generate hCaptcha API script
43
38
 
44
39
  var mountCaptchaScript = function mountCaptchaScript() {
45
40
  var params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
46
-
47
- if (document.getElementById(SCRIPT_ID)) {
41
+ var element = (0, _utils.getMountElement)(params.scriptLocation);
42
+ delete params.scriptLocation;
43
+ var frame = (0, _utils.getFrame)(element);
44
+ var script = scripts.find(function (_ref) {
45
+ var scope = _ref.scope;
46
+ return scope === frame.window;
47
+ });
48
+
49
+ if (frame.document.getElementById(SCRIPT_ID) && script) {
48
50
  // API was already requested
49
- return mountPromise;
50
- } // Create global onload callback
51
-
52
-
53
- window[HCAPTCHA_LOAD_FN_NAME] = resolveFn;
54
- var domain = params.apihost || "https://js.hcaptcha.com";
55
- delete params.apihost;
56
- var script = document.createElement("script");
57
- script.id = SCRIPT_ID;
58
- script.src = "".concat(domain, "/1/api.js?render=explicit&onload=").concat(HCAPTCHA_LOAD_FN_NAME);
59
- script.async = params.loadAsync !== undefined ? params.loadAsync : true;
60
- delete params.loadAsync;
61
-
62
- script.onerror = function (event) {
63
- return rejectFn('script-error');
64
- };
65
-
66
- var query = (0, _utils.generateQuery)(params);
67
- script.src += query !== "" ? "&".concat(query) : "";
68
- document.head.appendChild(script);
69
- return mountPromise;
51
+ return script.promise;
52
+ }
53
+
54
+ var promise = new Promise(function (resolve, reject) {
55
+ // Create global onload callback
56
+ frame.window[HCAPTCHA_LOAD_FN_NAME] = resolve;
57
+ var domain = params.apihost || "https://js.hcaptcha.com";
58
+ delete params.apihost;
59
+ var script = frame.document.createElement("script");
60
+ script.id = SCRIPT_ID;
61
+ script.src = "".concat(domain, "/1/api.js?render=explicit&onload=").concat(HCAPTCHA_LOAD_FN_NAME);
62
+ script.async = params.loadAsync !== undefined ? params.loadAsync : true;
63
+ delete params.loadAsync;
64
+
65
+ script.onerror = function (event) {
66
+ return reject('script-error');
67
+ };
68
+
69
+ var query = (0, _utils.generateQuery)(params);
70
+ script.src += query !== "" ? "&".concat(query) : "";
71
+ element.appendChild(script);
72
+ });
73
+ scripts.push({
74
+ promise: promise,
75
+ scope: frame.window
76
+ });
77
+ return promise;
70
78
  };
71
79
 
72
80
  var HCaptcha = /*#__PURE__*/function (_React$Component) {
@@ -78,7 +86,17 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
78
86
  var _this;
79
87
 
80
88
  (0, _classCallCheck2["default"])(this, HCaptcha);
81
- _this = _super.call(this, props); // API Methods
89
+ _this = _super.call(this, props);
90
+ var element = (0, _utils.getMountElement)(_this.props.scriptLocation);
91
+ var frame = (0, _utils.getFrame)(element);
92
+ /**
93
+ * Internal reference to track hCaptcha API
94
+ *
95
+ * Required as window is relative to initialization in application
96
+ * not where the script and iFrames have been loaded.
97
+ */
98
+
99
+ _this._hcaptcha = frame.window.hcaptcha || undefined; // API Methods
82
100
 
83
101
  _this.renderCaptcha = _this.renderCaptcha.bind((0, _assertThisInitialized2["default"])(_this));
84
102
  _this.resetCaptcha = _this.resetCaptcha.bind((0, _assertThisInitialized2["default"])(_this));
@@ -93,7 +111,7 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
93
111
  _this.handleOpen = _this.handleOpen.bind((0, _assertThisInitialized2["default"])(_this));
94
112
  _this.handleClose = _this.handleClose.bind((0, _assertThisInitialized2["default"])(_this));
95
113
  _this.handleChallengeExpired = _this.handleChallengeExpired.bind((0, _assertThisInitialized2["default"])(_this));
96
- var isApiReady = typeof hcaptcha !== 'undefined';
114
+ var isApiReady = typeof _this._hcaptcha !== 'undefined';
97
115
  _this.ref = /*#__PURE__*/React.createRef();
98
116
  _this.apiScriptRequested = false;
99
117
  _this.state = {
@@ -127,6 +145,7 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
127
145
  key: "componentWillUnmount",
128
146
  value: function componentWillUnmount() {
129
147
  var captchaId = this.state.captchaId;
148
+ var hcaptcha = this._hcaptcha;
130
149
 
131
150
  if (!this.isReady()) {
132
151
  return;
@@ -182,7 +201,8 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
182
201
  reportapi = _this$props.reportapi,
183
202
  sentry = _this$props.sentry,
184
203
  custom = _this$props.custom,
185
- loadAsync = _this$props.loadAsync;
204
+ loadAsync = _this$props.loadAsync,
205
+ scriptLocation = _this$props.scriptLocation;
186
206
  var mountParams = {
187
207
  apihost: apihost,
188
208
  assethost: assethost,
@@ -194,7 +214,8 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
194
214
  reportapi: reportapi,
195
215
  sentry: sentry,
196
216
  custom: custom,
197
- loadAsync: loadAsync
217
+ loadAsync: loadAsync,
218
+ scriptLocation: scriptLocation
198
219
  };
199
220
  mountCaptchaScript(mountParams).then(this.handleOnLoad)["catch"](this.handleError);
200
221
  this.apiScriptRequested = true;
@@ -214,7 +235,8 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
214
235
  }, this.props, {
215
236
  hl: this.props.hl || this.props.languageOverride,
216
237
  languageOverride: undefined
217
- }); //Render hCaptcha widget and provide necessary callbacks - hCaptcha
238
+ });
239
+ var hcaptcha = this._hcaptcha; //Render hCaptcha widget and provide necessary callbacks - hCaptcha
218
240
 
219
241
  var captchaId = hcaptcha.render(this.ref.current, renderParams);
220
242
  this.setState({
@@ -228,6 +250,7 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
228
250
  key: "resetCaptcha",
229
251
  value: function resetCaptcha() {
230
252
  var captchaId = this.state.captchaId;
253
+ var hcaptcha = this._hcaptcha;
231
254
 
232
255
  if (!this.isReady()) {
233
256
  return;
@@ -240,6 +263,7 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
240
263
  key: "removeCaptcha",
241
264
  value: function removeCaptcha(callback) {
242
265
  var captchaId = this.state.captchaId;
266
+ var hcaptcha = this._hcaptcha;
243
267
 
244
268
  if (!this.isReady()) {
245
269
  return;
@@ -260,7 +284,10 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
260
284
  this.setState({
261
285
  isApiReady: true
262
286
  }, function () {
263
- // render captcha and wait for captcha id
287
+ var element = (0, _utils.getMountElement)(_this3.props.scriptLocation);
288
+ var frame = (0, _utils.getFrame)(element);
289
+ _this3._hcaptcha = frame.window.hcaptcha; // render captcha and wait for captcha id
290
+
264
291
  _this3.renderCaptcha(function () {
265
292
  // trigger onLoad if it exists
266
293
  var onLoad = _this3.props.onLoad;
@@ -275,6 +302,7 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
275
302
  var _this$state = this.state,
276
303
  isRemoved = _this$state.isRemoved,
277
304
  captchaId = _this$state.captchaId;
305
+ var hcaptcha = this._hcaptcha;
278
306
  if (typeof hcaptcha === 'undefined' || isRemoved) return;
279
307
  var token = hcaptcha.getResponse(captchaId); //Get response token from hCaptcha widget
280
308
 
@@ -287,6 +315,7 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
287
315
  value: function handleExpire() {
288
316
  var onExpire = this.props.onExpire;
289
317
  var captchaId = this.state.captchaId;
318
+ var hcaptcha = this._hcaptcha;
290
319
 
291
320
  if (!this.isReady()) {
292
321
  return;
@@ -301,6 +330,7 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
301
330
  value: function handleError(event) {
302
331
  var onError = this.props.onError;
303
332
  var captchaId = this.state.captchaId;
333
+ var hcaptcha = this._hcaptcha;
304
334
 
305
335
  if (this.isReady()) {
306
336
  // If hCaptcha runs into error, reset captcha - hCaptcha
@@ -349,6 +379,7 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
349
379
  value: function execute() {
350
380
  var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
351
381
  var captchaId = this.state.captchaId;
382
+ var hcaptcha = this._hcaptcha;
352
383
 
353
384
  if (!this.isReady()) {
354
385
  return;
@@ -364,6 +395,7 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
364
395
  key: "setData",
365
396
  value: function setData(data) {
366
397
  var captchaId = this.state.captchaId;
398
+ var hcaptcha = this._hcaptcha;
367
399
 
368
400
  if (!this.isReady()) {
369
401
  return;
@@ -378,11 +410,13 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
378
410
  }, {
379
411
  key: "getResponse",
380
412
  value: function getResponse() {
413
+ var hcaptcha = this._hcaptcha;
381
414
  return hcaptcha.getResponse(this.state.captchaId);
382
415
  }
383
416
  }, {
384
417
  key: "getRespKey",
385
418
  value: function getRespKey() {
419
+ var hcaptcha = this._hcaptcha;
386
420
  return hcaptcha.getRespKey(this.state.captchaId);
387
421
  }
388
422
  }, {
package/dist/utils.js CHANGED
@@ -6,6 +6,8 @@ Object.defineProperty(exports, "__esModule", {
6
6
  value: true
7
7
  });
8
8
  exports.generateQuery = generateQuery;
9
+ exports.getFrame = getFrame;
10
+ exports.getMountElement = getMountElement;
9
11
 
10
12
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
11
13
 
@@ -25,4 +27,17 @@ function generateQuery(params) {
25
27
  }).join("&");
26
28
  }
27
29
 
28
- ;
30
+ ;
31
+
32
+ function getFrame(element) {
33
+ var doc = element && element.ownerDocument || document;
34
+ var win = doc.defaultView || doc.parentWindow;
35
+ return {
36
+ document: doc,
37
+ window: win
38
+ };
39
+ }
40
+
41
+ function getMountElement(element) {
42
+ return element || document.head;
43
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hcaptcha/react-hcaptcha",
3
- "version": "1.6.1",
3
+ "version": "1.8.0",
4
4
  "types": "types/index.d.ts",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -11,7 +11,7 @@
11
11
  ],
12
12
  "description": "A React library for hCaptcha",
13
13
  "scripts": {
14
- "start": "webpack serve",
14
+ "start": "webpack serve -c ./demo/webpack.config.mjs",
15
15
  "test": "jest",
16
16
  "watch": "babel src -d dist --copy-files --watch",
17
17
  "transpile": "babel src -d dist --copy-files",
package/src/index.js CHANGED
@@ -1,44 +1,50 @@
1
1
  import * as React from 'react';
2
- import { generateQuery } from "./utils.js";
2
+ import { generateQuery, getFrame, getMountElement } from "./utils.js";
3
3
 
4
4
  const SCRIPT_ID = 'hcaptcha-api-script-id';
5
5
  const HCAPTCHA_LOAD_FN_NAME = 'hcaptchaOnLoad';
6
6
 
7
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
- });
8
+ const scripts = [];
14
9
 
15
10
  // Generate hCaptcha API script
16
11
  const mountCaptchaScript = (params={}) => {
17
- if (document.getElementById(SCRIPT_ID)) {
12
+ const element = getMountElement(params.scriptLocation);
13
+ delete params.scriptLocation;
14
+
15
+ const frame = getFrame(element);
16
+ const script = scripts.find(({ scope }) => scope === frame.window);
17
+
18
+ if (frame.document.getElementById(SCRIPT_ID) && script) {
18
19
  // API was already requested
19
- return mountPromise;
20
+ return script.promise;
20
21
  }
21
22
 
22
- // Create global onload callback
23
- window[HCAPTCHA_LOAD_FN_NAME] = resolveFn;
23
+ const promise = new Promise((resolve, reject) => {
24
+ // Create global onload callback
25
+ frame.window[HCAPTCHA_LOAD_FN_NAME] = resolve;
26
+
27
+ const domain = params.apihost || "https://js.hcaptcha.com";
28
+ delete params.apihost;
24
29
 
25
- const domain = params.apihost || "https://js.hcaptcha.com";
26
- delete params.apihost;
30
+ const script = frame.document.createElement("script");
31
+ script.id = SCRIPT_ID;
32
+ script.src = `${domain}/1/api.js?render=explicit&onload=${HCAPTCHA_LOAD_FN_NAME}`;
27
33
 
28
- const script = document.createElement("script");
29
- script.id = SCRIPT_ID;
30
- script.src = `${domain}/1/api.js?render=explicit&onload=${HCAPTCHA_LOAD_FN_NAME}`;
34
+ script.async = params.loadAsync !== undefined? params.loadAsync : true;
35
+ delete params.loadAsync;
31
36
 
32
- script.async = params.loadAsync !== undefined? params.loadAsync : true;
33
- delete params.loadAsync;
37
+ script.onerror = (event) => reject('script-error');
34
38
 
35
- script.onerror = (event) => rejectFn('script-error');
39
+ const query = generateQuery(params);
40
+ script.src += query !== ""? `&${query}` : "";
36
41
 
37
- const query = generateQuery(params);
38
- script.src += query !== ""? `&${query}` : "";
42
+ element.appendChild(script);
43
+ });
39
44
 
40
- document.head.appendChild(script);
41
- return mountPromise;
45
+ scripts.push({ promise, scope: frame.window });
46
+
47
+ return promise;
42
48
  };
43
49
 
44
50
 
@@ -46,6 +52,17 @@ class HCaptcha extends React.Component {
46
52
  constructor (props) {
47
53
  super(props);
48
54
 
55
+ const element = getMountElement(this.props.scriptLocation);
56
+ const frame = getFrame(element);
57
+
58
+ /**
59
+ * Internal reference to track hCaptcha API
60
+ *
61
+ * Required as window is relative to initialization in application
62
+ * not where the script and iFrames have been loaded.
63
+ */
64
+ this._hcaptcha = frame.window.hcaptcha || undefined;
65
+
49
66
  // API Methods
50
67
  this.renderCaptcha = this.renderCaptcha.bind(this);
51
68
  this.resetCaptcha = this.resetCaptcha.bind(this);
@@ -62,7 +79,8 @@ class HCaptcha extends React.Component {
62
79
  this.handleClose = this.handleClose.bind(this);
63
80
  this.handleChallengeExpired = this.handleChallengeExpired.bind(this);
64
81
 
65
- const isApiReady = typeof hcaptcha !== 'undefined';
82
+
83
+ const isApiReady = typeof this._hcaptcha !== 'undefined';
66
84
 
67
85
  this.ref = React.createRef();
68
86
  this.apiScriptRequested = false;
@@ -94,6 +112,7 @@ class HCaptcha extends React.Component {
94
112
 
95
113
  componentWillUnmount() {
96
114
  const { captchaId } = this.state;
115
+ const hcaptcha = this._hcaptcha;
97
116
 
98
117
  if (!this.isReady()) {
99
118
  return;
@@ -143,7 +162,8 @@ class HCaptcha extends React.Component {
143
162
  reportapi,
144
163
  sentry,
145
164
  custom,
146
- loadAsync
165
+ loadAsync,
166
+ scriptLocation
147
167
  } = this.props;
148
168
  const mountParams = {
149
169
  apihost,
@@ -156,7 +176,8 @@ class HCaptcha extends React.Component {
156
176
  reportapi,
157
177
  sentry,
158
178
  custom,
159
- loadAsync
179
+ loadAsync,
180
+ scriptLocation
160
181
  };
161
182
 
162
183
  mountCaptchaScript(mountParams)
@@ -181,6 +202,7 @@ class HCaptcha extends React.Component {
181
202
  languageOverride: undefined
182
203
  });
183
204
 
205
+ const hcaptcha = this._hcaptcha;
184
206
  //Render hCaptcha widget and provide necessary callbacks - hCaptcha
185
207
  const captchaId = hcaptcha.render(this.ref.current, renderParams);
186
208
 
@@ -191,6 +213,7 @@ class HCaptcha extends React.Component {
191
213
 
192
214
  resetCaptcha() {
193
215
  const { captchaId } = this.state;
216
+ const hcaptcha = this._hcaptcha;
194
217
 
195
218
  if (!this.isReady()) {
196
219
  return;
@@ -201,6 +224,7 @@ class HCaptcha extends React.Component {
201
224
 
202
225
  removeCaptcha(callback) {
203
226
  const { captchaId } = this.state;
227
+ const hcaptcha = this._hcaptcha;
204
228
 
205
229
  if (!this.isReady()) {
206
230
  return;
@@ -214,6 +238,10 @@ class HCaptcha extends React.Component {
214
238
 
215
239
  handleOnLoad () {
216
240
  this.setState({ isApiReady: true }, () => {
241
+ const element = getMountElement(this.props.scriptLocation);
242
+ const frame = getFrame(element);
243
+
244
+ this._hcaptcha = frame.window.hcaptcha;
217
245
 
218
246
  // render captcha and wait for captcha id
219
247
  this.renderCaptcha(() => {
@@ -227,6 +255,7 @@ class HCaptcha extends React.Component {
227
255
  handleSubmit (event) {
228
256
  const { onVerify } = this.props;
229
257
  const { isRemoved, captchaId } = this.state;
258
+ const hcaptcha = this._hcaptcha;
230
259
 
231
260
  if (typeof hcaptcha === 'undefined' || isRemoved) return
232
261
 
@@ -238,6 +267,7 @@ class HCaptcha extends React.Component {
238
267
  handleExpire () {
239
268
  const { onExpire } = this.props;
240
269
  const { captchaId } = this.state;
270
+ const hcaptcha = this._hcaptcha;
241
271
 
242
272
  if (!this.isReady()) {
243
273
  return;
@@ -251,6 +281,7 @@ class HCaptcha extends React.Component {
251
281
  handleError (event) {
252
282
  const { onError } = this.props;
253
283
  const { captchaId } = this.state;
284
+ const hcaptcha = this._hcaptcha;
254
285
 
255
286
  if (this.isReady()) {
256
287
  // If hCaptcha runs into error, reset captcha - hCaptcha
@@ -292,6 +323,7 @@ class HCaptcha extends React.Component {
292
323
 
293
324
  execute (opts = null) {
294
325
  const { captchaId } = this.state;
326
+ const hcaptcha = this._hcaptcha;
295
327
 
296
328
  if (!this.isReady()) {
297
329
  return;
@@ -306,6 +338,7 @@ class HCaptcha extends React.Component {
306
338
 
307
339
  setData (data) {
308
340
  const { captchaId } = this.state;
341
+ const hcaptcha = this._hcaptcha;
309
342
 
310
343
  if (!this.isReady()) {
311
344
  return;
@@ -319,10 +352,12 @@ class HCaptcha extends React.Component {
319
352
  }
320
353
 
321
354
  getResponse() {
355
+ const hcaptcha = this._hcaptcha;
322
356
  return hcaptcha.getResponse(this.state.captchaId);
323
357
  }
324
358
 
325
359
  getRespKey() {
360
+ const hcaptcha = this._hcaptcha;
326
361
  return hcaptcha.getRespKey(this.state.captchaId)
327
362
  }
328
363
 
package/src/utils.js CHANGED
@@ -6,4 +6,18 @@ function generateQuery(params) {
6
6
  }).join("&");
7
7
  };
8
8
 
9
- export { generateQuery };
9
+ function getFrame(element) {
10
+ const doc = (element && element.ownerDocument) || document;
11
+ const win = doc.defaultView || doc.parentWindow;
12
+ return { document: doc, window: win };
13
+ }
14
+
15
+ function getMountElement(element) {
16
+ return element || document.head;
17
+ }
18
+
19
+ export {
20
+ generateQuery,
21
+ getFrame,
22
+ getMountElement
23
+ };
package/types/index.d.ts CHANGED
@@ -29,6 +29,7 @@ interface HCaptchaProps {
29
29
  id?: string;
30
30
  reCaptchaCompat?: boolean;
31
31
  loadAsync?: boolean;
32
+ scriptLocation?: HTMLElement | null;
32
33
  }
33
34
 
34
35
  interface ExecuteResponse {