@dynamic-labs-sdk/react-native-captcha 1.29.0 → 1.30.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.
- package/dist/components/HCaptchaChallenge/HCaptchaChallenge.d.ts.map +1 -1
- package/dist/components/TurnstileChallenge/TurnstileChallenge.d.ts +44 -1
- package/dist/components/TurnstileChallenge/TurnstileChallenge.d.ts.map +1 -1
- package/dist/exports/index.d.ts +1 -1
- package/dist/exports/index.d.ts.map +1 -1
- package/dist/index.cjs +34 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +34 -13
- package/dist/index.esm.js.map +1 -1
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HCaptchaChallenge.d.ts","sourceRoot":"","sources":["../../../src/components/HCaptchaChallenge/HCaptchaChallenge.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAEhC,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAIzD,OAAO,EAAE,KAAK,QAAQ,EAAgB,MAAM,+BAA+B,CAAC;AAG5E,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAE3D;;;;;;;;;GASG;AACH,eAAO,MAAM,0BAA0B,uBAAuB,CAAC;AAmB/D;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB;;;;;OAKG;IACH,OAAO,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IACxC,uEAAuE;IACvE,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B,CAAC;
|
|
1
|
+
{"version":3,"file":"HCaptchaChallenge.d.ts","sourceRoot":"","sources":["../../../src/components/HCaptchaChallenge/HCaptchaChallenge.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAEhC,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAIzD,OAAO,EAAE,KAAK,QAAQ,EAAgB,MAAM,+BAA+B,CAAC;AAG5E,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAE3D;;;;;;;;;GASG;AACH,eAAO,MAAM,0BAA0B,uBAAuB,CAAC;AAmB/D;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB;;;;;OAKG;IACH,OAAO,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IACxC,uEAAuE;IACvE,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B,CAAC;AAmDF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,iBAAiB,EAAE,EAAE,CAAC,sBAAsB,CA2FxD,CAAC"}
|
|
@@ -13,6 +13,30 @@ import { TurnstileError } from '../../errors/TurnstileError';
|
|
|
13
13
|
* @see TurnstileChallengeProps
|
|
14
14
|
*/
|
|
15
15
|
export declare const TURNSTILE_CHALLENGE_TEST_ID = "turnstile-challenge";
|
|
16
|
+
/**
|
|
17
|
+
* Mirrors Cloudflare Turnstile's `data-appearance` values this component
|
|
18
|
+
* supports.
|
|
19
|
+
*
|
|
20
|
+
* - `'always'` (default): the widget is visible as soon as it renders.
|
|
21
|
+
* - `'interaction-only'`: the widget stays invisible and solves silently
|
|
22
|
+
* for low-risk clients; it only renders UI when Turnstile decides the
|
|
23
|
+
* visitor must interact. Pair with `onInteractive` on
|
|
24
|
+
* {@link TurnstileChallengeProps} to reveal the host container at that
|
|
25
|
+
* point instead of showing a captcha card up front.
|
|
26
|
+
*
|
|
27
|
+
* Cloudflare also defines `'execute'`, which leaves the widget invisible
|
|
28
|
+
* until something calls `turnstile.execute()` — this component never does,
|
|
29
|
+
* so it is omitted here rather than exposed as an option that would hang
|
|
30
|
+
* forever.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```tsx
|
|
34
|
+
* const appearance: TurnstileAppearance = 'interaction-only';
|
|
35
|
+
* ```
|
|
36
|
+
* @see TurnstileChallenge
|
|
37
|
+
* @see TurnstileChallengeProps
|
|
38
|
+
*/
|
|
39
|
+
export type TurnstileAppearance = 'always' | 'interaction-only';
|
|
16
40
|
/**
|
|
17
41
|
* Props for the {@link TurnstileChallenge} component.
|
|
18
42
|
*
|
|
@@ -30,6 +54,13 @@ export declare const TURNSTILE_CHALLENGE_TEST_ID = "turnstile-challenge";
|
|
|
30
54
|
* @see TurnstileError
|
|
31
55
|
*/
|
|
32
56
|
export type TurnstileChallengeProps = {
|
|
57
|
+
/**
|
|
58
|
+
* How the widget presents itself. Defaults to `'always'`, matching
|
|
59
|
+
* Cloudflare's own default. Pass `'interaction-only'` to solve silently for
|
|
60
|
+
* low-risk clients and only surface UI when Turnstile demands it — combine
|
|
61
|
+
* with `onInteractive` to reveal the host container at that point.
|
|
62
|
+
*/
|
|
63
|
+
appearance?: TurnstileAppearance;
|
|
33
64
|
/**
|
|
34
65
|
* Origin the widget document is served under. Turnstile refuses to render on
|
|
35
66
|
* origin-less documents, and real (non-test) site keys only render on hosts
|
|
@@ -48,6 +79,15 @@ export type TurnstileChallengeProps = {
|
|
|
48
79
|
* their own timeout.
|
|
49
80
|
*/
|
|
50
81
|
onError: (error: TurnstileError) => void;
|
|
82
|
+
/**
|
|
83
|
+
* Called right before Turnstile renders its interactive challenge UI
|
|
84
|
+
* (Turnstile's `before-interactive-callback`). Fires under any
|
|
85
|
+
* `appearance`, but only `'interaction-only'` makes it meaningful — under
|
|
86
|
+
* `'always'` the UI is already showing by the time this fires, so there's
|
|
87
|
+
* nothing to reveal. Use it to unhide a container that was kept invisible
|
|
88
|
+
* while the widget solved silently.
|
|
89
|
+
*/
|
|
90
|
+
onInteractive?: () => void;
|
|
51
91
|
/** Called with the token Turnstile issues when the widget is solved. */
|
|
52
92
|
onToken: (token: string) => void;
|
|
53
93
|
/**
|
|
@@ -65,13 +105,16 @@ export type TurnstileChallengeProps = {
|
|
|
65
105
|
* resulting captcha token over the postMessage bridge. The caller hands the
|
|
66
106
|
* token to the SDK's `setCaptchaToken`. Exactly one of `onToken`/`onError`
|
|
67
107
|
* fires, exactly once — the first terminal event (token, challenge error,
|
|
68
|
-
* expiry, or WebView load failure) wins.
|
|
108
|
+
* expiry, or WebView load failure) wins. `onInteractive` is not terminal and
|
|
109
|
+
* may fire any number of times before that.
|
|
69
110
|
*
|
|
70
111
|
* @example
|
|
71
112
|
* ```tsx
|
|
72
113
|
* <TurnstileChallenge
|
|
73
114
|
* baseUrl="https://your-whitelisted-origin.com"
|
|
74
115
|
* siteKey="1x00000000000000000000AA"
|
|
116
|
+
* appearance="interaction-only"
|
|
117
|
+
* onInteractive={() => setCaptchaVisible(true)}
|
|
75
118
|
* onToken={(token) => setCaptchaToken({ captchaToken: token })}
|
|
76
119
|
* onError={(error) => console.error(error.message)}
|
|
77
120
|
* style={{ height: 120 }}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TurnstileChallenge.d.ts","sourceRoot":"","sources":["../../../src/components/TurnstileChallenge/TurnstileChallenge.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAEhC,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAIzD,OAAO,EAAE,KAAK,QAAQ,EAAgB,MAAM,+BAA+B,CAAC;AAG5E,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAE7D;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"TurnstileChallenge.d.ts","sourceRoot":"","sources":["../../../src/components/TurnstileChallenge/TurnstileChallenge.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAEhC,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAIzD,OAAO,EAAE,KAAK,QAAQ,EAAgB,MAAM,+BAA+B,CAAC;AAG5E,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAE7D;;;;;;;;;GASG;AACH,eAAO,MAAM,2BAA2B,wBAAwB,CAAC;AAsBjE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,kBAAkB,CAAC;AAShE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC;;;;;OAKG;IACH,UAAU,CAAC,EAAE,mBAAmB,CAAC;IACjC;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB;;;;;OAKG;IACH,OAAO,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IACzC;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,wEAAwE;IACxE,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B,CAAC;AAkEF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,kBAAkB,EAAE,EAAE,CAAC,uBAAuB,CA0G1D,CAAC"}
|
package/dist/exports/index.d.ts
CHANGED
|
@@ -2,6 +2,6 @@ export { HCAPTCHA_CHALLENGE_TEST_ID, HCaptchaChallenge, } from '../components/HC
|
|
|
2
2
|
export type { HCaptchaChallengeProps } from '../components/HCaptchaChallenge/HCaptchaChallenge';
|
|
3
3
|
export { HCaptchaError } from '../errors/HCaptchaError';
|
|
4
4
|
export { TURNSTILE_CHALLENGE_TEST_ID, TurnstileChallenge, } from '../components/TurnstileChallenge/TurnstileChallenge';
|
|
5
|
-
export type { TurnstileChallengeProps } from '../components/TurnstileChallenge/TurnstileChallenge';
|
|
5
|
+
export type { TurnstileAppearance, TurnstileChallengeProps, } from '../components/TurnstileChallenge/TurnstileChallenge';
|
|
6
6
|
export { TurnstileError } from '../errors/TurnstileError';
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/exports/index.ts"],"names":[],"mappings":"AASA,OAAO,EACL,0BAA0B,EAC1B,iBAAiB,GAClB,MAAM,mDAAmD,CAAC;AAC3D,YAAY,EAAE,sBAAsB,EAAE,MAAM,mDAAmD,CAAC;AAChG,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAExD,OAAO,EACL,2BAA2B,EAC3B,kBAAkB,GACnB,MAAM,qDAAqD,CAAC;AAC7D,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/exports/index.ts"],"names":[],"mappings":"AASA,OAAO,EACL,0BAA0B,EAC1B,iBAAiB,GAClB,MAAM,mDAAmD,CAAC;AAC3D,YAAY,EAAE,sBAAsB,EAAE,MAAM,mDAAmD,CAAC;AAChG,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAExD,OAAO,EACL,2BAA2B,EAC3B,kBAAkB,GACnB,MAAM,qDAAqD,CAAC;AAC7D,YAAY,EACV,mBAAmB,EACnB,uBAAuB,GACxB,MAAM,qDAAqD,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC"}
|
package/dist/index.cjs
CHANGED
|
@@ -38,7 +38,7 @@ let react_jsx_runtime = require("react/jsx-runtime");
|
|
|
38
38
|
|
|
39
39
|
//#region package.json
|
|
40
40
|
var name = "@dynamic-labs-sdk/react-native-captcha";
|
|
41
|
-
var version = "1.
|
|
41
|
+
var version = "1.30.2";
|
|
42
42
|
|
|
43
43
|
//#endregion
|
|
44
44
|
//#region src/errors/HCaptchaError.ts
|
|
@@ -280,15 +280,20 @@ var TurnstileError = class extends _dynamic_labs_sdk_client.BaseError {
|
|
|
280
280
|
* @see TurnstileChallengeProps
|
|
281
281
|
*/
|
|
282
282
|
const TURNSTILE_CHALLENGE_TEST_ID = "turnstile-challenge";
|
|
283
|
-
const bridgeMessageSchema = zod_mini.union([
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
type: zod_mini.enum(["
|
|
289
|
-
|
|
283
|
+
const bridgeMessageSchema = zod_mini.union([
|
|
284
|
+
zod_mini.object({
|
|
285
|
+
message: zod_mini.optional(zod_mini.string()),
|
|
286
|
+
type: zod_mini.enum(["error"])
|
|
287
|
+
}),
|
|
288
|
+
zod_mini.object({ type: zod_mini.enum(["interactive"]) }),
|
|
289
|
+
zod_mini.object({
|
|
290
|
+
token: zod_mini.string(),
|
|
291
|
+
type: zod_mini.enum(["token"])
|
|
292
|
+
})
|
|
293
|
+
]);
|
|
294
|
+
const resolveAppearance = (appearance) => appearance === "interaction-only" ? appearance : "always";
|
|
290
295
|
const sanitizeSiteKey = (siteKey) => siteKey.replace(/[^\w-]/g, "");
|
|
291
|
-
const buildTurnstileHtml = (siteKey) => `
|
|
296
|
+
const buildTurnstileHtml = ({ appearance, siteKey }) => `
|
|
292
297
|
<!DOCTYPE html>
|
|
293
298
|
<html>
|
|
294
299
|
<head>
|
|
@@ -302,9 +307,11 @@ const buildTurnstileHtml = (siteKey) => `
|
|
|
302
307
|
<div
|
|
303
308
|
class="cf-turnstile"
|
|
304
309
|
data-sitekey="${siteKey}"
|
|
310
|
+
data-appearance="${appearance}"
|
|
305
311
|
data-callback="onSuccess"
|
|
306
312
|
data-error-callback="onError"
|
|
307
313
|
data-expired-callback="onExpired"
|
|
314
|
+
data-before-interactive-callback="onBeforeInteractive"
|
|
308
315
|
></div>
|
|
309
316
|
<script>
|
|
310
317
|
const post = (payload) => {
|
|
@@ -326,6 +333,13 @@ const buildTurnstileHtml = (siteKey) => `
|
|
|
326
333
|
window.onExpired = () => {
|
|
327
334
|
post({ type: 'error', message: 'token expired before it was claimed' });
|
|
328
335
|
};
|
|
336
|
+
// Fires right before Turnstile shows interactive UI, under any
|
|
337
|
+
// appearance — meaningful mainly under interaction-only, where it's the
|
|
338
|
+
// signal that the widget, otherwise solving silently, now needs the
|
|
339
|
+
// visitor to act.
|
|
340
|
+
window.onBeforeInteractive = () => {
|
|
341
|
+
post({ type: 'interactive' });
|
|
342
|
+
};
|
|
329
343
|
<\/script>
|
|
330
344
|
</body>
|
|
331
345
|
</html>
|
|
@@ -335,13 +349,16 @@ const buildTurnstileHtml = (siteKey) => `
|
|
|
335
349
|
* resulting captcha token over the postMessage bridge. The caller hands the
|
|
336
350
|
* token to the SDK's `setCaptchaToken`. Exactly one of `onToken`/`onError`
|
|
337
351
|
* fires, exactly once — the first terminal event (token, challenge error,
|
|
338
|
-
* expiry, or WebView load failure) wins.
|
|
352
|
+
* expiry, or WebView load failure) wins. `onInteractive` is not terminal and
|
|
353
|
+
* may fire any number of times before that.
|
|
339
354
|
*
|
|
340
355
|
* @example
|
|
341
356
|
* ```tsx
|
|
342
357
|
* <TurnstileChallenge
|
|
343
358
|
* baseUrl="https://your-whitelisted-origin.com"
|
|
344
359
|
* siteKey="1x00000000000000000000AA"
|
|
360
|
+
* appearance="interaction-only"
|
|
361
|
+
* onInteractive={() => setCaptchaVisible(true)}
|
|
345
362
|
* onToken={(token) => setCaptchaToken({ captchaToken: token })}
|
|
346
363
|
* onError={(error) => console.error(error.message)}
|
|
347
364
|
* style={{ height: 120 }}
|
|
@@ -352,7 +369,7 @@ const buildTurnstileHtml = (siteKey) => `
|
|
|
352
369
|
* @see TurnstileChallengeProps
|
|
353
370
|
* @see TurnstileError
|
|
354
371
|
*/
|
|
355
|
-
const TurnstileChallenge = ({ baseUrl, logLevel, onError, onToken, siteKey, style }) => {
|
|
372
|
+
const TurnstileChallenge = ({ appearance, baseUrl, logLevel, onError, onInteractive, onToken, siteKey, style }) => {
|
|
356
373
|
const settledRef = (0, react.useRef)(false);
|
|
357
374
|
const logger = (0, react.useMemo)(() => (0, _dynamic_labs_sdk_client_core.createLogger)({ level: logLevel ?? "warn" }), [logLevel]);
|
|
358
375
|
const settleWith = (dispatch) => {
|
|
@@ -382,7 +399,8 @@ const TurnstileChallenge = ({ baseUrl, logLevel, onError, onToken, siteKey, styl
|
|
|
382
399
|
if (parsed.data.type === "token") {
|
|
383
400
|
const token = parsed.data.token;
|
|
384
401
|
settleWith(() => onToken(token));
|
|
385
|
-
} else
|
|
402
|
+
} else if (parsed.data.type === "interactive") onInteractive?.();
|
|
403
|
+
else {
|
|
386
404
|
const message = parsed.data.message;
|
|
387
405
|
settleWith(() => onError(new TurnstileError({ description: message })));
|
|
388
406
|
}
|
|
@@ -403,7 +421,10 @@ const TurnstileChallenge = ({ baseUrl, logLevel, onError, onToken, siteKey, styl
|
|
|
403
421
|
originWhitelist: ["https://*", "about:srcdoc"],
|
|
404
422
|
source: {
|
|
405
423
|
baseUrl,
|
|
406
|
-
html: buildTurnstileHtml(
|
|
424
|
+
html: buildTurnstileHtml({
|
|
425
|
+
appearance: resolveAppearance(appearance),
|
|
426
|
+
siteKey: sanitizeSiteKey(siteKey)
|
|
427
|
+
})
|
|
407
428
|
},
|
|
408
429
|
style: styles.webView
|
|
409
430
|
})
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["BaseError","bridgeMessageSchema","z","sanitizeSiteKey","HCaptchaChallenge: FC<HCaptchaChallengeProps>","data: unknown","View","styles","WebView","StyleSheet","BaseError","z","TurnstileChallenge: FC<TurnstileChallengeProps>","data: unknown","View","WebView","StyleSheet","packageName","packageVersion"],"sources":["../package.json","../src/errors/HCaptchaError.ts","../src/components/HCaptchaChallenge/HCaptchaChallenge.tsx","../src/errors/TurnstileError.ts","../src/components/TurnstileChallenge/TurnstileChallenge.tsx","../src/exports/index.ts"],"sourcesContent":["","import { BaseError } from '@dynamic-labs-sdk/client';\n\n/**\n * Thrown when the embedded hCaptcha challenge fails to resolve — for example\n * the widget reports an error, the token expires, or the user closes the\n * challenge without completing it.\n *\n * The {@link HCaptchaChallenge} component surfaces this typed error through its\n * `onError` callback so consumers can distinguish a failed captcha from other\n * failures and prompt the user to retry rather than treating it as a fatal\n * error.\n *\n * @example\n * ```tsx\n * <HCaptchaChallenge\n * onError={(error) => {\n * if (error instanceof HCaptchaError) {\n * promptRetry(error.message);\n * }\n * }}\n * />\n * ```\n * @see HCaptchaChallenge\n * @see HCaptchaChallengeProps\n */\nexport class HCaptchaError extends BaseError {\n constructor({ description }: { description: string | undefined }) {\n super({\n cause: null,\n code: 'hcaptcha_failed',\n docsUrl: null,\n name: 'HCaptchaError',\n shortMessage: `hCaptcha failed: ${description ?? 'unknown error'}`,\n });\n }\n}\n","'use client';\n\nimport type { FC } from 'react';\nimport { useMemo, useRef } from 'react';\nimport type { StyleProp, ViewStyle } from 'react-native';\nimport { StyleSheet, View } from 'react-native';\nimport type { WebViewMessageEvent } from 'react-native-webview';\nimport WebView from 'react-native-webview';\nimport { type LogLevel, createLogger } from '@dynamic-labs-sdk/client/core';\nimport * as z from 'zod/mini';\n\nimport { HCaptchaError } from '../../errors/HCaptchaError';\n\n/**\n * `testID` e2e drivers use to locate the hCaptcha widget on screen.\n *\n * @example\n * ```tsx\n * <View testID={HCAPTCHA_CHALLENGE_TEST_ID} />\n * ```\n * @see HCaptchaChallenge\n * @see HCaptchaChallengeProps\n */\nexport const HCAPTCHA_CHALLENGE_TEST_ID = 'hcaptcha-challenge';\n\n// react-native-webview only exports the message event type from its root;\n// for error events this is the minimal shape both onError and onHttpError\n// deliver.\ntype WebViewErrorEvent = { nativeEvent: { description?: string } };\n\n// Payload the widget document posts over the bridge (see buildHCaptchaHtml).\nconst bridgeMessageSchema = z.union([\n z.object({\n message: z.optional(z.string()),\n type: z.enum(['error']),\n }),\n z.object({\n token: z.string(),\n type: z.enum(['token']),\n }),\n]);\n\n/**\n * Props for the {@link HCaptchaChallenge} component.\n *\n * @example\n * ```tsx\n * const props: HCaptchaChallengeProps = {\n * baseUrl: 'https://your-whitelisted-origin.com',\n * onError: console.error,\n * onToken: setToken,\n * siteKey: '10000000-ffff-ffff-ffff-000000000001',\n * logLevel: 'warn',\n * };\n * ```\n * @see HCaptchaChallenge\n * @see HCaptchaError\n */\nexport type HCaptchaChallengeProps = {\n /**\n * Origin the widget document is served under. hCaptcha refuses to render on\n * origin-less documents, and real (non-test) site keys only render on hosts\n * whitelisted for the key — pass an origin your site key accepts.\n */\n baseUrl: string;\n /**\n * Optional minimum log level for the component's internal logger. Defaults\n * to `'warn'` when omitted.\n */\n logLevel?: LogLevel;\n /**\n * Called when the widget cannot produce a token: the WebView fails to load,\n * hCaptcha reports a challenge error, or the token expires unclaimed —\n * so callers awaiting `onToken` fail immediately instead of hanging until\n * their own timeout.\n */\n onError: (error: HCaptchaError) => void;\n /** Called with the token hCaptcha issues when the widget is solved. */\n onToken: (token: string) => void;\n /**\n * The environment's hCaptcha site key, from the SDK's `getCaptchaSettings`.\n */\n siteKey: string;\n /**\n * Optional style for the outer container. Use this to set width/height,\n * margins, border radius, or background color to match your app's UI.\n */\n style?: StyleProp<ViewStyle>;\n};\n\n// The site key is server-controlled, but it lands inside an HTML attribute —\n// keep only the charset real hCaptcha site keys use so a misconfigured value\n// cannot break out of the markup.\nconst sanitizeSiteKey = (siteKey: string) => siteKey.replace(/[^\\w-]/g, '');\n\nconst buildHCaptchaHtml = (siteKey: string) => `\n<!DOCTYPE html>\n<html>\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <!-- No Subresource Integrity: hCaptcha serves a mutable loader script and\n does not support SRI pinning; a pinned hash would break on their next\n deploy. The script loads from hCaptcha's own origin over HTTPS. -->\n <script src=\"https://js.hcaptcha.com/1/api.js\" async defer></script>\n</head>\n<body style=\"margin:0;padding:0;display:flex;justify-content:center;align-items:center;min-height:100vh;\">\n <div\n class=\"h-captcha\"\n data-sitekey=\"${siteKey}\"\n data-callback=\"onSuccess\"\n data-error-callback=\"onChallengeError\"\n data-expired-callback=\"onExpired\"\n ></div>\n <script>\n const post = (payload) => {\n window.ReactNativeWebView.postMessage(JSON.stringify(payload));\n };\n // hCaptcha resolves the data-*-callback attributes by name off the global\n // scope, so these handlers must be assigned onto window — a bare const\n // binding would not be reachable and the callbacks would never fire.\n window.onSuccess = (token) => {\n post({ type: 'token', token: token });\n };\n window.onChallengeError = (error) => {\n // Cap the hCaptcha-supplied error before forwarding it: it only ends up\n // in an HCaptchaError message, so bound its length rather than surface an\n // arbitrarily long string from the widget.\n const errorMessage = String(error).substring(0, 200);\n post({ type: 'error', message: 'challenge error: ' + errorMessage });\n };\n window.onExpired = () => {\n post({ type: 'error', message: 'token expired before it was claimed' });\n };\n </script>\n</body>\n</html>\n`;\n\n/**\n * Renders the hCaptcha checkbox widget in an inline WebView and reports the\n * resulting captcha token over the postMessage bridge. The caller hands the\n * token to the SDK's `setCaptchaToken`. Exactly one of `onToken`/`onError`\n * fires, exactly once — the first terminal event (token, challenge error,\n * expiry, or WebView load failure) wins.\n *\n * @example\n * ```tsx\n * <HCaptchaChallenge\n * baseUrl=\"https://your-whitelisted-origin.com\"\n * siteKey=\"10000000-ffff-ffff-ffff-000000000001\"\n * onToken={(token) => setCaptchaToken({ captchaToken: token })}\n * onError={(error) => console.error(error.message)}\n * style={{ height: 260 }}\n * logLevel=\"warn\"\n * />\n * ```\n * @returns A React Native view that hosts the captcha WebView.\n * @see HCaptchaChallengeProps\n * @see HCaptchaError\n */\nexport const HCaptchaChallenge: FC<HCaptchaChallengeProps> = ({\n baseUrl,\n logLevel,\n onError,\n onToken,\n siteKey,\n style,\n}) => {\n // First terminal event wins: hCaptcha only issues one token per solve, but\n // guard against duplicate bridge messages and double error events (onError\n // and onHttpError can both fire for one failure) so the callbacks keep\n // their called-once contract.\n const settledRef = useRef(false);\n\n const logger = useMemo(\n () => createLogger({ level: logLevel ?? 'warn' }),\n [logLevel]\n );\n\n // eslint-disable-next-line custom-rules/require-single-object-param -- (dispatch) is the entire input of this local helper\n const settleWith = (dispatch: () => void) => {\n if (settledRef.current) {\n return;\n }\n\n settledRef.current = true;\n dispatch();\n };\n\n const handleMessage = (event: WebViewMessageEvent) => {\n let data: unknown;\n try {\n data = JSON.parse(event.nativeEvent.data);\n } catch (error) {\n logger.debug('Malformed hCaptcha bridge message', {\n error: error instanceof Error ? error.message : String(error),\n rawData: String(event.nativeEvent.data).slice(0, 200),\n });\n return;\n }\n\n const parsed = bridgeMessageSchema.safeParse(data);\n if (!parsed.success) {\n logger.debug('Malformed hCaptcha bridge message', {\n error: parsed.error.message,\n rawData: String(event.nativeEvent.data).slice(0, 200),\n });\n return;\n }\n\n if (parsed.data.type === 'token') {\n const token = parsed.data.token;\n settleWith(() => onToken(token));\n } else {\n const message = parsed.data.message;\n settleWith(() => onError(new HCaptchaError({ description: message })));\n }\n };\n\n const handleLoadError = (event: WebViewErrorEvent) => {\n settleWith(() =>\n onError(\n new HCaptchaError({\n description: `WebView failed to load: ${event.nativeEvent.description ?? 'unknown error'}`,\n })\n )\n );\n };\n\n return (\n <View style={[styles.container, style]} testID={HCAPTCHA_CHALLENGE_TEST_ID}>\n <WebView\n domStorageEnabled\n javaScriptEnabled\n thirdPartyCookiesEnabled\n onError={handleLoadError}\n onHttpError={handleLoadError}\n onMessage={handleMessage}\n // https-only: hCaptcha's iframes may come from several hosts, but a\n // custom-scheme navigation escaping the WebView is never legitimate.\n originWhitelist={['https://*']}\n source={{\n baseUrl,\n html: buildHCaptchaHtml(sanitizeSiteKey(siteKey)),\n }}\n style={styles.webView}\n />\n </View>\n );\n};\n\nconst styles = StyleSheet.create({\n container: {\n // Tall enough for the checkbox widget plus the verification animation;\n // the challenge popup never appears with the always-pass test site key.\n height: 260,\n overflow: 'hidden',\n },\n webView: {\n backgroundColor: 'transparent',\n flex: 1,\n },\n});\n","import { BaseError } from '@dynamic-labs-sdk/client';\n\n/**\n * Thrown when the embedded Cloudflare Turnstile challenge fails to resolve —\n * for example the widget reports an error, the token expires, or the user\n * closes the challenge without completing it.\n *\n * The {@link TurnstileChallenge} component surfaces this typed error through\n * its `onError` callback so consumers can distinguish a failed captcha from\n * other failures and prompt the user to retry rather than treating it as a\n * fatal error.\n *\n * @example\n * ```tsx\n * <TurnstileChallenge\n * onError={(error) => {\n * if (error instanceof TurnstileError) {\n * promptRetry(error.message);\n * }\n * }}\n * />\n * ```\n * @see TurnstileChallenge\n * @see TurnstileChallengeProps\n */\nexport class TurnstileError extends BaseError {\n constructor({ description }: { description: string | undefined }) {\n super({\n cause: null,\n code: 'turnstile_failed',\n docsUrl: null,\n name: 'TurnstileError',\n shortMessage: `Turnstile failed: ${description ?? 'unknown error'}`,\n });\n }\n}\n","'use client';\n\nimport type { FC } from 'react';\nimport { useMemo, useRef } from 'react';\nimport type { StyleProp, ViewStyle } from 'react-native';\nimport { StyleSheet, View } from 'react-native';\nimport type { WebViewMessageEvent } from 'react-native-webview';\nimport WebView from 'react-native-webview';\nimport { type LogLevel, createLogger } from '@dynamic-labs-sdk/client/core';\nimport * as z from 'zod/mini';\n\nimport { TurnstileError } from '../../errors/TurnstileError';\n\n/**\n * `testID` e2e drivers use to locate the Turnstile widget on screen.\n *\n * @example\n * ```tsx\n * <View testID={TURNSTILE_CHALLENGE_TEST_ID} />\n * ```\n * @see TurnstileChallenge\n * @see TurnstileChallengeProps\n */\nexport const TURNSTILE_CHALLENGE_TEST_ID = 'turnstile-challenge';\n\n// react-native-webview only exports the message event type from its root;\n// for error events this is the minimal shape both onError and onHttpError\n// deliver.\ntype WebViewErrorEvent = { nativeEvent: { description?: string } };\n\n// Payload the widget document posts over the bridge (see buildTurnstileHtml).\nconst bridgeMessageSchema = z.union([\n z.object({\n message: z.optional(z.string()),\n type: z.enum(['error']),\n }),\n z.object({\n token: z.string(),\n type: z.enum(['token']),\n }),\n]);\n\n/**\n * Props for the {@link TurnstileChallenge} component.\n *\n * @example\n * ```tsx\n * const props: TurnstileChallengeProps = {\n * baseUrl: 'https://your-whitelisted-origin.com',\n * onError: console.error,\n * onToken: setToken,\n * siteKey: '1x00000000000000000000AA',\n * logLevel: 'warn',\n * };\n * ```\n * @see TurnstileChallenge\n * @see TurnstileError\n */\nexport type TurnstileChallengeProps = {\n /**\n * Origin the widget document is served under. Turnstile refuses to render on\n * origin-less documents, and real (non-test) site keys only render on hosts\n * whitelisted for the key — pass an origin your site key accepts.\n */\n baseUrl: string;\n /**\n * Optional minimum log level for the component's internal logger. Defaults\n * to `'warn'` when omitted.\n */\n logLevel?: LogLevel;\n /**\n * Called when the widget cannot produce a token: the WebView fails to load,\n * Turnstile reports a challenge error, or the token expires unclaimed —\n * so callers awaiting `onToken` fail immediately instead of hanging until\n * their own timeout.\n */\n onError: (error: TurnstileError) => void;\n /** Called with the token Turnstile issues when the widget is solved. */\n onToken: (token: string) => void;\n /**\n * The environment's Turnstile site key, from the SDK's `getCaptchaSettings`.\n */\n siteKey: string;\n /**\n * Optional style for the outer container. Use this to set width/height,\n * margins, border radius, or background color to match your app's UI.\n */\n style?: StyleProp<ViewStyle>;\n};\n\n// The site key is server-controlled, but it lands inside an HTML attribute —\n// keep only the charset real Turnstile site keys use so a misconfigured value\n// cannot break out of the markup.\nconst sanitizeSiteKey = (siteKey: string) => siteKey.replace(/[^\\w-]/g, '');\n\nconst buildTurnstileHtml = (siteKey: string) => `\n<!DOCTYPE html>\n<html>\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <!-- No Subresource Integrity: Cloudflare serves a mutable loader script and\n does not support SRI pinning; a pinned hash would break on their next\n deploy. The script loads from Cloudflare's own origin over HTTPS. -->\n <script src=\"https://challenges.cloudflare.com/turnstile/v0/api.js\" async defer></script>\n</head>\n<body style=\"margin:0;padding:0;display:flex;justify-content:center;align-items:center;min-height:100vh;\">\n <div\n class=\"cf-turnstile\"\n data-sitekey=\"${siteKey}\"\n data-callback=\"onSuccess\"\n data-error-callback=\"onError\"\n data-expired-callback=\"onExpired\"\n ></div>\n <script>\n const post = (payload) => {\n window.ReactNativeWebView.postMessage(JSON.stringify(payload));\n };\n // Turnstile resolves the data-*-callback attributes by name off the global\n // scope, so these handlers must be assigned onto window — a bare const\n // binding would not be reachable and the callbacks would never fire.\n window.onSuccess = (token) => {\n post({ type: 'token', token: token });\n };\n window.onError = (error) => {\n // Cap the Turnstile-supplied error before forwarding it: it only ends up\n // in a TurnstileError message, so bound its length rather than surface an\n // arbitrarily long string from the widget.\n const errorMessage = String(error).substring(0, 200);\n post({ type: 'error', message: 'challenge error: ' + errorMessage });\n };\n window.onExpired = () => {\n post({ type: 'error', message: 'token expired before it was claimed' });\n };\n </script>\n</body>\n</html>\n`;\n\n/**\n * Renders the Cloudflare Turnstile widget in an inline WebView and reports the\n * resulting captcha token over the postMessage bridge. The caller hands the\n * token to the SDK's `setCaptchaToken`. Exactly one of `onToken`/`onError`\n * fires, exactly once — the first terminal event (token, challenge error,\n * expiry, or WebView load failure) wins.\n *\n * @example\n * ```tsx\n * <TurnstileChallenge\n * baseUrl=\"https://your-whitelisted-origin.com\"\n * siteKey=\"1x00000000000000000000AA\"\n * onToken={(token) => setCaptchaToken({ captchaToken: token })}\n * onError={(error) => console.error(error.message)}\n * style={{ height: 120 }}\n * logLevel=\"warn\"\n * />\n * ```\n * @returns A React Native view that hosts the captcha WebView.\n * @see TurnstileChallengeProps\n * @see TurnstileError\n */\nexport const TurnstileChallenge: FC<TurnstileChallengeProps> = ({\n baseUrl,\n logLevel,\n onError,\n onToken,\n siteKey,\n style,\n}) => {\n // First terminal event wins: Turnstile only issues one token per solve, but\n // guard against duplicate bridge messages and double error events (onError\n // and onHttpError can both fire for one failure) so the callbacks keep\n // their called-once contract.\n const settledRef = useRef(false);\n\n const logger = useMemo(\n () => createLogger({ level: logLevel ?? 'warn' }),\n [logLevel]\n );\n\n // eslint-disable-next-line custom-rules/require-single-object-param -- (dispatch) is the entire input of this local helper\n const settleWith = (dispatch: () => void) => {\n if (settledRef.current) {\n return;\n }\n\n settledRef.current = true;\n dispatch();\n };\n\n const handleMessage = (event: WebViewMessageEvent) => {\n let data: unknown;\n try {\n data = JSON.parse(event.nativeEvent.data);\n } catch (error) {\n logger.debug('Malformed Turnstile bridge message', {\n error: error instanceof Error ? error.message : String(error),\n rawData: String(event.nativeEvent.data).slice(0, 200),\n });\n return;\n }\n\n const parsed = bridgeMessageSchema.safeParse(data);\n if (!parsed.success) {\n logger.debug('Malformed Turnstile bridge message', {\n error: parsed.error.message,\n rawData: String(event.nativeEvent.data).slice(0, 200),\n });\n return;\n }\n\n if (parsed.data.type === 'token') {\n const token = parsed.data.token;\n settleWith(() => onToken(token));\n } else {\n const message = parsed.data.message;\n settleWith(() => onError(new TurnstileError({ description: message })));\n }\n };\n\n const handleLoadError = (event: WebViewErrorEvent) => {\n settleWith(() =>\n onError(\n new TurnstileError({\n description: `WebView failed to load: ${event.nativeEvent.description ?? 'unknown error'}`,\n })\n )\n );\n };\n\n return (\n <View style={[styles.container, style]} testID={TURNSTILE_CHALLENGE_TEST_ID}>\n <WebView\n domStorageEnabled\n javaScriptEnabled\n thirdPartyCookiesEnabled\n onError={handleLoadError}\n onHttpError={handleLoadError}\n onMessage={handleMessage}\n // Turnstile renders challenge frames over HTTPS, but its iOS WebView\n // placeholder iframes use about:blank/about:srcdoc (the WebView already\n // allows about:blank by default, so we only need to add about:srcdoc).\n // Keep the whitelist https-only to block cleartext or custom-scheme nav.\n originWhitelist={['https://*', 'about:srcdoc']}\n source={{\n baseUrl,\n html: buildTurnstileHtml(sanitizeSiteKey(siteKey)),\n }}\n style={styles.webView}\n />\n </View>\n );\n};\n\nconst styles = StyleSheet.create({\n container: {\n // Tall enough for the widget plus the verification animation.\n height: 120,\n overflow: 'hidden',\n },\n webView: {\n backgroundColor: 'transparent',\n flex: 1,\n },\n});\n","import { assertPackageVersion } from '@dynamic-labs-sdk/assert-package-version';\n\nimport {\n name as packageName,\n version as packageVersion,\n} from '../../package.json';\n\nassertPackageVersion(packageName, packageVersion);\n\nexport {\n HCAPTCHA_CHALLENGE_TEST_ID,\n HCaptchaChallenge,\n} from '../components/HCaptchaChallenge/HCaptchaChallenge';\nexport type { HCaptchaChallengeProps } from '../components/HCaptchaChallenge/HCaptchaChallenge';\nexport { HCaptchaError } from '../errors/HCaptchaError';\n\nexport {\n TURNSTILE_CHALLENGE_TEST_ID,\n TurnstileChallenge,\n} from '../components/TurnstileChallenge/TurnstileChallenge';\nexport type { TurnstileChallengeProps } from '../components/TurnstileChallenge/TurnstileChallenge';\nexport { TurnstileError } from '../errors/TurnstileError';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["BaseError","bridgeMessageSchema","z","sanitizeSiteKey","HCaptchaChallenge: FC<HCaptchaChallengeProps>","data: unknown","View","styles","WebView","StyleSheet","BaseError","z","TurnstileChallenge: FC<TurnstileChallengeProps>","data: unknown","View","WebView","StyleSheet","packageName","packageVersion"],"sources":["../package.json","../src/errors/HCaptchaError.ts","../src/components/HCaptchaChallenge/HCaptchaChallenge.tsx","../src/errors/TurnstileError.ts","../src/components/TurnstileChallenge/TurnstileChallenge.tsx","../src/exports/index.ts"],"sourcesContent":["","import { BaseError } from '@dynamic-labs-sdk/client';\n\n/**\n * Thrown when the embedded hCaptcha challenge fails to resolve — for example\n * the widget reports an error, the token expires, or the user closes the\n * challenge without completing it.\n *\n * The {@link HCaptchaChallenge} component surfaces this typed error through its\n * `onError` callback so consumers can distinguish a failed captcha from other\n * failures and prompt the user to retry rather than treating it as a fatal\n * error.\n *\n * @example\n * ```tsx\n * <HCaptchaChallenge\n * onError={(error) => {\n * if (error instanceof HCaptchaError) {\n * promptRetry(error.message);\n * }\n * }}\n * />\n * ```\n * @see HCaptchaChallenge\n * @see HCaptchaChallengeProps\n */\nexport class HCaptchaError extends BaseError {\n constructor({ description }: { description: string | undefined }) {\n super({\n cause: null,\n code: 'hcaptcha_failed',\n docsUrl: null,\n name: 'HCaptchaError',\n shortMessage: `hCaptcha failed: ${description ?? 'unknown error'}`,\n });\n }\n}\n","'use client';\n\nimport type { FC } from 'react';\nimport { useMemo, useRef } from 'react';\nimport type { StyleProp, ViewStyle } from 'react-native';\nimport { StyleSheet, View } from 'react-native';\nimport type { WebViewMessageEvent } from 'react-native-webview';\nimport WebView from 'react-native-webview';\nimport { type LogLevel, createLogger } from '@dynamic-labs-sdk/client/core';\nimport * as z from 'zod/mini';\n\nimport { HCaptchaError } from '../../errors/HCaptchaError';\n\n/**\n * `testID` e2e drivers use to locate the hCaptcha widget on screen.\n *\n * @example\n * ```tsx\n * <View testID={HCAPTCHA_CHALLENGE_TEST_ID} />\n * ```\n * @see HCaptchaChallenge\n * @see HCaptchaChallengeProps\n */\nexport const HCAPTCHA_CHALLENGE_TEST_ID = 'hcaptcha-challenge';\n\n// react-native-webview only exports the message event type from its root;\n// for error events this is the minimal shape both onError and onHttpError\n// deliver.\ntype WebViewErrorEvent = { nativeEvent: { description?: string } };\n\n// Payload the widget document posts over the bridge (see buildHCaptchaHtml).\nconst bridgeMessageSchema = z.union([\n z.object({\n message: z.optional(z.string()),\n type: z.enum(['error']),\n }),\n z.object({\n token: z.string(),\n type: z.enum(['token']),\n }),\n]);\n\n/**\n * Props for the {@link HCaptchaChallenge} component.\n *\n * @example\n * ```tsx\n * const props: HCaptchaChallengeProps = {\n * baseUrl: 'https://your-whitelisted-origin.com',\n * onError: console.error,\n * onToken: setToken,\n * siteKey: '10000000-ffff-ffff-ffff-000000000001',\n * logLevel: 'warn',\n * };\n * ```\n * @see HCaptchaChallenge\n * @see HCaptchaError\n */\nexport type HCaptchaChallengeProps = {\n /**\n * Origin the widget document is served under. hCaptcha refuses to render on\n * origin-less documents, and real (non-test) site keys only render on hosts\n * whitelisted for the key — pass an origin your site key accepts.\n */\n baseUrl: string;\n /**\n * Optional minimum log level for the component's internal logger. Defaults\n * to `'warn'` when omitted.\n */\n logLevel?: LogLevel;\n /**\n * Called when the widget cannot produce a token: the WebView fails to load,\n * hCaptcha reports a challenge error, or the token expires unclaimed —\n * so callers awaiting `onToken` fail immediately instead of hanging until\n * their own timeout.\n */\n onError: (error: HCaptchaError) => void;\n /** Called with the token hCaptcha issues when the widget is solved. */\n onToken: (token: string) => void;\n /**\n * The environment's hCaptcha site key, from the SDK's `getCaptchaSettings`.\n */\n siteKey: string;\n /**\n * Optional style for the outer container. Use this to set width/height,\n * margins, border radius, or background color to match your app's UI.\n */\n style?: StyleProp<ViewStyle>;\n};\n\n// The site key is server-controlled, but it lands inside an HTML attribute —\n// keep only the charset real hCaptcha site keys use so a misconfigured value\n// cannot break out of the markup.\nconst sanitizeSiteKey = (siteKey: string): string =>\n siteKey.replace(/[^\\w-]/g, '');\n\nconst buildHCaptchaHtml = (siteKey: string): string => `\n<!DOCTYPE html>\n<html>\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <!-- No Subresource Integrity: hCaptcha serves a mutable loader script and\n does not support SRI pinning; a pinned hash would break on their next\n deploy. The script loads from hCaptcha's own origin over HTTPS. -->\n <script src=\"https://js.hcaptcha.com/1/api.js\" async defer></script>\n</head>\n<body style=\"margin:0;padding:0;display:flex;justify-content:center;align-items:center;min-height:100vh;\">\n <div\n class=\"h-captcha\"\n data-sitekey=\"${siteKey}\"\n data-callback=\"onSuccess\"\n data-error-callback=\"onChallengeError\"\n data-expired-callback=\"onExpired\"\n ></div>\n <script>\n const post = (payload) => {\n window.ReactNativeWebView.postMessage(JSON.stringify(payload));\n };\n // hCaptcha resolves the data-*-callback attributes by name off the global\n // scope, so these handlers must be assigned onto window — a bare const\n // binding would not be reachable and the callbacks would never fire.\n window.onSuccess = (token) => {\n post({ type: 'token', token: token });\n };\n window.onChallengeError = (error) => {\n // Cap the hCaptcha-supplied error before forwarding it: it only ends up\n // in an HCaptchaError message, so bound its length rather than surface an\n // arbitrarily long string from the widget.\n const errorMessage = String(error).substring(0, 200);\n post({ type: 'error', message: 'challenge error: ' + errorMessage });\n };\n window.onExpired = () => {\n post({ type: 'error', message: 'token expired before it was claimed' });\n };\n </script>\n</body>\n</html>\n`;\n\n/**\n * Renders the hCaptcha checkbox widget in an inline WebView and reports the\n * resulting captcha token over the postMessage bridge. The caller hands the\n * token to the SDK's `setCaptchaToken`. Exactly one of `onToken`/`onError`\n * fires, exactly once — the first terminal event (token, challenge error,\n * expiry, or WebView load failure) wins.\n *\n * @example\n * ```tsx\n * <HCaptchaChallenge\n * baseUrl=\"https://your-whitelisted-origin.com\"\n * siteKey=\"10000000-ffff-ffff-ffff-000000000001\"\n * onToken={(token) => setCaptchaToken({ captchaToken: token })}\n * onError={(error) => console.error(error.message)}\n * style={{ height: 260 }}\n * logLevel=\"warn\"\n * />\n * ```\n * @returns A React Native view that hosts the captcha WebView.\n * @see HCaptchaChallengeProps\n * @see HCaptchaError\n */\nexport const HCaptchaChallenge: FC<HCaptchaChallengeProps> = ({\n baseUrl,\n logLevel,\n onError,\n onToken,\n siteKey,\n style,\n}) => {\n // First terminal event wins: hCaptcha only issues one token per solve, but\n // guard against duplicate bridge messages and double error events (onError\n // and onHttpError can both fire for one failure) so the callbacks keep\n // their called-once contract.\n const settledRef = useRef(false);\n\n const logger = useMemo(\n () => createLogger({ level: logLevel ?? 'warn' }),\n [logLevel]\n );\n\n // eslint-disable-next-line custom-rules/require-single-object-param -- (dispatch) is the entire input of this local helper\n const settleWith = (dispatch: () => void) => {\n if (settledRef.current) {\n return;\n }\n\n settledRef.current = true;\n dispatch();\n };\n\n const handleMessage = (event: WebViewMessageEvent) => {\n let data: unknown;\n try {\n data = JSON.parse(event.nativeEvent.data);\n } catch (error) {\n logger.debug('Malformed hCaptcha bridge message', {\n error: error instanceof Error ? error.message : String(error),\n rawData: String(event.nativeEvent.data).slice(0, 200),\n });\n return;\n }\n\n const parsed = bridgeMessageSchema.safeParse(data);\n if (!parsed.success) {\n logger.debug('Malformed hCaptcha bridge message', {\n error: parsed.error.message,\n rawData: String(event.nativeEvent.data).slice(0, 200),\n });\n return;\n }\n\n if (parsed.data.type === 'token') {\n const token = parsed.data.token;\n settleWith(() => onToken(token));\n } else {\n const message = parsed.data.message;\n settleWith(() => onError(new HCaptchaError({ description: message })));\n }\n };\n\n const handleLoadError = (event: WebViewErrorEvent) => {\n settleWith(() =>\n onError(\n new HCaptchaError({\n description: `WebView failed to load: ${\n event.nativeEvent.description ?? 'unknown error'\n }`,\n })\n )\n );\n };\n\n return (\n <View style={[styles.container, style]} testID={HCAPTCHA_CHALLENGE_TEST_ID}>\n <WebView\n domStorageEnabled\n javaScriptEnabled\n thirdPartyCookiesEnabled\n onError={handleLoadError}\n onHttpError={handleLoadError}\n onMessage={handleMessage}\n // https-only: hCaptcha's iframes may come from several hosts, but a\n // custom-scheme navigation escaping the WebView is never legitimate.\n originWhitelist={['https://*']}\n source={{\n baseUrl,\n html: buildHCaptchaHtml(sanitizeSiteKey(siteKey)),\n }}\n style={styles.webView}\n />\n </View>\n );\n};\n\nconst styles = StyleSheet.create({\n container: {\n // Tall enough for the checkbox widget plus the verification animation;\n // the challenge popup never appears with the always-pass test site key.\n height: 260,\n overflow: 'hidden',\n },\n webView: {\n backgroundColor: 'transparent',\n flex: 1,\n },\n});\n","import { BaseError } from '@dynamic-labs-sdk/client';\n\n/**\n * Thrown when the embedded Cloudflare Turnstile challenge fails to resolve —\n * for example the widget reports an error, the token expires, or the user\n * closes the challenge without completing it.\n *\n * The {@link TurnstileChallenge} component surfaces this typed error through\n * its `onError` callback so consumers can distinguish a failed captcha from\n * other failures and prompt the user to retry rather than treating it as a\n * fatal error.\n *\n * @example\n * ```tsx\n * <TurnstileChallenge\n * onError={(error) => {\n * if (error instanceof TurnstileError) {\n * promptRetry(error.message);\n * }\n * }}\n * />\n * ```\n * @see TurnstileChallenge\n * @see TurnstileChallengeProps\n */\nexport class TurnstileError extends BaseError {\n constructor({ description }: { description: string | undefined }) {\n super({\n cause: null,\n code: 'turnstile_failed',\n docsUrl: null,\n name: 'TurnstileError',\n shortMessage: `Turnstile failed: ${description ?? 'unknown error'}`,\n });\n }\n}\n","'use client';\n\nimport type { FC } from 'react';\nimport { useMemo, useRef } from 'react';\nimport type { StyleProp, ViewStyle } from 'react-native';\nimport { StyleSheet, View } from 'react-native';\nimport type { WebViewMessageEvent } from 'react-native-webview';\nimport WebView from 'react-native-webview';\nimport { type LogLevel, createLogger } from '@dynamic-labs-sdk/client/core';\nimport * as z from 'zod/mini';\n\nimport { TurnstileError } from '../../errors/TurnstileError';\n\n/**\n * `testID` e2e drivers use to locate the Turnstile widget on screen.\n *\n * @example\n * ```tsx\n * <View testID={TURNSTILE_CHALLENGE_TEST_ID} />\n * ```\n * @see TurnstileChallenge\n * @see TurnstileChallengeProps\n */\nexport const TURNSTILE_CHALLENGE_TEST_ID = 'turnstile-challenge';\n\n// react-native-webview only exports the message event type from its root;\n// for error events this is the minimal shape both onError and onHttpError\n// deliver.\ntype WebViewErrorEvent = { nativeEvent: { description?: string } };\n\n// Payload the widget document posts over the bridge (see buildTurnstileHtml).\nconst bridgeMessageSchema = z.union([\n z.object({\n message: z.optional(z.string()),\n type: z.enum(['error']),\n }),\n z.object({\n type: z.enum(['interactive']),\n }),\n z.object({\n token: z.string(),\n type: z.enum(['token']),\n }),\n]);\n\n/**\n * Mirrors Cloudflare Turnstile's `data-appearance` values this component\n * supports.\n *\n * - `'always'` (default): the widget is visible as soon as it renders.\n * - `'interaction-only'`: the widget stays invisible and solves silently\n * for low-risk clients; it only renders UI when Turnstile decides the\n * visitor must interact. Pair with `onInteractive` on\n * {@link TurnstileChallengeProps} to reveal the host container at that\n * point instead of showing a captcha card up front.\n *\n * Cloudflare also defines `'execute'`, which leaves the widget invisible\n * until something calls `turnstile.execute()` — this component never does,\n * so it is omitted here rather than exposed as an option that would hang\n * forever.\n *\n * @example\n * ```tsx\n * const appearance: TurnstileAppearance = 'interaction-only';\n * ```\n * @see TurnstileChallenge\n * @see TurnstileChallengeProps\n */\nexport type TurnstileAppearance = 'always' | 'interaction-only';\n\n// Guards against a runtime type-system bypass landing an arbitrary string in\n// an HTML attribute — mirrors the defensiveness of sanitizeSiteKey below.\nconst resolveAppearance = (\n appearance: TurnstileAppearance | undefined\n): TurnstileAppearance =>\n appearance === 'interaction-only' ? appearance : 'always';\n\n/**\n * Props for the {@link TurnstileChallenge} component.\n *\n * @example\n * ```tsx\n * const props: TurnstileChallengeProps = {\n * baseUrl: 'https://your-whitelisted-origin.com',\n * onError: console.error,\n * onToken: setToken,\n * siteKey: '1x00000000000000000000AA',\n * logLevel: 'warn',\n * };\n * ```\n * @see TurnstileChallenge\n * @see TurnstileError\n */\nexport type TurnstileChallengeProps = {\n /**\n * How the widget presents itself. Defaults to `'always'`, matching\n * Cloudflare's own default. Pass `'interaction-only'` to solve silently for\n * low-risk clients and only surface UI when Turnstile demands it — combine\n * with `onInteractive` to reveal the host container at that point.\n */\n appearance?: TurnstileAppearance;\n /**\n * Origin the widget document is served under. Turnstile refuses to render on\n * origin-less documents, and real (non-test) site keys only render on hosts\n * whitelisted for the key — pass an origin your site key accepts.\n */\n baseUrl: string;\n /**\n * Optional minimum log level for the component's internal logger. Defaults\n * to `'warn'` when omitted.\n */\n logLevel?: LogLevel;\n /**\n * Called when the widget cannot produce a token: the WebView fails to load,\n * Turnstile reports a challenge error, or the token expires unclaimed —\n * so callers awaiting `onToken` fail immediately instead of hanging until\n * their own timeout.\n */\n onError: (error: TurnstileError) => void;\n /**\n * Called right before Turnstile renders its interactive challenge UI\n * (Turnstile's `before-interactive-callback`). Fires under any\n * `appearance`, but only `'interaction-only'` makes it meaningful — under\n * `'always'` the UI is already showing by the time this fires, so there's\n * nothing to reveal. Use it to unhide a container that was kept invisible\n * while the widget solved silently.\n */\n onInteractive?: () => void;\n /** Called with the token Turnstile issues when the widget is solved. */\n onToken: (token: string) => void;\n /**\n * The environment's Turnstile site key, from the SDK's `getCaptchaSettings`.\n */\n siteKey: string;\n /**\n * Optional style for the outer container. Use this to set width/height,\n * margins, border radius, or background color to match your app's UI.\n */\n style?: StyleProp<ViewStyle>;\n};\n\n// The site key is server-controlled, but it lands inside an HTML attribute —\n// keep only the charset real Turnstile site keys use so a misconfigured value\n// cannot break out of the markup.\nconst sanitizeSiteKey = (siteKey: string): string =>\n siteKey.replace(/[^\\w-]/g, '');\n\nconst buildTurnstileHtml = ({\n appearance,\n siteKey,\n}: {\n appearance: TurnstileAppearance;\n siteKey: string;\n}): string => `\n<!DOCTYPE html>\n<html>\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <!-- No Subresource Integrity: Cloudflare serves a mutable loader script and\n does not support SRI pinning; a pinned hash would break on their next\n deploy. The script loads from Cloudflare's own origin over HTTPS. -->\n <script src=\"https://challenges.cloudflare.com/turnstile/v0/api.js\" async defer></script>\n</head>\n<body style=\"margin:0;padding:0;display:flex;justify-content:center;align-items:center;min-height:100vh;\">\n <div\n class=\"cf-turnstile\"\n data-sitekey=\"${siteKey}\"\n data-appearance=\"${appearance}\"\n data-callback=\"onSuccess\"\n data-error-callback=\"onError\"\n data-expired-callback=\"onExpired\"\n data-before-interactive-callback=\"onBeforeInteractive\"\n ></div>\n <script>\n const post = (payload) => {\n window.ReactNativeWebView.postMessage(JSON.stringify(payload));\n };\n // Turnstile resolves the data-*-callback attributes by name off the global\n // scope, so these handlers must be assigned onto window — a bare const\n // binding would not be reachable and the callbacks would never fire.\n window.onSuccess = (token) => {\n post({ type: 'token', token: token });\n };\n window.onError = (error) => {\n // Cap the Turnstile-supplied error before forwarding it: it only ends up\n // in a TurnstileError message, so bound its length rather than surface an\n // arbitrarily long string from the widget.\n const errorMessage = String(error).substring(0, 200);\n post({ type: 'error', message: 'challenge error: ' + errorMessage });\n };\n window.onExpired = () => {\n post({ type: 'error', message: 'token expired before it was claimed' });\n };\n // Fires right before Turnstile shows interactive UI, under any\n // appearance — meaningful mainly under interaction-only, where it's the\n // signal that the widget, otherwise solving silently, now needs the\n // visitor to act.\n window.onBeforeInteractive = () => {\n post({ type: 'interactive' });\n };\n </script>\n</body>\n</html>\n`;\n\n/**\n * Renders the Cloudflare Turnstile widget in an inline WebView and reports the\n * resulting captcha token over the postMessage bridge. The caller hands the\n * token to the SDK's `setCaptchaToken`. Exactly one of `onToken`/`onError`\n * fires, exactly once — the first terminal event (token, challenge error,\n * expiry, or WebView load failure) wins. `onInteractive` is not terminal and\n * may fire any number of times before that.\n *\n * @example\n * ```tsx\n * <TurnstileChallenge\n * baseUrl=\"https://your-whitelisted-origin.com\"\n * siteKey=\"1x00000000000000000000AA\"\n * appearance=\"interaction-only\"\n * onInteractive={() => setCaptchaVisible(true)}\n * onToken={(token) => setCaptchaToken({ captchaToken: token })}\n * onError={(error) => console.error(error.message)}\n * style={{ height: 120 }}\n * logLevel=\"warn\"\n * />\n * ```\n * @returns A React Native view that hosts the captcha WebView.\n * @see TurnstileChallengeProps\n * @see TurnstileError\n */\nexport const TurnstileChallenge: FC<TurnstileChallengeProps> = ({\n appearance,\n baseUrl,\n logLevel,\n onError,\n onInteractive,\n onToken,\n siteKey,\n style,\n}) => {\n // First terminal event wins: Turnstile only issues one token per solve, but\n // guard against duplicate bridge messages and double error events (onError\n // and onHttpError can both fire for one failure) so the callbacks keep\n // their called-once contract.\n const settledRef = useRef(false);\n\n const logger = useMemo(\n () => createLogger({ level: logLevel ?? 'warn' }),\n [logLevel]\n );\n\n // eslint-disable-next-line custom-rules/require-single-object-param -- (dispatch) is the entire input of this local helper\n const settleWith = (dispatch: () => void) => {\n if (settledRef.current) {\n return;\n }\n\n settledRef.current = true;\n dispatch();\n };\n\n const handleMessage = (event: WebViewMessageEvent) => {\n let data: unknown;\n try {\n data = JSON.parse(event.nativeEvent.data);\n } catch (error) {\n logger.debug('Malformed Turnstile bridge message', {\n error: error instanceof Error ? error.message : String(error),\n rawData: String(event.nativeEvent.data).slice(0, 200),\n });\n return;\n }\n\n const parsed = bridgeMessageSchema.safeParse(data);\n if (!parsed.success) {\n logger.debug('Malformed Turnstile bridge message', {\n error: parsed.error.message,\n rawData: String(event.nativeEvent.data).slice(0, 200),\n });\n return;\n }\n\n if (parsed.data.type === 'token') {\n const token = parsed.data.token;\n settleWith(() => onToken(token));\n } else if (parsed.data.type === 'interactive') {\n // Not a terminal event — the widget is still solving, just now needs\n // the visitor's input — so this bypasses settleWith and can fire\n // independently of whether onToken/onError have already run.\n onInteractive?.();\n } else {\n const message = parsed.data.message;\n settleWith(() => onError(new TurnstileError({ description: message })));\n }\n };\n\n const handleLoadError = (event: WebViewErrorEvent) => {\n settleWith(() =>\n onError(\n new TurnstileError({\n description: `WebView failed to load: ${\n event.nativeEvent.description ?? 'unknown error'\n }`,\n })\n )\n );\n };\n\n return (\n <View\n style={[styles.container, style]}\n testID={TURNSTILE_CHALLENGE_TEST_ID}\n >\n <WebView\n domStorageEnabled\n javaScriptEnabled\n thirdPartyCookiesEnabled\n onError={handleLoadError}\n onHttpError={handleLoadError}\n onMessage={handleMessage}\n // Turnstile renders challenge frames over HTTPS, but its iOS WebView\n // placeholder iframes use about:blank/about:srcdoc (the WebView already\n // allows about:blank by default, so we only need to add about:srcdoc).\n // Keep the whitelist https-only to block cleartext or custom-scheme nav.\n originWhitelist={['https://*', 'about:srcdoc']}\n source={{\n baseUrl,\n html: buildTurnstileHtml({\n appearance: resolveAppearance(appearance),\n siteKey: sanitizeSiteKey(siteKey),\n }),\n }}\n style={styles.webView}\n />\n </View>\n );\n};\n\nconst styles = StyleSheet.create({\n container: {\n // Tall enough for the widget plus the verification animation.\n height: 120,\n overflow: 'hidden',\n },\n webView: {\n backgroundColor: 'transparent',\n flex: 1,\n },\n});\n","import { assertPackageVersion } from '@dynamic-labs-sdk/assert-package-version';\n\nimport {\n name as packageName,\n version as packageVersion,\n} from '../../package.json';\n\nassertPackageVersion(packageName, packageVersion);\n\nexport {\n HCAPTCHA_CHALLENGE_TEST_ID,\n HCaptchaChallenge,\n} from '../components/HCaptchaChallenge/HCaptchaChallenge';\nexport type { HCaptchaChallengeProps } from '../components/HCaptchaChallenge/HCaptchaChallenge';\nexport { HCaptchaError } from '../errors/HCaptchaError';\n\nexport {\n TURNSTILE_CHALLENGE_TEST_ID,\n TurnstileChallenge,\n} from '../components/TurnstileChallenge/TurnstileChallenge';\nexport type {\n TurnstileAppearance,\n TurnstileChallengeProps,\n} from '../components/TurnstileChallenge/TurnstileChallenge';\nexport { TurnstileError } from '../errors/TurnstileError';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACyBA,IAAa,gBAAb,cAAmCA,mCAAU;CAC3C,YAAY,EAAE,eAAoD;AAChE,QAAM;GACJ,OAAO;GACP,MAAM;GACN,SAAS;GACT,MAAM;GACN,cAAc,oBAAoB,eAAe;GAClD,CAAC;;;;;;;;;;;;;;;;ACVN,MAAa,6BAA6B;AAQ1C,MAAMC,wBAAsBC,SAAE,MAAM,CAClCA,SAAE,OAAO;CACP,SAASA,SAAE,SAASA,SAAE,QAAQ,CAAC;CAC/B,MAAMA,SAAE,KAAK,CAAC,QAAQ,CAAC;CACxB,CAAC,EACFA,SAAE,OAAO;CACP,OAAOA,SAAE,QAAQ;CACjB,MAAMA,SAAE,KAAK,CAAC,QAAQ,CAAC;CACxB,CAAC,CACH,CAAC;AAqDF,MAAMC,qBAAmB,YACvB,QAAQ,QAAQ,WAAW,GAAG;AAEhC,MAAM,qBAAqB,YAA4B;;;;;;;;;;;;;oBAanC,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoD5B,MAAaC,qBAAiD,EAC5D,SACA,UACA,SACA,SACA,SACA,YACI;CAKJ,MAAM,+BAAoB,MAAM;CAEhC,MAAM,kFACe,EAAE,OAAO,YAAY,QAAQ,CAAC,EACjD,CAAC,SAAS,CACX;CAGD,MAAM,cAAc,aAAyB;AAC3C,MAAI,WAAW,QACb;AAGF,aAAW,UAAU;AACrB,YAAU;;CAGZ,MAAM,iBAAiB,UAA+B;EACpD,IAAIC;AACJ,MAAI;AACF,UAAO,KAAK,MAAM,MAAM,YAAY,KAAK;WAClC,OAAO;AACd,UAAO,MAAM,qCAAqC;IAChD,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;IAC7D,SAAS,OAAO,MAAM,YAAY,KAAK,CAAC,MAAM,GAAG,IAAI;IACtD,CAAC;AACF;;EAGF,MAAM,SAASJ,sBAAoB,UAAU,KAAK;AAClD,MAAI,CAAC,OAAO,SAAS;AACnB,UAAO,MAAM,qCAAqC;IAChD,OAAO,OAAO,MAAM;IACpB,SAAS,OAAO,MAAM,YAAY,KAAK,CAAC,MAAM,GAAG,IAAI;IACtD,CAAC;AACF;;AAGF,MAAI,OAAO,KAAK,SAAS,SAAS;GAChC,MAAM,QAAQ,OAAO,KAAK;AAC1B,oBAAiB,QAAQ,MAAM,CAAC;SAC3B;GACL,MAAM,UAAU,OAAO,KAAK;AAC5B,oBAAiB,QAAQ,IAAI,cAAc,EAAE,aAAa,SAAS,CAAC,CAAC,CAAC;;;CAI1E,MAAM,mBAAmB,UAA6B;AACpD,mBACE,QACE,IAAI,cAAc,EAChB,aAAa,2BACX,MAAM,YAAY,eAAe,mBAEpC,CAAC,CACH,CACF;;AAGH,QACE,2CAACK;EAAK,OAAO,CAACC,SAAO,WAAW,MAAM;EAAE,QAAQ;YAC9C,2CAACC;GACC;GACA;GACA;GACA,SAAS;GACT,aAAa;GACb,WAAW;GAGX,iBAAiB,CAAC,YAAY;GAC9B,QAAQ;IACN;IACA,MAAM,kBAAkBL,kBAAgB,QAAQ,CAAC;IAClD;GACD,OAAOI,SAAO;IACd;GACG;;AAIX,MAAMA,WAASE,wBAAW,OAAO;CAC/B,WAAW;EAGT,QAAQ;EACR,UAAU;EACX;CACD,SAAS;EACP,iBAAiB;EACjB,MAAM;EACP;CACF,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;AChPF,IAAa,iBAAb,cAAoCC,mCAAU;CAC5C,YAAY,EAAE,eAAoD;AAChE,QAAM;GACJ,OAAO;GACP,MAAM;GACN,SAAS;GACT,MAAM;GACN,cAAc,qBAAqB,eAAe;GACnD,CAAC;;;;;;;;;;;;;;;;ACVN,MAAa,8BAA8B;AAQ3C,MAAM,sBAAsBC,SAAE,MAAM;CAClCA,SAAE,OAAO;EACP,SAASA,SAAE,SAASA,SAAE,QAAQ,CAAC;EAC/B,MAAMA,SAAE,KAAK,CAAC,QAAQ,CAAC;EACxB,CAAC;CACFA,SAAE,OAAO,EACP,MAAMA,SAAE,KAAK,CAAC,cAAc,CAAC,EAC9B,CAAC;CACFA,SAAE,OAAO;EACP,OAAOA,SAAE,QAAQ;EACjB,MAAMA,SAAE,KAAK,CAAC,QAAQ,CAAC;EACxB,CAAC;CACH,CAAC;AA6BF,MAAM,qBACJ,eAEA,eAAe,qBAAqB,aAAa;AAqEnD,MAAM,mBAAmB,YACvB,QAAQ,QAAQ,WAAW,GAAG;AAEhC,MAAM,sBAAsB,EAC1B,YACA,cAIY;;;;;;;;;;;;;oBAaM,QAAQ;uBACL,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+DlC,MAAaC,sBAAmD,EAC9D,YACA,SACA,UACA,SACA,eACA,SACA,SACA,YACI;CAKJ,MAAM,+BAAoB,MAAM;CAEhC,MAAM,kFACe,EAAE,OAAO,YAAY,QAAQ,CAAC,EACjD,CAAC,SAAS,CACX;CAGD,MAAM,cAAc,aAAyB;AAC3C,MAAI,WAAW,QACb;AAGF,aAAW,UAAU;AACrB,YAAU;;CAGZ,MAAM,iBAAiB,UAA+B;EACpD,IAAIC;AACJ,MAAI;AACF,UAAO,KAAK,MAAM,MAAM,YAAY,KAAK;WAClC,OAAO;AACd,UAAO,MAAM,sCAAsC;IACjD,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;IAC7D,SAAS,OAAO,MAAM,YAAY,KAAK,CAAC,MAAM,GAAG,IAAI;IACtD,CAAC;AACF;;EAGF,MAAM,SAAS,oBAAoB,UAAU,KAAK;AAClD,MAAI,CAAC,OAAO,SAAS;AACnB,UAAO,MAAM,sCAAsC;IACjD,OAAO,OAAO,MAAM;IACpB,SAAS,OAAO,MAAM,YAAY,KAAK,CAAC,MAAM,GAAG,IAAI;IACtD,CAAC;AACF;;AAGF,MAAI,OAAO,KAAK,SAAS,SAAS;GAChC,MAAM,QAAQ,OAAO,KAAK;AAC1B,oBAAiB,QAAQ,MAAM,CAAC;aACvB,OAAO,KAAK,SAAS,cAI9B,kBAAiB;OACZ;GACL,MAAM,UAAU,OAAO,KAAK;AAC5B,oBAAiB,QAAQ,IAAI,eAAe,EAAE,aAAa,SAAS,CAAC,CAAC,CAAC;;;CAI3E,MAAM,mBAAmB,UAA6B;AACpD,mBACE,QACE,IAAI,eAAe,EACjB,aAAa,2BACX,MAAM,YAAY,eAAe,mBAEpC,CAAC,CACH,CACF;;AAGH,QACE,2CAACC;EACC,OAAO,CAAC,OAAO,WAAW,MAAM;EAChC,QAAQ;YAER,2CAACC;GACC;GACA;GACA;GACA,SAAS;GACT,aAAa;GACb,WAAW;GAKX,iBAAiB,CAAC,aAAa,eAAe;GAC9C,QAAQ;IACN;IACA,MAAM,mBAAmB;KACvB,YAAY,kBAAkB,WAAW;KACzC,SAAS,gBAAgB,QAAQ;KAClC,CAAC;IACH;GACD,OAAO,OAAO;IACd;GACG;;AAIX,MAAM,SAASC,wBAAW,OAAO;CAC/B,WAAW;EAET,QAAQ;EACR,UAAU;EACX;CACD,SAAS;EACP,iBAAiB;EACjB,MAAM;EACP;CACF,CAAC;;;;mECrVmBC,MAAaC,QAAe"}
|
package/dist/index.esm.js
CHANGED
|
@@ -9,7 +9,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
9
9
|
|
|
10
10
|
//#region package.json
|
|
11
11
|
var name = "@dynamic-labs-sdk/react-native-captcha";
|
|
12
|
-
var version = "1.
|
|
12
|
+
var version = "1.30.2";
|
|
13
13
|
|
|
14
14
|
//#endregion
|
|
15
15
|
//#region src/errors/HCaptchaError.ts
|
|
@@ -251,15 +251,20 @@ var TurnstileError = class extends BaseError {
|
|
|
251
251
|
* @see TurnstileChallengeProps
|
|
252
252
|
*/
|
|
253
253
|
const TURNSTILE_CHALLENGE_TEST_ID = "turnstile-challenge";
|
|
254
|
-
const bridgeMessageSchema = z.union([
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
type: z.enum(["
|
|
260
|
-
|
|
254
|
+
const bridgeMessageSchema = z.union([
|
|
255
|
+
z.object({
|
|
256
|
+
message: z.optional(z.string()),
|
|
257
|
+
type: z.enum(["error"])
|
|
258
|
+
}),
|
|
259
|
+
z.object({ type: z.enum(["interactive"]) }),
|
|
260
|
+
z.object({
|
|
261
|
+
token: z.string(),
|
|
262
|
+
type: z.enum(["token"])
|
|
263
|
+
})
|
|
264
|
+
]);
|
|
265
|
+
const resolveAppearance = (appearance) => appearance === "interaction-only" ? appearance : "always";
|
|
261
266
|
const sanitizeSiteKey = (siteKey) => siteKey.replace(/[^\w-]/g, "");
|
|
262
|
-
const buildTurnstileHtml = (siteKey) => `
|
|
267
|
+
const buildTurnstileHtml = ({ appearance, siteKey }) => `
|
|
263
268
|
<!DOCTYPE html>
|
|
264
269
|
<html>
|
|
265
270
|
<head>
|
|
@@ -273,9 +278,11 @@ const buildTurnstileHtml = (siteKey) => `
|
|
|
273
278
|
<div
|
|
274
279
|
class="cf-turnstile"
|
|
275
280
|
data-sitekey="${siteKey}"
|
|
281
|
+
data-appearance="${appearance}"
|
|
276
282
|
data-callback="onSuccess"
|
|
277
283
|
data-error-callback="onError"
|
|
278
284
|
data-expired-callback="onExpired"
|
|
285
|
+
data-before-interactive-callback="onBeforeInteractive"
|
|
279
286
|
></div>
|
|
280
287
|
<script>
|
|
281
288
|
const post = (payload) => {
|
|
@@ -297,6 +304,13 @@ const buildTurnstileHtml = (siteKey) => `
|
|
|
297
304
|
window.onExpired = () => {
|
|
298
305
|
post({ type: 'error', message: 'token expired before it was claimed' });
|
|
299
306
|
};
|
|
307
|
+
// Fires right before Turnstile shows interactive UI, under any
|
|
308
|
+
// appearance — meaningful mainly under interaction-only, where it's the
|
|
309
|
+
// signal that the widget, otherwise solving silently, now needs the
|
|
310
|
+
// visitor to act.
|
|
311
|
+
window.onBeforeInteractive = () => {
|
|
312
|
+
post({ type: 'interactive' });
|
|
313
|
+
};
|
|
300
314
|
<\/script>
|
|
301
315
|
</body>
|
|
302
316
|
</html>
|
|
@@ -306,13 +320,16 @@ const buildTurnstileHtml = (siteKey) => `
|
|
|
306
320
|
* resulting captcha token over the postMessage bridge. The caller hands the
|
|
307
321
|
* token to the SDK's `setCaptchaToken`. Exactly one of `onToken`/`onError`
|
|
308
322
|
* fires, exactly once — the first terminal event (token, challenge error,
|
|
309
|
-
* expiry, or WebView load failure) wins.
|
|
323
|
+
* expiry, or WebView load failure) wins. `onInteractive` is not terminal and
|
|
324
|
+
* may fire any number of times before that.
|
|
310
325
|
*
|
|
311
326
|
* @example
|
|
312
327
|
* ```tsx
|
|
313
328
|
* <TurnstileChallenge
|
|
314
329
|
* baseUrl="https://your-whitelisted-origin.com"
|
|
315
330
|
* siteKey="1x00000000000000000000AA"
|
|
331
|
+
* appearance="interaction-only"
|
|
332
|
+
* onInteractive={() => setCaptchaVisible(true)}
|
|
316
333
|
* onToken={(token) => setCaptchaToken({ captchaToken: token })}
|
|
317
334
|
* onError={(error) => console.error(error.message)}
|
|
318
335
|
* style={{ height: 120 }}
|
|
@@ -323,7 +340,7 @@ const buildTurnstileHtml = (siteKey) => `
|
|
|
323
340
|
* @see TurnstileChallengeProps
|
|
324
341
|
* @see TurnstileError
|
|
325
342
|
*/
|
|
326
|
-
const TurnstileChallenge = ({ baseUrl, logLevel, onError, onToken, siteKey, style }) => {
|
|
343
|
+
const TurnstileChallenge = ({ appearance, baseUrl, logLevel, onError, onInteractive, onToken, siteKey, style }) => {
|
|
327
344
|
const settledRef = useRef(false);
|
|
328
345
|
const logger = useMemo(() => createLogger({ level: logLevel ?? "warn" }), [logLevel]);
|
|
329
346
|
const settleWith = (dispatch) => {
|
|
@@ -353,7 +370,8 @@ const TurnstileChallenge = ({ baseUrl, logLevel, onError, onToken, siteKey, styl
|
|
|
353
370
|
if (parsed.data.type === "token") {
|
|
354
371
|
const token = parsed.data.token;
|
|
355
372
|
settleWith(() => onToken(token));
|
|
356
|
-
} else
|
|
373
|
+
} else if (parsed.data.type === "interactive") onInteractive?.();
|
|
374
|
+
else {
|
|
357
375
|
const message = parsed.data.message;
|
|
358
376
|
settleWith(() => onError(new TurnstileError({ description: message })));
|
|
359
377
|
}
|
|
@@ -374,7 +392,10 @@ const TurnstileChallenge = ({ baseUrl, logLevel, onError, onToken, siteKey, styl
|
|
|
374
392
|
originWhitelist: ["https://*", "about:srcdoc"],
|
|
375
393
|
source: {
|
|
376
394
|
baseUrl,
|
|
377
|
-
html: buildTurnstileHtml(
|
|
395
|
+
html: buildTurnstileHtml({
|
|
396
|
+
appearance: resolveAppearance(appearance),
|
|
397
|
+
siteKey: sanitizeSiteKey(siteKey)
|
|
398
|
+
})
|
|
378
399
|
},
|
|
379
400
|
style: styles.webView
|
|
380
401
|
})
|
package/dist/index.esm.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.esm.js","names":["bridgeMessageSchema","sanitizeSiteKey","HCaptchaChallenge: FC<HCaptchaChallengeProps>","data: unknown","styles","TurnstileChallenge: FC<TurnstileChallengeProps>","data: unknown","packageName","packageVersion"],"sources":["../package.json","../src/errors/HCaptchaError.ts","../src/components/HCaptchaChallenge/HCaptchaChallenge.tsx","../src/errors/TurnstileError.ts","../src/components/TurnstileChallenge/TurnstileChallenge.tsx","../src/exports/index.ts"],"sourcesContent":["","import { BaseError } from '@dynamic-labs-sdk/client';\n\n/**\n * Thrown when the embedded hCaptcha challenge fails to resolve — for example\n * the widget reports an error, the token expires, or the user closes the\n * challenge without completing it.\n *\n * The {@link HCaptchaChallenge} component surfaces this typed error through its\n * `onError` callback so consumers can distinguish a failed captcha from other\n * failures and prompt the user to retry rather than treating it as a fatal\n * error.\n *\n * @example\n * ```tsx\n * <HCaptchaChallenge\n * onError={(error) => {\n * if (error instanceof HCaptchaError) {\n * promptRetry(error.message);\n * }\n * }}\n * />\n * ```\n * @see HCaptchaChallenge\n * @see HCaptchaChallengeProps\n */\nexport class HCaptchaError extends BaseError {\n constructor({ description }: { description: string | undefined }) {\n super({\n cause: null,\n code: 'hcaptcha_failed',\n docsUrl: null,\n name: 'HCaptchaError',\n shortMessage: `hCaptcha failed: ${description ?? 'unknown error'}`,\n });\n }\n}\n","'use client';\n\nimport type { FC } from 'react';\nimport { useMemo, useRef } from 'react';\nimport type { StyleProp, ViewStyle } from 'react-native';\nimport { StyleSheet, View } from 'react-native';\nimport type { WebViewMessageEvent } from 'react-native-webview';\nimport WebView from 'react-native-webview';\nimport { type LogLevel, createLogger } from '@dynamic-labs-sdk/client/core';\nimport * as z from 'zod/mini';\n\nimport { HCaptchaError } from '../../errors/HCaptchaError';\n\n/**\n * `testID` e2e drivers use to locate the hCaptcha widget on screen.\n *\n * @example\n * ```tsx\n * <View testID={HCAPTCHA_CHALLENGE_TEST_ID} />\n * ```\n * @see HCaptchaChallenge\n * @see HCaptchaChallengeProps\n */\nexport const HCAPTCHA_CHALLENGE_TEST_ID = 'hcaptcha-challenge';\n\n// react-native-webview only exports the message event type from its root;\n// for error events this is the minimal shape both onError and onHttpError\n// deliver.\ntype WebViewErrorEvent = { nativeEvent: { description?: string } };\n\n// Payload the widget document posts over the bridge (see buildHCaptchaHtml).\nconst bridgeMessageSchema = z.union([\n z.object({\n message: z.optional(z.string()),\n type: z.enum(['error']),\n }),\n z.object({\n token: z.string(),\n type: z.enum(['token']),\n }),\n]);\n\n/**\n * Props for the {@link HCaptchaChallenge} component.\n *\n * @example\n * ```tsx\n * const props: HCaptchaChallengeProps = {\n * baseUrl: 'https://your-whitelisted-origin.com',\n * onError: console.error,\n * onToken: setToken,\n * siteKey: '10000000-ffff-ffff-ffff-000000000001',\n * logLevel: 'warn',\n * };\n * ```\n * @see HCaptchaChallenge\n * @see HCaptchaError\n */\nexport type HCaptchaChallengeProps = {\n /**\n * Origin the widget document is served under. hCaptcha refuses to render on\n * origin-less documents, and real (non-test) site keys only render on hosts\n * whitelisted for the key — pass an origin your site key accepts.\n */\n baseUrl: string;\n /**\n * Optional minimum log level for the component's internal logger. Defaults\n * to `'warn'` when omitted.\n */\n logLevel?: LogLevel;\n /**\n * Called when the widget cannot produce a token: the WebView fails to load,\n * hCaptcha reports a challenge error, or the token expires unclaimed —\n * so callers awaiting `onToken` fail immediately instead of hanging until\n * their own timeout.\n */\n onError: (error: HCaptchaError) => void;\n /** Called with the token hCaptcha issues when the widget is solved. */\n onToken: (token: string) => void;\n /**\n * The environment's hCaptcha site key, from the SDK's `getCaptchaSettings`.\n */\n siteKey: string;\n /**\n * Optional style for the outer container. Use this to set width/height,\n * margins, border radius, or background color to match your app's UI.\n */\n style?: StyleProp<ViewStyle>;\n};\n\n// The site key is server-controlled, but it lands inside an HTML attribute —\n// keep only the charset real hCaptcha site keys use so a misconfigured value\n// cannot break out of the markup.\nconst sanitizeSiteKey = (siteKey: string) => siteKey.replace(/[^\\w-]/g, '');\n\nconst buildHCaptchaHtml = (siteKey: string) => `\n<!DOCTYPE html>\n<html>\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <!-- No Subresource Integrity: hCaptcha serves a mutable loader script and\n does not support SRI pinning; a pinned hash would break on their next\n deploy. The script loads from hCaptcha's own origin over HTTPS. -->\n <script src=\"https://js.hcaptcha.com/1/api.js\" async defer></script>\n</head>\n<body style=\"margin:0;padding:0;display:flex;justify-content:center;align-items:center;min-height:100vh;\">\n <div\n class=\"h-captcha\"\n data-sitekey=\"${siteKey}\"\n data-callback=\"onSuccess\"\n data-error-callback=\"onChallengeError\"\n data-expired-callback=\"onExpired\"\n ></div>\n <script>\n const post = (payload) => {\n window.ReactNativeWebView.postMessage(JSON.stringify(payload));\n };\n // hCaptcha resolves the data-*-callback attributes by name off the global\n // scope, so these handlers must be assigned onto window — a bare const\n // binding would not be reachable and the callbacks would never fire.\n window.onSuccess = (token) => {\n post({ type: 'token', token: token });\n };\n window.onChallengeError = (error) => {\n // Cap the hCaptcha-supplied error before forwarding it: it only ends up\n // in an HCaptchaError message, so bound its length rather than surface an\n // arbitrarily long string from the widget.\n const errorMessage = String(error).substring(0, 200);\n post({ type: 'error', message: 'challenge error: ' + errorMessage });\n };\n window.onExpired = () => {\n post({ type: 'error', message: 'token expired before it was claimed' });\n };\n </script>\n</body>\n</html>\n`;\n\n/**\n * Renders the hCaptcha checkbox widget in an inline WebView and reports the\n * resulting captcha token over the postMessage bridge. The caller hands the\n * token to the SDK's `setCaptchaToken`. Exactly one of `onToken`/`onError`\n * fires, exactly once — the first terminal event (token, challenge error,\n * expiry, or WebView load failure) wins.\n *\n * @example\n * ```tsx\n * <HCaptchaChallenge\n * baseUrl=\"https://your-whitelisted-origin.com\"\n * siteKey=\"10000000-ffff-ffff-ffff-000000000001\"\n * onToken={(token) => setCaptchaToken({ captchaToken: token })}\n * onError={(error) => console.error(error.message)}\n * style={{ height: 260 }}\n * logLevel=\"warn\"\n * />\n * ```\n * @returns A React Native view that hosts the captcha WebView.\n * @see HCaptchaChallengeProps\n * @see HCaptchaError\n */\nexport const HCaptchaChallenge: FC<HCaptchaChallengeProps> = ({\n baseUrl,\n logLevel,\n onError,\n onToken,\n siteKey,\n style,\n}) => {\n // First terminal event wins: hCaptcha only issues one token per solve, but\n // guard against duplicate bridge messages and double error events (onError\n // and onHttpError can both fire for one failure) so the callbacks keep\n // their called-once contract.\n const settledRef = useRef(false);\n\n const logger = useMemo(\n () => createLogger({ level: logLevel ?? 'warn' }),\n [logLevel]\n );\n\n // eslint-disable-next-line custom-rules/require-single-object-param -- (dispatch) is the entire input of this local helper\n const settleWith = (dispatch: () => void) => {\n if (settledRef.current) {\n return;\n }\n\n settledRef.current = true;\n dispatch();\n };\n\n const handleMessage = (event: WebViewMessageEvent) => {\n let data: unknown;\n try {\n data = JSON.parse(event.nativeEvent.data);\n } catch (error) {\n logger.debug('Malformed hCaptcha bridge message', {\n error: error instanceof Error ? error.message : String(error),\n rawData: String(event.nativeEvent.data).slice(0, 200),\n });\n return;\n }\n\n const parsed = bridgeMessageSchema.safeParse(data);\n if (!parsed.success) {\n logger.debug('Malformed hCaptcha bridge message', {\n error: parsed.error.message,\n rawData: String(event.nativeEvent.data).slice(0, 200),\n });\n return;\n }\n\n if (parsed.data.type === 'token') {\n const token = parsed.data.token;\n settleWith(() => onToken(token));\n } else {\n const message = parsed.data.message;\n settleWith(() => onError(new HCaptchaError({ description: message })));\n }\n };\n\n const handleLoadError = (event: WebViewErrorEvent) => {\n settleWith(() =>\n onError(\n new HCaptchaError({\n description: `WebView failed to load: ${event.nativeEvent.description ?? 'unknown error'}`,\n })\n )\n );\n };\n\n return (\n <View style={[styles.container, style]} testID={HCAPTCHA_CHALLENGE_TEST_ID}>\n <WebView\n domStorageEnabled\n javaScriptEnabled\n thirdPartyCookiesEnabled\n onError={handleLoadError}\n onHttpError={handleLoadError}\n onMessage={handleMessage}\n // https-only: hCaptcha's iframes may come from several hosts, but a\n // custom-scheme navigation escaping the WebView is never legitimate.\n originWhitelist={['https://*']}\n source={{\n baseUrl,\n html: buildHCaptchaHtml(sanitizeSiteKey(siteKey)),\n }}\n style={styles.webView}\n />\n </View>\n );\n};\n\nconst styles = StyleSheet.create({\n container: {\n // Tall enough for the checkbox widget plus the verification animation;\n // the challenge popup never appears with the always-pass test site key.\n height: 260,\n overflow: 'hidden',\n },\n webView: {\n backgroundColor: 'transparent',\n flex: 1,\n },\n});\n","import { BaseError } from '@dynamic-labs-sdk/client';\n\n/**\n * Thrown when the embedded Cloudflare Turnstile challenge fails to resolve —\n * for example the widget reports an error, the token expires, or the user\n * closes the challenge without completing it.\n *\n * The {@link TurnstileChallenge} component surfaces this typed error through\n * its `onError` callback so consumers can distinguish a failed captcha from\n * other failures and prompt the user to retry rather than treating it as a\n * fatal error.\n *\n * @example\n * ```tsx\n * <TurnstileChallenge\n * onError={(error) => {\n * if (error instanceof TurnstileError) {\n * promptRetry(error.message);\n * }\n * }}\n * />\n * ```\n * @see TurnstileChallenge\n * @see TurnstileChallengeProps\n */\nexport class TurnstileError extends BaseError {\n constructor({ description }: { description: string | undefined }) {\n super({\n cause: null,\n code: 'turnstile_failed',\n docsUrl: null,\n name: 'TurnstileError',\n shortMessage: `Turnstile failed: ${description ?? 'unknown error'}`,\n });\n }\n}\n","'use client';\n\nimport type { FC } from 'react';\nimport { useMemo, useRef } from 'react';\nimport type { StyleProp, ViewStyle } from 'react-native';\nimport { StyleSheet, View } from 'react-native';\nimport type { WebViewMessageEvent } from 'react-native-webview';\nimport WebView from 'react-native-webview';\nimport { type LogLevel, createLogger } from '@dynamic-labs-sdk/client/core';\nimport * as z from 'zod/mini';\n\nimport { TurnstileError } from '../../errors/TurnstileError';\n\n/**\n * `testID` e2e drivers use to locate the Turnstile widget on screen.\n *\n * @example\n * ```tsx\n * <View testID={TURNSTILE_CHALLENGE_TEST_ID} />\n * ```\n * @see TurnstileChallenge\n * @see TurnstileChallengeProps\n */\nexport const TURNSTILE_CHALLENGE_TEST_ID = 'turnstile-challenge';\n\n// react-native-webview only exports the message event type from its root;\n// for error events this is the minimal shape both onError and onHttpError\n// deliver.\ntype WebViewErrorEvent = { nativeEvent: { description?: string } };\n\n// Payload the widget document posts over the bridge (see buildTurnstileHtml).\nconst bridgeMessageSchema = z.union([\n z.object({\n message: z.optional(z.string()),\n type: z.enum(['error']),\n }),\n z.object({\n token: z.string(),\n type: z.enum(['token']),\n }),\n]);\n\n/**\n * Props for the {@link TurnstileChallenge} component.\n *\n * @example\n * ```tsx\n * const props: TurnstileChallengeProps = {\n * baseUrl: 'https://your-whitelisted-origin.com',\n * onError: console.error,\n * onToken: setToken,\n * siteKey: '1x00000000000000000000AA',\n * logLevel: 'warn',\n * };\n * ```\n * @see TurnstileChallenge\n * @see TurnstileError\n */\nexport type TurnstileChallengeProps = {\n /**\n * Origin the widget document is served under. Turnstile refuses to render on\n * origin-less documents, and real (non-test) site keys only render on hosts\n * whitelisted for the key — pass an origin your site key accepts.\n */\n baseUrl: string;\n /**\n * Optional minimum log level for the component's internal logger. Defaults\n * to `'warn'` when omitted.\n */\n logLevel?: LogLevel;\n /**\n * Called when the widget cannot produce a token: the WebView fails to load,\n * Turnstile reports a challenge error, or the token expires unclaimed —\n * so callers awaiting `onToken` fail immediately instead of hanging until\n * their own timeout.\n */\n onError: (error: TurnstileError) => void;\n /** Called with the token Turnstile issues when the widget is solved. */\n onToken: (token: string) => void;\n /**\n * The environment's Turnstile site key, from the SDK's `getCaptchaSettings`.\n */\n siteKey: string;\n /**\n * Optional style for the outer container. Use this to set width/height,\n * margins, border radius, or background color to match your app's UI.\n */\n style?: StyleProp<ViewStyle>;\n};\n\n// The site key is server-controlled, but it lands inside an HTML attribute —\n// keep only the charset real Turnstile site keys use so a misconfigured value\n// cannot break out of the markup.\nconst sanitizeSiteKey = (siteKey: string) => siteKey.replace(/[^\\w-]/g, '');\n\nconst buildTurnstileHtml = (siteKey: string) => `\n<!DOCTYPE html>\n<html>\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <!-- No Subresource Integrity: Cloudflare serves a mutable loader script and\n does not support SRI pinning; a pinned hash would break on their next\n deploy. The script loads from Cloudflare's own origin over HTTPS. -->\n <script src=\"https://challenges.cloudflare.com/turnstile/v0/api.js\" async defer></script>\n</head>\n<body style=\"margin:0;padding:0;display:flex;justify-content:center;align-items:center;min-height:100vh;\">\n <div\n class=\"cf-turnstile\"\n data-sitekey=\"${siteKey}\"\n data-callback=\"onSuccess\"\n data-error-callback=\"onError\"\n data-expired-callback=\"onExpired\"\n ></div>\n <script>\n const post = (payload) => {\n window.ReactNativeWebView.postMessage(JSON.stringify(payload));\n };\n // Turnstile resolves the data-*-callback attributes by name off the global\n // scope, so these handlers must be assigned onto window — a bare const\n // binding would not be reachable and the callbacks would never fire.\n window.onSuccess = (token) => {\n post({ type: 'token', token: token });\n };\n window.onError = (error) => {\n // Cap the Turnstile-supplied error before forwarding it: it only ends up\n // in a TurnstileError message, so bound its length rather than surface an\n // arbitrarily long string from the widget.\n const errorMessage = String(error).substring(0, 200);\n post({ type: 'error', message: 'challenge error: ' + errorMessage });\n };\n window.onExpired = () => {\n post({ type: 'error', message: 'token expired before it was claimed' });\n };\n </script>\n</body>\n</html>\n`;\n\n/**\n * Renders the Cloudflare Turnstile widget in an inline WebView and reports the\n * resulting captcha token over the postMessage bridge. The caller hands the\n * token to the SDK's `setCaptchaToken`. Exactly one of `onToken`/`onError`\n * fires, exactly once — the first terminal event (token, challenge error,\n * expiry, or WebView load failure) wins.\n *\n * @example\n * ```tsx\n * <TurnstileChallenge\n * baseUrl=\"https://your-whitelisted-origin.com\"\n * siteKey=\"1x00000000000000000000AA\"\n * onToken={(token) => setCaptchaToken({ captchaToken: token })}\n * onError={(error) => console.error(error.message)}\n * style={{ height: 120 }}\n * logLevel=\"warn\"\n * />\n * ```\n * @returns A React Native view that hosts the captcha WebView.\n * @see TurnstileChallengeProps\n * @see TurnstileError\n */\nexport const TurnstileChallenge: FC<TurnstileChallengeProps> = ({\n baseUrl,\n logLevel,\n onError,\n onToken,\n siteKey,\n style,\n}) => {\n // First terminal event wins: Turnstile only issues one token per solve, but\n // guard against duplicate bridge messages and double error events (onError\n // and onHttpError can both fire for one failure) so the callbacks keep\n // their called-once contract.\n const settledRef = useRef(false);\n\n const logger = useMemo(\n () => createLogger({ level: logLevel ?? 'warn' }),\n [logLevel]\n );\n\n // eslint-disable-next-line custom-rules/require-single-object-param -- (dispatch) is the entire input of this local helper\n const settleWith = (dispatch: () => void) => {\n if (settledRef.current) {\n return;\n }\n\n settledRef.current = true;\n dispatch();\n };\n\n const handleMessage = (event: WebViewMessageEvent) => {\n let data: unknown;\n try {\n data = JSON.parse(event.nativeEvent.data);\n } catch (error) {\n logger.debug('Malformed Turnstile bridge message', {\n error: error instanceof Error ? error.message : String(error),\n rawData: String(event.nativeEvent.data).slice(0, 200),\n });\n return;\n }\n\n const parsed = bridgeMessageSchema.safeParse(data);\n if (!parsed.success) {\n logger.debug('Malformed Turnstile bridge message', {\n error: parsed.error.message,\n rawData: String(event.nativeEvent.data).slice(0, 200),\n });\n return;\n }\n\n if (parsed.data.type === 'token') {\n const token = parsed.data.token;\n settleWith(() => onToken(token));\n } else {\n const message = parsed.data.message;\n settleWith(() => onError(new TurnstileError({ description: message })));\n }\n };\n\n const handleLoadError = (event: WebViewErrorEvent) => {\n settleWith(() =>\n onError(\n new TurnstileError({\n description: `WebView failed to load: ${event.nativeEvent.description ?? 'unknown error'}`,\n })\n )\n );\n };\n\n return (\n <View style={[styles.container, style]} testID={TURNSTILE_CHALLENGE_TEST_ID}>\n <WebView\n domStorageEnabled\n javaScriptEnabled\n thirdPartyCookiesEnabled\n onError={handleLoadError}\n onHttpError={handleLoadError}\n onMessage={handleMessage}\n // Turnstile renders challenge frames over HTTPS, but its iOS WebView\n // placeholder iframes use about:blank/about:srcdoc (the WebView already\n // allows about:blank by default, so we only need to add about:srcdoc).\n // Keep the whitelist https-only to block cleartext or custom-scheme nav.\n originWhitelist={['https://*', 'about:srcdoc']}\n source={{\n baseUrl,\n html: buildTurnstileHtml(sanitizeSiteKey(siteKey)),\n }}\n style={styles.webView}\n />\n </View>\n );\n};\n\nconst styles = StyleSheet.create({\n container: {\n // Tall enough for the widget plus the verification animation.\n height: 120,\n overflow: 'hidden',\n },\n webView: {\n backgroundColor: 'transparent',\n flex: 1,\n },\n});\n","import { assertPackageVersion } from '@dynamic-labs-sdk/assert-package-version';\n\nimport {\n name as packageName,\n version as packageVersion,\n} from '../../package.json';\n\nassertPackageVersion(packageName, packageVersion);\n\nexport {\n HCAPTCHA_CHALLENGE_TEST_ID,\n HCaptchaChallenge,\n} from '../components/HCaptchaChallenge/HCaptchaChallenge';\nexport type { HCaptchaChallengeProps } from '../components/HCaptchaChallenge/HCaptchaChallenge';\nexport { HCaptchaError } from '../errors/HCaptchaError';\n\nexport {\n TURNSTILE_CHALLENGE_TEST_ID,\n TurnstileChallenge,\n} from '../components/TurnstileChallenge/TurnstileChallenge';\nexport type { TurnstileChallengeProps } from '../components/TurnstileChallenge/TurnstileChallenge';\nexport { TurnstileError } from '../errors/TurnstileError';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.esm.js","names":["bridgeMessageSchema","sanitizeSiteKey","HCaptchaChallenge: FC<HCaptchaChallengeProps>","data: unknown","styles","TurnstileChallenge: FC<TurnstileChallengeProps>","data: unknown","packageName","packageVersion"],"sources":["../package.json","../src/errors/HCaptchaError.ts","../src/components/HCaptchaChallenge/HCaptchaChallenge.tsx","../src/errors/TurnstileError.ts","../src/components/TurnstileChallenge/TurnstileChallenge.tsx","../src/exports/index.ts"],"sourcesContent":["","import { BaseError } from '@dynamic-labs-sdk/client';\n\n/**\n * Thrown when the embedded hCaptcha challenge fails to resolve — for example\n * the widget reports an error, the token expires, or the user closes the\n * challenge without completing it.\n *\n * The {@link HCaptchaChallenge} component surfaces this typed error through its\n * `onError` callback so consumers can distinguish a failed captcha from other\n * failures and prompt the user to retry rather than treating it as a fatal\n * error.\n *\n * @example\n * ```tsx\n * <HCaptchaChallenge\n * onError={(error) => {\n * if (error instanceof HCaptchaError) {\n * promptRetry(error.message);\n * }\n * }}\n * />\n * ```\n * @see HCaptchaChallenge\n * @see HCaptchaChallengeProps\n */\nexport class HCaptchaError extends BaseError {\n constructor({ description }: { description: string | undefined }) {\n super({\n cause: null,\n code: 'hcaptcha_failed',\n docsUrl: null,\n name: 'HCaptchaError',\n shortMessage: `hCaptcha failed: ${description ?? 'unknown error'}`,\n });\n }\n}\n","'use client';\n\nimport type { FC } from 'react';\nimport { useMemo, useRef } from 'react';\nimport type { StyleProp, ViewStyle } from 'react-native';\nimport { StyleSheet, View } from 'react-native';\nimport type { WebViewMessageEvent } from 'react-native-webview';\nimport WebView from 'react-native-webview';\nimport { type LogLevel, createLogger } from '@dynamic-labs-sdk/client/core';\nimport * as z from 'zod/mini';\n\nimport { HCaptchaError } from '../../errors/HCaptchaError';\n\n/**\n * `testID` e2e drivers use to locate the hCaptcha widget on screen.\n *\n * @example\n * ```tsx\n * <View testID={HCAPTCHA_CHALLENGE_TEST_ID} />\n * ```\n * @see HCaptchaChallenge\n * @see HCaptchaChallengeProps\n */\nexport const HCAPTCHA_CHALLENGE_TEST_ID = 'hcaptcha-challenge';\n\n// react-native-webview only exports the message event type from its root;\n// for error events this is the minimal shape both onError and onHttpError\n// deliver.\ntype WebViewErrorEvent = { nativeEvent: { description?: string } };\n\n// Payload the widget document posts over the bridge (see buildHCaptchaHtml).\nconst bridgeMessageSchema = z.union([\n z.object({\n message: z.optional(z.string()),\n type: z.enum(['error']),\n }),\n z.object({\n token: z.string(),\n type: z.enum(['token']),\n }),\n]);\n\n/**\n * Props for the {@link HCaptchaChallenge} component.\n *\n * @example\n * ```tsx\n * const props: HCaptchaChallengeProps = {\n * baseUrl: 'https://your-whitelisted-origin.com',\n * onError: console.error,\n * onToken: setToken,\n * siteKey: '10000000-ffff-ffff-ffff-000000000001',\n * logLevel: 'warn',\n * };\n * ```\n * @see HCaptchaChallenge\n * @see HCaptchaError\n */\nexport type HCaptchaChallengeProps = {\n /**\n * Origin the widget document is served under. hCaptcha refuses to render on\n * origin-less documents, and real (non-test) site keys only render on hosts\n * whitelisted for the key — pass an origin your site key accepts.\n */\n baseUrl: string;\n /**\n * Optional minimum log level for the component's internal logger. Defaults\n * to `'warn'` when omitted.\n */\n logLevel?: LogLevel;\n /**\n * Called when the widget cannot produce a token: the WebView fails to load,\n * hCaptcha reports a challenge error, or the token expires unclaimed —\n * so callers awaiting `onToken` fail immediately instead of hanging until\n * their own timeout.\n */\n onError: (error: HCaptchaError) => void;\n /** Called with the token hCaptcha issues when the widget is solved. */\n onToken: (token: string) => void;\n /**\n * The environment's hCaptcha site key, from the SDK's `getCaptchaSettings`.\n */\n siteKey: string;\n /**\n * Optional style for the outer container. Use this to set width/height,\n * margins, border radius, or background color to match your app's UI.\n */\n style?: StyleProp<ViewStyle>;\n};\n\n// The site key is server-controlled, but it lands inside an HTML attribute —\n// keep only the charset real hCaptcha site keys use so a misconfigured value\n// cannot break out of the markup.\nconst sanitizeSiteKey = (siteKey: string): string =>\n siteKey.replace(/[^\\w-]/g, '');\n\nconst buildHCaptchaHtml = (siteKey: string): string => `\n<!DOCTYPE html>\n<html>\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <!-- No Subresource Integrity: hCaptcha serves a mutable loader script and\n does not support SRI pinning; a pinned hash would break on their next\n deploy. The script loads from hCaptcha's own origin over HTTPS. -->\n <script src=\"https://js.hcaptcha.com/1/api.js\" async defer></script>\n</head>\n<body style=\"margin:0;padding:0;display:flex;justify-content:center;align-items:center;min-height:100vh;\">\n <div\n class=\"h-captcha\"\n data-sitekey=\"${siteKey}\"\n data-callback=\"onSuccess\"\n data-error-callback=\"onChallengeError\"\n data-expired-callback=\"onExpired\"\n ></div>\n <script>\n const post = (payload) => {\n window.ReactNativeWebView.postMessage(JSON.stringify(payload));\n };\n // hCaptcha resolves the data-*-callback attributes by name off the global\n // scope, so these handlers must be assigned onto window — a bare const\n // binding would not be reachable and the callbacks would never fire.\n window.onSuccess = (token) => {\n post({ type: 'token', token: token });\n };\n window.onChallengeError = (error) => {\n // Cap the hCaptcha-supplied error before forwarding it: it only ends up\n // in an HCaptchaError message, so bound its length rather than surface an\n // arbitrarily long string from the widget.\n const errorMessage = String(error).substring(0, 200);\n post({ type: 'error', message: 'challenge error: ' + errorMessage });\n };\n window.onExpired = () => {\n post({ type: 'error', message: 'token expired before it was claimed' });\n };\n </script>\n</body>\n</html>\n`;\n\n/**\n * Renders the hCaptcha checkbox widget in an inline WebView and reports the\n * resulting captcha token over the postMessage bridge. The caller hands the\n * token to the SDK's `setCaptchaToken`. Exactly one of `onToken`/`onError`\n * fires, exactly once — the first terminal event (token, challenge error,\n * expiry, or WebView load failure) wins.\n *\n * @example\n * ```tsx\n * <HCaptchaChallenge\n * baseUrl=\"https://your-whitelisted-origin.com\"\n * siteKey=\"10000000-ffff-ffff-ffff-000000000001\"\n * onToken={(token) => setCaptchaToken({ captchaToken: token })}\n * onError={(error) => console.error(error.message)}\n * style={{ height: 260 }}\n * logLevel=\"warn\"\n * />\n * ```\n * @returns A React Native view that hosts the captcha WebView.\n * @see HCaptchaChallengeProps\n * @see HCaptchaError\n */\nexport const HCaptchaChallenge: FC<HCaptchaChallengeProps> = ({\n baseUrl,\n logLevel,\n onError,\n onToken,\n siteKey,\n style,\n}) => {\n // First terminal event wins: hCaptcha only issues one token per solve, but\n // guard against duplicate bridge messages and double error events (onError\n // and onHttpError can both fire for one failure) so the callbacks keep\n // their called-once contract.\n const settledRef = useRef(false);\n\n const logger = useMemo(\n () => createLogger({ level: logLevel ?? 'warn' }),\n [logLevel]\n );\n\n // eslint-disable-next-line custom-rules/require-single-object-param -- (dispatch) is the entire input of this local helper\n const settleWith = (dispatch: () => void) => {\n if (settledRef.current) {\n return;\n }\n\n settledRef.current = true;\n dispatch();\n };\n\n const handleMessage = (event: WebViewMessageEvent) => {\n let data: unknown;\n try {\n data = JSON.parse(event.nativeEvent.data);\n } catch (error) {\n logger.debug('Malformed hCaptcha bridge message', {\n error: error instanceof Error ? error.message : String(error),\n rawData: String(event.nativeEvent.data).slice(0, 200),\n });\n return;\n }\n\n const parsed = bridgeMessageSchema.safeParse(data);\n if (!parsed.success) {\n logger.debug('Malformed hCaptcha bridge message', {\n error: parsed.error.message,\n rawData: String(event.nativeEvent.data).slice(0, 200),\n });\n return;\n }\n\n if (parsed.data.type === 'token') {\n const token = parsed.data.token;\n settleWith(() => onToken(token));\n } else {\n const message = parsed.data.message;\n settleWith(() => onError(new HCaptchaError({ description: message })));\n }\n };\n\n const handleLoadError = (event: WebViewErrorEvent) => {\n settleWith(() =>\n onError(\n new HCaptchaError({\n description: `WebView failed to load: ${\n event.nativeEvent.description ?? 'unknown error'\n }`,\n })\n )\n );\n };\n\n return (\n <View style={[styles.container, style]} testID={HCAPTCHA_CHALLENGE_TEST_ID}>\n <WebView\n domStorageEnabled\n javaScriptEnabled\n thirdPartyCookiesEnabled\n onError={handleLoadError}\n onHttpError={handleLoadError}\n onMessage={handleMessage}\n // https-only: hCaptcha's iframes may come from several hosts, but a\n // custom-scheme navigation escaping the WebView is never legitimate.\n originWhitelist={['https://*']}\n source={{\n baseUrl,\n html: buildHCaptchaHtml(sanitizeSiteKey(siteKey)),\n }}\n style={styles.webView}\n />\n </View>\n );\n};\n\nconst styles = StyleSheet.create({\n container: {\n // Tall enough for the checkbox widget plus the verification animation;\n // the challenge popup never appears with the always-pass test site key.\n height: 260,\n overflow: 'hidden',\n },\n webView: {\n backgroundColor: 'transparent',\n flex: 1,\n },\n});\n","import { BaseError } from '@dynamic-labs-sdk/client';\n\n/**\n * Thrown when the embedded Cloudflare Turnstile challenge fails to resolve —\n * for example the widget reports an error, the token expires, or the user\n * closes the challenge without completing it.\n *\n * The {@link TurnstileChallenge} component surfaces this typed error through\n * its `onError` callback so consumers can distinguish a failed captcha from\n * other failures and prompt the user to retry rather than treating it as a\n * fatal error.\n *\n * @example\n * ```tsx\n * <TurnstileChallenge\n * onError={(error) => {\n * if (error instanceof TurnstileError) {\n * promptRetry(error.message);\n * }\n * }}\n * />\n * ```\n * @see TurnstileChallenge\n * @see TurnstileChallengeProps\n */\nexport class TurnstileError extends BaseError {\n constructor({ description }: { description: string | undefined }) {\n super({\n cause: null,\n code: 'turnstile_failed',\n docsUrl: null,\n name: 'TurnstileError',\n shortMessage: `Turnstile failed: ${description ?? 'unknown error'}`,\n });\n }\n}\n","'use client';\n\nimport type { FC } from 'react';\nimport { useMemo, useRef } from 'react';\nimport type { StyleProp, ViewStyle } from 'react-native';\nimport { StyleSheet, View } from 'react-native';\nimport type { WebViewMessageEvent } from 'react-native-webview';\nimport WebView from 'react-native-webview';\nimport { type LogLevel, createLogger } from '@dynamic-labs-sdk/client/core';\nimport * as z from 'zod/mini';\n\nimport { TurnstileError } from '../../errors/TurnstileError';\n\n/**\n * `testID` e2e drivers use to locate the Turnstile widget on screen.\n *\n * @example\n * ```tsx\n * <View testID={TURNSTILE_CHALLENGE_TEST_ID} />\n * ```\n * @see TurnstileChallenge\n * @see TurnstileChallengeProps\n */\nexport const TURNSTILE_CHALLENGE_TEST_ID = 'turnstile-challenge';\n\n// react-native-webview only exports the message event type from its root;\n// for error events this is the minimal shape both onError and onHttpError\n// deliver.\ntype WebViewErrorEvent = { nativeEvent: { description?: string } };\n\n// Payload the widget document posts over the bridge (see buildTurnstileHtml).\nconst bridgeMessageSchema = z.union([\n z.object({\n message: z.optional(z.string()),\n type: z.enum(['error']),\n }),\n z.object({\n type: z.enum(['interactive']),\n }),\n z.object({\n token: z.string(),\n type: z.enum(['token']),\n }),\n]);\n\n/**\n * Mirrors Cloudflare Turnstile's `data-appearance` values this component\n * supports.\n *\n * - `'always'` (default): the widget is visible as soon as it renders.\n * - `'interaction-only'`: the widget stays invisible and solves silently\n * for low-risk clients; it only renders UI when Turnstile decides the\n * visitor must interact. Pair with `onInteractive` on\n * {@link TurnstileChallengeProps} to reveal the host container at that\n * point instead of showing a captcha card up front.\n *\n * Cloudflare also defines `'execute'`, which leaves the widget invisible\n * until something calls `turnstile.execute()` — this component never does,\n * so it is omitted here rather than exposed as an option that would hang\n * forever.\n *\n * @example\n * ```tsx\n * const appearance: TurnstileAppearance = 'interaction-only';\n * ```\n * @see TurnstileChallenge\n * @see TurnstileChallengeProps\n */\nexport type TurnstileAppearance = 'always' | 'interaction-only';\n\n// Guards against a runtime type-system bypass landing an arbitrary string in\n// an HTML attribute — mirrors the defensiveness of sanitizeSiteKey below.\nconst resolveAppearance = (\n appearance: TurnstileAppearance | undefined\n): TurnstileAppearance =>\n appearance === 'interaction-only' ? appearance : 'always';\n\n/**\n * Props for the {@link TurnstileChallenge} component.\n *\n * @example\n * ```tsx\n * const props: TurnstileChallengeProps = {\n * baseUrl: 'https://your-whitelisted-origin.com',\n * onError: console.error,\n * onToken: setToken,\n * siteKey: '1x00000000000000000000AA',\n * logLevel: 'warn',\n * };\n * ```\n * @see TurnstileChallenge\n * @see TurnstileError\n */\nexport type TurnstileChallengeProps = {\n /**\n * How the widget presents itself. Defaults to `'always'`, matching\n * Cloudflare's own default. Pass `'interaction-only'` to solve silently for\n * low-risk clients and only surface UI when Turnstile demands it — combine\n * with `onInteractive` to reveal the host container at that point.\n */\n appearance?: TurnstileAppearance;\n /**\n * Origin the widget document is served under. Turnstile refuses to render on\n * origin-less documents, and real (non-test) site keys only render on hosts\n * whitelisted for the key — pass an origin your site key accepts.\n */\n baseUrl: string;\n /**\n * Optional minimum log level for the component's internal logger. Defaults\n * to `'warn'` when omitted.\n */\n logLevel?: LogLevel;\n /**\n * Called when the widget cannot produce a token: the WebView fails to load,\n * Turnstile reports a challenge error, or the token expires unclaimed —\n * so callers awaiting `onToken` fail immediately instead of hanging until\n * their own timeout.\n */\n onError: (error: TurnstileError) => void;\n /**\n * Called right before Turnstile renders its interactive challenge UI\n * (Turnstile's `before-interactive-callback`). Fires under any\n * `appearance`, but only `'interaction-only'` makes it meaningful — under\n * `'always'` the UI is already showing by the time this fires, so there's\n * nothing to reveal. Use it to unhide a container that was kept invisible\n * while the widget solved silently.\n */\n onInteractive?: () => void;\n /** Called with the token Turnstile issues when the widget is solved. */\n onToken: (token: string) => void;\n /**\n * The environment's Turnstile site key, from the SDK's `getCaptchaSettings`.\n */\n siteKey: string;\n /**\n * Optional style for the outer container. Use this to set width/height,\n * margins, border radius, or background color to match your app's UI.\n */\n style?: StyleProp<ViewStyle>;\n};\n\n// The site key is server-controlled, but it lands inside an HTML attribute —\n// keep only the charset real Turnstile site keys use so a misconfigured value\n// cannot break out of the markup.\nconst sanitizeSiteKey = (siteKey: string): string =>\n siteKey.replace(/[^\\w-]/g, '');\n\nconst buildTurnstileHtml = ({\n appearance,\n siteKey,\n}: {\n appearance: TurnstileAppearance;\n siteKey: string;\n}): string => `\n<!DOCTYPE html>\n<html>\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <!-- No Subresource Integrity: Cloudflare serves a mutable loader script and\n does not support SRI pinning; a pinned hash would break on their next\n deploy. The script loads from Cloudflare's own origin over HTTPS. -->\n <script src=\"https://challenges.cloudflare.com/turnstile/v0/api.js\" async defer></script>\n</head>\n<body style=\"margin:0;padding:0;display:flex;justify-content:center;align-items:center;min-height:100vh;\">\n <div\n class=\"cf-turnstile\"\n data-sitekey=\"${siteKey}\"\n data-appearance=\"${appearance}\"\n data-callback=\"onSuccess\"\n data-error-callback=\"onError\"\n data-expired-callback=\"onExpired\"\n data-before-interactive-callback=\"onBeforeInteractive\"\n ></div>\n <script>\n const post = (payload) => {\n window.ReactNativeWebView.postMessage(JSON.stringify(payload));\n };\n // Turnstile resolves the data-*-callback attributes by name off the global\n // scope, so these handlers must be assigned onto window — a bare const\n // binding would not be reachable and the callbacks would never fire.\n window.onSuccess = (token) => {\n post({ type: 'token', token: token });\n };\n window.onError = (error) => {\n // Cap the Turnstile-supplied error before forwarding it: it only ends up\n // in a TurnstileError message, so bound its length rather than surface an\n // arbitrarily long string from the widget.\n const errorMessage = String(error).substring(0, 200);\n post({ type: 'error', message: 'challenge error: ' + errorMessage });\n };\n window.onExpired = () => {\n post({ type: 'error', message: 'token expired before it was claimed' });\n };\n // Fires right before Turnstile shows interactive UI, under any\n // appearance — meaningful mainly under interaction-only, where it's the\n // signal that the widget, otherwise solving silently, now needs the\n // visitor to act.\n window.onBeforeInteractive = () => {\n post({ type: 'interactive' });\n };\n </script>\n</body>\n</html>\n`;\n\n/**\n * Renders the Cloudflare Turnstile widget in an inline WebView and reports the\n * resulting captcha token over the postMessage bridge. The caller hands the\n * token to the SDK's `setCaptchaToken`. Exactly one of `onToken`/`onError`\n * fires, exactly once — the first terminal event (token, challenge error,\n * expiry, or WebView load failure) wins. `onInteractive` is not terminal and\n * may fire any number of times before that.\n *\n * @example\n * ```tsx\n * <TurnstileChallenge\n * baseUrl=\"https://your-whitelisted-origin.com\"\n * siteKey=\"1x00000000000000000000AA\"\n * appearance=\"interaction-only\"\n * onInteractive={() => setCaptchaVisible(true)}\n * onToken={(token) => setCaptchaToken({ captchaToken: token })}\n * onError={(error) => console.error(error.message)}\n * style={{ height: 120 }}\n * logLevel=\"warn\"\n * />\n * ```\n * @returns A React Native view that hosts the captcha WebView.\n * @see TurnstileChallengeProps\n * @see TurnstileError\n */\nexport const TurnstileChallenge: FC<TurnstileChallengeProps> = ({\n appearance,\n baseUrl,\n logLevel,\n onError,\n onInteractive,\n onToken,\n siteKey,\n style,\n}) => {\n // First terminal event wins: Turnstile only issues one token per solve, but\n // guard against duplicate bridge messages and double error events (onError\n // and onHttpError can both fire for one failure) so the callbacks keep\n // their called-once contract.\n const settledRef = useRef(false);\n\n const logger = useMemo(\n () => createLogger({ level: logLevel ?? 'warn' }),\n [logLevel]\n );\n\n // eslint-disable-next-line custom-rules/require-single-object-param -- (dispatch) is the entire input of this local helper\n const settleWith = (dispatch: () => void) => {\n if (settledRef.current) {\n return;\n }\n\n settledRef.current = true;\n dispatch();\n };\n\n const handleMessage = (event: WebViewMessageEvent) => {\n let data: unknown;\n try {\n data = JSON.parse(event.nativeEvent.data);\n } catch (error) {\n logger.debug('Malformed Turnstile bridge message', {\n error: error instanceof Error ? error.message : String(error),\n rawData: String(event.nativeEvent.data).slice(0, 200),\n });\n return;\n }\n\n const parsed = bridgeMessageSchema.safeParse(data);\n if (!parsed.success) {\n logger.debug('Malformed Turnstile bridge message', {\n error: parsed.error.message,\n rawData: String(event.nativeEvent.data).slice(0, 200),\n });\n return;\n }\n\n if (parsed.data.type === 'token') {\n const token = parsed.data.token;\n settleWith(() => onToken(token));\n } else if (parsed.data.type === 'interactive') {\n // Not a terminal event — the widget is still solving, just now needs\n // the visitor's input — so this bypasses settleWith and can fire\n // independently of whether onToken/onError have already run.\n onInteractive?.();\n } else {\n const message = parsed.data.message;\n settleWith(() => onError(new TurnstileError({ description: message })));\n }\n };\n\n const handleLoadError = (event: WebViewErrorEvent) => {\n settleWith(() =>\n onError(\n new TurnstileError({\n description: `WebView failed to load: ${\n event.nativeEvent.description ?? 'unknown error'\n }`,\n })\n )\n );\n };\n\n return (\n <View\n style={[styles.container, style]}\n testID={TURNSTILE_CHALLENGE_TEST_ID}\n >\n <WebView\n domStorageEnabled\n javaScriptEnabled\n thirdPartyCookiesEnabled\n onError={handleLoadError}\n onHttpError={handleLoadError}\n onMessage={handleMessage}\n // Turnstile renders challenge frames over HTTPS, but its iOS WebView\n // placeholder iframes use about:blank/about:srcdoc (the WebView already\n // allows about:blank by default, so we only need to add about:srcdoc).\n // Keep the whitelist https-only to block cleartext or custom-scheme nav.\n originWhitelist={['https://*', 'about:srcdoc']}\n source={{\n baseUrl,\n html: buildTurnstileHtml({\n appearance: resolveAppearance(appearance),\n siteKey: sanitizeSiteKey(siteKey),\n }),\n }}\n style={styles.webView}\n />\n </View>\n );\n};\n\nconst styles = StyleSheet.create({\n container: {\n // Tall enough for the widget plus the verification animation.\n height: 120,\n overflow: 'hidden',\n },\n webView: {\n backgroundColor: 'transparent',\n flex: 1,\n },\n});\n","import { assertPackageVersion } from '@dynamic-labs-sdk/assert-package-version';\n\nimport {\n name as packageName,\n version as packageVersion,\n} from '../../package.json';\n\nassertPackageVersion(packageName, packageVersion);\n\nexport {\n HCAPTCHA_CHALLENGE_TEST_ID,\n HCaptchaChallenge,\n} from '../components/HCaptchaChallenge/HCaptchaChallenge';\nexport type { HCaptchaChallengeProps } from '../components/HCaptchaChallenge/HCaptchaChallenge';\nexport { HCaptchaError } from '../errors/HCaptchaError';\n\nexport {\n TURNSTILE_CHALLENGE_TEST_ID,\n TurnstileChallenge,\n} from '../components/TurnstileChallenge/TurnstileChallenge';\nexport type {\n TurnstileAppearance,\n TurnstileChallengeProps,\n} from '../components/TurnstileChallenge/TurnstileChallenge';\nexport { TurnstileError } from '../errors/TurnstileError';\n"],"mappings":"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"}
|