Repository navigation
Release 2.4.0
v2.4.0 — Lifecycle hardening, security defaults, enterprise & abort signals
A comprehensive audit-and-improve pass. All changes are non-breaking — existing code continues to work; new features are additive; behavior changes have escape-hatch props.
See PR #6 for the full review.
✨ New features
AbortSignalsupport — cancel an in-flightgetToken():const controller = new AbortController(); recaptchaRef.current?.getToken('checkout', { signal: controller.signal }); controller.abort(); // promise rejects with 'Token request was aborted.'
- reCAPTCHA Enterprise — opt in with the new
useEnterpriseprop. Switches toenterprise.jsandgrecaptcha.enterprise.execute. onVerifynow receives the action —onVerify?: (token, action) => void. Useful when one component handles multiple actions. Existing handlers that ignore the extra arg keep working.
🐛 Bug fixes
onLoadEndfires exactly once per load cycle (was 2–3×).- Concurrent
reset()calls no longer leak the first promise — all queued resolvers now fire on the next load. reset()rejects in-flight and queued token requests immediately with a clearRESETerror (used to hang until the request's own timeout).- Superseded requests now reject with a
SUPERSEDEDerror instead of the misleadingTOKEN_REQUEST_TIMEOUT. - Force-READY timer no longer reads closure-stale
isReady/hasError— uses refs so the latest values win. handleLoadStartno longer wipespendingRequests— previously droppedgetToken()calls queued between mount and the WebView's firstonLoadStart.- Unmount cleanup — all timers (init, force-READY, in-flight, queued) and abort listeners are now cleared on unmount, fixing a real memory leak when the component unmounts mid-flight.
🛡️ Security
These change defaults but each has an opt-out prop, so existing setups can pin the old behavior if needed.
mixedContentModeis now configurable, defaulting to'never'(was hard-coded'always'). PassmixedContentMode="always"to restore.originWhitelistis now configurable, defaulting to['https://www.google.com', 'https://www.gstatic.com', baseUrl](was['*']). PassoriginWhitelist={['*']}to restore.siteKeyis regex-validated at mount (/^[A-Za-z0-9_-]{20,80}$/). Invalid keys surface viaonErrorinstead of being interpolated into the WebView's HTML/URL.
🧱 Internal
- HTML template memoized (was rebuilt every render).
- Magic numbers extracted to named constants.
NodeJS.Timeout→ portableReturnType<typeof setTimeout>.
🧪 Tests & CI (new)
- 32 unit tests covering all bug fixes and the public API, using
jest+@testing-library/react-nativewith a controllable WebView mock. - GitHub Actions CI runs lint · typecheck · test · build on every PR across Node 20 and 22.
🔌 Migrating
- "@valture/react-native-recaptcha-v3": "^2.3.0"
+ "@valture/react-native-recaptcha-v3": "^2.4.0"No source changes required.
Full Changelog: v2.3.0...v2.4.0