@dynamic-labs-sdk/react-native-captcha 1.29.0 → 1.30.3

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.
@@ -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;AAkDF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,iBAAiB,EAAE,EAAE,CAAC,sBAAsB,CAyFxD,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;AAmBjE;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC;;;;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,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;AAkDF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,kBAAkB,EAAE,EAAE,CAAC,uBAAuB,CA2F1D,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"}
@@ -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,EAAE,uBAAuB,EAAE,MAAM,qDAAqD,CAAC;AACnG,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC"}
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.29.0";
41
+ var version = "1.30.3";
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([zod_mini.object({
284
- message: zod_mini.optional(zod_mini.string()),
285
- type: zod_mini.enum(["error"])
286
- }), zod_mini.object({
287
- token: zod_mini.string(),
288
- type: zod_mini.enum(["token"])
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(sanitizeSiteKey(siteKey))
424
+ html: buildTurnstileHtml({
425
+ appearance: resolveAppearance(appearance),
426
+ siteKey: sanitizeSiteKey(siteKey)
427
+ })
407
428
  },
408
429
  style: styles.webView
409
430
  })
@@ -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.29.0";
12
+ var version = "1.30.3";
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([z.object({
255
- message: z.optional(z.string()),
256
- type: z.enum(["error"])
257
- }), z.object({
258
- token: z.string(),
259
- type: z.enum(["token"])
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(sanitizeSiteKey(siteKey))
395
+ html: buildTurnstileHtml({
396
+ appearance: resolveAppearance(appearance),
397
+ siteKey: sanitizeSiteKey(siteKey)
398
+ })
378
399
  },
379
400
  style: styles.webView
380
401
  })
@@ -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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACyBA,IAAa,gBAAb,cAAmC,UAAU;CAC3C,YAAY,EAAE,eAAoD;AAChE,QAAM;GACJ,OAAO;GACP,MAAM;GACN,SAAS;GACT,MAAM;GACN,cAAc,oBAAoB,eAAe;GAClD,CAAC;;;;;;;;;;;;;;;;ACVN,MAAa,6BAA6B;AAQ1C,MAAMA,wBAAsB,EAAE,MAAM,CAClC,EAAE,OAAO;CACP,SAAS,EAAE,SAAS,EAAE,QAAQ,CAAC;CAC/B,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC;CACxB,CAAC,EACF,EAAE,OAAO;CACP,OAAO,EAAE,QAAQ;CACjB,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC;CACxB,CAAC,CACH,CAAC;AAqDF,MAAMC,qBAAmB,YAAoB,QAAQ,QAAQ,WAAW,GAAG;AAE3E,MAAM,qBAAqB,YAAoB;;;;;;;;;;;;;oBAa3B,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoD5B,MAAaC,qBAAiD,EAC5D,SACA,UACA,SACA,SACA,SACA,YACI;CAKJ,MAAM,aAAa,OAAO,MAAM;CAEhC,MAAM,SAAS,cACP,aAAa,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,SAASH,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,2BAA2B,MAAM,YAAY,eAAe,mBAC1E,CAAC,CACH,CACF;;AAGH,QACE,oBAAC;EAAK,OAAO,CAACI,SAAO,WAAW,MAAM;EAAE,QAAQ;YAC9C,oBAAC;GACC;GACA;GACA;GACA,SAAS;GACT,aAAa;GACb,WAAW;GAGX,iBAAiB,CAAC,YAAY;GAC9B,QAAQ;IACN;IACA,MAAM,kBAAkBH,kBAAgB,QAAQ,CAAC;IAClD;GACD,OAAOG,SAAO;IACd;GACG;;AAIX,MAAMA,WAAS,WAAW,OAAO;CAC/B,WAAW;EAGT,QAAQ;EACR,UAAU;EACX;CACD,SAAS;EACP,iBAAiB;EACjB,MAAM;EACP;CACF,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;AC7OF,IAAa,iBAAb,cAAoC,UAAU;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,sBAAsB,EAAE,MAAM,CAClC,EAAE,OAAO;CACP,SAAS,EAAE,SAAS,EAAE,QAAQ,CAAC;CAC/B,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC;CACxB,CAAC,EACF,EAAE,OAAO;CACP,OAAO,EAAE,QAAQ;CACjB,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC;CACxB,CAAC,CACH,CAAC;AAqDF,MAAM,mBAAmB,YAAoB,QAAQ,QAAQ,WAAW,GAAG;AAE3E,MAAM,sBAAsB,YAAoB;;;;;;;;;;;;;oBAa5B,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoD5B,MAAaC,sBAAmD,EAC9D,SACA,UACA,SACA,SACA,SACA,YACI;CAKJ,MAAM,aAAa,OAAO,MAAM;CAEhC,MAAM,SAAS,cACP,aAAa,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;SAC3B;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,2BAA2B,MAAM,YAAY,eAAe,mBAC1E,CAAC,CACH,CACF;;AAGH,QACE,oBAAC;EAAK,OAAO,CAAC,OAAO,WAAW,MAAM;EAAE,QAAQ;YAC9C,oBAAC;GACC;GACA;GACA;GACA,SAAS;GACT,aAAa;GACb,WAAW;GAKX,iBAAiB,CAAC,aAAa,eAAe;GAC9C,QAAQ;IACN;IACA,MAAM,mBAAmB,gBAAgB,QAAQ,CAAC;IACnD;GACD,OAAO,OAAO;IACd;GACG;;AAIX,MAAM,SAAS,WAAW,OAAO;CAC/B,WAAW;EAET,QAAQ;EACR,UAAU;EACX;CACD,SAAS;EACP,iBAAiB;EACjB,MAAM;EACP;CACF,CAAC;;;;AChQF,qBAAqBC,MAAaC,QAAe"}
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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACyBA,IAAa,gBAAb,cAAmC,UAAU;CAC3C,YAAY,EAAE,eAAoD;AAChE,QAAM;GACJ,OAAO;GACP,MAAM;GACN,SAAS;GACT,MAAM;GACN,cAAc,oBAAoB,eAAe;GAClD,CAAC;;;;;;;;;;;;;;;;ACVN,MAAa,6BAA6B;AAQ1C,MAAMA,wBAAsB,EAAE,MAAM,CAClC,EAAE,OAAO;CACP,SAAS,EAAE,SAAS,EAAE,QAAQ,CAAC;CAC/B,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC;CACxB,CAAC,EACF,EAAE,OAAO;CACP,OAAO,EAAE,QAAQ;CACjB,MAAM,EAAE,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,aAAa,OAAO,MAAM;CAEhC,MAAM,SAAS,cACP,aAAa,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,SAASH,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,oBAAC;EAAK,OAAO,CAACI,SAAO,WAAW,MAAM;EAAE,QAAQ;YAC9C,oBAAC;GACC;GACA;GACA;GACA,SAAS;GACT,aAAa;GACb,WAAW;GAGX,iBAAiB,CAAC,YAAY;GAC9B,QAAQ;IACN;IACA,MAAM,kBAAkBH,kBAAgB,QAAQ,CAAC;IAClD;GACD,OAAOG,SAAO;IACd;GACG;;AAIX,MAAMA,WAAS,WAAW,OAAO;CAC/B,WAAW;EAGT,QAAQ;EACR,UAAU;EACX;CACD,SAAS;EACP,iBAAiB;EACjB,MAAM;EACP;CACF,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;AChPF,IAAa,iBAAb,cAAoC,UAAU;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,sBAAsB,EAAE,MAAM;CAClC,EAAE,OAAO;EACP,SAAS,EAAE,SAAS,EAAE,QAAQ,CAAC;EAC/B,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC;EACxB,CAAC;CACF,EAAE,OAAO,EACP,MAAM,EAAE,KAAK,CAAC,cAAc,CAAC,EAC9B,CAAC;CACF,EAAE,OAAO;EACP,OAAO,EAAE,QAAQ;EACjB,MAAM,EAAE,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,aAAa,OAAO,MAAM;CAEhC,MAAM,SAAS,cACP,aAAa,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,oBAAC;EACC,OAAO,CAAC,OAAO,WAAW,MAAM;EAChC,QAAQ;YAER,oBAAC;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,SAAS,WAAW,OAAO;CAC/B,WAAW;EAET,QAAQ;EACR,UAAU;EACX;CACD,SAAS;EACP,iBAAiB;EACjB,MAAM;EACP;CACF,CAAC;;;;ACrVF,qBAAqBC,MAAaC,QAAe"}