@hcaptcha/react-hcaptcha 1.13.1 → 1.15.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/dist/esm/index.js CHANGED
@@ -37,6 +37,12 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
37
37
  _this.apiScriptRequested = false;
38
38
  _this.sentryHub = null;
39
39
  _this.captchaId = '';
40
+
41
+ /**
42
+ * Tracks the currently pending async execute() promise.
43
+ * Stores { resolve, reject } so we can cancel on unmount/errors/etc.
44
+ */
45
+ _this._pendingExecute = null;
40
46
  _this.state = {
41
47
  isApiReady: false,
42
48
  isRemoved: false,
@@ -71,6 +77,7 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
71
77
  _proto.componentWillUnmount = function componentWillUnmount() {
72
78
  var hcaptcha = this._hcaptcha;
73
79
  var captchaId = this.captchaId;
80
+ this._cancelPendingExecute('react-component-unmounted');
74
81
  if (!this.isReady()) {
75
82
  return;
76
83
  }
@@ -185,13 +192,16 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
185
192
  if (!this.isReady()) {
186
193
  return;
187
194
  }
195
+
188
196
  // Reset captcha state, removes stored token and unticks checkbox
189
197
  hcaptcha.reset(captchaId);
198
+ this._cancelPendingExecute('hcaptcha-reset');
190
199
  };
191
200
  _proto.removeCaptcha = function removeCaptcha(callback) {
192
201
  var _this5 = this;
193
202
  var hcaptcha = this._hcaptcha;
194
203
  var captchaId = this.captchaId;
204
+ this._cancelPendingExecute('hcaptcha-removed');
195
205
  if (!this.isReady()) {
196
206
  return;
197
207
  }
@@ -257,6 +267,20 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
257
267
  isApiReady = _this$state.isApiReady,
258
268
  isRemoved = _this$state.isRemoved;
259
269
  return isApiReady && !isRemoved;
270
+ }
271
+
272
+ /**
273
+ * Cancel any pending async execute() promise
274
+ * Called when the component unmounts, errors occur, resets, etc.
275
+ */;
276
+ _proto._cancelPendingExecute = function _cancelPendingExecute(reason) {
277
+ if (!this._pendingExecute) {
278
+ return;
279
+ }
280
+ var pending = this._pendingExecute;
281
+ this._pendingExecute = null;
282
+ var error = new Error(reason);
283
+ pending.reject(error);
260
284
  };
261
285
  _proto.handleOpen = function handleOpen() {
262
286
  if (!this.isReady() || !this.props.onOpen) {
@@ -285,25 +309,71 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
285
309
  try {
286
310
  var hcaptcha = this._hcaptcha;
287
311
  var captchaId = this.captchaId;
312
+
313
+ // Is an async execute and there's already 1 pending, cancel the old one.
314
+ if (opts && opts.async && this._pendingExecute) {
315
+ this._cancelPendingExecute('hcaptcha-execute-replaced');
316
+ }
288
317
  if (!this.isReady()) {
289
- var _opts;
290
- var onReady = new Promise(function (resolve, reject) {
291
- _this7._onReady = function (id) {
292
- try {
293
- var _hcaptcha = _this7._hcaptcha;
294
- if (opts && opts.async) {
295
- _hcaptcha.execute(id, opts).then(resolve)["catch"](reject);
296
- } else {
297
- resolve(_hcaptcha.execute(id, opts));
318
+ if (opts && opts.async) {
319
+ return new Promise(function (resolve, reject) {
320
+ _this7._pendingExecute = {
321
+ resolve: resolve,
322
+ reject: reject
323
+ };
324
+ _this7._onReady = function (id) {
325
+ if (!_this7._pendingExecute) {
326
+ return;
298
327
  }
299
- } catch (e) {
300
- reject(e);
301
- }
328
+ try {
329
+ var _result = hcaptcha.execute(id, opts);
330
+ if (_result && typeof _result.then === 'function') {
331
+ _result.then(function (val) {
332
+ _this7._pendingExecute = null;
333
+ resolve(val);
334
+ })["catch"](function (err) {
335
+ _this7._pendingExecute = null;
336
+ reject(err);
337
+ });
338
+ } else {
339
+ _this7._pendingExecute = null;
340
+ reject(new Error('hcaptcha-execute-no-promise'));
341
+ }
342
+ } catch (e) {
343
+ _this7._pendingExecute = null;
344
+ reject(e);
345
+ }
346
+ };
347
+ });
348
+ } else {
349
+ // Non-async: don't return a promise.
350
+ this._onReady = function (id) {
351
+ hcaptcha.execute(id, opts);
352
+ };
353
+ return null;
354
+ }
355
+ }
356
+
357
+ // hCaptcha is ready, execute directly.
358
+ var result = hcaptcha.execute(captchaId, opts);
359
+
360
+ // If it's async execute, track it.
361
+ if (opts && opts.async && result && typeof result.then === 'function') {
362
+ return new Promise(function (resolve, reject) {
363
+ _this7._pendingExecute = {
364
+ resolve: resolve,
365
+ reject: reject
302
366
  };
367
+ result.then(function (val) {
368
+ _this7._pendingExecute = null;
369
+ resolve(val);
370
+ })["catch"](function (err) {
371
+ _this7._pendingExecute = null;
372
+ reject(err);
373
+ });
303
374
  });
304
- return (_opts = opts) != null && _opts.async ? onReady : null;
305
375
  }
306
- return hcaptcha.execute(captchaId, opts);
376
+ return result;
307
377
  } catch (error) {
308
378
  if (opts && opts.async) {
309
379
  return Promise.reject(error);
@@ -314,6 +384,7 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
314
384
  _proto.close = function close() {
315
385
  var hcaptcha = this._hcaptcha;
316
386
  var captchaId = this.captchaId;
387
+ this._cancelPendingExecute('hcaptcha-closed');
317
388
  if (!this.isReady()) {
318
389
  return;
319
390
  }
package/dist/index.js CHANGED
@@ -55,6 +55,12 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
55
55
  _this.apiScriptRequested = false;
56
56
  _this.sentryHub = null;
57
57
  _this.captchaId = '';
58
+
59
+ /**
60
+ * Tracks the currently pending async execute() promise.
61
+ * Stores { resolve, reject } so we can cancel on unmount/errors/etc.
62
+ */
63
+ _this._pendingExecute = null;
58
64
  _this.state = {
59
65
  isApiReady: false,
60
66
  isRemoved: false,
@@ -92,6 +98,7 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
92
98
  value: function componentWillUnmount() {
93
99
  var hcaptcha = this._hcaptcha;
94
100
  var captchaId = this.captchaId;
101
+ this._cancelPendingExecute('react-component-unmounted');
95
102
  if (!this.isReady()) {
96
103
  return;
97
104
  }
@@ -216,8 +223,10 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
216
223
  if (!this.isReady()) {
217
224
  return;
218
225
  }
226
+
219
227
  // Reset captcha state, removes stored token and unticks checkbox
220
228
  hcaptcha.reset(captchaId);
229
+ this._cancelPendingExecute('hcaptcha-reset');
221
230
  }
222
231
  }, {
223
232
  key: "removeCaptcha",
@@ -225,6 +234,7 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
225
234
  var _this5 = this;
226
235
  var hcaptcha = this._hcaptcha;
227
236
  var captchaId = this.captchaId;
237
+ this._cancelPendingExecute('hcaptcha-removed');
228
238
  if (!this.isReady()) {
229
239
  return;
230
240
  }
@@ -301,6 +311,22 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
301
311
  isRemoved = _this$state.isRemoved;
302
312
  return isApiReady && !isRemoved;
303
313
  }
314
+
315
+ /**
316
+ * Cancel any pending async execute() promise
317
+ * Called when the component unmounts, errors occur, resets, etc.
318
+ */
319
+ }, {
320
+ key: "_cancelPendingExecute",
321
+ value: function _cancelPendingExecute(reason) {
322
+ if (!this._pendingExecute) {
323
+ return;
324
+ }
325
+ var pending = this._pendingExecute;
326
+ this._pendingExecute = null;
327
+ var error = new Error(reason);
328
+ pending.reject(error);
329
+ }
304
330
  }, {
305
331
  key: "handleOpen",
306
332
  value: function handleOpen() {
@@ -334,25 +360,71 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
334
360
  try {
335
361
  var hcaptcha = this._hcaptcha;
336
362
  var captchaId = this.captchaId;
363
+
364
+ // Is an async execute and there's already 1 pending, cancel the old one.
365
+ if (opts && opts.async && this._pendingExecute) {
366
+ this._cancelPendingExecute('hcaptcha-execute-replaced');
367
+ }
337
368
  if (!this.isReady()) {
338
- var _opts;
339
- var onReady = new Promise(function (resolve, reject) {
340
- _this7._onReady = function (id) {
341
- try {
342
- var _hcaptcha = _this7._hcaptcha;
343
- if (opts && opts.async) {
344
- _hcaptcha.execute(id, opts).then(resolve)["catch"](reject);
345
- } else {
346
- resolve(_hcaptcha.execute(id, opts));
369
+ if (opts && opts.async) {
370
+ return new Promise(function (resolve, reject) {
371
+ _this7._pendingExecute = {
372
+ resolve: resolve,
373
+ reject: reject
374
+ };
375
+ _this7._onReady = function (id) {
376
+ if (!_this7._pendingExecute) {
377
+ return;
378
+ }
379
+ try {
380
+ var _result = hcaptcha.execute(id, opts);
381
+ if (_result && typeof _result.then === 'function') {
382
+ _result.then(function (val) {
383
+ _this7._pendingExecute = null;
384
+ resolve(val);
385
+ })["catch"](function (err) {
386
+ _this7._pendingExecute = null;
387
+ reject(err);
388
+ });
389
+ } else {
390
+ _this7._pendingExecute = null;
391
+ reject(new Error('hcaptcha-execute-no-promise'));
392
+ }
393
+ } catch (e) {
394
+ _this7._pendingExecute = null;
395
+ reject(e);
347
396
  }
348
- } catch (e) {
349
- reject(e);
350
- }
397
+ };
398
+ });
399
+ } else {
400
+ // Non-async: don't return a promise.
401
+ this._onReady = function (id) {
402
+ hcaptcha.execute(id, opts);
403
+ };
404
+ return null;
405
+ }
406
+ }
407
+
408
+ // hCaptcha is ready, execute directly.
409
+ var result = hcaptcha.execute(captchaId, opts);
410
+
411
+ // If it's async execute, track it.
412
+ if (opts && opts.async && result && typeof result.then === 'function') {
413
+ return new Promise(function (resolve, reject) {
414
+ _this7._pendingExecute = {
415
+ resolve: resolve,
416
+ reject: reject
351
417
  };
418
+ result.then(function (val) {
419
+ _this7._pendingExecute = null;
420
+ resolve(val);
421
+ })["catch"](function (err) {
422
+ _this7._pendingExecute = null;
423
+ reject(err);
424
+ });
352
425
  });
353
- return (_opts = opts) !== null && _opts !== void 0 && _opts.async ? onReady : null;
354
426
  }
355
- return hcaptcha.execute(captchaId, opts);
427
+ return result;
356
428
  } catch (error) {
357
429
  if (opts && opts.async) {
358
430
  return Promise.reject(error);
@@ -365,6 +437,7 @@ var HCaptcha = /*#__PURE__*/function (_React$Component) {
365
437
  value: function close() {
366
438
  var hcaptcha = this._hcaptcha;
367
439
  var captchaId = this.captchaId;
440
+ this._cancelPendingExecute('hcaptcha-closed');
368
441
  if (!this.isReady()) {
369
442
  return;
370
443
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hcaptcha/react-hcaptcha",
3
- "version": "1.13.1",
3
+ "version": "1.15.0",
4
4
  "types": "types/index.d.ts",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/esm/index.js",
package/src/index.js CHANGED
@@ -38,6 +38,12 @@ class HCaptcha extends React.Component {
38
38
  this.sentryHub = null;
39
39
  this.captchaId = '';
40
40
 
41
+ /**
42
+ * Tracks the currently pending async execute() promise.
43
+ * Stores { resolve, reject } so we can cancel on unmount/errors/etc.
44
+ */
45
+ this._pendingExecute = null;
46
+
41
47
  this.state = {
42
48
  isApiReady: false,
43
49
  isRemoved: false,
@@ -77,6 +83,8 @@ class HCaptcha extends React.Component {
77
83
  const hcaptcha = this._hcaptcha;
78
84
  const captchaId = this.captchaId;
79
85
 
86
+ this._cancelPendingExecute('react-component-unmounted');
87
+
80
88
  if (!this.isReady()) {
81
89
  return;
82
90
  }
@@ -201,14 +209,19 @@ class HCaptcha extends React.Component {
201
209
  if (!this.isReady()) {
202
210
  return;
203
211
  }
212
+
204
213
  // Reset captcha state, removes stored token and unticks checkbox
205
214
  hcaptcha.reset(captchaId)
215
+
216
+ this._cancelPendingExecute('hcaptcha-reset');
206
217
  }
207
218
 
208
219
  removeCaptcha(callback) {
209
220
  const hcaptcha = this._hcaptcha;
210
221
  const captchaId = this.captchaId;
211
222
 
223
+ this._cancelPendingExecute('hcaptcha-removed');
224
+
212
225
  if (!this.isReady()) {
213
226
  return;
214
227
  }
@@ -217,6 +230,7 @@ class HCaptcha extends React.Component {
217
230
  this.captchaId = '';
218
231
 
219
232
  hcaptcha.remove(captchaId);
233
+
220
234
  callback && callback()
221
235
  });
222
236
  }
@@ -285,6 +299,22 @@ class HCaptcha extends React.Component {
285
299
  return isApiReady && !isRemoved;
286
300
  }
287
301
 
302
+ /**
303
+ * Cancel any pending async execute() promise
304
+ * Called when the component unmounts, errors occur, resets, etc.
305
+ */
306
+ _cancelPendingExecute(reason) {
307
+ if (!this._pendingExecute) {
308
+ return;
309
+ }
310
+
311
+ const pending = this._pendingExecute;
312
+ this._pendingExecute = null;
313
+
314
+ const error = new Error(reason);
315
+ pending.reject(error);
316
+ }
317
+
288
318
  handleOpen () {
289
319
  if (!this.isReady() || !this.props.onOpen) {
290
320
  return;
@@ -309,7 +339,7 @@ class HCaptcha extends React.Component {
309
339
  this.props.onChalExpired();
310
340
  }
311
341
 
312
- execute (opts = null) {
342
+ execute(opts = null) {
313
343
 
314
344
  opts = typeof opts === 'object' ? opts : null;
315
345
 
@@ -317,28 +347,75 @@ class HCaptcha extends React.Component {
317
347
  const hcaptcha = this._hcaptcha;
318
348
  const captchaId = this.captchaId;
319
349
 
350
+ // Is an async execute and there's already 1 pending, cancel the old one.
351
+ if (opts && opts.async && this._pendingExecute) {
352
+ this._cancelPendingExecute('hcaptcha-execute-replaced');
353
+ }
354
+
320
355
  if (!this.isReady()) {
321
- const onReady = new Promise((resolve, reject) => {
356
+ if (opts && opts.async) {
357
+ return new Promise((resolve, reject) => {
358
+ this._pendingExecute = { resolve, reject };
322
359
 
323
- this._onReady = (id) => {
324
- try {
325
- const hcaptcha = this._hcaptcha;
360
+ this._onReady = (id) => {
361
+ if (!this._pendingExecute) {
362
+ return;
363
+ }
326
364
 
327
- if (opts && opts.async) {
328
- hcaptcha.execute(id, opts).then(resolve).catch(reject);
329
- } else {
330
- resolve(hcaptcha.execute(id, opts));
365
+ try {
366
+ const result = hcaptcha.execute(id, opts);
367
+
368
+ if (result && typeof result.then === 'function') {
369
+ result
370
+ .then((val) => {
371
+ this._pendingExecute = null;
372
+ resolve(val);
373
+ })
374
+ .catch((err) => {
375
+ this._pendingExecute = null;
376
+ reject(err);
377
+ });
378
+ } else {
379
+ this._pendingExecute = null;
380
+ reject(new Error('hcaptcha-execute-no-promise'));
381
+ }
382
+ } catch (e) {
383
+ this._pendingExecute = null;
384
+ reject(e);
331
385
  }
332
- } catch (e) {
333
- reject(e);
334
- }
386
+ };
387
+ });
388
+ } else {
389
+ // Non-async: don't return a promise.
390
+ this._onReady = (id) => {
391
+ hcaptcha.execute(id, opts);
335
392
  };
336
- });
393
+
394
+ return null;
395
+ }
396
+ }
337
397
 
338
- return opts?.async ? onReady : null;
398
+ // hCaptcha is ready, execute directly.
399
+ const result = hcaptcha.execute(captchaId, opts);
400
+
401
+ // If it's async execute, track it.
402
+ if (opts && opts.async && result && typeof result.then === 'function') {
403
+ return new Promise((resolve, reject) => {
404
+ this._pendingExecute = { resolve, reject };
405
+
406
+ result
407
+ .then((val) => {
408
+ this._pendingExecute = null;
409
+ resolve(val);
410
+ })
411
+ .catch((err) => {
412
+ this._pendingExecute = null;
413
+ reject(err);
414
+ });
415
+ });
339
416
  }
340
417
 
341
- return hcaptcha.execute(captchaId, opts);
418
+ return result;
342
419
  } catch (error) {
343
420
  if (opts && opts.async) {
344
421
  return Promise.reject(error);
@@ -351,6 +428,8 @@ class HCaptcha extends React.Component {
351
428
  const hcaptcha = this._hcaptcha;
352
429
  const captchaId = this.captchaId;
353
430
 
431
+ this._cancelPendingExecute('hcaptcha-closed');
432
+
354
433
  if (!this.isReady()) {
355
434
  return;
356
435
  }
package/types/index.d.ts CHANGED
@@ -21,6 +21,7 @@ interface HCaptchaProps {
21
21
  onError?: (event: string) => any;
22
22
  onVerify?: (token: string, ekey: string) => any;
23
23
  onLoad?: () => any;
24
+ onReady?: () => any;
24
25
  languageOverride?: string;
25
26
  sitekey: string;
26
27
  size?: "normal" | "compact" | "invisible";