@virex-tech/paywallo-sdk 2.2.6 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2386,8 +2386,8 @@ var init_NotificationHandlerSetup = __esm({
2386
2386
  // src/core/version.ts
2387
2387
  function resolveVersion() {
2388
2388
  try {
2389
- if ("2.2.6") {
2390
- return "2.2.6";
2389
+ if ("2.3.0") {
2390
+ return "2.3.0";
2391
2391
  }
2392
2392
  } catch {
2393
2393
  }
@@ -3057,6 +3057,41 @@ var init_emitPaywallClosed = __esm({
3057
3057
  }
3058
3058
  });
3059
3059
 
3060
+ // src/domains/paywall/extractFirstPageBackground.ts
3061
+ function extractFirstPageBackground(craftData) {
3062
+ if (!craftData) return DEFAULT_BACKGROUND;
3063
+ try {
3064
+ const data = JSON.parse(craftData);
3065
+ const nodes = { ...data.nodes, ...data.sharedNodes };
3066
+ const rootId = data.rootId;
3067
+ if (!rootId || !nodes[rootId]) return DEFAULT_BACKGROUND;
3068
+ const firstPageId = nodes[rootId].children?.[0];
3069
+ if (!firstPageId) return DEFAULT_BACKGROUND;
3070
+ const bg = nodes[firstPageId]?.props?.backgroundColor;
3071
+ return typeof bg === "string" && bg.length > 0 && bg !== "transparent" ? bg : DEFAULT_BACKGROUND;
3072
+ } catch {
3073
+ return DEFAULT_BACKGROUND;
3074
+ }
3075
+ }
3076
+ function isColorDark(color) {
3077
+ const hex = color.replace("#", "").trim();
3078
+ const normalized = hex.length === 3 ? hex.split("").map((c) => c + c).join("") : hex;
3079
+ if (normalized.length !== 6) return false;
3080
+ const r = parseInt(normalized.slice(0, 2), 16);
3081
+ const g = parseInt(normalized.slice(2, 4), 16);
3082
+ const b = parseInt(normalized.slice(4, 6), 16);
3083
+ if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) return false;
3084
+ const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
3085
+ return luminance < 0.5;
3086
+ }
3087
+ var DEFAULT_BACKGROUND;
3088
+ var init_extractFirstPageBackground = __esm({
3089
+ "src/domains/paywall/extractFirstPageBackground.ts"() {
3090
+ "use strict";
3091
+ DEFAULT_BACKGROUND = "#FFFFFF";
3092
+ }
3093
+ });
3094
+
3060
3095
  // src/domains/paywall/parseWebViewMessage.ts
3061
3096
  function deriveMessageId(message) {
3062
3097
  if (message.id) return message.id;
@@ -3299,6 +3334,66 @@ var init_localization = __esm({
3299
3334
  }
3300
3335
  });
3301
3336
 
3337
+ // src/domains/paywall/PaywallSkeleton.tsx
3338
+ function SkeletonBlock({ style, color, opacity }) {
3339
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native14.Animated.View, { style: [style, { backgroundColor: color, opacity }] });
3340
+ }
3341
+ function PaywallSkeleton({ backgroundColor = "#FFFFFF" }) {
3342
+ const pulse = (0, import_react3.useRef)(new import_react_native14.Animated.Value(0.4)).current;
3343
+ (0, import_react3.useEffect)(() => {
3344
+ const loop = import_react_native14.Animated.loop(
3345
+ import_react_native14.Animated.sequence([
3346
+ import_react_native14.Animated.timing(pulse, { toValue: 1, duration: 700, useNativeDriver: true }),
3347
+ import_react_native14.Animated.timing(pulse, { toValue: 0.4, duration: 700, useNativeDriver: true })
3348
+ ])
3349
+ );
3350
+ loop.start();
3351
+ return () => loop.stop();
3352
+ }, [pulse]);
3353
+ const placeholder = isColorDark(backgroundColor) ? "rgba(255,255,255,0.13)" : "rgba(0,0,0,0.07)";
3354
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native14.View, { style: [styles.container, { backgroundColor }], pointerEvents: "none", children: [
3355
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native14.View, { style: styles.header, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SkeletonBlock, { style: styles.logo, color: placeholder, opacity: pulse }) }),
3356
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SkeletonBlock, { style: styles.hero, color: placeholder, opacity: pulse }),
3357
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native14.View, { style: styles.titleGroup, children: [
3358
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SkeletonBlock, { style: styles.titleLine, color: placeholder, opacity: pulse }),
3359
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SkeletonBlock, { style: styles.titleLineShort, color: placeholder, opacity: pulse })
3360
+ ] }),
3361
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native14.View, { style: styles.cardsRow, children: [
3362
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SkeletonBlock, { style: styles.card, color: placeholder, opacity: pulse }),
3363
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SkeletonBlock, { style: styles.card, color: placeholder, opacity: pulse })
3364
+ ] }),
3365
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native14.View, { style: styles.footerGroup, children: [
3366
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SkeletonBlock, { style: styles.cta, color: placeholder, opacity: pulse }),
3367
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SkeletonBlock, { style: styles.footer, color: placeholder, opacity: pulse })
3368
+ ] })
3369
+ ] });
3370
+ }
3371
+ var import_react3, import_react_native14, import_jsx_runtime, RADIUS, styles;
3372
+ var init_PaywallSkeleton = __esm({
3373
+ "src/domains/paywall/PaywallSkeleton.tsx"() {
3374
+ "use strict";
3375
+ import_react3 = require("react");
3376
+ import_react_native14 = require("react-native");
3377
+ init_extractFirstPageBackground();
3378
+ import_jsx_runtime = require("react/jsx-runtime");
3379
+ RADIUS = 14;
3380
+ styles = import_react_native14.StyleSheet.create({
3381
+ card: { borderRadius: RADIUS, flex: 1, height: 120 },
3382
+ cardsRow: { flexDirection: "row", gap: 12, marginTop: "auto", paddingHorizontal: 24 },
3383
+ container: { flex: 1, paddingBottom: 40, paddingTop: 64 },
3384
+ cta: { borderRadius: 30, height: 58, marginHorizontal: 24 },
3385
+ footer: { alignSelf: "center", borderRadius: 6, height: 12, marginTop: 20, width: 180 },
3386
+ footerGroup: { marginTop: 28 },
3387
+ header: { alignItems: "center", paddingVertical: 12 },
3388
+ hero: { alignSelf: "center", borderRadius: 20, height: 220, marginTop: 16, width: "70%" },
3389
+ logo: { borderRadius: 6, height: 22, width: 110 },
3390
+ titleGroup: { alignItems: "center", gap: 12, marginTop: 28, paddingHorizontal: 24 },
3391
+ titleLine: { borderRadius: 8, height: 26, width: "85%" },
3392
+ titleLineShort: { borderRadius: 8, height: 26, width: "60%" }
3393
+ });
3394
+ }
3395
+ });
3396
+
3302
3397
  // src/domains/paywall/paywallScripts.ts
3303
3398
  function buildPaywallInjectionScript(data) {
3304
3399
  const paywallData = {
@@ -3308,6 +3403,7 @@ function buildPaywallInjectionScript(data) {
3308
3403
  products: data.products,
3309
3404
  primaryProductId: data.primaryProductId,
3310
3405
  secondaryProductId: data.secondaryProductId,
3406
+ tertiaryProductId: data.tertiaryProductId,
3311
3407
  currentLanguage: getCurrentLanguage(),
3312
3408
  defaultLanguage: getDefaultLanguage()
3313
3409
  }
@@ -3347,7 +3443,7 @@ function getNativeWebView() {
3347
3443
  if (NativeWebViewComponent) return NativeWebViewComponent;
3348
3444
  if (nativeWebViewLookupFailed) return null;
3349
3445
  try {
3350
- NativeWebViewComponent = (0, import_react_native14.requireNativeComponent)("PaywalloWebView");
3446
+ NativeWebViewComponent = (0, import_react_native15.requireNativeComponent)("PaywalloWebView");
3351
3447
  return NativeWebViewComponent;
3352
3448
  } catch {
3353
3449
  nativeWebViewLookupFailed = true;
@@ -3356,9 +3452,9 @@ function getNativeWebView() {
3356
3452
  }
3357
3453
  function getWebViewEmitter() {
3358
3454
  if (webViewEmitter) return webViewEmitter;
3359
- const mod = import_react_native14.NativeModules.PaywalloWebViewModule;
3455
+ const mod = import_react_native15.NativeModules.PaywalloWebViewModule;
3360
3456
  if (!mod) return null;
3361
- webViewEmitter = new import_react_native14.NativeEventEmitter(mod);
3457
+ webViewEmitter = new import_react_native15.NativeEventEmitter(mod);
3362
3458
  return webViewEmitter;
3363
3459
  }
3364
3460
  async function triggerNativeHaptic(style) {
@@ -3392,6 +3488,7 @@ function PaywallWebView({
3392
3488
  products,
3393
3489
  primaryProductId,
3394
3490
  secondaryProductId,
3491
+ tertiaryProductId,
3395
3492
  isPurchasing,
3396
3493
  webUrl: webUrlProp,
3397
3494
  onPurchase,
@@ -3400,16 +3497,16 @@ function PaywallWebView({
3400
3497
  onReady,
3401
3498
  onError
3402
3499
  }) {
3403
- const paywallDataSent = (0, import_react3.useRef)(false);
3404
- const loadEndReceived = (0, import_react3.useRef)(false);
3405
- const [scriptToInject, setScriptToInject] = (0, import_react3.useState)(void 0);
3406
- const webViewRef = (0, import_react3.useRef)(null);
3407
- const onErrorRef = (0, import_react3.useRef)(onError);
3500
+ const paywallDataSent = (0, import_react4.useRef)(false);
3501
+ const loadEndReceived = (0, import_react4.useRef)(false);
3502
+ const [scriptToInject, setScriptToInject] = (0, import_react4.useState)(void 0);
3503
+ const webViewRef = (0, import_react4.useRef)(null);
3504
+ const onErrorRef = (0, import_react4.useRef)(onError);
3408
3505
  onErrorRef.current = onError;
3409
- const onReadyRef = (0, import_react3.useRef)(onReady);
3506
+ const onReadyRef = (0, import_react4.useRef)(onReady);
3410
3507
  onReadyRef.current = onReady;
3411
- const NativeWebView = (0, import_react3.useMemo)(() => getNativeWebView(), []);
3412
- (0, import_react3.useEffect)(() => {
3508
+ const NativeWebView = (0, import_react4.useMemo)(() => getNativeWebView(), []);
3509
+ (0, import_react4.useEffect)(() => {
3413
3510
  if (!NativeWebView) {
3414
3511
  onErrorRef.current?.({
3415
3512
  code: -1,
@@ -3417,10 +3514,10 @@ function PaywallWebView({
3417
3514
  });
3418
3515
  }
3419
3516
  }, [NativeWebView]);
3420
- const [resolvedWebUrl, setResolvedWebUrl] = (0, import_react3.useState)(
3517
+ const [resolvedWebUrl, setResolvedWebUrl] = (0, import_react4.useState)(
3421
3518
  () => webUrlProp ?? PaywalloClient.getWebUrl()
3422
3519
  );
3423
- (0, import_react3.useEffect)(() => {
3520
+ (0, import_react4.useEffect)(() => {
3424
3521
  if (webUrlProp !== void 0) {
3425
3522
  setResolvedWebUrl(webUrlProp ?? null);
3426
3523
  return;
@@ -3439,7 +3536,7 @@ function PaywallWebView({
3439
3536
  }, 200);
3440
3537
  return () => clearInterval(interval);
3441
3538
  }, [webUrlProp]);
3442
- (0, import_react3.useEffect)(() => {
3539
+ (0, import_react4.useEffect)(() => {
3443
3540
  if (!resolvedWebUrl) {
3444
3541
  onErrorRef.current?.({
3445
3542
  code: -1,
@@ -3447,36 +3544,37 @@ function PaywallWebView({
3447
3544
  });
3448
3545
  }
3449
3546
  }, []);
3450
- const injectScript = (0, import_react3.useCallback)((script) => {
3451
- if (import_react_native14.Platform.OS === "android") {
3452
- const handle = (0, import_react_native14.findNodeHandle)(webViewRef.current);
3547
+ const injectScript = (0, import_react4.useCallback)((script) => {
3548
+ if (import_react_native15.Platform.OS === "android") {
3549
+ const handle = (0, import_react_native15.findNodeHandle)(webViewRef.current);
3453
3550
  if (handle == null) return;
3454
- import_react_native14.UIManager.dispatchViewManagerCommand(handle, "injectJavaScript", [script]);
3551
+ import_react_native15.UIManager.dispatchViewManagerCommand(handle, "injectJavaScript", [script]);
3455
3552
  return;
3456
3553
  }
3457
3554
  setScriptToInject(script);
3458
3555
  }, []);
3459
- const buildAndSetInjectionScript = (0, import_react3.useCallback)(() => {
3556
+ const buildAndSetInjectionScript = (0, import_react4.useCallback)(() => {
3460
3557
  if (!craftData || paywallDataSent.current) return;
3461
3558
  paywallDataSent.current = true;
3462
3559
  const script = buildPaywallInjectionScript({
3463
3560
  craftData,
3464
3561
  products: Array.from(products.values()),
3465
3562
  primaryProductId,
3466
- secondaryProductId
3563
+ secondaryProductId,
3564
+ tertiaryProductId
3467
3565
  });
3468
3566
  injectScript(script);
3469
- }, [craftData, products, primaryProductId, secondaryProductId, injectScript]);
3470
- const processedIdsRef = (0, import_react3.useRef)(/* @__PURE__ */ new Set());
3471
- const dedupeTimersRef = (0, import_react3.useRef)(/* @__PURE__ */ new Map());
3472
- (0, import_react3.useEffect)(() => {
3567
+ }, [craftData, products, primaryProductId, secondaryProductId, tertiaryProductId, injectScript]);
3568
+ const processedIdsRef = (0, import_react4.useRef)(/* @__PURE__ */ new Set());
3569
+ const dedupeTimersRef = (0, import_react4.useRef)(/* @__PURE__ */ new Map());
3570
+ (0, import_react4.useEffect)(() => {
3473
3571
  return () => {
3474
3572
  for (const timerId of dedupeTimersRef.current.values()) clearTimeout(timerId);
3475
3573
  dedupeTimersRef.current.clear();
3476
3574
  processedIdsRef.current.clear();
3477
3575
  };
3478
3576
  }, []);
3479
- const handleParsedMessage = (0, import_react3.useCallback)(
3577
+ const handleParsedMessage = (0, import_react4.useCallback)(
3480
3578
  (message) => {
3481
3579
  const msgId = deriveMessageId(message);
3482
3580
  if (processedIdsRef.current.has(msgId)) return;
@@ -3510,7 +3608,7 @@ function PaywallWebView({
3510
3608
  if (message.payload?.url) {
3511
3609
  const url = message.payload.url;
3512
3610
  if (/^https?:\/\//i.test(url) || /^mailto:/i.test(url) || /^itms-apps:/i.test(url) || /^market:/i.test(url)) {
3513
- void import_react_native14.Linking.openURL(url);
3611
+ void import_react_native15.Linking.openURL(url);
3514
3612
  }
3515
3613
  }
3516
3614
  break;
@@ -3521,7 +3619,7 @@ function PaywallWebView({
3521
3619
  },
3522
3620
  [buildAndSetInjectionScript, onPurchase, onClose, onRestore]
3523
3621
  );
3524
- (0, import_react3.useEffect)(() => {
3622
+ (0, import_react4.useEffect)(() => {
3525
3623
  const emitter = getWebViewEmitter();
3526
3624
  if (!emitter) return;
3527
3625
  const loadEndSub = emitter.addListener("PaywalloWebViewLoadEnd", () => {
@@ -3546,7 +3644,7 @@ function PaywallWebView({
3546
3644
  errorSub.remove();
3547
3645
  };
3548
3646
  }, [buildAndSetInjectionScript, handleParsedMessage]);
3549
- (0, import_react3.useEffect)(() => {
3647
+ (0, import_react4.useEffect)(() => {
3550
3648
  const timer = setTimeout(() => {
3551
3649
  if (!paywallDataSent.current) {
3552
3650
  onErrorRef.current?.({
@@ -3557,13 +3655,13 @@ function PaywallWebView({
3557
3655
  }, 1e4);
3558
3656
  return () => clearTimeout(timer);
3559
3657
  }, []);
3560
- const handleLoadEnd = (0, import_react3.useCallback)(() => {
3658
+ const handleLoadEnd = (0, import_react4.useCallback)(() => {
3561
3659
  if (!loadEndReceived.current) {
3562
3660
  loadEndReceived.current = true;
3563
3661
  buildAndSetInjectionScript();
3564
3662
  }
3565
3663
  }, [buildAndSetInjectionScript]);
3566
- const handleNativeError = (0, import_react3.useCallback)((event) => {
3664
+ const handleNativeError = (0, import_react4.useCallback)((event) => {
3567
3665
  const ne = event.nativeEvent;
3568
3666
  let errorData;
3569
3667
  if (ne["payload"] && typeof ne["payload"] === "object") {
@@ -3579,30 +3677,30 @@ function PaywallWebView({
3579
3677
  }
3580
3678
  onErrorRef.current?.(parseErrorEvent(errorData));
3581
3679
  }, []);
3582
- const handleMessage = (0, import_react3.useCallback)(
3680
+ const handleMessage = (0, import_react4.useCallback)(
3583
3681
  (event) => {
3584
3682
  const message = parseWebViewMessage(event.nativeEvent.data);
3585
3683
  if (message) handleParsedMessage(message);
3586
3684
  },
3587
3685
  [handleParsedMessage]
3588
3686
  );
3589
- (0, import_react3.useEffect)(() => {
3687
+ (0, import_react4.useEffect)(() => {
3590
3688
  if (!paywallDataSent.current) return;
3591
3689
  injectScript(buildPurchaseStateScript(isPurchasing));
3592
3690
  }, [isPurchasing, injectScript]);
3593
- (0, import_react3.useEffect)(() => {
3594
- if (import_react_native14.Platform.OS !== "android") return;
3691
+ (0, import_react4.useEffect)(() => {
3692
+ if (import_react_native15.Platform.OS !== "android") return;
3595
3693
  const onBackPress = () => {
3596
3694
  if (!paywallDataSent.current) return false;
3597
3695
  injectScript(buildBackButtonScript());
3598
3696
  return true;
3599
3697
  };
3600
- const subscription = import_react_native14.BackHandler.addEventListener("hardwareBackPress", onBackPress);
3698
+ const subscription = import_react_native15.BackHandler.addEventListener("hardwareBackPress", onBackPress);
3601
3699
  return () => subscription.remove();
3602
3700
  }, [injectScript]);
3603
3701
  if (!resolvedWebUrl || !NativeWebView) return null;
3604
3702
  const paywallUrl = `${resolvedWebUrl}/paywall/${paywallId}`;
3605
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native14.View, { style: styles.container, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
3703
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native15.View, { style: styles2.container, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3606
3704
  NativeWebView,
3607
3705
  {
3608
3706
  ref: webViewRef,
@@ -3611,24 +3709,24 @@ function PaywallWebView({
3611
3709
  onMessage: handleMessage,
3612
3710
  onLoadEnd: handleLoadEnd,
3613
3711
  onError: handleNativeError,
3614
- style: styles.webview
3712
+ style: styles2.webview
3615
3713
  }
3616
3714
  ) });
3617
3715
  }
3618
- var import_react3, import_react_native14, import_jsx_runtime, NativeWebViewComponent, nativeWebViewLookupFailed, webViewEmitter, styles;
3716
+ var import_react4, import_react_native15, import_jsx_runtime2, NativeWebViewComponent, nativeWebViewLookupFailed, webViewEmitter, styles2;
3619
3717
  var init_PaywallWebView = __esm({
3620
3718
  "src/domains/paywall/PaywallWebView.tsx"() {
3621
3719
  "use strict";
3622
- import_react3 = require("react");
3623
- import_react_native14 = require("react-native");
3720
+ import_react4 = require("react");
3721
+ import_react_native15 = require("react-native");
3624
3722
  init_PaywalloClient();
3625
3723
  init_parseWebViewMessage();
3626
3724
  init_paywallScripts();
3627
- import_jsx_runtime = require("react/jsx-runtime");
3725
+ import_jsx_runtime2 = require("react/jsx-runtime");
3628
3726
  NativeWebViewComponent = null;
3629
3727
  nativeWebViewLookupFailed = false;
3630
3728
  webViewEmitter = null;
3631
- styles = import_react_native14.StyleSheet.create({
3729
+ styles2 = import_react_native15.StyleSheet.create({
3632
3730
  container: { flex: 1 },
3633
3731
  webview: { flex: 1, backgroundColor: "transparent" }
3634
3732
  });
@@ -3763,7 +3861,7 @@ function mapNativePurchase(native) {
3763
3861
  transactionId: native.transactionId,
3764
3862
  transactionDate: native.transactionDate,
3765
3863
  receipt: native.receipt,
3766
- platform: import_react_native15.Platform.OS
3864
+ platform: import_react_native16.Platform.OS
3767
3865
  };
3768
3866
  }
3769
3867
  function normalizeTransactionUpdate(raw) {
@@ -3783,17 +3881,17 @@ function normalizeTransactionUpdate(raw) {
3783
3881
  if (typeof r.currency === "string") update.currency = r.currency;
3784
3882
  return update;
3785
3883
  }
3786
- var import_react_native15, TRANSACTION_UPDATE_EVENT, PaywalloStoreKitNative, nativeStoreKit, NativeStoreKit;
3884
+ var import_react_native16, TRANSACTION_UPDATE_EVENT, PaywalloStoreKitNative, nativeStoreKit, NativeStoreKit;
3787
3885
  var init_NativeStoreKit = __esm({
3788
3886
  "src/domains/iap/NativeStoreKit.ts"() {
3789
3887
  "use strict";
3790
- import_react_native15 = require("react-native");
3888
+ import_react_native16 = require("react-native");
3791
3889
  init_PaywalloClient();
3792
3890
  init_uuid();
3793
3891
  init_IdentityManager();
3794
3892
  init_PurchaseError();
3795
3893
  TRANSACTION_UPDATE_EVENT = "PaywalloTransactionUpdate";
3796
- PaywalloStoreKitNative = import_react_native15.NativeModules.PaywalloStoreKit ?? null;
3894
+ PaywalloStoreKitNative = import_react_native16.NativeModules.PaywalloStoreKit ?? null;
3797
3895
  nativeStoreKit = {
3798
3896
  isAvailable: isAvailable3,
3799
3897
  async getProducts(productIds) {
@@ -3862,7 +3960,7 @@ var init_NativeStoreKit = __esm({
3862
3960
  subscribeToTransactionUpdates(listener) {
3863
3961
  if (!PaywalloStoreKitNative) return null;
3864
3962
  try {
3865
- const EmitterCtor = import_react_native15.NativeEventEmitter;
3963
+ const EmitterCtor = import_react_native16.NativeEventEmitter;
3866
3964
  const emitter = new EmitterCtor(PaywalloStoreKitNative);
3867
3965
  const sub = emitter.addListener(TRANSACTION_UPDATE_EVENT, (payload) => {
3868
3966
  const update = normalizeTransactionUpdate(payload);
@@ -4518,11 +4616,11 @@ var init_types2 = __esm({
4518
4616
  });
4519
4617
 
4520
4618
  // src/core/network/NetworkMonitor.ts
4521
- var import_react_native16, NetworkMonitorClass, networkMonitor;
4619
+ var import_react_native17, NetworkMonitorClass, networkMonitor;
4522
4620
  var init_NetworkMonitor = __esm({
4523
4621
  "src/core/network/NetworkMonitor.ts"() {
4524
4622
  "use strict";
4525
- import_react_native16 = require("react-native");
4623
+ import_react_native17 = require("react-native");
4526
4624
  init_types2();
4527
4625
  NetworkMonitorClass = class {
4528
4626
  constructor() {
@@ -4551,7 +4649,7 @@ var init_NetworkMonitor = __esm({
4551
4649
  }, this.config.checkInterval);
4552
4650
  }
4553
4651
  setupAppStateListener() {
4554
- this.appStateSubscription = import_react_native16.AppState.addEventListener("change", (state) => {
4652
+ this.appStateSubscription = import_react_native17.AppState.addEventListener("change", (state) => {
4555
4653
  if (state === "active") {
4556
4654
  if (!this.checkIntervalId) {
4557
4655
  this.checkIntervalId = setInterval(() => {
@@ -4920,11 +5018,11 @@ var init_queue = __esm({
4920
5018
  });
4921
5019
 
4922
5020
  // src/domains/iap/IAPValidator.ts
4923
- var import_react_native17, IAPValidator;
5021
+ var import_react_native18, IAPValidator;
4924
5022
  var init_IAPValidator = __esm({
4925
5023
  "src/domains/iap/IAPValidator.ts"() {
4926
5024
  "use strict";
4927
- import_react_native17 = require("react-native");
5025
+ import_react_native18 = require("react-native");
4928
5026
  init_PaywalloClient();
4929
5027
  init_queue();
4930
5028
  init_PurchaseError();
@@ -4950,7 +5048,7 @@ var init_IAPValidator = __esm({
4950
5048
  async validateWithServer(purchase, productId, options) {
4951
5049
  const apiClient = this.getApiClientFn();
4952
5050
  const product = this.getProductFromCache(productId);
4953
- const platform = import_react_native17.Platform.OS;
5051
+ const platform = import_react_native18.Platform.OS;
4954
5052
  const distinctId = PaywalloClient.getDistinctId();
4955
5053
  return this.validateWithRetry(
4956
5054
  () => apiClient.validatePurchase(
@@ -4985,7 +5083,7 @@ var init_IAPValidator = __esm({
4985
5083
  try {
4986
5084
  const apiClient = this.getApiClientFn();
4987
5085
  const product = this.getProductFromCache(productId);
4988
- const platform = import_react_native17.Platform.OS;
5086
+ const platform = import_react_native18.Platform.OS;
4989
5087
  const distinctId = PaywalloClient.getDistinctId();
4990
5088
  const appKey = apiClient.getAppKey();
4991
5089
  const body = {
@@ -5025,11 +5123,11 @@ function getIAPService() {
5025
5123
  }
5026
5124
  return _instance;
5027
5125
  }
5028
- var import_react_native18, IAPService, _instance;
5126
+ var import_react_native19, IAPService, _instance;
5029
5127
  var init_IAPService = __esm({
5030
5128
  "src/domains/iap/IAPService.ts"() {
5031
5129
  "use strict";
5032
- import_react_native18 = require("react-native");
5130
+ import_react_native19 = require("react-native");
5033
5131
  init_PaywalloClient();
5034
5132
  init_IAPTransactionEmitter();
5035
5133
  init_IAPValidator();
@@ -5179,7 +5277,7 @@ var init_IAPService = __esm({
5179
5277
  const transactions = await nativeStoreKit.getActiveTransactions();
5180
5278
  const apiClient = this.getApiClient();
5181
5279
  const distinctId = PaywalloClient.getDistinctId();
5182
- const platform = import_react_native18.Platform.OS;
5280
+ const platform = import_react_native19.Platform.OS;
5183
5281
  const results = await Promise.allSettled(
5184
5282
  transactions.map((tx) => {
5185
5283
  const product = this.productsCache.get(tx.productId);
@@ -5237,11 +5335,11 @@ var init_IAPService = __esm({
5237
5335
 
5238
5336
  // src/domains/paywall/usePaywallTracking.ts
5239
5337
  function usePaywallTracking() {
5240
- const trackClosed = (0, import_react4.useCallback)(async (opts) => {
5338
+ const trackClosed = (0, import_react5.useCallback)(async (opts) => {
5241
5339
  await emitPaywallClosed(opts);
5242
5340
  await stopHeartbeat();
5243
5341
  }, []);
5244
- const trackDismissed = (0, import_react4.useCallback)(async (opts) => {
5342
+ const trackDismissed = (0, import_react5.useCallback)(async (opts) => {
5245
5343
  const presentedAt = Date.now() - Math.max(0, opts.duration_ms);
5246
5344
  await trackClosed({
5247
5345
  placement: opts.placement,
@@ -5253,17 +5351,17 @@ function usePaywallTracking() {
5253
5351
  ...opts.variantId && { variantId: opts.variantId }
5254
5352
  });
5255
5353
  }, [trackClosed]);
5256
- const trackProductSelected = (0, import_react4.useCallback)((_opts) => {
5354
+ const trackProductSelected = (0, import_react5.useCallback)((_opts) => {
5257
5355
  }, []);
5258
- const trackPurchased = (0, import_react4.useCallback)(async (_opts) => {
5356
+ const trackPurchased = (0, import_react5.useCallback)(async (_opts) => {
5259
5357
  }, []);
5260
5358
  return { trackClosed, trackDismissed, trackProductSelected, trackPurchased };
5261
5359
  }
5262
- var import_react4;
5360
+ var import_react5;
5263
5361
  var init_usePaywallTracking = __esm({
5264
5362
  "src/domains/paywall/usePaywallTracking.ts"() {
5265
5363
  "use strict";
5266
- import_react4 = require("react");
5364
+ import_react5 = require("react");
5267
5365
  init_emitPaywallClosed();
5268
5366
  init_paywallHeartbeat();
5269
5367
  }
@@ -5271,19 +5369,19 @@ var init_usePaywallTracking = __esm({
5271
5369
 
5272
5370
  // src/domains/paywall/usePaywallActions.ts
5273
5371
  function usePaywallActions(placement, paywallConfig, onResult, variantKey, campaignId, openAnim, variantId) {
5274
- const [isPurchasing, setIsPurchasing] = (0, import_react5.useState)(false);
5275
- const isPurchasingRef = (0, import_react5.useRef)(false);
5276
- const [isClosing, setIsClosing] = (0, import_react5.useState)(false);
5277
- const slideAnim = (0, import_react5.useRef)(new import_react_native19.Animated.Value(0)).current;
5278
- const presentedAtRef = (0, import_react5.useRef)(Date.now());
5279
- const closedRef = (0, import_react5.useRef)(false);
5372
+ const [isPurchasing, setIsPurchasing] = (0, import_react6.useState)(false);
5373
+ const isPurchasingRef = (0, import_react6.useRef)(false);
5374
+ const [isClosing, setIsClosing] = (0, import_react6.useState)(false);
5375
+ const slideAnim = (0, import_react6.useRef)(new import_react_native20.Animated.Value(0)).current;
5376
+ const presentedAtRef = (0, import_react6.useRef)(Date.now());
5377
+ const closedRef = (0, import_react6.useRef)(false);
5280
5378
  const { trackDismissed, trackProductSelected, trackPurchased } = usePaywallTracking();
5281
- const closeWithReason = (0, import_react5.useCallback)((reason, resultOverride) => {
5379
+ const closeWithReason = (0, import_react6.useCallback)((reason, resultOverride) => {
5282
5380
  if (closedRef.current) return;
5283
5381
  closedRef.current = true;
5284
5382
  const animTarget = openAnim ?? slideAnim;
5285
- import_react_native19.Animated.timing(animTarget, {
5286
- toValue: import_react_native19.Dimensions.get("window").height,
5383
+ import_react_native20.Animated.timing(animTarget, {
5384
+ toValue: import_react_native20.Dimensions.get("window").height,
5287
5385
  duration: 350,
5288
5386
  useNativeDriver: true
5289
5387
  }).start(() => {
@@ -5297,13 +5395,13 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
5297
5395
  onResult(resultOverride ?? { presented: true, purchased: false, cancelled: true, restored: false });
5298
5396
  });
5299
5397
  }, [openAnim, slideAnim, paywallConfig, placement, trackDismissed, variantKey, campaignId, variantId, onResult]);
5300
- const handleClose = (0, import_react5.useCallback)(() => {
5398
+ const handleClose = (0, import_react6.useCallback)(() => {
5301
5399
  if (isClosing) return;
5302
5400
  setIsClosing(true);
5303
5401
  closeWithReason("dismissed");
5304
5402
  }, [isClosing, closeWithReason]);
5305
- (0, import_react5.useEffect)(() => {
5306
- const sub = import_react_native19.AppState.addEventListener("change", (nextState) => {
5403
+ (0, import_react6.useEffect)(() => {
5404
+ const sub = import_react_native20.AppState.addEventListener("change", (nextState) => {
5307
5405
  if (isPurchasingRef.current) return;
5308
5406
  if (nextState === "background" || nextState === "inactive") {
5309
5407
  closeWithReason("backgrounded");
@@ -5311,13 +5409,13 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
5311
5409
  });
5312
5410
  return () => sub.remove();
5313
5411
  }, [closeWithReason]);
5314
- (0, import_react5.useEffect)(() => {
5412
+ (0, import_react6.useEffect)(() => {
5315
5413
  const timer = setTimeout(() => {
5316
5414
  closeWithReason("timeout");
5317
5415
  }, PAYWALL_TIMEOUT_MS);
5318
5416
  return () => clearTimeout(timer);
5319
5417
  }, [closeWithReason]);
5320
- const handlePurchase = (0, import_react5.useCallback)(async (productId) => {
5418
+ const handlePurchase = (0, import_react6.useCallback)(async (productId) => {
5321
5419
  if (isPurchasing) return;
5322
5420
  setIsPurchasing(true);
5323
5421
  isPurchasingRef.current = true;
@@ -5347,7 +5445,7 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
5347
5445
  setIsPurchasing(false);
5348
5446
  }
5349
5447
  }, [isPurchasing, onResult, paywallConfig, placement, trackDismissed, trackProductSelected, trackPurchased, variantKey, campaignId, variantId]);
5350
- const handleRestore = (0, import_react5.useCallback)(async () => {
5448
+ const handleRestore = (0, import_react6.useCallback)(async () => {
5351
5449
  if (isPurchasing) return;
5352
5450
  setIsPurchasing(true);
5353
5451
  isPurchasingRef.current = true;
@@ -5371,12 +5469,12 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
5371
5469
  }, [isPurchasing, onResult, paywallConfig, placement, trackDismissed, variantKey, campaignId, variantId]);
5372
5470
  return { isPurchasing, slideAnim, handleClose, handlePurchase, handleRestore };
5373
5471
  }
5374
- var import_react5, import_react_native19, PAYWALL_TIMEOUT_MS;
5472
+ var import_react6, import_react_native20, PAYWALL_TIMEOUT_MS;
5375
5473
  var init_usePaywallActions = __esm({
5376
5474
  "src/domains/paywall/usePaywallActions.ts"() {
5377
5475
  "use strict";
5378
- import_react5 = require("react");
5379
- import_react_native19 = require("react-native");
5476
+ import_react6 = require("react");
5477
+ import_react_native20 = require("react-native");
5380
5478
  init_IAPService();
5381
5479
  init_usePaywallTracking();
5382
5480
  PAYWALL_TIMEOUT_MS = 30 * 60 * 1e3;
@@ -5385,15 +5483,15 @@ var init_usePaywallActions = __esm({
5385
5483
 
5386
5484
  // src/domains/paywall/usePaywallLoader.ts
5387
5485
  function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts, variantKey, campaignId, variantId) {
5388
- const [error, setError] = (0, import_react6.useState)(null);
5389
- const [paywallConfig, setPaywallConfig] = (0, import_react6.useState)(null);
5390
- const [products, setProducts] = (0, import_react6.useState)(/* @__PURE__ */ new Map());
5391
- const preloadedConfigRef = (0, import_react6.useRef)(preloadedConfig);
5486
+ const [error, setError] = (0, import_react7.useState)(null);
5487
+ const [paywallConfig, setPaywallConfig] = (0, import_react7.useState)(null);
5488
+ const [products, setProducts] = (0, import_react7.useState)(/* @__PURE__ */ new Map());
5489
+ const preloadedConfigRef = (0, import_react7.useRef)(preloadedConfig);
5392
5490
  preloadedConfigRef.current = preloadedConfig;
5393
- const preloadedProductsRef = (0, import_react6.useRef)(preloadedProducts);
5491
+ const preloadedProductsRef = (0, import_react7.useRef)(preloadedProducts);
5394
5492
  preloadedProductsRef.current = preloadedProducts;
5395
- const trackingDoneRef = (0, import_react6.useRef)(false);
5396
- (0, import_react6.useEffect)(() => {
5493
+ const trackingDoneRef = (0, import_react7.useRef)(false);
5494
+ (0, import_react7.useEffect)(() => {
5397
5495
  if (!visible) {
5398
5496
  trackingDoneRef.current = false;
5399
5497
  return;
@@ -5412,7 +5510,8 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
5412
5510
  placement: snap.placement,
5413
5511
  config: snap.config,
5414
5512
  primaryProductId: snap.primaryProductId,
5415
- secondaryProductId: snap.secondaryProductId
5513
+ secondaryProductId: snap.secondaryProductId,
5514
+ tertiaryProductId: snap.tertiaryProductId
5416
5515
  };
5417
5516
  } else {
5418
5517
  const fetched = await apiClient.getPaywall(placement);
@@ -5424,6 +5523,7 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
5424
5523
  const productIds = [];
5425
5524
  if (config.primaryProductId) productIds.push(config.primaryProductId);
5426
5525
  if (config.secondaryProductId) productIds.push(config.secondaryProductId);
5526
+ if (config.tertiaryProductId) productIds.push(config.tertiaryProductId);
5427
5527
  const snapProducts = preloadedProductsRef.current;
5428
5528
  if (snapProducts && snapProducts.size > 0) {
5429
5529
  if (!cancelled) setProducts(snapProducts);
@@ -5478,11 +5578,11 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
5478
5578
  }, [placement, visible, variantKey, campaignId, variantId]);
5479
5579
  return { paywallConfig, products, error };
5480
5580
  }
5481
- var import_react6;
5581
+ var import_react7;
5482
5582
  var init_usePaywallLoader = __esm({
5483
5583
  "src/domains/paywall/usePaywallLoader.ts"() {
5484
5584
  "use strict";
5485
- import_react6 = require("react");
5585
+ import_react7 = require("react");
5486
5586
  init_PaywalloClient();
5487
5587
  init_IAPService();
5488
5588
  init_PaywallError();
@@ -5495,11 +5595,11 @@ function ErrorFallback({ description, onRetry, onClose }) {
5495
5595
  const title = overrides?.title ?? getSdkString("paywallErrorTitle");
5496
5596
  const retryLabel = overrides?.retry ?? getSdkString("paywallRetryButton");
5497
5597
  const closeLabel = overrides?.close ?? getSdkString("paywallCloseButton");
5498
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native20.View, { style: styles2.fallbackContainer, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native20.View, { style: styles2.fallbackCard, children: [
5499
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native20.Text, { style: styles2.fallbackTitle, children: title }),
5500
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native20.Text, { style: styles2.fallbackDescription, children: description }),
5501
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native20.Pressable, { style: styles2.retryButton, onPress: onRetry, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native20.Text, { style: styles2.retryButtonText, children: retryLabel }) }),
5502
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native20.Pressable, { style: styles2.closeButton, onPress: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native20.Text, { style: styles2.closeButtonText, children: closeLabel }) })
5598
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native21.View, { style: styles3.fallbackContainer, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native21.View, { style: styles3.fallbackCard, children: [
5599
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native21.Text, { style: styles3.fallbackTitle, children: title }),
5600
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native21.Text, { style: styles3.fallbackDescription, children: description }),
5601
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native21.Pressable, { style: styles3.retryButton, onPress: onRetry, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native21.Text, { style: styles3.retryButtonText, children: retryLabel }) }),
5602
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native21.Pressable, { style: styles3.closeButton, onPress: onClose, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native21.Text, { style: styles3.closeButtonText, children: closeLabel }) })
5503
5603
  ] }) });
5504
5604
  }
5505
5605
  function PaywallModal({
@@ -5521,18 +5621,20 @@ function PaywallModal({
5521
5621
  campaignId,
5522
5622
  variantId
5523
5623
  );
5524
- const screenHeight = import_react_native20.Dimensions.get("window").height;
5525
- const openAnim = (0, import_react7.useRef)(new import_react_native20.Animated.Value(screenHeight)).current;
5526
- const [modalVisible, setModalVisible] = (0, import_react7.useState)(false);
5527
- const handleResult = (0, import_react7.useCallback)((result) => {
5624
+ const screenHeight = import_react_native21.Dimensions.get("window").height;
5625
+ const openAnim = (0, import_react8.useRef)(new import_react_native21.Animated.Value(screenHeight)).current;
5626
+ const [modalVisible, setModalVisible] = (0, import_react8.useState)(false);
5627
+ const handleResult = (0, import_react8.useCallback)((result) => {
5528
5628
  setModalVisible(false);
5529
5629
  onResult(result);
5530
5630
  }, [onResult]);
5531
5631
  const { isPurchasing, slideAnim, handleClose, handlePurchase, handleRestore } = usePaywallActions(placement, paywallConfig, handleResult, variantKey, campaignId, openAnim, variantId);
5532
- const [webViewError, setWebViewError] = (0, import_react7.useState)(null);
5533
- const [retryCount, setRetryCount] = (0, import_react7.useState)(0);
5534
- const retryCountRef = (0, import_react7.useRef)(0);
5535
- const handleWebViewError = (0, import_react7.useCallback)(
5632
+ const [webViewError, setWebViewError] = (0, import_react8.useState)(null);
5633
+ const [retryCount, setRetryCount] = (0, import_react8.useState)(0);
5634
+ const [showSkeleton, setShowSkeleton] = (0, import_react8.useState)(true);
5635
+ const skeletonOpacity = (0, import_react8.useRef)(new import_react_native21.Animated.Value(1)).current;
5636
+ const retryCountRef = (0, import_react8.useRef)(0);
5637
+ const handleWebViewError = (0, import_react8.useCallback)(
5536
5638
  (err) => {
5537
5639
  if (retryCountRef.current >= 1) {
5538
5640
  setWebViewError({ code: err.code, description: err.description });
@@ -5543,26 +5645,44 @@ function PaywallModal({
5543
5645
  },
5544
5646
  []
5545
5647
  );
5546
- const handleRetry = (0, import_react7.useCallback)(() => {
5648
+ const handleRetry = (0, import_react8.useCallback)(() => {
5547
5649
  retryCountRef.current = 0;
5548
5650
  setWebViewError(null);
5549
5651
  setRetryCount(0);
5550
- }, []);
5551
- (0, import_react7.useEffect)(() => {
5652
+ setShowSkeleton(true);
5653
+ skeletonOpacity.setValue(1);
5654
+ }, [skeletonOpacity]);
5655
+ const handleWebViewReady = (0, import_react8.useCallback)(() => {
5656
+ import_react_native21.Animated.timing(skeletonOpacity, {
5657
+ toValue: 0,
5658
+ duration: 250,
5659
+ easing: import_react_native21.Easing.out(import_react_native21.Easing.cubic),
5660
+ useNativeDriver: true
5661
+ }).start(() => setShowSkeleton(false));
5662
+ }, [skeletonOpacity]);
5663
+ (0, import_react8.useEffect)(() => {
5664
+ if (!visible || !showSkeleton) return;
5665
+ const timer = setTimeout(handleWebViewReady, 8e3);
5666
+ return () => clearTimeout(timer);
5667
+ }, [visible, showSkeleton, handleWebViewReady]);
5668
+ (0, import_react8.useEffect)(() => {
5552
5669
  if (visible) {
5553
5670
  setModalVisible(true);
5554
- openAnim.setValue(import_react_native20.Dimensions.get("window").height);
5555
- import_react_native20.Animated.timing(openAnim, {
5671
+ setShowSkeleton(true);
5672
+ skeletonOpacity.setValue(1);
5673
+ openAnim.setValue(import_react_native21.Dimensions.get("window").height);
5674
+ import_react_native21.Animated.timing(openAnim, {
5556
5675
  toValue: 0,
5557
5676
  duration: 550,
5558
- easing: import_react_native20.Easing.out(import_react_native20.Easing.cubic),
5677
+ easing: import_react_native21.Easing.out(import_react_native21.Easing.cubic),
5559
5678
  useNativeDriver: true
5560
5679
  }).start();
5561
5680
  }
5562
- }, [visible, openAnim]);
5681
+ }, [visible, openAnim, skeletonOpacity]);
5563
5682
  if (!modalVisible && !visible) return null;
5564
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
5565
- import_react_native20.Modal,
5683
+ const skeletonBackground = paywallConfig && "craftData" in paywallConfig.config ? extractFirstPageBackground(paywallConfig.config.craftData) : "#FFFFFF";
5684
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
5685
+ import_react_native21.Modal,
5566
5686
  {
5567
5687
  visible: modalVisible,
5568
5688
  animationType: "none",
@@ -5570,16 +5690,16 @@ function PaywallModal({
5570
5690
  onRequestClose: handleClose,
5571
5691
  transparent: true,
5572
5692
  statusBarTranslucent: true,
5573
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native20.View, { style: styles2.overlay, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native20.Animated.View, { style: [styles2.animatedContainer, { transform: [{ translateY: openAnim }] }], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native20.View, { style: styles2.container, children: [
5574
- error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native20.View, { style: styles2.errorContainer, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native20.Text, { style: styles2.errorText, children: error.message }) }),
5575
- !error && paywallConfig && "craftData" in paywallConfig.config && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: webViewError ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
5693
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native21.View, { style: styles3.overlay, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native21.Animated.View, { style: [styles3.animatedContainer, { transform: [{ translateY: openAnim }] }], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native21.View, { style: styles3.container, children: [
5694
+ error && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native21.View, { style: styles3.errorContainer, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native21.Text, { style: styles3.errorText, children: error.message }) }),
5695
+ !error && paywallConfig && "craftData" in paywallConfig.config && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: webViewError ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
5576
5696
  ErrorFallback,
5577
5697
  {
5578
5698
  description: webViewError.description,
5579
5699
  onRetry: handleRetry,
5580
5700
  onClose: handleClose
5581
5701
  }
5582
- ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
5702
+ ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
5583
5703
  PaywallWebView,
5584
5704
  {
5585
5705
  paywallId: paywallConfig.id,
@@ -5587,31 +5707,43 @@ function PaywallModal({
5587
5707
  products,
5588
5708
  primaryProductId: paywallConfig.primaryProductId,
5589
5709
  secondaryProductId: paywallConfig.secondaryProductId,
5710
+ tertiaryProductId: paywallConfig.tertiaryProductId,
5590
5711
  isPurchasing,
5712
+ onReady: handleWebViewReady,
5591
5713
  onClose: handleClose,
5592
5714
  onPurchase: handlePurchase,
5593
5715
  onRestore: handleRestore,
5594
5716
  onError: handleWebViewError
5595
5717
  },
5596
5718
  retryCount
5597
- ) })
5719
+ ) }),
5720
+ showSkeleton && !error && !webViewError && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
5721
+ import_react_native21.Animated.View,
5722
+ {
5723
+ style: [import_react_native21.StyleSheet.absoluteFill, { opacity: skeletonOpacity }],
5724
+ pointerEvents: "none",
5725
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PaywallSkeleton, { backgroundColor: skeletonBackground })
5726
+ }
5727
+ )
5598
5728
  ] }) }) })
5599
5729
  }
5600
5730
  );
5601
5731
  }
5602
- var import_react7, import_react_native20, import_jsx_runtime2, styles2;
5732
+ var import_react8, import_react_native21, import_jsx_runtime3, styles3;
5603
5733
  var init_PaywallModal = __esm({
5604
5734
  "src/domains/paywall/PaywallModal.tsx"() {
5605
5735
  "use strict";
5606
- import_react7 = require("react");
5607
- import_react_native20 = require("react-native");
5736
+ import_react8 = require("react");
5737
+ import_react_native21 = require("react-native");
5608
5738
  init_PaywalloClient();
5609
5739
  init_localization();
5740
+ init_extractFirstPageBackground();
5741
+ init_PaywallSkeleton();
5610
5742
  init_PaywallWebView();
5611
5743
  init_usePaywallActions();
5612
5744
  init_usePaywallLoader();
5613
- import_jsx_runtime2 = require("react/jsx-runtime");
5614
- styles2 = import_react_native20.StyleSheet.create({
5745
+ import_jsx_runtime3 = require("react/jsx-runtime");
5746
+ styles3 = import_react_native21.StyleSheet.create({
5615
5747
  overlay: { flex: 1, backgroundColor: "rgba(0,0,0,0.3)" },
5616
5748
  animatedContainer: { flex: 1, backgroundColor: "#fff" },
5617
5749
  container: { flex: 1, backgroundColor: "#fff" },
@@ -5667,12 +5799,176 @@ var init_PaywallModal = __esm({
5667
5799
  }
5668
5800
  });
5669
5801
 
5802
+ // src/domains/paywall/PaywallPreloadService.ts
5803
+ var PRELOAD_TTL_MS, STALE_THRESHOLD, PaywallPreloadService, paywallPreloadService;
5804
+ var init_PaywallPreloadService = __esm({
5805
+ "src/domains/paywall/PaywallPreloadService.ts"() {
5806
+ "use strict";
5807
+ init_PaywalloClient();
5808
+ init_IAPService();
5809
+ init_PaywallError();
5810
+ PRELOAD_TTL_MS = 5 * 60 * 1e3;
5811
+ STALE_THRESHOLD = 0.75;
5812
+ PaywallPreloadService = class {
5813
+ constructor() {
5814
+ this.preloaded = /* @__PURE__ */ new Map();
5815
+ this.activePreloads = /* @__PURE__ */ new Map();
5816
+ this.apiClientRef = null;
5817
+ }
5818
+ async preload(apiClient, placement) {
5819
+ this.apiClientRef = apiClient;
5820
+ const existing = this.activePreloads.get(placement);
5821
+ if (existing) return existing;
5822
+ const promise = this.doPreload(apiClient, placement);
5823
+ this.activePreloads.set(placement, promise);
5824
+ try {
5825
+ return await promise;
5826
+ } finally {
5827
+ this.activePreloads.delete(placement);
5828
+ }
5829
+ }
5830
+ async preloadMany(apiClient, placements) {
5831
+ this.apiClientRef = apiClient;
5832
+ if (placements.length === 0) return;
5833
+ const debug = PaywalloClient.getConfig()?.debug;
5834
+ if (debug) console.log(`[Paywallo PRELOAD] paywalls: ${placements.length} placements`);
5835
+ for (let i = 0; i < placements.length; i++) {
5836
+ if (i > 0) await new Promise((r) => setTimeout(r, 200));
5837
+ void this.preload(apiClient, placements[i]).catch((err) => {
5838
+ if (debug) console.log(`[Paywallo PRELOAD] paywall failed ${placements[i]}:`, err);
5839
+ });
5840
+ }
5841
+ }
5842
+ getPreloaded(placement) {
5843
+ const cached2 = this.preloaded.get(placement);
5844
+ if (!cached2) return null;
5845
+ const age = Date.now() - cached2.preloadedAt;
5846
+ if (age >= PRELOAD_TTL_MS) {
5847
+ this.preloaded.delete(placement);
5848
+ return null;
5849
+ }
5850
+ if (age >= PRELOAD_TTL_MS * STALE_THRESHOLD && this.apiClientRef) {
5851
+ void this.preload(this.apiClientRef, placement).catch(() => {
5852
+ });
5853
+ }
5854
+ return { config: cached2.config, products: new Map(cached2.products) };
5855
+ }
5856
+ isPreloaded(placement) {
5857
+ return this.getPreloaded(placement) !== null;
5858
+ }
5859
+ clear() {
5860
+ this.preloaded.clear();
5861
+ this.activePreloads.clear();
5862
+ }
5863
+ async doPreload(apiClient, placement) {
5864
+ try {
5865
+ const config = await apiClient.getPaywall(placement);
5866
+ if (!config) {
5867
+ return {
5868
+ success: false,
5869
+ error: new PaywallError(
5870
+ PAYWALL_ERROR_CODES.NOT_FOUND,
5871
+ `Paywall not found for placement: ${placement}`
5872
+ )
5873
+ };
5874
+ }
5875
+ const products = await this.loadProductsFor(config);
5876
+ this.preloaded.set(placement, { config, products, preloadedAt: Date.now() });
5877
+ if (PaywalloClient.getConfig()?.debug) {
5878
+ console.log(`[Paywallo PRELOAD] paywall cached \u2014 placement: ${placement}`);
5879
+ }
5880
+ return { success: true };
5881
+ } catch (error) {
5882
+ return {
5883
+ success: false,
5884
+ error: error instanceof Error ? error : new PaywallError(PAYWALL_ERROR_CODES.LOAD_FAILED, String(error))
5885
+ };
5886
+ }
5887
+ }
5888
+ async loadProductsFor(config) {
5889
+ const productIds = [];
5890
+ if (config.primaryProductId) productIds.push(config.primaryProductId);
5891
+ if (config.secondaryProductId) productIds.push(config.secondaryProductId);
5892
+ if (config.tertiaryProductId) productIds.push(config.tertiaryProductId);
5893
+ if (productIds.length === 0) return /* @__PURE__ */ new Map();
5894
+ try {
5895
+ const loaded = await getIAPService().loadProducts(productIds);
5896
+ const map = /* @__PURE__ */ new Map();
5897
+ for (const p of loaded) map.set(p.productId, p);
5898
+ return map;
5899
+ } catch (err) {
5900
+ if (PaywalloClient.getConfig()?.debug) {
5901
+ console.log("[Paywallo PRELOAD] products failed, caching config only:", err);
5902
+ }
5903
+ return /* @__PURE__ */ new Map();
5904
+ }
5905
+ }
5906
+ };
5907
+ paywallPreloadService = new PaywallPreloadService();
5908
+ }
5909
+ });
5910
+
5911
+ // src/domains/paywall/PaywallWebViewPrewarmer.tsx
5912
+ function getNativeWebView2() {
5913
+ if (NativeWebViewComponent2) return NativeWebViewComponent2;
5914
+ if (nativeLookupFailed) return null;
5915
+ try {
5916
+ NativeWebViewComponent2 = (0, import_react_native22.requireNativeComponent)("PaywalloWebView");
5917
+ return NativeWebViewComponent2;
5918
+ } catch {
5919
+ nativeLookupFailed = true;
5920
+ return null;
5921
+ }
5922
+ }
5923
+ function PaywallWebViewPrewarmer({
5924
+ webUrl,
5925
+ durationMs = DEFAULT_DURATION_MS
5926
+ }) {
5927
+ const NativeWebView = (0, import_react9.useMemo)(() => getNativeWebView2(), []);
5928
+ const [mounted, setMounted] = (0, import_react9.useState)(true);
5929
+ (0, import_react9.useEffect)(() => {
5930
+ if (!NativeWebView) return;
5931
+ const timer = setTimeout(() => setMounted(false), durationMs);
5932
+ return () => clearTimeout(timer);
5933
+ }, [NativeWebView, durationMs]);
5934
+ if (!NativeWebView || !mounted) return null;
5935
+ const preheatUrl = `${webUrl}/paywall/preheat`;
5936
+ if (PaywalloClient.getConfig()?.debug) {
5937
+ console.log(`[Paywallo PREWARM] mounting hidden WebView: ${preheatUrl}`);
5938
+ }
5939
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native22.View, { pointerEvents: "none", style: styles4.hidden, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(NativeWebView, { sourceUrl: preheatUrl, style: styles4.fill }) });
5940
+ }
5941
+ var import_react9, import_react_native22, import_jsx_runtime4, NativeWebViewComponent2, nativeLookupFailed, DEFAULT_DURATION_MS, styles4;
5942
+ var init_PaywallWebViewPrewarmer = __esm({
5943
+ "src/domains/paywall/PaywallWebViewPrewarmer.tsx"() {
5944
+ "use strict";
5945
+ import_react9 = require("react");
5946
+ import_react_native22 = require("react-native");
5947
+ init_PaywalloClient();
5948
+ import_jsx_runtime4 = require("react/jsx-runtime");
5949
+ NativeWebViewComponent2 = null;
5950
+ nativeLookupFailed = false;
5951
+ DEFAULT_DURATION_MS = 60 * 1e3;
5952
+ styles4 = import_react_native22.StyleSheet.create({
5953
+ hidden: {
5954
+ height: 1,
5955
+ left: -9999,
5956
+ opacity: 0,
5957
+ position: "absolute",
5958
+ top: -9999,
5959
+ width: 1
5960
+ },
5961
+ fill: { flex: 1 }
5962
+ });
5963
+ }
5964
+ });
5965
+
5670
5966
  // src/domains/paywall/usePaywallSession.ts
5671
5967
  function usePaywallSession(options) {
5672
- const closedRef = (0, import_react8.useRef)(false);
5673
- const optsRef = (0, import_react8.useRef)(options);
5968
+ const closedRef = (0, import_react10.useRef)(false);
5969
+ const optsRef = (0, import_react10.useRef)(options);
5674
5970
  optsRef.current = options;
5675
- (0, import_react8.useEffect)(() => {
5971
+ (0, import_react10.useEffect)(() => {
5676
5972
  if (!options) return;
5677
5973
  closedRef.current = false;
5678
5974
  startHeartbeat({
@@ -5702,16 +5998,16 @@ function usePaywallSession(options) {
5702
5998
  });
5703
5999
  };
5704
6000
  }, [options?.paywallId, options?.placement]);
5705
- const markClosed = (0, import_react8.useCallback)(() => {
6001
+ const markClosed = (0, import_react10.useCallback)(() => {
5706
6002
  closedRef.current = true;
5707
6003
  }, []);
5708
6004
  return { markClosed };
5709
6005
  }
5710
- var import_react8;
6006
+ var import_react10;
5711
6007
  var init_usePaywallSession = __esm({
5712
6008
  "src/domains/paywall/usePaywallSession.ts"() {
5713
6009
  "use strict";
5714
- import_react8 = require("react");
6010
+ import_react10 = require("react");
5715
6011
  init_emitPaywallClosed();
5716
6012
  init_paywallHeartbeat();
5717
6013
  }
@@ -5719,9 +6015,9 @@ var init_usePaywallSession = __esm({
5719
6015
 
5720
6016
  // src/domains/paywall/usePaywallPresenter.ts
5721
6017
  function usePaywallPresenter(preloadedWebView, setPreloadedWebView) {
5722
- const [showingPreloadedWebView, setShowingPreloadedWebView] = (0, import_react9.useState)(false);
5723
- const resolverRef = (0, import_react9.useRef)(null);
5724
- const presentedAtRef = (0, import_react9.useRef)(Date.now());
6018
+ const [showingPreloadedWebView, setShowingPreloadedWebView] = (0, import_react11.useState)(false);
6019
+ const resolverRef = (0, import_react11.useRef)(null);
6020
+ const presentedAtRef = (0, import_react11.useRef)(Date.now());
5725
6021
  const sessionOpts = showingPreloadedWebView && preloadedWebView ? {
5726
6022
  placement: preloadedWebView.placement,
5727
6023
  paywallId: preloadedWebView.paywallId,
@@ -5731,7 +6027,7 @@ function usePaywallPresenter(preloadedWebView, setPreloadedWebView) {
5731
6027
  ...preloadedWebView.campaignId && { campaignId: preloadedWebView.campaignId }
5732
6028
  } : null;
5733
6029
  const { markClosed } = usePaywallSession(sessionOpts);
5734
- (0, import_react9.useEffect)(() => {
6030
+ (0, import_react11.useEffect)(() => {
5735
6031
  return () => {
5736
6032
  if (resolverRef.current) {
5737
6033
  resolverRef.current({ presented: false, purchased: false, cancelled: true, restored: false });
@@ -5740,7 +6036,7 @@ function usePaywallPresenter(preloadedWebView, setPreloadedWebView) {
5740
6036
  setShowingPreloadedWebView(false);
5741
6037
  };
5742
6038
  }, []);
5743
- const presentPreloadedWebView = (0, import_react9.useCallback)(() => {
6039
+ const presentPreloadedWebView = (0, import_react11.useCallback)(() => {
5744
6040
  return new Promise((resolve) => {
5745
6041
  resolverRef.current = resolve;
5746
6042
  presentedAtRef.current = Date.now();
@@ -5774,16 +6070,16 @@ function usePaywallPresenter(preloadedWebView, setPreloadedWebView) {
5774
6070
  }
5775
6071
  });
5776
6072
  }, [preloadedWebView]);
5777
- const resolvePreloadedPaywall = (0, import_react9.useCallback)((result) => {
6073
+ const resolvePreloadedPaywall = (0, import_react11.useCallback)((result) => {
5778
6074
  if (resolverRef.current) {
5779
6075
  resolverRef.current(result);
5780
6076
  resolverRef.current = null;
5781
6077
  }
5782
6078
  }, []);
5783
- const handlePreloadedWebViewReady = (0, import_react9.useCallback)(() => {
6079
+ const handlePreloadedWebViewReady = (0, import_react11.useCallback)(() => {
5784
6080
  setPreloadedWebView((prev) => prev ? { ...prev, loaded: true } : null);
5785
6081
  }, [setPreloadedWebView]);
5786
- const handlePreloadedClose = (0, import_react9.useCallback)(() => {
6082
+ const handlePreloadedClose = (0, import_react11.useCallback)(() => {
5787
6083
  if (preloadedWebView) {
5788
6084
  markClosed();
5789
6085
  void emitPaywallClosed({
@@ -5801,11 +6097,11 @@ function usePaywallPresenter(preloadedWebView, setPreloadedWebView) {
5801
6097
  }, [preloadedWebView, markClosed, resolvePreloadedPaywall]);
5802
6098
  return { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady, handlePreloadedClose, setShowingPreloadedWebView, presentedAtRef };
5803
6099
  }
5804
- var import_react9;
6100
+ var import_react11;
5805
6101
  var init_usePaywallPresenter = __esm({
5806
6102
  "src/domains/paywall/usePaywallPresenter.ts"() {
5807
6103
  "use strict";
5808
- import_react9 = require("react");
6104
+ import_react11 = require("react");
5809
6105
  init_PaywalloClient();
5810
6106
  init_emitPaywallClosed();
5811
6107
  init_usePaywallSession();
@@ -5820,7 +6116,9 @@ var init_paywall = __esm({
5820
6116
  init_PaywallError();
5821
6117
  init_PaywallErrorBoundary();
5822
6118
  init_PaywallModal();
6119
+ init_PaywallPreloadService();
5823
6120
  init_PaywallWebView();
6121
+ init_PaywallWebViewPrewarmer();
5824
6122
  init_usePaywallPresenter();
5825
6123
  init_usePaywallTracking();
5826
6124
  }
@@ -7146,7 +7444,7 @@ function buildV2Envelope(events, providerContext) {
7146
7444
  };
7147
7445
  });
7148
7446
  if (context.sdk_version === void 0) context.sdk_version = SDK_VERSION;
7149
- if (context.platform === void 0) context.platform = import_react_native21.Platform.OS;
7447
+ if (context.platform === void 0) context.platform = import_react_native23.Platform.OS;
7150
7448
  if (context.distinct_id === void 0 && events.length > 0) {
7151
7449
  context.distinct_id = events[0].distinctId;
7152
7450
  }
@@ -7186,11 +7484,11 @@ function splitPayloadAndContext(properties) {
7186
7484
  }
7187
7485
  return { payload, promoted };
7188
7486
  }
7189
- var import_react_native21, import_zod2, v2EventSchema, v2ContextSchema, v2EnvelopeSchema, CONTEXT_KEY_ALIASES;
7487
+ var import_react_native23, import_zod2, v2EventSchema, v2ContextSchema, v2EnvelopeSchema, CONTEXT_KEY_ALIASES;
7190
7488
  var init_eventEnvelopeV2 = __esm({
7191
7489
  "src/core/eventEnvelopeV2.ts"() {
7192
7490
  "use strict";
7193
- import_react_native21 = require("react-native");
7491
+ import_react_native23 = require("react-native");
7194
7492
  import_zod2 = require("zod");
7195
7493
  init_uuid();
7196
7494
  init_eventFamilies();
@@ -7260,11 +7558,11 @@ var init_events = __esm({
7260
7558
  });
7261
7559
 
7262
7560
  // src/core/EventBatcher.ts
7263
- var import_react_native22, BATCH_MAX_SIZE, BATCH_FLUSH_MS, EVENT_NAME_REGEX, V2_BATCH_ENDPOINT, EventBatcher;
7561
+ var import_react_native24, BATCH_MAX_SIZE, BATCH_FLUSH_MS, EVENT_NAME_REGEX, V2_BATCH_ENDPOINT, EventBatcher;
7264
7562
  var init_EventBatcher = __esm({
7265
7563
  "src/core/EventBatcher.ts"() {
7266
7564
  "use strict";
7267
- import_react_native22 = require("react-native");
7565
+ import_react_native24 = require("react-native");
7268
7566
  init_eventEnvelopeV2();
7269
7567
  init_events();
7270
7568
  BATCH_MAX_SIZE = 25;
@@ -7313,7 +7611,7 @@ var init_EventBatcher = __esm({
7313
7611
  const event = {
7314
7612
  eventName,
7315
7613
  distinctId,
7316
- properties: { ...properties, platform: import_react_native22.Platform.OS },
7614
+ properties: { ...properties, platform: import_react_native24.Platform.OS },
7317
7615
  timestamp: timestamp ?? Date.now(),
7318
7616
  environment: environment.toLowerCase()
7319
7617
  };
@@ -7639,11 +7937,11 @@ var init_http = __esm({
7639
7937
  });
7640
7938
 
7641
7939
  // src/core/ApiClient.ts
7642
- var import_react_native23, ApiClient;
7940
+ var import_react_native25, ApiClient;
7643
7941
  var init_ApiClient = __esm({
7644
7942
  "src/core/ApiClient.ts"() {
7645
7943
  "use strict";
7646
- import_react_native23 = require("react-native");
7944
+ import_react_native25 = require("react-native");
7647
7945
  init_apiPurchaseMethods();
7648
7946
  init_ApiCache();
7649
7947
  init_ApiClientFlags();
@@ -7745,7 +8043,7 @@ var init_ApiClient = __esm({
7745
8043
  buildSdkHeaders() {
7746
8044
  return {
7747
8045
  "x-sdk-version": SDK_VERSION,
7748
- "x-sdk-platform": import_react_native23.Platform.OS,
8046
+ "x-sdk-platform": import_react_native25.Platform.OS,
7749
8047
  "x-sdk-environment": this.environment
7750
8048
  };
7751
8049
  }
@@ -7775,7 +8073,7 @@ var init_ApiClient = __esm({
7775
8073
  message,
7776
8074
  context,
7777
8075
  sdkVersion: SDK_VERSION,
7778
- platform: import_react_native23.Platform.OS
8076
+ platform: import_react_native25.Platform.OS
7779
8077
  }, true);
7780
8078
  } catch (err) {
7781
8079
  if (this.debug) console.log("[Paywallo:ApiClient] reportError failed", err);
@@ -7793,7 +8091,7 @@ var init_ApiClient = __esm({
7793
8091
  async identify(distinctId, properties, email, deviceId, pii) {
7794
8092
  const rawTraits = {
7795
8093
  email: email || void 0,
7796
- platform: import_react_native23.Platform.OS,
8094
+ platform: import_react_native25.Platform.OS,
7797
8095
  name: properties?.name,
7798
8096
  country: properties?.country,
7799
8097
  locale: properties?.locale,
@@ -7811,6 +8109,8 @@ var init_ApiClient = __esm({
7811
8109
  }
7812
8110
  }
7813
8111
  const attribution = Object.keys(rawAttribution).length > 0 ? rawAttribution : void 0;
8112
+ const rawUserId = properties?.["userId"];
8113
+ const externalUserId = typeof rawUserId === "string" && rawUserId.length > 0 ? rawUserId : void 0;
7814
8114
  await postWithQueue(
7815
8115
  {
7816
8116
  isOfflineQueueEnabled: () => this.offlineQueueEnabled,
@@ -7821,6 +8121,7 @@ var init_ApiClient = __esm({
7821
8121
  "/sdk/identity/identify",
7822
8122
  {
7823
8123
  distinct_id: distinctId,
8124
+ ...externalUserId && { external_user_id: externalUserId },
7824
8125
  traits,
7825
8126
  ...attribution && { attribution },
7826
8127
  ...deviceId && { deviceId },
@@ -8089,12 +8390,12 @@ async function detectEnvironment() {
8089
8390
  return cached;
8090
8391
  }
8091
8392
  }
8092
- var import_react_native24, nativeModule, cached;
8393
+ var import_react_native26, nativeModule, cached;
8093
8394
  var init_detectEnvironment = __esm({
8094
8395
  "src/utils/detectEnvironment.ts"() {
8095
8396
  "use strict";
8096
- import_react_native24 = require("react-native");
8097
- nativeModule = import_react_native24.NativeModules.PaywalloEnv ?? null;
8397
+ import_react_native26 = require("react-native");
8398
+ nativeModule = import_react_native26.NativeModules.PaywalloEnv ?? null;
8098
8399
  cached = null;
8099
8400
  }
8100
8401
  });
@@ -8276,10 +8577,10 @@ async function doInit(state, config) {
8276
8577
  let screenHeight;
8277
8578
  let screenDensity;
8278
8579
  try {
8279
- const screen = import_react_native25.Dimensions.get("screen");
8580
+ const screen = import_react_native27.Dimensions.get("screen");
8280
8581
  screenWidth = Math.round(screen.width) || void 0;
8281
8582
  screenHeight = Math.round(screen.height) || void 0;
8282
- screenDensity = import_react_native25.PixelRatio.get() || void 0;
8583
+ screenDensity = import_react_native27.PixelRatio.get() || void 0;
8283
8584
  } catch {
8284
8585
  }
8285
8586
  return {
@@ -8346,11 +8647,11 @@ async function doInit(state, config) {
8346
8647
  setupAutoPreload(state, apiClient, config, distinctId);
8347
8648
  startBatchPreload(apiClient, config.debug);
8348
8649
  }
8349
- var import_react_native25;
8650
+ var import_react_native27;
8350
8651
  var init_PaywalloInitializer = __esm({
8351
8652
  "src/core/PaywalloInitializer.ts"() {
8352
8653
  "use strict";
8353
- import_react_native25 = require("react-native");
8654
+ import_react_native27 = require("react-native");
8354
8655
  init_identity();
8355
8656
  init_AdvertisingIdManager();
8356
8657
  init_DeepLinkAttributionCapture();
@@ -8393,6 +8694,7 @@ async function doFullReset(state) {
8393
8694
  offlineQueue.dispose();
8394
8695
  networkMonitor.dispose();
8395
8696
  campaignGateService.clearPreloaded();
8697
+ paywallPreloadService.clear();
8396
8698
  await notificationsManager.invalidateLocalToken().catch(() => void 0);
8397
8699
  notificationsManager.destroy();
8398
8700
  deepLinkAttributionCapture.stop();
@@ -8427,6 +8729,7 @@ var init_PaywalloLifecycle = __esm({
8427
8729
  init_identity();
8428
8730
  init_DeepLinkAttributionCapture();
8429
8731
  init_notifications();
8732
+ init_paywall();
8430
8733
  init_session();
8431
8734
  init_SubscriptionCache();
8432
8735
  init_network();
@@ -8730,6 +9033,15 @@ var init_PaywalloClient = __esm({
8730
9033
  isPreloaded(placement) {
8731
9034
  return campaignGateService.getPreloadedCampaign(placement) !== null;
8732
9035
  }
9036
+ async preloadPaywall(placement) {
9037
+ return paywallPreloadService.preload(this.getApiClientOrThrow(), placement);
9038
+ }
9039
+ async preloadPaywalls(placements) {
9040
+ return paywallPreloadService.preloadMany(this.getApiClientOrThrow(), placements);
9041
+ }
9042
+ isPaywallPreloaded(placement) {
9043
+ return paywallPreloadService.isPreloaded(placement);
9044
+ }
8733
9045
  getAutoPreloadedPlacement() {
8734
9046
  return this.state.autoPreloadedPlacement;
8735
9047
  }
@@ -8775,7 +9087,7 @@ __export(OfferingService_exports, {
8775
9087
  offeringService: () => offeringService
8776
9088
  });
8777
9089
  function resolveProductId(raw) {
8778
- const isIos = import_react_native26.Platform.OS === "ios";
9090
+ const isIos = import_react_native28.Platform.OS === "ios";
8779
9091
  const productId = isIos ? raw.apple_product_id : raw.google_product_id;
8780
9092
  return productId ?? "";
8781
9093
  }
@@ -8794,7 +9106,7 @@ function mapProduct2(raw) {
8794
9106
  };
8795
9107
  }
8796
9108
  function pickPlatformProduct(products) {
8797
- const isIos = import_react_native26.Platform.OS === "ios";
9109
+ const isIos = import_react_native28.Platform.OS === "ios";
8798
9110
  const match = products.find((p) => isIos ? p.apple_product_id : p.google_product_id);
8799
9111
  return match ?? products[0] ?? null;
8800
9112
  }
@@ -8810,11 +9122,11 @@ function mapOffering(raw) {
8810
9122
  product: mapProduct2(rawProduct)
8811
9123
  };
8812
9124
  }
8813
- var import_react_native26, OFFERINGS_CACHE_KEY, DEFAULT_TTL3, OfferingService, offeringService;
9125
+ var import_react_native28, OFFERINGS_CACHE_KEY, DEFAULT_TTL3, OfferingService, offeringService;
8814
9126
  var init_OfferingService = __esm({
8815
9127
  "src/domains/offering/OfferingService.ts"() {
8816
9128
  "use strict";
8817
- import_react_native26 = require("react-native");
9129
+ import_react_native28 = require("react-native");
8818
9130
  init_SecureStorage();
8819
9131
  OFFERINGS_CACHE_KEY = "@paywallo:offerings_cache";
8820
9132
  DEFAULT_TTL3 = 5 * 60 * 1e3;
@@ -8975,8 +9287,8 @@ module.exports = __toCommonJS(index_exports);
8975
9287
  init_PaywalloClient();
8976
9288
 
8977
9289
  // src/context/PaywalloContext.ts
8978
- var import_react10 = require("react");
8979
- var PaywalloContext = (0, import_react10.createContext)(null);
9290
+ var import_react12 = require("react");
9291
+ var PaywalloContext = (0, import_react12.createContext)(null);
8980
9292
 
8981
9293
  // src/index.ts
8982
9294
  init_ApiClient();
@@ -9021,7 +9333,7 @@ init_session();
9021
9333
  init_PaywalloError();
9022
9334
 
9023
9335
  // src/hooks/useOfferingPurchase.ts
9024
- var import_react11 = require("react");
9336
+ var import_react13 = require("react");
9025
9337
 
9026
9338
  // src/domains/plan/invalidateCache.ts
9027
9339
  async function invalidatePlanCache() {
@@ -9034,8 +9346,8 @@ async function invalidatePlanCache() {
9034
9346
 
9035
9347
  // src/hooks/useOfferingPurchase.ts
9036
9348
  function useOfferingPurchase() {
9037
- const [state, setState] = (0, import_react11.useState)("idle");
9038
- const purchase = (0, import_react11.useCallback)(async (productId) => {
9349
+ const [state, setState] = (0, import_react13.useState)("idle");
9350
+ const purchase = (0, import_react13.useCallback)(async (productId) => {
9039
9351
  setState("purchasing");
9040
9352
  try {
9041
9353
  const iapService = getIAPService();
@@ -9059,7 +9371,7 @@ function useOfferingPurchase() {
9059
9371
  }
9060
9372
 
9061
9373
  // src/hooks/useOfferings.ts
9062
- var import_react12 = require("react");
9374
+ var import_react14 = require("react");
9063
9375
  async function getOfferingService() {
9064
9376
  try {
9065
9377
  const mod = await Promise.resolve().then(() => (init_OfferingService(), OfferingService_exports));
@@ -9069,10 +9381,10 @@ async function getOfferingService() {
9069
9381
  }
9070
9382
  }
9071
9383
  function useOfferings(ids) {
9072
- const [offerings, setOfferings] = (0, import_react12.useState)([]);
9073
- const [isLoading, setIsLoading] = (0, import_react12.useState)(true);
9384
+ const [offerings, setOfferings] = (0, import_react14.useState)([]);
9385
+ const [isLoading, setIsLoading] = (0, import_react14.useState)(true);
9074
9386
  const idsKey = ids?.join(",") ?? "";
9075
- const fetchOfferings = (0, import_react12.useCallback)(async () => {
9387
+ const fetchOfferings = (0, import_react14.useCallback)(async () => {
9076
9388
  setIsLoading(true);
9077
9389
  try {
9078
9390
  const service = await getOfferingService();
@@ -9088,25 +9400,25 @@ function useOfferings(ids) {
9088
9400
  setIsLoading(false);
9089
9401
  }
9090
9402
  }, [idsKey]);
9091
- (0, import_react12.useEffect)(() => {
9403
+ (0, import_react14.useEffect)(() => {
9092
9404
  void fetchOfferings();
9093
9405
  }, [fetchOfferings]);
9094
9406
  return { offerings, isLoading };
9095
9407
  }
9096
9408
 
9097
9409
  // src/hooks/useOnboarding.ts
9098
- var import_react13 = require("react");
9410
+ var import_react15 = require("react");
9099
9411
  init_onboarding();
9100
9412
  function useOnboarding() {
9101
- const step = (0, import_react13.useCallback)(
9413
+ const step = (0, import_react15.useCallback)(
9102
9414
  (stepName) => onboardingManager.step(stepName),
9103
9415
  []
9104
9416
  );
9105
- const complete = (0, import_react13.useCallback)(
9417
+ const complete = (0, import_react15.useCallback)(
9106
9418
  () => onboardingManager.complete(),
9107
9419
  []
9108
9420
  );
9109
- const drop = (0, import_react13.useCallback)(
9421
+ const drop = (0, import_react15.useCallback)(
9110
9422
  (stepName) => onboardingManager.drop(stepName),
9111
9423
  []
9112
9424
  );
@@ -9114,10 +9426,10 @@ function useOnboarding() {
9114
9426
  }
9115
9427
 
9116
9428
  // src/hooks/usePaywallo.ts
9117
- var import_react14 = require("react");
9429
+ var import_react16 = require("react");
9118
9430
  init_ClientError();
9119
9431
  function usePaywallo() {
9120
- const context = (0, import_react14.useContext)(PaywalloContext);
9432
+ const context = (0, import_react16.useContext)(PaywalloContext);
9121
9433
  if (!context) {
9122
9434
  throw new ClientError(CLIENT_ERROR_CODES.PROVIDER_MISSING, "usePaywallo must be used within a PaywalloProvider");
9123
9435
  }
@@ -9125,11 +9437,11 @@ function usePaywallo() {
9125
9437
  }
9126
9438
 
9127
9439
  // src/hooks/usePlanPurchase.ts
9128
- var import_react15 = require("react");
9440
+ var import_react17 = require("react");
9129
9441
  function usePlanPurchase() {
9130
- const [state, setState] = (0, import_react15.useState)("idle");
9131
- const [error, setError] = (0, import_react15.useState)(null);
9132
- const purchase = (0, import_react15.useCallback)(async (productId) => {
9442
+ const [state, setState] = (0, import_react17.useState)("idle");
9443
+ const [error, setError] = (0, import_react17.useState)(null);
9444
+ const purchase = (0, import_react17.useCallback)(async (productId) => {
9133
9445
  setState("purchasing");
9134
9446
  setError(null);
9135
9447
  try {
@@ -9152,7 +9464,7 @@ function usePlanPurchase() {
9152
9464
  return { success: false, error: purchaseError };
9153
9465
  }
9154
9466
  }, []);
9155
- const restore = (0, import_react15.useCallback)(async () => {
9467
+ const restore = (0, import_react17.useCallback)(async () => {
9156
9468
  setState("restoring");
9157
9469
  setError(null);
9158
9470
  try {
@@ -9181,7 +9493,7 @@ function usePlanPurchase() {
9181
9493
  }
9182
9494
 
9183
9495
  // src/hooks/usePlans.ts
9184
- var import_react16 = require("react");
9496
+ var import_react18 = require("react");
9185
9497
  async function getPlanService() {
9186
9498
  try {
9187
9499
  const mod = await Promise.resolve().then(() => (init_PlanService(), PlanService_exports));
@@ -9191,11 +9503,11 @@ async function getPlanService() {
9191
9503
  }
9192
9504
  }
9193
9505
  function usePlans() {
9194
- const [allPlans, setAllPlans] = (0, import_react16.useState)([]);
9195
- const [currentPlan, setCurrentPlan] = (0, import_react16.useState)(null);
9196
- const [isLoading, setIsLoading] = (0, import_react16.useState)(true);
9197
- const [error, setError] = (0, import_react16.useState)(null);
9198
- const fetchPlans = (0, import_react16.useCallback)(async (forceRefresh = false) => {
9506
+ const [allPlans, setAllPlans] = (0, import_react18.useState)([]);
9507
+ const [currentPlan, setCurrentPlan] = (0, import_react18.useState)(null);
9508
+ const [isLoading, setIsLoading] = (0, import_react18.useState)(true);
9509
+ const [error, setError] = (0, import_react18.useState)(null);
9510
+ const fetchPlans = (0, import_react18.useCallback)(async (forceRefresh = false) => {
9199
9511
  setIsLoading(true);
9200
9512
  setError(null);
9201
9513
  try {
@@ -9216,17 +9528,17 @@ function usePlans() {
9216
9528
  setIsLoading(false);
9217
9529
  }
9218
9530
  }, []);
9219
- const refresh = (0, import_react16.useCallback)(() => {
9531
+ const refresh = (0, import_react18.useCallback)(() => {
9220
9532
  return fetchPlans(true);
9221
9533
  }, [fetchPlans]);
9222
- (0, import_react16.useEffect)(() => {
9534
+ (0, import_react18.useEffect)(() => {
9223
9535
  void fetchPlans(false);
9224
9536
  }, [fetchPlans]);
9225
9537
  return { currentPlan, allPlans, isLoading, error, refresh };
9226
9538
  }
9227
9539
 
9228
9540
  // src/hooks/useProducts.ts
9229
- var import_react17 = require("react");
9541
+ var import_react19 = require("react");
9230
9542
 
9231
9543
  // src/utils/productMappers.ts
9232
9544
  function mapToIAPProduct(product) {
@@ -9265,11 +9577,11 @@ function mapToFormattedProduct(product) {
9265
9577
 
9266
9578
  // src/hooks/useProducts.ts
9267
9579
  function useProducts(initialProductIds) {
9268
- const [products, setProducts] = (0, import_react17.useState)([]);
9269
- const [formattedProducts, setFormattedProducts] = (0, import_react17.useState)([]);
9270
- const [isLoading, setIsLoading] = (0, import_react17.useState)(false);
9271
- const [error, setError] = (0, import_react17.useState)(null);
9272
- const loadProducts = (0, import_react17.useCallback)(async (productIds) => {
9580
+ const [products, setProducts] = (0, import_react19.useState)([]);
9581
+ const [formattedProducts, setFormattedProducts] = (0, import_react19.useState)([]);
9582
+ const [isLoading, setIsLoading] = (0, import_react19.useState)(false);
9583
+ const [error, setError] = (0, import_react19.useState)(null);
9584
+ const loadProducts = (0, import_react19.useCallback)(async (productIds) => {
9273
9585
  setIsLoading(true);
9274
9586
  setError(null);
9275
9587
  try {
@@ -9283,24 +9595,24 @@ function useProducts(initialProductIds) {
9283
9595
  setIsLoading(false);
9284
9596
  }
9285
9597
  }, []);
9286
- (0, import_react17.useEffect)(() => {
9598
+ (0, import_react19.useEffect)(() => {
9287
9599
  if (initialProductIds && initialProductIds.length > 0) {
9288
9600
  void loadProducts(initialProductIds);
9289
9601
  }
9290
9602
  }, []);
9291
- const getProduct = (0, import_react17.useCallback)(
9603
+ const getProduct = (0, import_react19.useCallback)(
9292
9604
  (productId) => {
9293
9605
  return products.find((p) => p.productId === productId);
9294
9606
  },
9295
9607
  [products]
9296
9608
  );
9297
- const getFormattedProduct = (0, import_react17.useCallback)(
9609
+ const getFormattedProduct = (0, import_react19.useCallback)(
9298
9610
  (productId) => {
9299
9611
  return formattedProducts.find((p) => p.productId === productId);
9300
9612
  },
9301
9613
  [formattedProducts]
9302
9614
  );
9303
- const getPriceInfo = (0, import_react17.useCallback)(
9615
+ const getPriceInfo = (0, import_react19.useCallback)(
9304
9616
  (productId) => {
9305
9617
  const p = formattedProducts.find((fp) => fp.productId === productId);
9306
9618
  if (!p) return void 0;
@@ -9329,14 +9641,14 @@ function useProducts(initialProductIds) {
9329
9641
  }
9330
9642
 
9331
9643
  // src/hooks/usePurchase.ts
9332
- var import_react18 = require("react");
9644
+ var import_react20 = require("react");
9333
9645
  function toIAPPurchase(p) {
9334
9646
  return { productId: p.productId, transactionId: p.transactionId, transactionDate: p.transactionDate, transactionReceipt: p.receipt };
9335
9647
  }
9336
9648
  function usePurchase() {
9337
- const [state, setState] = (0, import_react18.useState)("idle");
9338
- const [error, setError] = (0, import_react18.useState)(null);
9339
- const purchase = (0, import_react18.useCallback)(async (productId) => {
9649
+ const [state, setState] = (0, import_react20.useState)("idle");
9650
+ const [error, setError] = (0, import_react20.useState)(null);
9651
+ const purchase = (0, import_react20.useCallback)(async (productId) => {
9340
9652
  setState("purchasing");
9341
9653
  setError(null);
9342
9654
  try {
@@ -9364,7 +9676,7 @@ function usePurchase() {
9364
9676
  return { status: "failed", error: purchaseError };
9365
9677
  }
9366
9678
  }, []);
9367
- const restore = (0, import_react18.useCallback)(async () => {
9679
+ const restore = (0, import_react20.useCallback)(async () => {
9368
9680
  setState("restoring");
9369
9681
  setError(null);
9370
9682
  try {
@@ -9383,14 +9695,14 @@ function usePurchase() {
9383
9695
  }
9384
9696
 
9385
9697
  // src/hooks/useSubscription.ts
9386
- var import_react19 = require("react");
9698
+ var import_react21 = require("react");
9387
9699
  init_SubscriptionManager();
9388
9700
  var EMPTY_ENTITLEMENTS = [];
9389
9701
  function useSubscription() {
9390
- const [status, setStatus] = (0, import_react19.useState)(null);
9391
- const [isLoading, setIsLoading] = (0, import_react19.useState)(true);
9392
- const [error, setError] = (0, import_react19.useState)(null);
9393
- const refresh = (0, import_react19.useCallback)(async () => {
9702
+ const [status, setStatus] = (0, import_react21.useState)(null);
9703
+ const [isLoading, setIsLoading] = (0, import_react21.useState)(true);
9704
+ const [error, setError] = (0, import_react21.useState)(null);
9705
+ const refresh = (0, import_react21.useCallback)(async () => {
9394
9706
  setIsLoading(true);
9395
9707
  setError(null);
9396
9708
  try {
@@ -9403,7 +9715,7 @@ function useSubscription() {
9403
9715
  setIsLoading(false);
9404
9716
  }
9405
9717
  }, []);
9406
- const restore = (0, import_react19.useCallback)(async () => {
9718
+ const restore = (0, import_react21.useCallback)(async () => {
9407
9719
  setIsLoading(true);
9408
9720
  setError(null);
9409
9721
  try {
@@ -9416,7 +9728,7 @@ function useSubscription() {
9416
9728
  setIsLoading(false);
9417
9729
  }
9418
9730
  }, []);
9419
- (0, import_react19.useEffect)(() => {
9731
+ (0, import_react21.useEffect)(() => {
9420
9732
  void subscriptionManager.getSubscriptionStatus().then((s) => {
9421
9733
  setStatus(s);
9422
9734
  setIsLoading(false);
@@ -9444,63 +9756,257 @@ function useSubscription() {
9444
9756
  }
9445
9757
 
9446
9758
  // src/PaywalloProvider.tsx
9447
- var import_react25 = require("react");
9448
- var import_react_native27 = require("react-native");
9759
+ var import_react27 = require("react");
9760
+ var import_react_native29 = require("react-native");
9449
9761
  init_PaywalloClient();
9450
- init_SecureStorage();
9451
9762
  init_NativeStorage();
9763
+ init_SecureStorage();
9452
9764
  init_campaign();
9453
9765
  init_paywall();
9454
9766
  init_paywall();
9455
9767
  init_paywall();
9456
9768
  init_paywall();
9457
- init_session();
9458
- init_ClientError();
9769
+ init_paywall();
9770
+ init_paywall();
9459
9771
 
9460
- // src/hooks/useAppAutoEvents.ts
9461
- var import_react20 = require("react");
9772
+ // src/domains/paywall/SuperwallAutoBridge.ts
9773
+ init_PaywalloClient();
9462
9774
 
9463
- // src/utils/platformDetection.ts
9464
- async function detectPlatform3() {
9465
- try {
9466
- const rn = await import("react-native");
9467
- return rn.Platform?.OS === "ios" ? "ios" : "android";
9468
- } catch {
9469
- return "android";
9775
+ // src/core/utils/eventId.ts
9776
+ function hashString(input) {
9777
+ let h1 = 3735928559;
9778
+ let h2 = 1103547991;
9779
+ for (let i = 0; i < input.length; i++) {
9780
+ const ch = input.charCodeAt(i);
9781
+ h1 = Math.imul(h1 ^ ch, 2654435769);
9782
+ h2 = Math.imul(h2 ^ ch, 1598716949);
9470
9783
  }
9784
+ h1 = Math.imul(h1 ^ h1 >>> 16, 73244475);
9785
+ h1 ^= Math.imul(h2 ^ h2 >>> 13, 351452291);
9786
+ h2 = Math.imul(h2 ^ h2 >>> 16, 73244475);
9787
+ h2 ^= Math.imul(h1 ^ h1 >>> 13, 351452291);
9788
+ const toHex = (n) => (n >>> 0).toString(16).padStart(8, "0");
9789
+ return toHex(h1) + toHex(h2);
9471
9790
  }
9472
- async function detectOsVersion() {
9473
- try {
9474
- const rn = await import("react-native");
9475
- const version = rn.Platform?.Version;
9476
- return version != null ? String(version) : "unknown";
9477
- } catch {
9478
- return "unknown";
9479
- }
9791
+ function buildDeterministicEventId(parts) {
9792
+ const raw = hashString(parts.join("|"));
9793
+ const p1 = raw.slice(0, 8);
9794
+ const p2 = raw.slice(8, 12);
9795
+ const p3 = "4" + raw.slice(13, 16);
9796
+ const p4 = (parseInt(raw[16], 16) & 3 | 8).toString(16) + raw.slice(17, 20);
9797
+ const p5 = raw.padEnd(32, "0").slice(20, 32);
9798
+ return `${p1}-${p2}-${p3}-${p4}-${p5}`;
9480
9799
  }
9481
9800
 
9482
- // src/hooks/useAppAutoEvents.ts
9483
- var FIRST_SEEN_KEY = "@paywallo:firstSeen_v226";
9484
- var DEBOUNCE_MS = 1e3;
9485
- function useAppAutoEvents(config) {
9486
- const { isInitialized, getAppVersion: getAppVersion2, getSessionId, emitLifecycle, storageGet, storageSet, debug } = config;
9487
- const didRunRef = (0, import_react20.useRef)(false);
9488
- (0, import_react20.useEffect)(() => {
9489
- if (debug) console.log("[Paywallo EVENTS] useEffect fired", { isInitialized, didRunRefCurrent: didRunRef.current });
9490
- if (!isInitialized || didRunRef.current) return;
9491
- const timer = setTimeout(() => {
9492
- if (didRunRef.current) return;
9493
- didRunRef.current = true;
9494
- void (async () => {
9495
- const sessionId = getSessionId();
9496
- const appVersion = getAppVersion2();
9497
- const deviceType = await detectPlatform3();
9498
- const osVersion = await detectOsVersion();
9499
- if (debug) console.log("[Paywallo EVENTS] computed device context", { deviceType, osVersion, appVersion, sessionId, hasSessionId: !!sessionId });
9500
- try {
9501
- if (debug) console.log("[Paywallo EVENTS] checking install \u2014 about to read FIRST_SEEN_KEY");
9502
- const firstSeen = await storageGet(FIRST_SEEN_KEY);
9503
- if (!firstSeen) {
9801
+ // src/domains/paywall/SuperwallAutoBridge.ts
9802
+ var bridgeStarted = false;
9803
+ var unsubscribe = null;
9804
+ var debugEnabled = false;
9805
+ function log(msg, data) {
9806
+ if (!debugEnabled) return;
9807
+ if (data !== void 0) {
9808
+ console.log(`[Paywallo:SuperwallBridge] ${msg}`, data);
9809
+ } else {
9810
+ console.log(`[Paywallo:SuperwallBridge] ${msg}`);
9811
+ }
9812
+ }
9813
+ function minuteBucket(ts) {
9814
+ return String(Math.floor(ts / 6e4));
9815
+ }
9816
+ function buildEventId(type, identifier, ts) {
9817
+ return buildDeterministicEventId(["sw", type, identifier, minuteBucket(ts)]);
9818
+ }
9819
+ async function trackPaywallEvent(type, identifier, ts, extra) {
9820
+ const _eventId = buildEventId(type, identifier, ts);
9821
+ try {
9822
+ const cleanExtra = {};
9823
+ if (extra) {
9824
+ for (const [k, v] of Object.entries(extra)) if (v !== void 0) cleanExtra[k] = v;
9825
+ }
9826
+ await PaywalloClient.track("paywall", {
9827
+ priority: "critical",
9828
+ properties: {
9829
+ type,
9830
+ paywall_id: identifier,
9831
+ ...cleanExtra
9832
+ }
9833
+ });
9834
+ } catch (err) {
9835
+ log("track error", { type, identifier, eventId: _eventId, err: String(err) });
9836
+ }
9837
+ }
9838
+ function mapDismissToCloseReason(resultType) {
9839
+ switch (resultType) {
9840
+ case "purchased":
9841
+ return "purchased";
9842
+ case "restored":
9843
+ return "purchased";
9844
+ // restore counts as a purchase outcome for funnel
9845
+ case "declined":
9846
+ return "dismissed";
9847
+ default:
9848
+ return void 0;
9849
+ }
9850
+ }
9851
+ function buildCallbacks() {
9852
+ return {
9853
+ onPaywallPresent: (info) => {
9854
+ const ts = Date.now();
9855
+ log("onPaywallPresent", { identifier: info.identifier });
9856
+ void trackPaywallEvent("viewed", info.identifier, ts, {
9857
+ placement: info.presentedByEventWithName,
9858
+ variant_id: info.experiment?.variant?.id,
9859
+ campaign_id: info.experiment?.id
9860
+ });
9861
+ },
9862
+ onPaywallDismiss: (info, result) => {
9863
+ const ts = Date.now();
9864
+ log("onPaywallDismiss", { identifier: info.identifier, result: result.type });
9865
+ void trackPaywallEvent("closed", info.identifier, ts, {
9866
+ placement: info.presentedByEventWithName,
9867
+ variant_id: info.experiment?.variant?.id,
9868
+ campaign_id: info.experiment?.id,
9869
+ close_reason: mapDismissToCloseReason(result.type),
9870
+ closed_at: new Date(ts).toISOString()
9871
+ });
9872
+ },
9873
+ onPurchase: (params) => {
9874
+ const ts = Date.now();
9875
+ const safe = params !== null && typeof params === "object" ? params : {};
9876
+ const productId = "productId" in safe && typeof safe.productId === "string" ? safe.productId : "unknown";
9877
+ const identifier = "paywallIdentifier" in safe && typeof safe.paywallIdentifier === "string" ? safe.paywallIdentifier : productId;
9878
+ log("onPurchase", { productId, identifier });
9879
+ void trackPaywallEvent("purchased", identifier, ts, { product_id: productId });
9880
+ },
9881
+ // Global Superwall event listener — used specifically for `transactionComplete`
9882
+ // which is the only event that exposes the full product (currency/price) +
9883
+ // transaction (storeTransactionId) shape needed to create a server-side
9884
+ // `transactions` row. Other events are skipped (paywallOpen/Dismiss already
9885
+ // handled above with their typed callbacks).
9886
+ onSuperwallEvent: (info) => {
9887
+ const ev = info?.event;
9888
+ if (!ev || ev.event !== "transactionComplete") return;
9889
+ const ts = Date.now();
9890
+ const product = ev.product;
9891
+ const transaction = ev.transaction;
9892
+ if (!product) {
9893
+ log("transactionComplete missing product \u2014 skipping transaction track");
9894
+ return;
9895
+ }
9896
+ const productId = product.productIdentifier ?? product.id ?? "unknown";
9897
+ const currency = product.currencyCode ?? "USD";
9898
+ const price = product.price ?? 0;
9899
+ const transactionId = transaction?.storeTransactionId ?? transaction?.originalTransactionIdentifier ?? `${productId}_${String(ts)}`;
9900
+ const isTrial = product.hasFreeTrial === true || (product.trialPeriodPrice ?? 0) > 0;
9901
+ const paywallIdentifier = ev.paywallInfo?.identifier;
9902
+ log("onSuperwallEvent: transactionComplete", { productId, currency, price, transactionId, isTrial });
9903
+ void (async () => {
9904
+ try {
9905
+ await PaywalloClient.track("transaction", {
9906
+ priority: "critical",
9907
+ properties: {
9908
+ type: isTrial ? "trial_started" : "completed",
9909
+ // Server's IngestTranslator looks for `transaction_id`/`product_id`
9910
+ // /`amount`/`currency`/`paywall_id` keys (not `tx_id`). PurchaseHandler
9911
+ // then resolves type=trial/new/renewal based on amount being 0 vs >0.
9912
+ transaction_id: transactionId,
9913
+ product_id: productId,
9914
+ amount: price,
9915
+ currency,
9916
+ ...paywallIdentifier ? { paywall_id: paywallIdentifier } : {}
9917
+ }
9918
+ });
9919
+ } catch (err) {
9920
+ log("transactionComplete track error", { err: String(err) });
9921
+ }
9922
+ })();
9923
+ }
9924
+ };
9925
+ }
9926
+ function startSuperwallAutoBridge(debug = false) {
9927
+ debugEnabled = debug;
9928
+ if (bridgeStarted) {
9929
+ log("already started, skipping");
9930
+ return;
9931
+ }
9932
+ void detectAndStart();
9933
+ }
9934
+ async function detectAndStart() {
9935
+ let subscribeFn;
9936
+ try {
9937
+ const mod = await import("expo-superwall");
9938
+ if (!mod || typeof mod.subscribeToSuperwallEvents !== "function") {
9939
+ const internal = await import("expo-superwall/build/src/internal/superwallEventBridge");
9940
+ if (typeof internal.subscribeToSuperwallEvents !== "function") {
9941
+ log("expo-superwall subscribeToSuperwallEvents not found \u2014 bridge inactive");
9942
+ return;
9943
+ }
9944
+ subscribeFn = internal.subscribeToSuperwallEvents;
9945
+ } else {
9946
+ subscribeFn = mod.subscribeToSuperwallEvents;
9947
+ }
9948
+ } catch {
9949
+ log("expo-superwall not installed \u2014 bridge inactive");
9950
+ return;
9951
+ }
9952
+ try {
9953
+ const callbacks = buildCallbacks();
9954
+ unsubscribe = subscribeFn(void 0, () => callbacks);
9955
+ bridgeStarted = true;
9956
+ log("bridge started");
9957
+ } catch (err) {
9958
+ log("subscribe failed", { err: String(err) });
9959
+ }
9960
+ }
9961
+
9962
+ // src/PaywalloProvider.tsx
9963
+ init_session();
9964
+ init_ClientError();
9965
+
9966
+ // src/hooks/useAppAutoEvents.ts
9967
+ var import_react22 = require("react");
9968
+
9969
+ // src/utils/platformDetection.ts
9970
+ async function detectPlatform3() {
9971
+ try {
9972
+ const rn = await import("react-native");
9973
+ return rn.Platform?.OS === "ios" ? "ios" : "android";
9974
+ } catch {
9975
+ return "android";
9976
+ }
9977
+ }
9978
+ async function detectOsVersion() {
9979
+ try {
9980
+ const rn = await import("react-native");
9981
+ const version = rn.Platform?.Version;
9982
+ return version != null ? String(version) : "unknown";
9983
+ } catch {
9984
+ return "unknown";
9985
+ }
9986
+ }
9987
+
9988
+ // src/hooks/useAppAutoEvents.ts
9989
+ var FIRST_SEEN_KEY = "@paywallo:firstSeen_v226";
9990
+ var DEBOUNCE_MS = 1e3;
9991
+ function useAppAutoEvents(config) {
9992
+ const { isInitialized, getAppVersion: getAppVersion2, getSessionId, emitLifecycle, storageGet, storageSet, debug } = config;
9993
+ const didRunRef = (0, import_react22.useRef)(false);
9994
+ (0, import_react22.useEffect)(() => {
9995
+ if (debug) console.log("[Paywallo EVENTS] useEffect fired", { isInitialized, didRunRefCurrent: didRunRef.current });
9996
+ if (!isInitialized || didRunRef.current) return;
9997
+ const timer = setTimeout(() => {
9998
+ if (didRunRef.current) return;
9999
+ didRunRef.current = true;
10000
+ void (async () => {
10001
+ const sessionId = getSessionId();
10002
+ const appVersion = getAppVersion2();
10003
+ const deviceType = await detectPlatform3();
10004
+ const osVersion = await detectOsVersion();
10005
+ if (debug) console.log("[Paywallo EVENTS] computed device context", { deviceType, osVersion, appVersion, sessionId, hasSessionId: !!sessionId });
10006
+ try {
10007
+ if (debug) console.log("[Paywallo EVENTS] checking install \u2014 about to read FIRST_SEEN_KEY");
10008
+ const firstSeen = await storageGet(FIRST_SEEN_KEY);
10009
+ if (!firstSeen) {
9504
10010
  await emitLifecycle({ type: "install", device_type: deviceType, os_version: osVersion, app_version: appVersion, ...sessionId ? { session_id: sessionId } : {} }, "critical");
9505
10011
  await storageSet(FIRST_SEEN_KEY, (/* @__PURE__ */ new Date()).toISOString());
9506
10012
  if (debug) console.log("[Paywallo EVENTS] install event emitted (first time)");
@@ -9525,7 +10031,7 @@ function useAppAutoEvents(config) {
9525
10031
  }
9526
10032
 
9527
10033
  // src/hooks/useCampaignPreload.ts
9528
- var import_react21 = require("react");
10034
+ var import_react23 = require("react");
9529
10035
  init_PaywalloClient();
9530
10036
  init_CampaignError();
9531
10037
 
@@ -9596,17 +10102,17 @@ function getCacheEntry(cache, placement) {
9596
10102
 
9597
10103
  // src/hooks/useCampaignPreload.ts
9598
10104
  function useCampaignPreload() {
9599
- const [preloadedWebView, setPreloadedWebView] = (0, import_react21.useState)(null);
9600
- const preloadedCampaignsRef = (0, import_react21.useRef)(/* @__PURE__ */ new Map());
9601
- const isPreloadValid = (0, import_react21.useCallback)(
10105
+ const [preloadedWebView, setPreloadedWebView] = (0, import_react23.useState)(null);
10106
+ const preloadedCampaignsRef = (0, import_react23.useRef)(/* @__PURE__ */ new Map());
10107
+ const isPreloadValid = (0, import_react23.useCallback)(
9602
10108
  (placement) => isCacheEntryValid(preloadedCampaignsRef.current.get(placement)),
9603
10109
  []
9604
10110
  );
9605
- const consumePreloadedCampaign = (0, import_react21.useCallback)(
10111
+ const consumePreloadedCampaign = (0, import_react23.useCallback)(
9606
10112
  (placement) => getCacheEntry(preloadedCampaignsRef.current, placement),
9607
10113
  []
9608
10114
  );
9609
- const preloadCampaign = (0, import_react21.useCallback)(
10115
+ const preloadCampaign = (0, import_react23.useCallback)(
9610
10116
  async (placement, context) => {
9611
10117
  try {
9612
10118
  const campaign = await PaywalloClient.getCampaign(placement, context);
@@ -9675,11 +10181,11 @@ function useCampaignPreload() {
9675
10181
  }
9676
10182
 
9677
10183
  // src/hooks/useProductLoader.ts
9678
- var import_react22 = require("react");
10184
+ var import_react24 = require("react");
9679
10185
  function useProductLoader() {
9680
- const [products, setProducts] = (0, import_react22.useState)(/* @__PURE__ */ new Map());
9681
- const [isLoadingProducts, setIsLoadingProducts] = (0, import_react22.useState)(false);
9682
- const refreshProducts = (0, import_react22.useCallback)(async (productIds) => {
10186
+ const [products, setProducts] = (0, import_react24.useState)(/* @__PURE__ */ new Map());
10187
+ const [isLoadingProducts, setIsLoadingProducts] = (0, import_react24.useState)(false);
10188
+ const refreshProducts = (0, import_react24.useCallback)(async (productIds) => {
9683
10189
  setIsLoadingProducts(true);
9684
10190
  try {
9685
10191
  const iapService = getIAPService();
@@ -9697,11 +10203,11 @@ function useProductLoader() {
9697
10203
  }
9698
10204
 
9699
10205
  // src/hooks/usePurchaseOrchestrator.ts
9700
- var import_react23 = require("react");
10206
+ var import_react25 = require("react");
9701
10207
  init_paywall();
9702
10208
  function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, invalidateSubscriptionCache) {
9703
10209
  const { trackDismissed, trackProductSelected, trackPurchased } = usePaywallTracking();
9704
- const handlePreloadedPurchase = (0, import_react23.useCallback)(
10210
+ const handlePreloadedPurchase = (0, import_react25.useCallback)(
9705
10211
  async (productId) => {
9706
10212
  try {
9707
10213
  if (preloadedWebView) {
@@ -9739,7 +10245,7 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
9739
10245
  },
9740
10246
  [preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed, trackProductSelected, trackPurchased, invalidateSubscriptionCache]
9741
10247
  );
9742
- const handlePreloadedRestore = (0, import_react23.useCallback)(async () => {
10248
+ const handlePreloadedRestore = (0, import_react25.useCallback)(async () => {
9743
10249
  try {
9744
10250
  const transactions = await getIAPService().restore();
9745
10251
  if (transactions.length === 0) return;
@@ -9762,7 +10268,7 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
9762
10268
  }
9763
10269
 
9764
10270
  // src/hooks/useSubscriptionSync.ts
9765
- var import_react24 = require("react");
10271
+ var import_react26 = require("react");
9766
10272
  init_PaywalloClient();
9767
10273
  init_SubscriptionCache();
9768
10274
 
@@ -9872,18 +10378,18 @@ async function checkPersistedCache(distinctId, onHit) {
9872
10378
 
9873
10379
  // src/hooks/useSubscriptionSync.ts
9874
10380
  function useSubscriptionSync() {
9875
- const [subscription, setSubscription] = (0, import_react24.useState)(null);
9876
- const cacheRef = (0, import_react24.useRef)(null);
9877
- const setCacheAndState = (0, import_react24.useCallback)((sub) => {
10381
+ const [subscription, setSubscription] = (0, import_react26.useState)(null);
10382
+ const cacheRef = (0, import_react26.useRef)(null);
10383
+ const setCacheAndState = (0, import_react26.useCallback)((sub) => {
9878
10384
  cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
9879
10385
  setSubscription(sub);
9880
10386
  }, []);
9881
- const invalidateCache = (0, import_react24.useCallback)(() => {
10387
+ const invalidateCache = (0, import_react26.useCallback)(() => {
9882
10388
  cacheRef.current = null;
9883
10389
  const distinctId = PaywalloClient.getDistinctId();
9884
10390
  if (distinctId) void subscriptionCache.invalidate(distinctId);
9885
10391
  }, []);
9886
- const fetchAndCache = (0, import_react24.useCallback)(async () => {
10392
+ const fetchAndCache = (0, import_react26.useCallback)(async () => {
9887
10393
  const apiClient = PaywalloClient.getApiClient();
9888
10394
  const distinctId = PaywalloClient.getDistinctId();
9889
10395
  if (!apiClient || !distinctId) return null;
@@ -9893,7 +10399,7 @@ function useSubscriptionSync() {
9893
10399
  void subscriptionCache.set(distinctId, toDomainResponse(status));
9894
10400
  return sub;
9895
10401
  }, [setCacheAndState]);
9896
- const hasActiveSubscription = (0, import_react24.useCallback)(async () => {
10402
+ const hasActiveSubscription = (0, import_react26.useCallback)(async () => {
9897
10403
  const apiClient = PaywalloClient.getApiClient();
9898
10404
  const distinctId = PaywalloClient.getDistinctId();
9899
10405
  if (!apiClient || !distinctId) return false;
@@ -9913,7 +10419,7 @@ function useSubscriptionSync() {
9913
10419
  return resolveOfflineFromCache(distinctId);
9914
10420
  }
9915
10421
  }, [setCacheAndState]);
9916
- const getSubscription = (0, import_react24.useCallback)(async () => {
10422
+ const getSubscription = (0, import_react26.useCallback)(async () => {
9917
10423
  const apiClient = PaywalloClient.getApiClient();
9918
10424
  const distinctId = PaywalloClient.getDistinctId();
9919
10425
  if (!apiClient || !distinctId) return null;
@@ -9934,214 +10440,24 @@ function useSubscriptionSync() {
9934
10440
  return { subscription, hasActiveSubscription, getSubscription, invalidateCache };
9935
10441
  }
9936
10442
 
9937
- // src/domains/paywall/SuperwallAutoBridge.ts
9938
- init_PaywalloClient();
9939
-
9940
- // src/core/utils/eventId.ts
9941
- function hashString(input) {
9942
- let h1 = 3735928559;
9943
- let h2 = 1103547991;
9944
- for (let i = 0; i < input.length; i++) {
9945
- const ch = input.charCodeAt(i);
9946
- h1 = Math.imul(h1 ^ ch, 2654435769);
9947
- h2 = Math.imul(h2 ^ ch, 1598716949);
9948
- }
9949
- h1 = Math.imul(h1 ^ h1 >>> 16, 73244475);
9950
- h1 ^= Math.imul(h2 ^ h2 >>> 13, 351452291);
9951
- h2 = Math.imul(h2 ^ h2 >>> 16, 73244475);
9952
- h2 ^= Math.imul(h1 ^ h1 >>> 13, 351452291);
9953
- const toHex = (n) => (n >>> 0).toString(16).padStart(8, "0");
9954
- return toHex(h1) + toHex(h2);
9955
- }
9956
- function buildDeterministicEventId(parts) {
9957
- const raw = hashString(parts.join("|"));
9958
- const p1 = raw.slice(0, 8);
9959
- const p2 = raw.slice(8, 12);
9960
- const p3 = "4" + raw.slice(13, 16);
9961
- const p4 = (parseInt(raw[16], 16) & 3 | 8).toString(16) + raw.slice(17, 20);
9962
- const p5 = raw.padEnd(32, "0").slice(20, 32);
9963
- return `${p1}-${p2}-${p3}-${p4}-${p5}`;
9964
- }
9965
-
9966
- // src/domains/paywall/SuperwallAutoBridge.ts
9967
- var bridgeStarted = false;
9968
- var unsubscribe = null;
9969
- var debugEnabled = false;
9970
- function log(msg, data) {
9971
- if (!debugEnabled) return;
9972
- if (data !== void 0) {
9973
- console.log(`[Paywallo:SuperwallBridge] ${msg}`, data);
9974
- } else {
9975
- console.log(`[Paywallo:SuperwallBridge] ${msg}`);
9976
- }
9977
- }
9978
- function minuteBucket(ts) {
9979
- return String(Math.floor(ts / 6e4));
9980
- }
9981
- function buildEventId(type, identifier, ts) {
9982
- return buildDeterministicEventId(["sw", type, identifier, minuteBucket(ts)]);
9983
- }
9984
- async function trackPaywallEvent(type, identifier, ts, extra) {
9985
- const _eventId = buildEventId(type, identifier, ts);
9986
- try {
9987
- const cleanExtra = {};
9988
- if (extra) {
9989
- for (const [k, v] of Object.entries(extra)) if (v !== void 0) cleanExtra[k] = v;
9990
- }
9991
- await PaywalloClient.track("paywall", {
9992
- priority: "critical",
9993
- properties: {
9994
- type,
9995
- paywall_id: identifier,
9996
- ...cleanExtra
9997
- }
9998
- });
9999
- } catch (err) {
10000
- log("track error", { type, identifier, eventId: _eventId, err: String(err) });
10001
- }
10002
- }
10003
- function mapDismissToCloseReason(resultType) {
10004
- switch (resultType) {
10005
- case "purchased":
10006
- return "purchased";
10007
- case "restored":
10008
- return "purchased";
10009
- // restore counts as a purchase outcome for funnel
10010
- case "declined":
10011
- return "dismissed";
10012
- default:
10013
- return void 0;
10014
- }
10015
- }
10016
- function buildCallbacks() {
10017
- return {
10018
- onPaywallPresent: (info) => {
10019
- const ts = Date.now();
10020
- log("onPaywallPresent", { identifier: info.identifier });
10021
- void trackPaywallEvent("viewed", info.identifier, ts, {
10022
- placement: info.presentedByEventWithName,
10023
- variant_id: info.experiment?.variant?.id,
10024
- campaign_id: info.experiment?.id
10025
- });
10026
- },
10027
- onPaywallDismiss: (info, result) => {
10028
- const ts = Date.now();
10029
- log("onPaywallDismiss", { identifier: info.identifier, result: result.type });
10030
- void trackPaywallEvent("closed", info.identifier, ts, {
10031
- placement: info.presentedByEventWithName,
10032
- variant_id: info.experiment?.variant?.id,
10033
- campaign_id: info.experiment?.id,
10034
- close_reason: mapDismissToCloseReason(result.type),
10035
- closed_at: new Date(ts).toISOString()
10036
- });
10037
- },
10038
- onPurchase: (params) => {
10039
- const ts = Date.now();
10040
- const safe = params !== null && typeof params === "object" ? params : {};
10041
- const productId = "productId" in safe && typeof safe.productId === "string" ? safe.productId : "unknown";
10042
- const identifier = "paywallIdentifier" in safe && typeof safe.paywallIdentifier === "string" ? safe.paywallIdentifier : productId;
10043
- log("onPurchase", { productId, identifier });
10044
- void trackPaywallEvent("purchased", identifier, ts, { product_id: productId });
10045
- },
10046
- // Global Superwall event listener — used specifically for `transactionComplete`
10047
- // which is the only event that exposes the full product (currency/price) +
10048
- // transaction (storeTransactionId) shape needed to create a server-side
10049
- // `transactions` row. Other events are skipped (paywallOpen/Dismiss already
10050
- // handled above with their typed callbacks).
10051
- onSuperwallEvent: (info) => {
10052
- const ev = info?.event;
10053
- if (!ev || ev.event !== "transactionComplete") return;
10054
- const ts = Date.now();
10055
- const product = ev.product;
10056
- const transaction = ev.transaction;
10057
- if (!product) {
10058
- log("transactionComplete missing product \u2014 skipping transaction track");
10059
- return;
10060
- }
10061
- const productId = product.productIdentifier ?? product.id ?? "unknown";
10062
- const currency = product.currencyCode ?? "USD";
10063
- const price = product.price ?? 0;
10064
- const transactionId = transaction?.storeTransactionId ?? transaction?.originalTransactionIdentifier ?? `${productId}_${String(ts)}`;
10065
- const isTrial = product.hasFreeTrial === true || (product.trialPeriodPrice ?? 0) > 0;
10066
- const paywallIdentifier = ev.paywallInfo?.identifier;
10067
- log("onSuperwallEvent: transactionComplete", { productId, currency, price, transactionId, isTrial });
10068
- void (async () => {
10069
- try {
10070
- await PaywalloClient.track("transaction", {
10071
- priority: "critical",
10072
- properties: {
10073
- type: isTrial ? "trial_started" : "completed",
10074
- // Server's IngestTranslator looks for `transaction_id`/`product_id`
10075
- // /`amount`/`currency`/`paywall_id` keys (not `tx_id`). PurchaseHandler
10076
- // then resolves type=trial/new/renewal based on amount being 0 vs >0.
10077
- transaction_id: transactionId,
10078
- product_id: productId,
10079
- amount: price,
10080
- currency,
10081
- ...paywallIdentifier ? { paywall_id: paywallIdentifier } : {}
10082
- }
10083
- });
10084
- } catch (err) {
10085
- log("transactionComplete track error", { err: String(err) });
10086
- }
10087
- })();
10088
- }
10089
- };
10090
- }
10091
- function startSuperwallAutoBridge(debug = false) {
10092
- debugEnabled = debug;
10093
- if (bridgeStarted) {
10094
- log("already started, skipping");
10095
- return;
10096
- }
10097
- void detectAndStart();
10098
- }
10099
- async function detectAndStart() {
10100
- let subscribeFn;
10101
- try {
10102
- const mod = await import("expo-superwall");
10103
- if (!mod || typeof mod.subscribeToSuperwallEvents !== "function") {
10104
- const internal = await import("expo-superwall/build/src/internal/superwallEventBridge");
10105
- if (typeof internal.subscribeToSuperwallEvents !== "function") {
10106
- log("expo-superwall subscribeToSuperwallEvents not found \u2014 bridge inactive");
10107
- return;
10108
- }
10109
- subscribeFn = internal.subscribeToSuperwallEvents;
10110
- } else {
10111
- subscribeFn = mod.subscribeToSuperwallEvents;
10112
- }
10113
- } catch {
10114
- log("expo-superwall not installed \u2014 bridge inactive");
10115
- return;
10116
- }
10117
- try {
10118
- const callbacks = buildCallbacks();
10119
- unsubscribe = subscribeFn(void 0, () => callbacks);
10120
- bridgeStarted = true;
10121
- log("bridge started");
10122
- } catch (err) {
10123
- log("subscribe failed", { err: String(err) });
10124
- }
10125
- }
10126
-
10127
10443
  // src/PaywalloProvider.tsx
10128
10444
  init_localization();
10129
- var import_jsx_runtime3 = require("react/jsx-runtime");
10445
+ var import_jsx_runtime5 = require("react/jsx-runtime");
10130
10446
  function PaywalloProvider({ children, config }) {
10131
- const [isInitialized, setIsInitialized] = (0, import_react25.useState)(false);
10132
- const [initError, setInitError] = (0, import_react25.useState)(null);
10133
- const [distinctId, setDistinctId] = (0, import_react25.useState)(null);
10134
- const [activePaywall, setActivePaywall] = (0, import_react25.useState)(null);
10447
+ const [isInitialized, setIsInitialized] = (0, import_react27.useState)(false);
10448
+ const [initError, setInitError] = (0, import_react27.useState)(null);
10449
+ const [distinctId, setDistinctId] = (0, import_react27.useState)(null);
10450
+ const [activePaywall, setActivePaywall] = (0, import_react27.useState)(null);
10135
10451
  const { products, isLoadingProducts, refreshProducts } = useProductLoader();
10136
10452
  const { subscription, hasActiveSubscription, getSubscription, invalidateCache } = useSubscriptionSync();
10137
10453
  const { preloadedWebView, setPreloadedWebView, preloadCampaign, consumePreloadedCampaign, isPreloadValid } = useCampaignPreload();
10138
- const clearPreloadedWebView = (0, import_react25.useCallback)(() => setPreloadedWebView(null), [setPreloadedWebView]);
10139
- const autoPreloadTriggeredRef = (0, import_react25.useRef)(false);
10140
- const preloadedWebViewRef = (0, import_react25.useRef)(preloadedWebView);
10141
- const pollTimerRef = (0, import_react25.useRef)(null);
10142
- const pollCancelledRef = (0, import_react25.useRef)(false);
10143
- const secureStorageRef = (0, import_react25.useRef)(new SecureStorage(config.debug));
10144
- (0, import_react25.useEffect)(() => {
10454
+ const clearPreloadedWebView = (0, import_react27.useCallback)(() => setPreloadedWebView(null), [setPreloadedWebView]);
10455
+ const autoPreloadTriggeredRef = (0, import_react27.useRef)(false);
10456
+ const preloadedWebViewRef = (0, import_react27.useRef)(preloadedWebView);
10457
+ const pollTimerRef = (0, import_react27.useRef)(null);
10458
+ const pollCancelledRef = (0, import_react27.useRef)(false);
10459
+ const secureStorageRef = (0, import_react27.useRef)(new SecureStorage(config.debug));
10460
+ (0, import_react27.useEffect)(() => {
10145
10461
  preloadedWebViewRef.current = preloadedWebView;
10146
10462
  }, [preloadedWebView]);
10147
10463
  useAppAutoEvents({
@@ -10167,22 +10483,22 @@ function PaywalloProvider({ children, config }) {
10167
10483
  },
10168
10484
  debug: config.debug
10169
10485
  });
10170
- const triggerRepreload = (0, import_react25.useCallback)((placement) => {
10486
+ const triggerRepreload = (0, import_react27.useCallback)((placement) => {
10171
10487
  void PaywalloClient.preloadCampaign(placement).catch(() => {
10172
10488
  });
10173
10489
  }, []);
10174
10490
  const { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady: baseHandlePreloadedWebViewReady, handlePreloadedClose: baseHandlePreloadedClose, setShowingPreloadedWebView, presentedAtRef } = usePaywallPresenter(preloadedWebView, setPreloadedWebView);
10175
- const SCREEN_HEIGHT = (0, import_react25.useMemo)(() => import_react_native27.Dimensions.get("window").height, []);
10176
- const slideAnim = (0, import_react25.useRef)(new import_react_native27.Animated.Value(SCREEN_HEIGHT)).current;
10177
- const handlePreloadedWebViewReady = (0, import_react25.useCallback)(() => {
10491
+ const SCREEN_HEIGHT = (0, import_react27.useMemo)(() => import_react_native29.Dimensions.get("window").height, []);
10492
+ const slideAnim = (0, import_react27.useRef)(new import_react_native29.Animated.Value(SCREEN_HEIGHT)).current;
10493
+ const handlePreloadedWebViewReady = (0, import_react27.useCallback)(() => {
10178
10494
  if (PaywalloClient.getConfig()?.debug) {
10179
10495
  console.log(`[Paywallo WEBVIEW] loaded \u2014 placement: ${preloadedWebView?.placement}`);
10180
10496
  }
10181
10497
  baseHandlePreloadedWebViewReady();
10182
10498
  }, [preloadedWebView?.placement, baseHandlePreloadedWebViewReady]);
10183
- const handlePreloadedClose = (0, import_react25.useCallback)(() => {
10499
+ const handlePreloadedClose = (0, import_react27.useCallback)(() => {
10184
10500
  const placement = preloadedWebView?.placement;
10185
- import_react_native27.Animated.timing(slideAnim, { toValue: SCREEN_HEIGHT, duration: 200, easing: import_react_native27.Easing.in(import_react_native27.Easing.cubic), useNativeDriver: true }).start(() => {
10501
+ import_react_native29.Animated.timing(slideAnim, { toValue: SCREEN_HEIGHT, duration: 200, easing: import_react_native29.Easing.in(import_react_native29.Easing.cubic), useNativeDriver: true }).start(() => {
10186
10502
  baseHandlePreloadedClose();
10187
10503
  if (placement) triggerRepreload(placement);
10188
10504
  });
@@ -10195,8 +10511,8 @@ function PaywalloProvider({ children, config }) {
10195
10511
  presentedAtRef,
10196
10512
  invalidateCache
10197
10513
  );
10198
- const [isPreloadPurchasing, setIsPreloadPurchasing] = (0, import_react25.useState)(false);
10199
- const handlePreloadedPurchase = (0, import_react25.useCallback)(async (productId) => {
10514
+ const [isPreloadPurchasing, setIsPreloadPurchasing] = (0, import_react27.useState)(false);
10515
+ const handlePreloadedPurchase = (0, import_react27.useCallback)(async (productId) => {
10200
10516
  setIsPreloadPurchasing(true);
10201
10517
  try {
10202
10518
  await rawPreloadedPurchase(productId);
@@ -10204,8 +10520,8 @@ function PaywalloProvider({ children, config }) {
10204
10520
  setIsPreloadPurchasing(false);
10205
10521
  }
10206
10522
  }, [rawPreloadedPurchase]);
10207
- const configRef = (0, import_react25.useRef)(config);
10208
- (0, import_react25.useEffect)(() => {
10523
+ const configRef = (0, import_react27.useRef)(config);
10524
+ (0, import_react27.useEffect)(() => {
10209
10525
  let mounted = true;
10210
10526
  initLocalization({ detectDevice: true });
10211
10527
  PaywalloClient.init(configRef.current).then(async () => {
@@ -10218,6 +10534,11 @@ function PaywalloProvider({ children, config }) {
10218
10534
  const placement = await PaywalloClient.waitForAutoPreloadedPlacement();
10219
10535
  if (mounted && placement) void preloadCampaign(placement).catch(() => {
10220
10536
  });
10537
+ const preloadPlacements = configRef.current.preloadPaywalls;
10538
+ if (mounted && preloadPlacements && preloadPlacements.length > 0) {
10539
+ void PaywalloClient.preloadPaywalls(preloadPlacements).catch(() => {
10540
+ });
10541
+ }
10221
10542
  }
10222
10543
  }).catch((err) => {
10223
10544
  if (!mounted) return;
@@ -10227,14 +10548,29 @@ function PaywalloProvider({ children, config }) {
10227
10548
  mounted = false;
10228
10549
  };
10229
10550
  }, [preloadCampaign]);
10230
- const getPaywallConfig = (0, import_react25.useCallback)(async (p) => {
10551
+ const getPaywallConfig = (0, import_react27.useCallback)(async (p) => {
10231
10552
  const api = PaywalloClient.getApiClient();
10232
10553
  return api ? api.getPaywall(p) : null;
10233
10554
  }, []);
10234
- const presentPaywall = (0, import_react25.useCallback)((placement) => {
10235
- return new Promise((resolve) => setActivePaywall({ placement, resolver: resolve }));
10555
+ const presentPaywall = (0, import_react27.useCallback)((placement) => {
10556
+ const preloaded = paywallPreloadService.getPreloaded(placement);
10557
+ return new Promise((resolve) => setActivePaywall({
10558
+ placement,
10559
+ resolver: resolve,
10560
+ ...preloaded && {
10561
+ paywallConfig: {
10562
+ id: preloaded.config.id,
10563
+ placement: preloaded.config.placement,
10564
+ config: preloaded.config.config,
10565
+ primaryProductId: preloaded.config.primaryProductId,
10566
+ secondaryProductId: preloaded.config.secondaryProductId,
10567
+ tertiaryProductId: preloaded.config.tertiaryProductId
10568
+ },
10569
+ preloadedProducts: preloaded.products
10570
+ }
10571
+ }));
10236
10572
  }, []);
10237
- const trackCampaignImpression = (0, import_react25.useCallback)((placement, variantKey, campaignId) => {
10573
+ const trackCampaignImpression = (0, import_react27.useCallback)((placement, variantKey, campaignId) => {
10238
10574
  const api = PaywalloClient.getApiClient();
10239
10575
  if (!api) return;
10240
10576
  const sessionId = PaywalloClient.getSessionId();
@@ -10248,7 +10584,7 @@ function PaywalloProvider({ children, config }) {
10248
10584
  }).catch(() => {
10249
10585
  });
10250
10586
  }, []);
10251
- const presentCampaign = (0, import_react25.useCallback)(
10587
+ const presentCampaign = (0, import_react27.useCallback)(
10252
10588
  async (placement, context) => {
10253
10589
  try {
10254
10590
  if (preloadedWebView?.placement === placement && preloadedWebView.loaded && isPreloadValid(placement)) {
@@ -10322,7 +10658,7 @@ function PaywalloProvider({ children, config }) {
10322
10658
  },
10323
10659
  [preloadedWebView, isPreloadValid, consumePreloadedCampaign, presentPreloadedWebView, trackCampaignImpression]
10324
10660
  );
10325
- const restorePurchases = (0, import_react25.useCallback)(async () => {
10661
+ const restorePurchases = (0, import_react27.useCallback)(async () => {
10326
10662
  invalidateCache();
10327
10663
  try {
10328
10664
  const txs = await getIAPService().restore();
@@ -10331,13 +10667,13 @@ function PaywalloProvider({ children, config }) {
10331
10667
  return { success: false, restoredProducts: [], error: error instanceof Error ? error : new PurchaseError(PURCHASE_ERROR_CODES.RESTORE_FAILED, String(error)) };
10332
10668
  }
10333
10669
  }, [invalidateCache]);
10334
- const handlePaywallResult = (0, import_react25.useCallback)((result) => {
10670
+ const handlePaywallResult = (0, import_react27.useCallback)((result) => {
10335
10671
  if (activePaywall) {
10336
10672
  activePaywall.resolver(result);
10337
10673
  setActivePaywall(null);
10338
10674
  }
10339
10675
  }, [activePaywall]);
10340
- const bridgePaywallPresenter = (0, import_react25.useCallback)((placement) => {
10676
+ const bridgePaywallPresenter = (0, import_react27.useCallback)((placement) => {
10341
10677
  if (PaywalloClient.getConfig()?.debug) {
10342
10678
  console.log(`[Paywallo PRESENT] called \u2014 preloaded: ${preloadedWebViewRef.current?.loaded === true} placement: ${placement}`);
10343
10679
  }
@@ -10366,7 +10702,7 @@ function PaywalloProvider({ children, config }) {
10366
10702
  }
10367
10703
  return presentCampaign(placement);
10368
10704
  }, [presentPreloadedWebView, presentCampaign]);
10369
- (0, import_react25.useEffect)(() => {
10705
+ (0, import_react27.useEffect)(() => {
10370
10706
  if (!isInitialized) return;
10371
10707
  const onEmergency = async (id) => {
10372
10708
  try {
@@ -10393,9 +10729,9 @@ function PaywalloProvider({ children, config }) {
10393
10729
  };
10394
10730
  }, [isInitialized, bridgePaywallPresenter, getSubscription, hasActiveSubscription, restorePurchases]);
10395
10731
  const isReady = isInitialized && !isLoadingProducts;
10396
- const noOp = (0, import_react25.useCallback)(() => {
10732
+ const noOp = (0, import_react27.useCallback)(() => {
10397
10733
  }, []);
10398
- const contextValue = (0, import_react25.useMemo)(() => ({
10734
+ const contextValue = (0, import_react27.useMemo)(() => ({
10399
10735
  config: PaywalloClient.getConfig(),
10400
10736
  isInitialized,
10401
10737
  isReady,
@@ -10429,20 +10765,20 @@ function PaywalloProvider({ children, config }) {
10429
10765
  getPaywallConfig,
10430
10766
  refreshProducts
10431
10767
  ]);
10432
- (0, import_react25.useLayoutEffect)(() => {
10768
+ (0, import_react27.useLayoutEffect)(() => {
10433
10769
  if (showingPreloadedWebView) {
10434
10770
  slideAnim.setValue(SCREEN_HEIGHT);
10435
- import_react_native27.Animated.timing(slideAnim, { toValue: 0, duration: 200, easing: import_react_native27.Easing.out(import_react_native27.Easing.cubic), useNativeDriver: true }).start();
10771
+ import_react_native29.Animated.timing(slideAnim, { toValue: 0, duration: 200, easing: import_react_native29.Easing.out(import_react_native29.Easing.cubic), useNativeDriver: true }).start();
10436
10772
  }
10437
10773
  }, [showingPreloadedWebView, slideAnim, SCREEN_HEIGHT]);
10438
10774
  const preloadStyle = [
10439
- styles3.preloadOverlay,
10440
- showingPreloadedWebView ? styles3.visible : styles3.hidden,
10775
+ styles5.preloadOverlay,
10776
+ showingPreloadedWebView ? styles5.visible : styles5.hidden,
10441
10777
  showingPreloadedWebView ? { transform: [{ translateY: slideAnim }] } : void 0
10442
10778
  ];
10443
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(PaywalloContext.Provider, { value: contextValue, children: [
10779
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(PaywalloContext.Provider, { value: contextValue, children: [
10444
10780
  children,
10445
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PaywallErrorBoundary, { children: !!preloadedWebView?.craftData && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native27.Animated.View, { style: preloadStyle, pointerEvents: showingPreloadedWebView ? "auto" : "none", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
10781
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PaywallErrorBoundary, { children: !!preloadedWebView?.craftData && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native29.Animated.View, { style: preloadStyle, pointerEvents: showingPreloadedWebView ? "auto" : "none", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
10446
10782
  PaywallWebView,
10447
10783
  {
10448
10784
  paywallId: preloadedWebView.paywallId,
@@ -10458,7 +10794,7 @@ function PaywalloProvider({ children, config }) {
10458
10794
  onRestore: showingPreloadedWebView ? handlePreloadedRestore : noOp
10459
10795
  }
10460
10796
  ) }) }),
10461
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PaywallErrorBoundary, { children: activePaywall && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
10797
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PaywallErrorBoundary, { children: activePaywall && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
10462
10798
  PaywallModal,
10463
10799
  {
10464
10800
  placement: activePaywall.placement,
@@ -10470,10 +10806,11 @@ function PaywalloProvider({ children, config }) {
10470
10806
  campaignId: activePaywall.campaignId,
10471
10807
  variantId: activePaywall.variantId
10472
10808
  }
10473
- ) })
10809
+ ) }),
10810
+ isInitialized && (config.preloadPaywalls?.length ?? 0) > 0 && PaywalloClient.getWebUrl() !== null && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PaywallWebViewPrewarmer, { webUrl: PaywalloClient.getWebUrl() })
10474
10811
  ] });
10475
10812
  }
10476
- var styles3 = import_react_native27.StyleSheet.create({
10813
+ var styles5 = import_react_native29.StyleSheet.create({
10477
10814
  preloadOverlay: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, backgroundColor: "#ffffff" },
10478
10815
  hidden: { zIndex: -1, opacity: 0, width: 1, height: 1, overflow: "hidden" },
10479
10816
  visible: { zIndex: 9999, opacity: 1 }
@@ -10529,6 +10866,9 @@ function resolveProductsVariable(parts, context) {
10529
10866
  if (parts[1] === "secondary" && context.secondaryProductId) {
10530
10867
  return getProductVariable(context.products.get(context.secondaryProductId), parts[2]);
10531
10868
  }
10869
+ if (parts[1] === "tertiary" && context.tertiaryProductId) {
10870
+ return getProductVariable(context.products.get(context.tertiaryProductId), parts[2]);
10871
+ }
10532
10872
  if (parts[1] === "hasIntroductoryOffer") {
10533
10873
  return Array.from(context.products.values()).some((p) => p.introductoryPrice) ? "true" : "false";
10534
10874
  }