@virex-tech/paywallo-sdk 2.2.5 → 2.2.7

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
@@ -830,9 +830,9 @@ var init_CampaignGateService = __esm({
830
830
  }
831
831
  }
832
832
  getPreloadedCampaign(placement) {
833
- const cached = this.preloadedCampaigns.get(placement);
834
- if (!cached) return null;
835
- const age = Date.now() - cached.preloadedAt;
833
+ const cached2 = this.preloadedCampaigns.get(placement);
834
+ if (!cached2) return null;
835
+ const age = Date.now() - cached2.preloadedAt;
836
836
  if (age >= PRELOAD_CACHE_TTL_MS) {
837
837
  this.preloadedCampaigns.delete(placement);
838
838
  return null;
@@ -841,7 +841,7 @@ var init_CampaignGateService = __esm({
841
841
  void this.preloadCampaign(this.apiClientRef, placement).catch(() => {
842
842
  });
843
843
  }
844
- return cached.campaign;
844
+ return cached2.campaign;
845
845
  }
846
846
  async preloadAllActive(apiClient, debug) {
847
847
  this.apiClientRef = apiClient;
@@ -2173,7 +2173,7 @@ function extractPayload(data) {
2173
2173
  }
2174
2174
  function setupForegroundHandler(deps, onReceive) {
2175
2175
  const { bridge, tracker, debug } = deps;
2176
- const unsubscribe = bridge.onMessage(async (message) => {
2176
+ const unsubscribe2 = bridge.onMessage(async (message) => {
2177
2177
  const data = message.data ?? {};
2178
2178
  const payload = extractPayload({
2179
2179
  ...data,
@@ -2191,7 +2191,7 @@ function setupForegroundHandler(deps, onReceive) {
2191
2191
  messageId
2192
2192
  });
2193
2193
  });
2194
- return unsubscribe;
2194
+ return unsubscribe2;
2195
2195
  }
2196
2196
  var init_ForegroundHandler = __esm({
2197
2197
  "src/domains/notifications/handlers/ForegroundHandler.ts"() {
@@ -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.5") {
2390
- return "2.2.5";
2389
+ if ("2.2.7") {
2390
+ return "2.2.7";
2391
2391
  }
2392
2392
  } catch {
2393
2393
  }
@@ -2459,9 +2459,6 @@ function mapPermissionStatus(status) {
2459
2459
  function detectPlatform2() {
2460
2460
  return import_react_native11.Platform.OS === "ios" ? "ios" : "android";
2461
2461
  }
2462
- function detectEnvironment() {
2463
- return typeof __DEV__ !== "undefined" && __DEV__ ? "sandbox" : "production";
2464
- }
2465
2462
  async function getAppVersion() {
2466
2463
  try {
2467
2464
  const { collectDeviceInfo: collectDeviceInfo2 } = await Promise.resolve().then(() => (init_deviceInfo(), deviceInfo_exports));
@@ -2502,7 +2499,7 @@ async function clearPersistedToken(secureStorage2) {
2502
2499
  } catch {
2503
2500
  }
2504
2501
  }
2505
- async function buildRegistration(token, getDeviceId, getDistinctId) {
2502
+ async function buildRegistration(token, getDeviceId, getDistinctId, environment) {
2506
2503
  const appVersion = await getAppVersion();
2507
2504
  return {
2508
2505
  token,
@@ -2513,7 +2510,7 @@ async function buildRegistration(token, getDeviceId, getDistinctId) {
2513
2510
  deviceId: getDeviceId?.() ?? void 0,
2514
2511
  locale: await getLocale(),
2515
2512
  timezone: getTimezone2(),
2516
- environment: detectEnvironment()
2513
+ environment
2517
2514
  };
2518
2515
  }
2519
2516
  async function registerToken(deps, token) {
@@ -2522,7 +2519,8 @@ async function registerToken(deps, token) {
2522
2519
  if (debug) console.log("[Paywallo PUSH] httpClient not ready, skipping token registration");
2523
2520
  return;
2524
2521
  }
2525
- const registration = await buildRegistration(token, getDeviceId, getDistinctId);
2522
+ const { environment } = deps;
2523
+ const registration = await buildRegistration(token, getDeviceId, getDistinctId, environment);
2526
2524
  try {
2527
2525
  await httpClient.post(
2528
2526
  TOKEN_ENDPOINT,
@@ -2572,7 +2570,8 @@ async function registerAndPersistToken(deps, token) {
2572
2570
  appKey: deps.appKey,
2573
2571
  getDeviceId: deps.getDeviceId,
2574
2572
  getDistinctId: deps.getDistinctId,
2575
- debug: deps.debug
2573
+ debug: deps.debug,
2574
+ environment: deps.environment
2576
2575
  },
2577
2576
  token
2578
2577
  );
@@ -2589,7 +2588,8 @@ function subscribeTokenRefresh(bridge, deps, onToken) {
2589
2588
  appKey: deps.appKey,
2590
2589
  getDeviceId: deps.getDeviceId,
2591
2590
  getDistinctId: deps.getDistinctId,
2592
- debug: deps.debug
2591
+ debug: deps.debug,
2592
+ environment: deps.environment
2593
2593
  },
2594
2594
  newToken
2595
2595
  ).catch(() => void 0);
@@ -2797,6 +2797,7 @@ var init_NotificationsManager = __esm({
2797
2797
  this.getDistinctId = null;
2798
2798
  this.appKey = null;
2799
2799
  this.apiBaseUrl = null;
2800
+ this.environment = "production";
2800
2801
  this.tokenRefreshCleanup = null;
2801
2802
  this.foregroundCleanup = null;
2802
2803
  this.pendingHandlersConfig = null;
@@ -2816,6 +2817,7 @@ var init_NotificationsManager = __esm({
2816
2817
  this.secureStorage = deps.secureStorage;
2817
2818
  this.getDeviceId = deps.getDeviceId;
2818
2819
  if (deps.getDistinctId) this.getDistinctId = deps.getDistinctId;
2820
+ if (deps.environment) this.environment = deps.environment;
2819
2821
  }
2820
2822
  async initialize(config) {
2821
2823
  this.applyConfig(config);
@@ -2858,7 +2860,8 @@ var init_NotificationsManager = __esm({
2858
2860
  appKey: this.appKey,
2859
2861
  getDeviceId: this.getDeviceId,
2860
2862
  getDistinctId: this.getDistinctId,
2861
- debug: this.debug
2863
+ debug: this.debug,
2864
+ environment: this.environment
2862
2865
  };
2863
2866
  }
2864
2867
  async doRegisterToken(token) {
@@ -3054,6 +3057,41 @@ var init_emitPaywallClosed = __esm({
3054
3057
  }
3055
3058
  });
3056
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
+
3057
3095
  // src/domains/paywall/parseWebViewMessage.ts
3058
3096
  function deriveMessageId(message) {
3059
3097
  if (message.id) return message.id;
@@ -3296,6 +3334,66 @@ var init_localization = __esm({
3296
3334
  }
3297
3335
  });
3298
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
+
3299
3397
  // src/domains/paywall/paywallScripts.ts
3300
3398
  function buildPaywallInjectionScript(data) {
3301
3399
  const paywallData = {
@@ -3344,7 +3442,7 @@ function getNativeWebView() {
3344
3442
  if (NativeWebViewComponent) return NativeWebViewComponent;
3345
3443
  if (nativeWebViewLookupFailed) return null;
3346
3444
  try {
3347
- NativeWebViewComponent = (0, import_react_native14.requireNativeComponent)("PaywalloWebView");
3445
+ NativeWebViewComponent = (0, import_react_native15.requireNativeComponent)("PaywalloWebView");
3348
3446
  return NativeWebViewComponent;
3349
3447
  } catch {
3350
3448
  nativeWebViewLookupFailed = true;
@@ -3353,9 +3451,9 @@ function getNativeWebView() {
3353
3451
  }
3354
3452
  function getWebViewEmitter() {
3355
3453
  if (webViewEmitter) return webViewEmitter;
3356
- const mod = import_react_native14.NativeModules.PaywalloWebViewModule;
3454
+ const mod = import_react_native15.NativeModules.PaywalloWebViewModule;
3357
3455
  if (!mod) return null;
3358
- webViewEmitter = new import_react_native14.NativeEventEmitter(mod);
3456
+ webViewEmitter = new import_react_native15.NativeEventEmitter(mod);
3359
3457
  return webViewEmitter;
3360
3458
  }
3361
3459
  async function triggerNativeHaptic(style) {
@@ -3397,16 +3495,16 @@ function PaywallWebView({
3397
3495
  onReady,
3398
3496
  onError
3399
3497
  }) {
3400
- const paywallDataSent = (0, import_react3.useRef)(false);
3401
- const loadEndReceived = (0, import_react3.useRef)(false);
3402
- const [scriptToInject, setScriptToInject] = (0, import_react3.useState)(void 0);
3403
- const webViewRef = (0, import_react3.useRef)(null);
3404
- const onErrorRef = (0, import_react3.useRef)(onError);
3498
+ const paywallDataSent = (0, import_react4.useRef)(false);
3499
+ const loadEndReceived = (0, import_react4.useRef)(false);
3500
+ const [scriptToInject, setScriptToInject] = (0, import_react4.useState)(void 0);
3501
+ const webViewRef = (0, import_react4.useRef)(null);
3502
+ const onErrorRef = (0, import_react4.useRef)(onError);
3405
3503
  onErrorRef.current = onError;
3406
- const onReadyRef = (0, import_react3.useRef)(onReady);
3504
+ const onReadyRef = (0, import_react4.useRef)(onReady);
3407
3505
  onReadyRef.current = onReady;
3408
- const NativeWebView = (0, import_react3.useMemo)(() => getNativeWebView(), []);
3409
- (0, import_react3.useEffect)(() => {
3506
+ const NativeWebView = (0, import_react4.useMemo)(() => getNativeWebView(), []);
3507
+ (0, import_react4.useEffect)(() => {
3410
3508
  if (!NativeWebView) {
3411
3509
  onErrorRef.current?.({
3412
3510
  code: -1,
@@ -3414,10 +3512,10 @@ function PaywallWebView({
3414
3512
  });
3415
3513
  }
3416
3514
  }, [NativeWebView]);
3417
- const [resolvedWebUrl, setResolvedWebUrl] = (0, import_react3.useState)(
3515
+ const [resolvedWebUrl, setResolvedWebUrl] = (0, import_react4.useState)(
3418
3516
  () => webUrlProp ?? PaywalloClient.getWebUrl()
3419
3517
  );
3420
- (0, import_react3.useEffect)(() => {
3518
+ (0, import_react4.useEffect)(() => {
3421
3519
  if (webUrlProp !== void 0) {
3422
3520
  setResolvedWebUrl(webUrlProp ?? null);
3423
3521
  return;
@@ -3436,7 +3534,7 @@ function PaywallWebView({
3436
3534
  }, 200);
3437
3535
  return () => clearInterval(interval);
3438
3536
  }, [webUrlProp]);
3439
- (0, import_react3.useEffect)(() => {
3537
+ (0, import_react4.useEffect)(() => {
3440
3538
  if (!resolvedWebUrl) {
3441
3539
  onErrorRef.current?.({
3442
3540
  code: -1,
@@ -3444,16 +3542,16 @@ function PaywallWebView({
3444
3542
  });
3445
3543
  }
3446
3544
  }, []);
3447
- const injectScript = (0, import_react3.useCallback)((script) => {
3448
- if (import_react_native14.Platform.OS === "android") {
3449
- const handle = (0, import_react_native14.findNodeHandle)(webViewRef.current);
3545
+ const injectScript = (0, import_react4.useCallback)((script) => {
3546
+ if (import_react_native15.Platform.OS === "android") {
3547
+ const handle = (0, import_react_native15.findNodeHandle)(webViewRef.current);
3450
3548
  if (handle == null) return;
3451
- import_react_native14.UIManager.dispatchViewManagerCommand(handle, "injectJavaScript", [script]);
3549
+ import_react_native15.UIManager.dispatchViewManagerCommand(handle, "injectJavaScript", [script]);
3452
3550
  return;
3453
3551
  }
3454
3552
  setScriptToInject(script);
3455
3553
  }, []);
3456
- const buildAndSetInjectionScript = (0, import_react3.useCallback)(() => {
3554
+ const buildAndSetInjectionScript = (0, import_react4.useCallback)(() => {
3457
3555
  if (!craftData || paywallDataSent.current) return;
3458
3556
  paywallDataSent.current = true;
3459
3557
  const script = buildPaywallInjectionScript({
@@ -3464,16 +3562,16 @@ function PaywallWebView({
3464
3562
  });
3465
3563
  injectScript(script);
3466
3564
  }, [craftData, products, primaryProductId, secondaryProductId, injectScript]);
3467
- const processedIdsRef = (0, import_react3.useRef)(/* @__PURE__ */ new Set());
3468
- const dedupeTimersRef = (0, import_react3.useRef)(/* @__PURE__ */ new Map());
3469
- (0, import_react3.useEffect)(() => {
3565
+ const processedIdsRef = (0, import_react4.useRef)(/* @__PURE__ */ new Set());
3566
+ const dedupeTimersRef = (0, import_react4.useRef)(/* @__PURE__ */ new Map());
3567
+ (0, import_react4.useEffect)(() => {
3470
3568
  return () => {
3471
3569
  for (const timerId of dedupeTimersRef.current.values()) clearTimeout(timerId);
3472
3570
  dedupeTimersRef.current.clear();
3473
3571
  processedIdsRef.current.clear();
3474
3572
  };
3475
3573
  }, []);
3476
- const handleParsedMessage = (0, import_react3.useCallback)(
3574
+ const handleParsedMessage = (0, import_react4.useCallback)(
3477
3575
  (message) => {
3478
3576
  const msgId = deriveMessageId(message);
3479
3577
  if (processedIdsRef.current.has(msgId)) return;
@@ -3507,7 +3605,7 @@ function PaywallWebView({
3507
3605
  if (message.payload?.url) {
3508
3606
  const url = message.payload.url;
3509
3607
  if (/^https?:\/\//i.test(url) || /^mailto:/i.test(url) || /^itms-apps:/i.test(url) || /^market:/i.test(url)) {
3510
- void import_react_native14.Linking.openURL(url);
3608
+ void import_react_native15.Linking.openURL(url);
3511
3609
  }
3512
3610
  }
3513
3611
  break;
@@ -3518,7 +3616,7 @@ function PaywallWebView({
3518
3616
  },
3519
3617
  [buildAndSetInjectionScript, onPurchase, onClose, onRestore]
3520
3618
  );
3521
- (0, import_react3.useEffect)(() => {
3619
+ (0, import_react4.useEffect)(() => {
3522
3620
  const emitter = getWebViewEmitter();
3523
3621
  if (!emitter) return;
3524
3622
  const loadEndSub = emitter.addListener("PaywalloWebViewLoadEnd", () => {
@@ -3543,7 +3641,7 @@ function PaywallWebView({
3543
3641
  errorSub.remove();
3544
3642
  };
3545
3643
  }, [buildAndSetInjectionScript, handleParsedMessage]);
3546
- (0, import_react3.useEffect)(() => {
3644
+ (0, import_react4.useEffect)(() => {
3547
3645
  const timer = setTimeout(() => {
3548
3646
  if (!paywallDataSent.current) {
3549
3647
  onErrorRef.current?.({
@@ -3554,13 +3652,13 @@ function PaywallWebView({
3554
3652
  }, 1e4);
3555
3653
  return () => clearTimeout(timer);
3556
3654
  }, []);
3557
- const handleLoadEnd = (0, import_react3.useCallback)(() => {
3655
+ const handleLoadEnd = (0, import_react4.useCallback)(() => {
3558
3656
  if (!loadEndReceived.current) {
3559
3657
  loadEndReceived.current = true;
3560
3658
  buildAndSetInjectionScript();
3561
3659
  }
3562
3660
  }, [buildAndSetInjectionScript]);
3563
- const handleNativeError = (0, import_react3.useCallback)((event) => {
3661
+ const handleNativeError = (0, import_react4.useCallback)((event) => {
3564
3662
  const ne = event.nativeEvent;
3565
3663
  let errorData;
3566
3664
  if (ne["payload"] && typeof ne["payload"] === "object") {
@@ -3576,30 +3674,30 @@ function PaywallWebView({
3576
3674
  }
3577
3675
  onErrorRef.current?.(parseErrorEvent(errorData));
3578
3676
  }, []);
3579
- const handleMessage = (0, import_react3.useCallback)(
3677
+ const handleMessage = (0, import_react4.useCallback)(
3580
3678
  (event) => {
3581
3679
  const message = parseWebViewMessage(event.nativeEvent.data);
3582
3680
  if (message) handleParsedMessage(message);
3583
3681
  },
3584
3682
  [handleParsedMessage]
3585
3683
  );
3586
- (0, import_react3.useEffect)(() => {
3684
+ (0, import_react4.useEffect)(() => {
3587
3685
  if (!paywallDataSent.current) return;
3588
3686
  injectScript(buildPurchaseStateScript(isPurchasing));
3589
3687
  }, [isPurchasing, injectScript]);
3590
- (0, import_react3.useEffect)(() => {
3591
- if (import_react_native14.Platform.OS !== "android") return;
3688
+ (0, import_react4.useEffect)(() => {
3689
+ if (import_react_native15.Platform.OS !== "android") return;
3592
3690
  const onBackPress = () => {
3593
3691
  if (!paywallDataSent.current) return false;
3594
3692
  injectScript(buildBackButtonScript());
3595
3693
  return true;
3596
3694
  };
3597
- const subscription = import_react_native14.BackHandler.addEventListener("hardwareBackPress", onBackPress);
3695
+ const subscription = import_react_native15.BackHandler.addEventListener("hardwareBackPress", onBackPress);
3598
3696
  return () => subscription.remove();
3599
3697
  }, [injectScript]);
3600
3698
  if (!resolvedWebUrl || !NativeWebView) return null;
3601
3699
  const paywallUrl = `${resolvedWebUrl}/paywall/${paywallId}`;
3602
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native14.View, { style: styles.container, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
3700
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native15.View, { style: styles2.container, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3603
3701
  NativeWebView,
3604
3702
  {
3605
3703
  ref: webViewRef,
@@ -3608,24 +3706,24 @@ function PaywallWebView({
3608
3706
  onMessage: handleMessage,
3609
3707
  onLoadEnd: handleLoadEnd,
3610
3708
  onError: handleNativeError,
3611
- style: styles.webview
3709
+ style: styles2.webview
3612
3710
  }
3613
3711
  ) });
3614
3712
  }
3615
- var import_react3, import_react_native14, import_jsx_runtime, NativeWebViewComponent, nativeWebViewLookupFailed, webViewEmitter, styles;
3713
+ var import_react4, import_react_native15, import_jsx_runtime2, NativeWebViewComponent, nativeWebViewLookupFailed, webViewEmitter, styles2;
3616
3714
  var init_PaywallWebView = __esm({
3617
3715
  "src/domains/paywall/PaywallWebView.tsx"() {
3618
3716
  "use strict";
3619
- import_react3 = require("react");
3620
- import_react_native14 = require("react-native");
3717
+ import_react4 = require("react");
3718
+ import_react_native15 = require("react-native");
3621
3719
  init_PaywalloClient();
3622
3720
  init_parseWebViewMessage();
3623
3721
  init_paywallScripts();
3624
- import_jsx_runtime = require("react/jsx-runtime");
3722
+ import_jsx_runtime2 = require("react/jsx-runtime");
3625
3723
  NativeWebViewComponent = null;
3626
3724
  nativeWebViewLookupFailed = false;
3627
3725
  webViewEmitter = null;
3628
- styles = import_react_native14.StyleSheet.create({
3726
+ styles2 = import_react_native15.StyleSheet.create({
3629
3727
  container: { flex: 1 },
3630
3728
  webview: { flex: 1, backgroundColor: "transparent" }
3631
3729
  });
@@ -3760,7 +3858,7 @@ function mapNativePurchase(native) {
3760
3858
  transactionId: native.transactionId,
3761
3859
  transactionDate: native.transactionDate,
3762
3860
  receipt: native.receipt,
3763
- platform: import_react_native15.Platform.OS
3861
+ platform: import_react_native16.Platform.OS
3764
3862
  };
3765
3863
  }
3766
3864
  function normalizeTransactionUpdate(raw) {
@@ -3780,17 +3878,17 @@ function normalizeTransactionUpdate(raw) {
3780
3878
  if (typeof r.currency === "string") update.currency = r.currency;
3781
3879
  return update;
3782
3880
  }
3783
- var import_react_native15, TRANSACTION_UPDATE_EVENT, PaywalloStoreKitNative, nativeStoreKit, NativeStoreKit;
3881
+ var import_react_native16, TRANSACTION_UPDATE_EVENT, PaywalloStoreKitNative, nativeStoreKit, NativeStoreKit;
3784
3882
  var init_NativeStoreKit = __esm({
3785
3883
  "src/domains/iap/NativeStoreKit.ts"() {
3786
3884
  "use strict";
3787
- import_react_native15 = require("react-native");
3885
+ import_react_native16 = require("react-native");
3788
3886
  init_PaywalloClient();
3789
3887
  init_uuid();
3790
3888
  init_IdentityManager();
3791
3889
  init_PurchaseError();
3792
3890
  TRANSACTION_UPDATE_EVENT = "PaywalloTransactionUpdate";
3793
- PaywalloStoreKitNative = import_react_native15.NativeModules.PaywalloStoreKit ?? null;
3891
+ PaywalloStoreKitNative = import_react_native16.NativeModules.PaywalloStoreKit ?? null;
3794
3892
  nativeStoreKit = {
3795
3893
  isAvailable: isAvailable3,
3796
3894
  async getProducts(productIds) {
@@ -3859,7 +3957,7 @@ var init_NativeStoreKit = __esm({
3859
3957
  subscribeToTransactionUpdates(listener) {
3860
3958
  if (!PaywalloStoreKitNative) return null;
3861
3959
  try {
3862
- const EmitterCtor = import_react_native15.NativeEventEmitter;
3960
+ const EmitterCtor = import_react_native16.NativeEventEmitter;
3863
3961
  const emitter = new EmitterCtor(PaywalloStoreKitNative);
3864
3962
  const sub = emitter.addListener(TRANSACTION_UPDATE_EVENT, (payload) => {
3865
3963
  const update = normalizeTransactionUpdate(payload);
@@ -3919,6 +4017,32 @@ var init_IAPTransactionEmitter = __esm({
3919
4017
  if (this.getDebug()) console.log("[Paywallo PURCHASE] transaction event emission failed", err);
3920
4018
  }
3921
4019
  }
4020
+ /**
4021
+ * Emits `checkout_started` quando o usuário inicia a compra (toca em comprar),
4022
+ * antes do StoreKit/Billing abrir — conta mesmo que a sheet nativa seja cancelada.
4023
+ * Vira InitiateCheckout no Meta/TikTok. Fire-and-forget — uma falha de tracking
4024
+ * nunca pode travar a compra.
4025
+ */
4026
+ async emitCheckoutStarted(productId) {
4027
+ try {
4028
+ const apiClient = this.getApiClientFn();
4029
+ if (!apiClient) return;
4030
+ const distinctId = PaywalloClient.getDistinctId();
4031
+ if (!distinctId) return;
4032
+ const product = this.getProductFromCache(productId);
4033
+ const properties = {
4034
+ product_id: productId,
4035
+ amount: product?.priceValue ?? 0,
4036
+ currency: product?.currency ?? "USD"
4037
+ };
4038
+ await apiClient.trackEvent("checkout_started", distinctId, properties, void 0, {
4039
+ priority: "critical"
4040
+ });
4041
+ if (this.getDebug()) console.log("[Paywallo PURCHASE] emitted checkout_started", productId);
4042
+ } catch (err) {
4043
+ if (this.getDebug()) console.log("[Paywallo PURCHASE] checkout_started emission failed", err);
4044
+ }
4045
+ }
3922
4046
  /**
3923
4047
  * Forwards a native transaction update to the EventBatcher.
3924
4048
  * Public so tests can exercise the wiring without a fake NativeEventEmitter.
@@ -3941,9 +4065,9 @@ var init_IAPTransactionEmitter = __esm({
3941
4065
  product_id: update.productId
3942
4066
  };
3943
4067
  if (update.type !== "canceled") {
3944
- const cached = this.getProductFromCache(update.productId);
3945
- const amount = update.amount ?? cached?.priceValue;
3946
- const currency = update.currency ?? cached?.currency;
4068
+ const cached2 = this.getProductFromCache(update.productId);
4069
+ const amount = update.amount ?? cached2?.priceValue;
4070
+ const currency = update.currency ?? cached2?.currency;
3947
4071
  if (typeof amount === "number" && amount > 0) properties.amount = amount;
3948
4072
  if (currency) properties.currency = currency;
3949
4073
  }
@@ -4122,10 +4246,10 @@ var init_OfflineQueueStorage = __esm({
4122
4246
  init_NativeStorage();
4123
4247
  init_QueueJournal();
4124
4248
  OfflineQueueStorage = class {
4125
- constructor(config, emit, log) {
4249
+ constructor(config, emit, log2) {
4126
4250
  this.config = config;
4127
4251
  this.emit = emit;
4128
- this.log = log;
4252
+ this.log = log2;
4129
4253
  }
4130
4254
  async loadFromStorage(queue) {
4131
4255
  const { merged, recovered, hadJournal } = await mergeJournalIntoMain(
@@ -4489,11 +4613,11 @@ var init_types2 = __esm({
4489
4613
  });
4490
4614
 
4491
4615
  // src/core/network/NetworkMonitor.ts
4492
- var import_react_native16, NetworkMonitorClass, networkMonitor;
4616
+ var import_react_native17, NetworkMonitorClass, networkMonitor;
4493
4617
  var init_NetworkMonitor = __esm({
4494
4618
  "src/core/network/NetworkMonitor.ts"() {
4495
4619
  "use strict";
4496
- import_react_native16 = require("react-native");
4620
+ import_react_native17 = require("react-native");
4497
4621
  init_types2();
4498
4622
  NetworkMonitorClass = class {
4499
4623
  constructor() {
@@ -4522,7 +4646,7 @@ var init_NetworkMonitor = __esm({
4522
4646
  }, this.config.checkInterval);
4523
4647
  }
4524
4648
  setupAppStateListener() {
4525
- this.appStateSubscription = import_react_native16.AppState.addEventListener("change", (state) => {
4649
+ this.appStateSubscription = import_react_native17.AppState.addEventListener("change", (state) => {
4526
4650
  if (state === "active") {
4527
4651
  if (!this.checkIntervalId) {
4528
4652
  this.checkIntervalId = setInterval(() => {
@@ -4744,6 +4868,7 @@ var init_QueueProcessor = __esm({
4744
4868
  for (const item of batch) {
4745
4869
  retryIds.add(item.id);
4746
4870
  }
4871
+ console.error("[Paywallo:QUEUE] event batch network error \u2014 items will retry", { size: batch.length, error: error instanceof Error ? error.message : String(error) });
4747
4872
  this.log("Event batch threw network error - will retry", {
4748
4873
  size: batch.length,
4749
4874
  error: error instanceof Error ? error.message : String(error)
@@ -4761,14 +4886,25 @@ var init_QueueProcessor = __esm({
4761
4886
  await offlineQueue.removeItem(item.id);
4762
4887
  failed++;
4763
4888
  }
4889
+ const responseError = typeof response.data === "object" && response.data !== null && "error" in response.data ? response.data.error : void 0;
4890
+ const isDuplicate = typeof responseError === "string" && responseError.toUpperCase().includes("DUPLICATE");
4891
+ if (isDuplicate) {
4892
+ if (this.config.debug) {
4893
+ console.log("[Paywallo:QUEUE] event batch deduplicated by server \u2014 items dropped (benign)", { status: response.status, size: batch.length, error: responseError });
4894
+ }
4895
+ } else {
4896
+ console.error("[Paywallo:QUEUE] event batch failed PERMANENTLY (4xx) \u2014 items dropped", { status: response.status, size: batch.length, responseBody: typeof response.data === "string" ? response.data.slice(0, 1e3) : JSON.stringify(response.data).slice(0, 1e3), firstItemBody: typeof batch[0].body === "string" ? batch[0].body.slice(0, 1e3) : JSON.stringify(batch[0].body).slice(0, 1e3) });
4897
+ }
4764
4898
  this.log("Event batch failed permanently (4xx) - removing from queue", {
4765
4899
  status: response.status,
4766
- size: batch.length
4900
+ size: batch.length,
4901
+ isDuplicate
4767
4902
  });
4768
4903
  } else {
4769
4904
  for (const item of batch) {
4770
4905
  retryIds.add(item.id);
4771
4906
  }
4907
+ console.error("[Paywallo:QUEUE] event batch server error (5xx) \u2014 will retry", { status: response.status, size: batch.length });
4772
4908
  this.log("Event batch failed with server error - will retry", {
4773
4909
  status: response.status,
4774
4910
  size: batch.length
@@ -4798,6 +4934,7 @@ var init_QueueProcessor = __esm({
4798
4934
  skipRetry: false
4799
4935
  });
4800
4936
  } catch (error) {
4937
+ console.error("[Paywallo:QUEUE] individual item network error \u2014 will retry", { id: item.id, url: item.url, error: error instanceof Error ? error.message : String(error) });
4801
4938
  this.log("Request threw network error - will retry", {
4802
4939
  id: item.id,
4803
4940
  error: error instanceof Error ? error.message : String(error)
@@ -4878,11 +5015,11 @@ var init_queue = __esm({
4878
5015
  });
4879
5016
 
4880
5017
  // src/domains/iap/IAPValidator.ts
4881
- var import_react_native17, IAPValidator;
5018
+ var import_react_native18, IAPValidator;
4882
5019
  var init_IAPValidator = __esm({
4883
5020
  "src/domains/iap/IAPValidator.ts"() {
4884
5021
  "use strict";
4885
- import_react_native17 = require("react-native");
5022
+ import_react_native18 = require("react-native");
4886
5023
  init_PaywalloClient();
4887
5024
  init_queue();
4888
5025
  init_PurchaseError();
@@ -4908,7 +5045,7 @@ var init_IAPValidator = __esm({
4908
5045
  async validateWithServer(purchase, productId, options) {
4909
5046
  const apiClient = this.getApiClientFn();
4910
5047
  const product = this.getProductFromCache(productId);
4911
- const platform = import_react_native17.Platform.OS;
5048
+ const platform = import_react_native18.Platform.OS;
4912
5049
  const distinctId = PaywalloClient.getDistinctId();
4913
5050
  return this.validateWithRetry(
4914
5051
  () => apiClient.validatePurchase(
@@ -4943,7 +5080,7 @@ var init_IAPValidator = __esm({
4943
5080
  try {
4944
5081
  const apiClient = this.getApiClientFn();
4945
5082
  const product = this.getProductFromCache(productId);
4946
- const platform = import_react_native17.Platform.OS;
5083
+ const platform = import_react_native18.Platform.OS;
4947
5084
  const distinctId = PaywalloClient.getDistinctId();
4948
5085
  const appKey = apiClient.getAppKey();
4949
5086
  const body = {
@@ -4983,11 +5120,11 @@ function getIAPService() {
4983
5120
  }
4984
5121
  return _instance;
4985
5122
  }
4986
- var import_react_native18, IAPService, _instance;
5123
+ var import_react_native19, IAPService, _instance;
4987
5124
  var init_IAPService = __esm({
4988
5125
  "src/domains/iap/IAPService.ts"() {
4989
5126
  "use strict";
4990
- import_react_native18 = require("react-native");
5127
+ import_react_native19 = require("react-native");
4991
5128
  init_PaywalloClient();
4992
5129
  init_IAPTransactionEmitter();
4993
5130
  init_IAPValidator();
@@ -5094,6 +5231,7 @@ var init_IAPService = __esm({
5094
5231
  this.purchaseInFlight = productId;
5095
5232
  if (this.debug) console.log(`[Paywallo PURCHASE] starting ${productId}`);
5096
5233
  try {
5234
+ void this.emitter.emitCheckoutStarted(productId);
5097
5235
  const purchase = await nativeStoreKit.purchase(productId);
5098
5236
  if (!purchase) {
5099
5237
  if (this.debug) console.log(`[Paywallo PURCHASE] ${productId} cancelled`);
@@ -5136,7 +5274,7 @@ var init_IAPService = __esm({
5136
5274
  const transactions = await nativeStoreKit.getActiveTransactions();
5137
5275
  const apiClient = this.getApiClient();
5138
5276
  const distinctId = PaywalloClient.getDistinctId();
5139
- const platform = import_react_native18.Platform.OS;
5277
+ const platform = import_react_native19.Platform.OS;
5140
5278
  const results = await Promise.allSettled(
5141
5279
  transactions.map((tx) => {
5142
5280
  const product = this.productsCache.get(tx.productId);
@@ -5194,11 +5332,11 @@ var init_IAPService = __esm({
5194
5332
 
5195
5333
  // src/domains/paywall/usePaywallTracking.ts
5196
5334
  function usePaywallTracking() {
5197
- const trackClosed = (0, import_react4.useCallback)(async (opts) => {
5335
+ const trackClosed = (0, import_react5.useCallback)(async (opts) => {
5198
5336
  await emitPaywallClosed(opts);
5199
5337
  await stopHeartbeat();
5200
5338
  }, []);
5201
- const trackDismissed = (0, import_react4.useCallback)(async (opts) => {
5339
+ const trackDismissed = (0, import_react5.useCallback)(async (opts) => {
5202
5340
  const presentedAt = Date.now() - Math.max(0, opts.duration_ms);
5203
5341
  await trackClosed({
5204
5342
  placement: opts.placement,
@@ -5210,17 +5348,17 @@ function usePaywallTracking() {
5210
5348
  ...opts.variantId && { variantId: opts.variantId }
5211
5349
  });
5212
5350
  }, [trackClosed]);
5213
- const trackProductSelected = (0, import_react4.useCallback)((_opts) => {
5351
+ const trackProductSelected = (0, import_react5.useCallback)((_opts) => {
5214
5352
  }, []);
5215
- const trackPurchased = (0, import_react4.useCallback)(async (_opts) => {
5353
+ const trackPurchased = (0, import_react5.useCallback)(async (_opts) => {
5216
5354
  }, []);
5217
5355
  return { trackClosed, trackDismissed, trackProductSelected, trackPurchased };
5218
5356
  }
5219
- var import_react4;
5357
+ var import_react5;
5220
5358
  var init_usePaywallTracking = __esm({
5221
5359
  "src/domains/paywall/usePaywallTracking.ts"() {
5222
5360
  "use strict";
5223
- import_react4 = require("react");
5361
+ import_react5 = require("react");
5224
5362
  init_emitPaywallClosed();
5225
5363
  init_paywallHeartbeat();
5226
5364
  }
@@ -5228,19 +5366,19 @@ var init_usePaywallTracking = __esm({
5228
5366
 
5229
5367
  // src/domains/paywall/usePaywallActions.ts
5230
5368
  function usePaywallActions(placement, paywallConfig, onResult, variantKey, campaignId, openAnim, variantId) {
5231
- const [isPurchasing, setIsPurchasing] = (0, import_react5.useState)(false);
5232
- const isPurchasingRef = (0, import_react5.useRef)(false);
5233
- const [isClosing, setIsClosing] = (0, import_react5.useState)(false);
5234
- const slideAnim = (0, import_react5.useRef)(new import_react_native19.Animated.Value(0)).current;
5235
- const presentedAtRef = (0, import_react5.useRef)(Date.now());
5236
- const closedRef = (0, import_react5.useRef)(false);
5369
+ const [isPurchasing, setIsPurchasing] = (0, import_react6.useState)(false);
5370
+ const isPurchasingRef = (0, import_react6.useRef)(false);
5371
+ const [isClosing, setIsClosing] = (0, import_react6.useState)(false);
5372
+ const slideAnim = (0, import_react6.useRef)(new import_react_native20.Animated.Value(0)).current;
5373
+ const presentedAtRef = (0, import_react6.useRef)(Date.now());
5374
+ const closedRef = (0, import_react6.useRef)(false);
5237
5375
  const { trackDismissed, trackProductSelected, trackPurchased } = usePaywallTracking();
5238
- const closeWithReason = (0, import_react5.useCallback)((reason, resultOverride) => {
5376
+ const closeWithReason = (0, import_react6.useCallback)((reason, resultOverride) => {
5239
5377
  if (closedRef.current) return;
5240
5378
  closedRef.current = true;
5241
5379
  const animTarget = openAnim ?? slideAnim;
5242
- import_react_native19.Animated.timing(animTarget, {
5243
- toValue: import_react_native19.Dimensions.get("window").height,
5380
+ import_react_native20.Animated.timing(animTarget, {
5381
+ toValue: import_react_native20.Dimensions.get("window").height,
5244
5382
  duration: 350,
5245
5383
  useNativeDriver: true
5246
5384
  }).start(() => {
@@ -5254,13 +5392,13 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
5254
5392
  onResult(resultOverride ?? { presented: true, purchased: false, cancelled: true, restored: false });
5255
5393
  });
5256
5394
  }, [openAnim, slideAnim, paywallConfig, placement, trackDismissed, variantKey, campaignId, variantId, onResult]);
5257
- const handleClose = (0, import_react5.useCallback)(() => {
5395
+ const handleClose = (0, import_react6.useCallback)(() => {
5258
5396
  if (isClosing) return;
5259
5397
  setIsClosing(true);
5260
5398
  closeWithReason("dismissed");
5261
5399
  }, [isClosing, closeWithReason]);
5262
- (0, import_react5.useEffect)(() => {
5263
- const sub = import_react_native19.AppState.addEventListener("change", (nextState) => {
5400
+ (0, import_react6.useEffect)(() => {
5401
+ const sub = import_react_native20.AppState.addEventListener("change", (nextState) => {
5264
5402
  if (isPurchasingRef.current) return;
5265
5403
  if (nextState === "background" || nextState === "inactive") {
5266
5404
  closeWithReason("backgrounded");
@@ -5268,13 +5406,13 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
5268
5406
  });
5269
5407
  return () => sub.remove();
5270
5408
  }, [closeWithReason]);
5271
- (0, import_react5.useEffect)(() => {
5409
+ (0, import_react6.useEffect)(() => {
5272
5410
  const timer = setTimeout(() => {
5273
5411
  closeWithReason("timeout");
5274
5412
  }, PAYWALL_TIMEOUT_MS);
5275
5413
  return () => clearTimeout(timer);
5276
5414
  }, [closeWithReason]);
5277
- const handlePurchase = (0, import_react5.useCallback)(async (productId) => {
5415
+ const handlePurchase = (0, import_react6.useCallback)(async (productId) => {
5278
5416
  if (isPurchasing) return;
5279
5417
  setIsPurchasing(true);
5280
5418
  isPurchasingRef.current = true;
@@ -5304,7 +5442,7 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
5304
5442
  setIsPurchasing(false);
5305
5443
  }
5306
5444
  }, [isPurchasing, onResult, paywallConfig, placement, trackDismissed, trackProductSelected, trackPurchased, variantKey, campaignId, variantId]);
5307
- const handleRestore = (0, import_react5.useCallback)(async () => {
5445
+ const handleRestore = (0, import_react6.useCallback)(async () => {
5308
5446
  if (isPurchasing) return;
5309
5447
  setIsPurchasing(true);
5310
5448
  isPurchasingRef.current = true;
@@ -5328,12 +5466,12 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
5328
5466
  }, [isPurchasing, onResult, paywallConfig, placement, trackDismissed, variantKey, campaignId, variantId]);
5329
5467
  return { isPurchasing, slideAnim, handleClose, handlePurchase, handleRestore };
5330
5468
  }
5331
- var import_react5, import_react_native19, PAYWALL_TIMEOUT_MS;
5469
+ var import_react6, import_react_native20, PAYWALL_TIMEOUT_MS;
5332
5470
  var init_usePaywallActions = __esm({
5333
5471
  "src/domains/paywall/usePaywallActions.ts"() {
5334
5472
  "use strict";
5335
- import_react5 = require("react");
5336
- import_react_native19 = require("react-native");
5473
+ import_react6 = require("react");
5474
+ import_react_native20 = require("react-native");
5337
5475
  init_IAPService();
5338
5476
  init_usePaywallTracking();
5339
5477
  PAYWALL_TIMEOUT_MS = 30 * 60 * 1e3;
@@ -5342,15 +5480,15 @@ var init_usePaywallActions = __esm({
5342
5480
 
5343
5481
  // src/domains/paywall/usePaywallLoader.ts
5344
5482
  function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts, variantKey, campaignId, variantId) {
5345
- const [error, setError] = (0, import_react6.useState)(null);
5346
- const [paywallConfig, setPaywallConfig] = (0, import_react6.useState)(null);
5347
- const [products, setProducts] = (0, import_react6.useState)(/* @__PURE__ */ new Map());
5348
- const preloadedConfigRef = (0, import_react6.useRef)(preloadedConfig);
5483
+ const [error, setError] = (0, import_react7.useState)(null);
5484
+ const [paywallConfig, setPaywallConfig] = (0, import_react7.useState)(null);
5485
+ const [products, setProducts] = (0, import_react7.useState)(/* @__PURE__ */ new Map());
5486
+ const preloadedConfigRef = (0, import_react7.useRef)(preloadedConfig);
5349
5487
  preloadedConfigRef.current = preloadedConfig;
5350
- const preloadedProductsRef = (0, import_react6.useRef)(preloadedProducts);
5488
+ const preloadedProductsRef = (0, import_react7.useRef)(preloadedProducts);
5351
5489
  preloadedProductsRef.current = preloadedProducts;
5352
- const trackingDoneRef = (0, import_react6.useRef)(false);
5353
- (0, import_react6.useEffect)(() => {
5490
+ const trackingDoneRef = (0, import_react7.useRef)(false);
5491
+ (0, import_react7.useEffect)(() => {
5354
5492
  if (!visible) {
5355
5493
  trackingDoneRef.current = false;
5356
5494
  return;
@@ -5435,11 +5573,11 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
5435
5573
  }, [placement, visible, variantKey, campaignId, variantId]);
5436
5574
  return { paywallConfig, products, error };
5437
5575
  }
5438
- var import_react6;
5576
+ var import_react7;
5439
5577
  var init_usePaywallLoader = __esm({
5440
5578
  "src/domains/paywall/usePaywallLoader.ts"() {
5441
5579
  "use strict";
5442
- import_react6 = require("react");
5580
+ import_react7 = require("react");
5443
5581
  init_PaywalloClient();
5444
5582
  init_IAPService();
5445
5583
  init_PaywallError();
@@ -5452,11 +5590,11 @@ function ErrorFallback({ description, onRetry, onClose }) {
5452
5590
  const title = overrides?.title ?? getSdkString("paywallErrorTitle");
5453
5591
  const retryLabel = overrides?.retry ?? getSdkString("paywallRetryButton");
5454
5592
  const closeLabel = overrides?.close ?? getSdkString("paywallCloseButton");
5455
- 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: [
5456
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native20.Text, { style: styles2.fallbackTitle, children: title }),
5457
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native20.Text, { style: styles2.fallbackDescription, children: description }),
5458
- /* @__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 }) }),
5459
- /* @__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 }) })
5593
+ 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: [
5594
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native21.Text, { style: styles3.fallbackTitle, children: title }),
5595
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native21.Text, { style: styles3.fallbackDescription, children: description }),
5596
+ /* @__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 }) }),
5597
+ /* @__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 }) })
5460
5598
  ] }) });
5461
5599
  }
5462
5600
  function PaywallModal({
@@ -5478,18 +5616,20 @@ function PaywallModal({
5478
5616
  campaignId,
5479
5617
  variantId
5480
5618
  );
5481
- const screenHeight = import_react_native20.Dimensions.get("window").height;
5482
- const openAnim = (0, import_react7.useRef)(new import_react_native20.Animated.Value(screenHeight)).current;
5483
- const [modalVisible, setModalVisible] = (0, import_react7.useState)(false);
5484
- const handleResult = (0, import_react7.useCallback)((result) => {
5619
+ const screenHeight = import_react_native21.Dimensions.get("window").height;
5620
+ const openAnim = (0, import_react8.useRef)(new import_react_native21.Animated.Value(screenHeight)).current;
5621
+ const [modalVisible, setModalVisible] = (0, import_react8.useState)(false);
5622
+ const handleResult = (0, import_react8.useCallback)((result) => {
5485
5623
  setModalVisible(false);
5486
5624
  onResult(result);
5487
5625
  }, [onResult]);
5488
5626
  const { isPurchasing, slideAnim, handleClose, handlePurchase, handleRestore } = usePaywallActions(placement, paywallConfig, handleResult, variantKey, campaignId, openAnim, variantId);
5489
- const [webViewError, setWebViewError] = (0, import_react7.useState)(null);
5490
- const [retryCount, setRetryCount] = (0, import_react7.useState)(0);
5491
- const retryCountRef = (0, import_react7.useRef)(0);
5492
- const handleWebViewError = (0, import_react7.useCallback)(
5627
+ const [webViewError, setWebViewError] = (0, import_react8.useState)(null);
5628
+ const [retryCount, setRetryCount] = (0, import_react8.useState)(0);
5629
+ const [showSkeleton, setShowSkeleton] = (0, import_react8.useState)(true);
5630
+ const skeletonOpacity = (0, import_react8.useRef)(new import_react_native21.Animated.Value(1)).current;
5631
+ const retryCountRef = (0, import_react8.useRef)(0);
5632
+ const handleWebViewError = (0, import_react8.useCallback)(
5493
5633
  (err) => {
5494
5634
  if (retryCountRef.current >= 1) {
5495
5635
  setWebViewError({ code: err.code, description: err.description });
@@ -5500,26 +5640,44 @@ function PaywallModal({
5500
5640
  },
5501
5641
  []
5502
5642
  );
5503
- const handleRetry = (0, import_react7.useCallback)(() => {
5643
+ const handleRetry = (0, import_react8.useCallback)(() => {
5504
5644
  retryCountRef.current = 0;
5505
5645
  setWebViewError(null);
5506
5646
  setRetryCount(0);
5507
- }, []);
5508
- (0, import_react7.useEffect)(() => {
5647
+ setShowSkeleton(true);
5648
+ skeletonOpacity.setValue(1);
5649
+ }, [skeletonOpacity]);
5650
+ const handleWebViewReady = (0, import_react8.useCallback)(() => {
5651
+ import_react_native21.Animated.timing(skeletonOpacity, {
5652
+ toValue: 0,
5653
+ duration: 250,
5654
+ easing: import_react_native21.Easing.out(import_react_native21.Easing.cubic),
5655
+ useNativeDriver: true
5656
+ }).start(() => setShowSkeleton(false));
5657
+ }, [skeletonOpacity]);
5658
+ (0, import_react8.useEffect)(() => {
5659
+ if (!visible || !showSkeleton) return;
5660
+ const timer = setTimeout(handleWebViewReady, 8e3);
5661
+ return () => clearTimeout(timer);
5662
+ }, [visible, showSkeleton, handleWebViewReady]);
5663
+ (0, import_react8.useEffect)(() => {
5509
5664
  if (visible) {
5510
5665
  setModalVisible(true);
5511
- openAnim.setValue(import_react_native20.Dimensions.get("window").height);
5512
- import_react_native20.Animated.timing(openAnim, {
5666
+ setShowSkeleton(true);
5667
+ skeletonOpacity.setValue(1);
5668
+ openAnim.setValue(import_react_native21.Dimensions.get("window").height);
5669
+ import_react_native21.Animated.timing(openAnim, {
5513
5670
  toValue: 0,
5514
5671
  duration: 550,
5515
- easing: import_react_native20.Easing.out(import_react_native20.Easing.cubic),
5672
+ easing: import_react_native21.Easing.out(import_react_native21.Easing.cubic),
5516
5673
  useNativeDriver: true
5517
5674
  }).start();
5518
5675
  }
5519
- }, [visible, openAnim]);
5676
+ }, [visible, openAnim, skeletonOpacity]);
5520
5677
  if (!modalVisible && !visible) return null;
5521
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
5522
- import_react_native20.Modal,
5678
+ const skeletonBackground = paywallConfig && "craftData" in paywallConfig.config ? extractFirstPageBackground(paywallConfig.config.craftData) : "#FFFFFF";
5679
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
5680
+ import_react_native21.Modal,
5523
5681
  {
5524
5682
  visible: modalVisible,
5525
5683
  animationType: "none",
@@ -5527,16 +5685,16 @@ function PaywallModal({
5527
5685
  onRequestClose: handleClose,
5528
5686
  transparent: true,
5529
5687
  statusBarTranslucent: true,
5530
- 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: [
5531
- 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 }) }),
5532
- !error && paywallConfig && "craftData" in paywallConfig.config && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: webViewError ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
5688
+ 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: [
5689
+ 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 }) }),
5690
+ !error && paywallConfig && "craftData" in paywallConfig.config && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: webViewError ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
5533
5691
  ErrorFallback,
5534
5692
  {
5535
5693
  description: webViewError.description,
5536
5694
  onRetry: handleRetry,
5537
5695
  onClose: handleClose
5538
5696
  }
5539
- ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
5697
+ ) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
5540
5698
  PaywallWebView,
5541
5699
  {
5542
5700
  paywallId: paywallConfig.id,
@@ -5545,30 +5703,41 @@ function PaywallModal({
5545
5703
  primaryProductId: paywallConfig.primaryProductId,
5546
5704
  secondaryProductId: paywallConfig.secondaryProductId,
5547
5705
  isPurchasing,
5706
+ onReady: handleWebViewReady,
5548
5707
  onClose: handleClose,
5549
5708
  onPurchase: handlePurchase,
5550
5709
  onRestore: handleRestore,
5551
5710
  onError: handleWebViewError
5552
5711
  },
5553
5712
  retryCount
5554
- ) })
5713
+ ) }),
5714
+ showSkeleton && !error && !webViewError && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
5715
+ import_react_native21.Animated.View,
5716
+ {
5717
+ style: [import_react_native21.StyleSheet.absoluteFill, { opacity: skeletonOpacity }],
5718
+ pointerEvents: "none",
5719
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PaywallSkeleton, { backgroundColor: skeletonBackground })
5720
+ }
5721
+ )
5555
5722
  ] }) }) })
5556
5723
  }
5557
5724
  );
5558
5725
  }
5559
- var import_react7, import_react_native20, import_jsx_runtime2, styles2;
5726
+ var import_react8, import_react_native21, import_jsx_runtime3, styles3;
5560
5727
  var init_PaywallModal = __esm({
5561
5728
  "src/domains/paywall/PaywallModal.tsx"() {
5562
5729
  "use strict";
5563
- import_react7 = require("react");
5564
- import_react_native20 = require("react-native");
5730
+ import_react8 = require("react");
5731
+ import_react_native21 = require("react-native");
5565
5732
  init_PaywalloClient();
5566
5733
  init_localization();
5734
+ init_extractFirstPageBackground();
5735
+ init_PaywallSkeleton();
5567
5736
  init_PaywallWebView();
5568
5737
  init_usePaywallActions();
5569
5738
  init_usePaywallLoader();
5570
- import_jsx_runtime2 = require("react/jsx-runtime");
5571
- styles2 = import_react_native20.StyleSheet.create({
5739
+ import_jsx_runtime3 = require("react/jsx-runtime");
5740
+ styles3 = import_react_native21.StyleSheet.create({
5572
5741
  overlay: { flex: 1, backgroundColor: "rgba(0,0,0,0.3)" },
5573
5742
  animatedContainer: { flex: 1, backgroundColor: "#fff" },
5574
5743
  container: { flex: 1, backgroundColor: "#fff" },
@@ -5624,12 +5793,175 @@ var init_PaywallModal = __esm({
5624
5793
  }
5625
5794
  });
5626
5795
 
5796
+ // src/domains/paywall/PaywallPreloadService.ts
5797
+ var PRELOAD_TTL_MS, STALE_THRESHOLD, PaywallPreloadService, paywallPreloadService;
5798
+ var init_PaywallPreloadService = __esm({
5799
+ "src/domains/paywall/PaywallPreloadService.ts"() {
5800
+ "use strict";
5801
+ init_PaywalloClient();
5802
+ init_IAPService();
5803
+ init_PaywallError();
5804
+ PRELOAD_TTL_MS = 5 * 60 * 1e3;
5805
+ STALE_THRESHOLD = 0.75;
5806
+ PaywallPreloadService = class {
5807
+ constructor() {
5808
+ this.preloaded = /* @__PURE__ */ new Map();
5809
+ this.activePreloads = /* @__PURE__ */ new Map();
5810
+ this.apiClientRef = null;
5811
+ }
5812
+ async preload(apiClient, placement) {
5813
+ this.apiClientRef = apiClient;
5814
+ const existing = this.activePreloads.get(placement);
5815
+ if (existing) return existing;
5816
+ const promise = this.doPreload(apiClient, placement);
5817
+ this.activePreloads.set(placement, promise);
5818
+ try {
5819
+ return await promise;
5820
+ } finally {
5821
+ this.activePreloads.delete(placement);
5822
+ }
5823
+ }
5824
+ async preloadMany(apiClient, placements) {
5825
+ this.apiClientRef = apiClient;
5826
+ if (placements.length === 0) return;
5827
+ const debug = PaywalloClient.getConfig()?.debug;
5828
+ if (debug) console.log(`[Paywallo PRELOAD] paywalls: ${placements.length} placements`);
5829
+ for (let i = 0; i < placements.length; i++) {
5830
+ if (i > 0) await new Promise((r) => setTimeout(r, 200));
5831
+ void this.preload(apiClient, placements[i]).catch((err) => {
5832
+ if (debug) console.log(`[Paywallo PRELOAD] paywall failed ${placements[i]}:`, err);
5833
+ });
5834
+ }
5835
+ }
5836
+ getPreloaded(placement) {
5837
+ const cached2 = this.preloaded.get(placement);
5838
+ if (!cached2) return null;
5839
+ const age = Date.now() - cached2.preloadedAt;
5840
+ if (age >= PRELOAD_TTL_MS) {
5841
+ this.preloaded.delete(placement);
5842
+ return null;
5843
+ }
5844
+ if (age >= PRELOAD_TTL_MS * STALE_THRESHOLD && this.apiClientRef) {
5845
+ void this.preload(this.apiClientRef, placement).catch(() => {
5846
+ });
5847
+ }
5848
+ return { config: cached2.config, products: new Map(cached2.products) };
5849
+ }
5850
+ isPreloaded(placement) {
5851
+ return this.getPreloaded(placement) !== null;
5852
+ }
5853
+ clear() {
5854
+ this.preloaded.clear();
5855
+ this.activePreloads.clear();
5856
+ }
5857
+ async doPreload(apiClient, placement) {
5858
+ try {
5859
+ const config = await apiClient.getPaywall(placement);
5860
+ if (!config) {
5861
+ return {
5862
+ success: false,
5863
+ error: new PaywallError(
5864
+ PAYWALL_ERROR_CODES.NOT_FOUND,
5865
+ `Paywall not found for placement: ${placement}`
5866
+ )
5867
+ };
5868
+ }
5869
+ const products = await this.loadProductsFor(config);
5870
+ this.preloaded.set(placement, { config, products, preloadedAt: Date.now() });
5871
+ if (PaywalloClient.getConfig()?.debug) {
5872
+ console.log(`[Paywallo PRELOAD] paywall cached \u2014 placement: ${placement}`);
5873
+ }
5874
+ return { success: true };
5875
+ } catch (error) {
5876
+ return {
5877
+ success: false,
5878
+ error: error instanceof Error ? error : new PaywallError(PAYWALL_ERROR_CODES.LOAD_FAILED, String(error))
5879
+ };
5880
+ }
5881
+ }
5882
+ async loadProductsFor(config) {
5883
+ const productIds = [];
5884
+ if (config.primaryProductId) productIds.push(config.primaryProductId);
5885
+ if (config.secondaryProductId) productIds.push(config.secondaryProductId);
5886
+ if (productIds.length === 0) return /* @__PURE__ */ new Map();
5887
+ try {
5888
+ const loaded = await getIAPService().loadProducts(productIds);
5889
+ const map = /* @__PURE__ */ new Map();
5890
+ for (const p of loaded) map.set(p.productId, p);
5891
+ return map;
5892
+ } catch (err) {
5893
+ if (PaywalloClient.getConfig()?.debug) {
5894
+ console.log("[Paywallo PRELOAD] products failed, caching config only:", err);
5895
+ }
5896
+ return /* @__PURE__ */ new Map();
5897
+ }
5898
+ }
5899
+ };
5900
+ paywallPreloadService = new PaywallPreloadService();
5901
+ }
5902
+ });
5903
+
5904
+ // src/domains/paywall/PaywallWebViewPrewarmer.tsx
5905
+ function getNativeWebView2() {
5906
+ if (NativeWebViewComponent2) return NativeWebViewComponent2;
5907
+ if (nativeLookupFailed) return null;
5908
+ try {
5909
+ NativeWebViewComponent2 = (0, import_react_native22.requireNativeComponent)("PaywalloWebView");
5910
+ return NativeWebViewComponent2;
5911
+ } catch {
5912
+ nativeLookupFailed = true;
5913
+ return null;
5914
+ }
5915
+ }
5916
+ function PaywallWebViewPrewarmer({
5917
+ webUrl,
5918
+ durationMs = DEFAULT_DURATION_MS
5919
+ }) {
5920
+ const NativeWebView = (0, import_react9.useMemo)(() => getNativeWebView2(), []);
5921
+ const [mounted, setMounted] = (0, import_react9.useState)(true);
5922
+ (0, import_react9.useEffect)(() => {
5923
+ if (!NativeWebView) return;
5924
+ const timer = setTimeout(() => setMounted(false), durationMs);
5925
+ return () => clearTimeout(timer);
5926
+ }, [NativeWebView, durationMs]);
5927
+ if (!NativeWebView || !mounted) return null;
5928
+ const preheatUrl = `${webUrl}/paywall/preheat`;
5929
+ if (PaywalloClient.getConfig()?.debug) {
5930
+ console.log(`[Paywallo PREWARM] mounting hidden WebView: ${preheatUrl}`);
5931
+ }
5932
+ 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 }) });
5933
+ }
5934
+ var import_react9, import_react_native22, import_jsx_runtime4, NativeWebViewComponent2, nativeLookupFailed, DEFAULT_DURATION_MS, styles4;
5935
+ var init_PaywallWebViewPrewarmer = __esm({
5936
+ "src/domains/paywall/PaywallWebViewPrewarmer.tsx"() {
5937
+ "use strict";
5938
+ import_react9 = require("react");
5939
+ import_react_native22 = require("react-native");
5940
+ init_PaywalloClient();
5941
+ import_jsx_runtime4 = require("react/jsx-runtime");
5942
+ NativeWebViewComponent2 = null;
5943
+ nativeLookupFailed = false;
5944
+ DEFAULT_DURATION_MS = 60 * 1e3;
5945
+ styles4 = import_react_native22.StyleSheet.create({
5946
+ hidden: {
5947
+ height: 1,
5948
+ left: -9999,
5949
+ opacity: 0,
5950
+ position: "absolute",
5951
+ top: -9999,
5952
+ width: 1
5953
+ },
5954
+ fill: { flex: 1 }
5955
+ });
5956
+ }
5957
+ });
5958
+
5627
5959
  // src/domains/paywall/usePaywallSession.ts
5628
5960
  function usePaywallSession(options) {
5629
- const closedRef = (0, import_react8.useRef)(false);
5630
- const optsRef = (0, import_react8.useRef)(options);
5961
+ const closedRef = (0, import_react10.useRef)(false);
5962
+ const optsRef = (0, import_react10.useRef)(options);
5631
5963
  optsRef.current = options;
5632
- (0, import_react8.useEffect)(() => {
5964
+ (0, import_react10.useEffect)(() => {
5633
5965
  if (!options) return;
5634
5966
  closedRef.current = false;
5635
5967
  startHeartbeat({
@@ -5659,16 +5991,16 @@ function usePaywallSession(options) {
5659
5991
  });
5660
5992
  };
5661
5993
  }, [options?.paywallId, options?.placement]);
5662
- const markClosed = (0, import_react8.useCallback)(() => {
5994
+ const markClosed = (0, import_react10.useCallback)(() => {
5663
5995
  closedRef.current = true;
5664
5996
  }, []);
5665
5997
  return { markClosed };
5666
5998
  }
5667
- var import_react8;
5999
+ var import_react10;
5668
6000
  var init_usePaywallSession = __esm({
5669
6001
  "src/domains/paywall/usePaywallSession.ts"() {
5670
6002
  "use strict";
5671
- import_react8 = require("react");
6003
+ import_react10 = require("react");
5672
6004
  init_emitPaywallClosed();
5673
6005
  init_paywallHeartbeat();
5674
6006
  }
@@ -5676,9 +6008,9 @@ var init_usePaywallSession = __esm({
5676
6008
 
5677
6009
  // src/domains/paywall/usePaywallPresenter.ts
5678
6010
  function usePaywallPresenter(preloadedWebView, setPreloadedWebView) {
5679
- const [showingPreloadedWebView, setShowingPreloadedWebView] = (0, import_react9.useState)(false);
5680
- const resolverRef = (0, import_react9.useRef)(null);
5681
- const presentedAtRef = (0, import_react9.useRef)(Date.now());
6011
+ const [showingPreloadedWebView, setShowingPreloadedWebView] = (0, import_react11.useState)(false);
6012
+ const resolverRef = (0, import_react11.useRef)(null);
6013
+ const presentedAtRef = (0, import_react11.useRef)(Date.now());
5682
6014
  const sessionOpts = showingPreloadedWebView && preloadedWebView ? {
5683
6015
  placement: preloadedWebView.placement,
5684
6016
  paywallId: preloadedWebView.paywallId,
@@ -5688,7 +6020,7 @@ function usePaywallPresenter(preloadedWebView, setPreloadedWebView) {
5688
6020
  ...preloadedWebView.campaignId && { campaignId: preloadedWebView.campaignId }
5689
6021
  } : null;
5690
6022
  const { markClosed } = usePaywallSession(sessionOpts);
5691
- (0, import_react9.useEffect)(() => {
6023
+ (0, import_react11.useEffect)(() => {
5692
6024
  return () => {
5693
6025
  if (resolverRef.current) {
5694
6026
  resolverRef.current({ presented: false, purchased: false, cancelled: true, restored: false });
@@ -5697,7 +6029,7 @@ function usePaywallPresenter(preloadedWebView, setPreloadedWebView) {
5697
6029
  setShowingPreloadedWebView(false);
5698
6030
  };
5699
6031
  }, []);
5700
- const presentPreloadedWebView = (0, import_react9.useCallback)(() => {
6032
+ const presentPreloadedWebView = (0, import_react11.useCallback)(() => {
5701
6033
  return new Promise((resolve) => {
5702
6034
  resolverRef.current = resolve;
5703
6035
  presentedAtRef.current = Date.now();
@@ -5731,16 +6063,16 @@ function usePaywallPresenter(preloadedWebView, setPreloadedWebView) {
5731
6063
  }
5732
6064
  });
5733
6065
  }, [preloadedWebView]);
5734
- const resolvePreloadedPaywall = (0, import_react9.useCallback)((result) => {
6066
+ const resolvePreloadedPaywall = (0, import_react11.useCallback)((result) => {
5735
6067
  if (resolverRef.current) {
5736
6068
  resolverRef.current(result);
5737
6069
  resolverRef.current = null;
5738
6070
  }
5739
6071
  }, []);
5740
- const handlePreloadedWebViewReady = (0, import_react9.useCallback)(() => {
6072
+ const handlePreloadedWebViewReady = (0, import_react11.useCallback)(() => {
5741
6073
  setPreloadedWebView((prev) => prev ? { ...prev, loaded: true } : null);
5742
6074
  }, [setPreloadedWebView]);
5743
- const handlePreloadedClose = (0, import_react9.useCallback)(() => {
6075
+ const handlePreloadedClose = (0, import_react11.useCallback)(() => {
5744
6076
  if (preloadedWebView) {
5745
6077
  markClosed();
5746
6078
  void emitPaywallClosed({
@@ -5758,11 +6090,11 @@ function usePaywallPresenter(preloadedWebView, setPreloadedWebView) {
5758
6090
  }, [preloadedWebView, markClosed, resolvePreloadedPaywall]);
5759
6091
  return { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady, handlePreloadedClose, setShowingPreloadedWebView, presentedAtRef };
5760
6092
  }
5761
- var import_react9;
6093
+ var import_react11;
5762
6094
  var init_usePaywallPresenter = __esm({
5763
6095
  "src/domains/paywall/usePaywallPresenter.ts"() {
5764
6096
  "use strict";
5765
- import_react9 = require("react");
6097
+ import_react11 = require("react");
5766
6098
  init_PaywalloClient();
5767
6099
  init_emitPaywallClosed();
5768
6100
  init_usePaywallSession();
@@ -5777,7 +6109,9 @@ var init_paywall = __esm({
5777
6109
  init_PaywallError();
5778
6110
  init_PaywallErrorBoundary();
5779
6111
  init_PaywallModal();
6112
+ init_PaywallPreloadService();
5780
6113
  init_PaywallWebView();
6114
+ init_PaywallWebViewPrewarmer();
5781
6115
  init_usePaywallPresenter();
5782
6116
  init_usePaywallTracking();
5783
6117
  }
@@ -5883,10 +6217,10 @@ var init_PlanService = __esm({
5883
6217
  }
5884
6218
  async getCurrentPlan(forceRefresh = false) {
5885
6219
  if (!forceRefresh) {
5886
- const cached = this.cache.get();
5887
- if (cached && !cached.isStale) {
5888
- const current = cached.data.plans.find(
5889
- (p) => p.identifier === cached.data.currentIdentifier
6220
+ const cached2 = this.cache.get();
6221
+ if (cached2 && !cached2.isStale) {
6222
+ const current = cached2.data.plans.find(
6223
+ (p) => p.identifier === cached2.data.currentIdentifier
5890
6224
  );
5891
6225
  return current ?? null;
5892
6226
  }
@@ -5913,8 +6247,8 @@ var init_PlanService = __esm({
5913
6247
  }
5914
6248
  async getAllPlans(forceRefresh = false) {
5915
6249
  if (!forceRefresh) {
5916
- const cached = this.cache.get();
5917
- if (cached && !cached.isStale) return cached.data;
6250
+ const cached2 = this.cache.get();
6251
+ if (cached2 && !cached2.isStale) return cached2.data;
5918
6252
  }
5919
6253
  if (!this.cache.get()) {
5920
6254
  try {
@@ -6032,95 +6366,257 @@ var init_analytics = __esm({
6032
6366
  }
6033
6367
  });
6034
6368
 
6035
- // src/core/PaywalloAnalytics.ts
6036
- function isValidEventName(name) {
6037
- if (name.startsWith("$")) return /^\$[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(name);
6038
- return /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(name);
6039
- }
6040
- async function trackEvent(apiClient, state, eventName, options) {
6041
- const distinctId = identityManager.getDistinctId();
6042
- if (!distinctId) {
6043
- if (state.config?.debug) console.log(`[Paywallo TRACK] ${eventName} skipped (no distinctId)`);
6044
- return;
6045
- }
6046
- if (!isValidEventName(eventName)) {
6047
- const msg = `Invalid event name: "${eventName}". Use snake_case or $reserved_name.`;
6048
- throw new ClientError(CLIENT_ERROR_CODES.INVALID_EVENT_NAME, msg);
6049
- }
6050
- if (state.config?.debug && eventName.startsWith("$purchase")) console.log(`[Paywallo TRACK] ${eventName}`, options?.properties ?? {});
6051
- const sessionId = sessionManager.getSessionId();
6052
- await apiClient.trackEvent(
6053
- eventName,
6054
- distinctId,
6055
- { ...identityManager.getProperties(), ...options?.properties, ...sessionId && { sessionId } },
6056
- options?.timestamp,
6057
- options?.priority ? { priority: options.priority } : void 0
6058
- );
6059
- }
6060
- async function trackOnboardingStep(apiClient, state, index, name) {
6061
- const distinctId = identityManager.getDistinctId();
6062
- if (!distinctId) return;
6063
- if (!Number.isInteger(index) || index < 0) {
6064
- const msg = `Invalid stepIndex: "${index}". Must be a non-negative integer.`;
6065
- throw new AnalyticsError(ANALYTICS_ERROR_CODES.INVALID_STEP_INDEX, msg);
6066
- }
6067
- const now = Date.now();
6068
- const timeOnPreviousStep = state.lastOnboardingStepTimestamp !== null ? Math.round((now - state.lastOnboardingStepTimestamp) / 1e3) : 0;
6069
- state.lastOnboardingStepTimestamp = now;
6070
- await apiClient.trackEvent("$onboarding_step", distinctId, { stepIndex: index, stepName: name, timeOnPreviousStep });
6071
- }
6072
- function validateCoreAction(actionName) {
6073
- if (!actionName || typeof actionName !== "string") {
6074
- const msg = `Invalid actionName: "${actionName}". Must be a non-empty string.`;
6075
- throw new AnalyticsError(ANALYTICS_ERROR_CODES.INVALID_ACTION_NAME, msg);
6076
- }
6077
- }
6078
- var init_PaywalloAnalytics = __esm({
6079
- "src/core/PaywalloAnalytics.ts"() {
6369
+ // src/core/events/schemas.ts
6370
+ var import_zod, lifecycleEventSchema, identifyEventSchema, paywallEventSchema, transactionEventSchema, onboardingEventSchema, notificationEventSchema, customEventSchema, EVENT_SCHEMAS;
6371
+ var init_schemas = __esm({
6372
+ "src/core/events/schemas.ts"() {
6080
6373
  "use strict";
6081
- init_analytics();
6082
- init_identity();
6083
- init_session();
6084
- init_ClientError();
6085
- }
6086
- });
6087
-
6088
- // src/core/PaywalloFlags.ts
6089
- async function getVariantCached(apiClient, flagKey, defaultValue) {
6090
- const distinctId = identityManager.getDistinctId();
6091
- const result = await apiClient.getVariantFromCache(flagKey, distinctId);
6092
- if (result) return result;
6093
- void apiClient.getVariant(flagKey, distinctId).catch(() => {
6094
- });
6095
- return { variant: defaultValue ?? null, payload: void 0 };
6096
- }
6097
- async function getVariant(apiClient, state, flagKey) {
6098
- try {
6099
- const distinctId = identityManager.getDistinctId();
6100
- if (!distinctId) {
6101
- if (state.config?.debug) console.log(`[Paywallo FLAG] ${flagKey} \u2192 null (no distinctId)`);
6102
- return { variant: null };
6103
- }
6104
- const result = await apiClient.getVariant(flagKey, distinctId);
6105
- if (state.config?.debug) console.log(`[Paywallo FLAG] ${flagKey} \u2192`, result.variant);
6106
- return result;
6107
- } catch (error) {
6108
- if (state.config?.debug) console.log(`[Paywallo FLAG] ${flagKey} \u2192 error`, error);
6109
- if (state.apiClient) void state.apiClient.reportError("GET_VARIANT_FAILED", error instanceof Error ? error.message : String(error));
6110
- return { variant: null };
6111
- }
6112
- }
6113
- function evaluateFlags(apiClient, keys) {
6114
- return apiClient.evaluateFlags(keys, identityManager.getDistinctId());
6115
- }
6116
- async function getConditionalFlag(apiClient, state, flagKey, context) {
6117
- try {
6118
- const distinctId = identityManager.getDistinctId();
6119
- if (!distinctId) return false;
6120
- const result = await apiClient.getConditionalFlag(flagKey, { distinctId, ...context });
6121
- return result.value;
6122
- } catch (error) {
6123
- if (state.apiClient) void state.apiClient.reportError("GET_CONDITIONAL_FLAG_FAILED", error instanceof Error ? error.message : String(error));
6374
+ import_zod = require("zod");
6375
+ lifecycleEventSchema = import_zod.z.object({
6376
+ type: import_zod.z.enum([
6377
+ "install",
6378
+ "cold_start",
6379
+ "foreground",
6380
+ "background",
6381
+ "session_start",
6382
+ "session_end"
6383
+ ]),
6384
+ session_id: import_zod.z.string().optional(),
6385
+ duration_s: import_zod.z.number().nonnegative().optional(),
6386
+ first_ever: import_zod.z.boolean().optional(),
6387
+ // Contexto leve transportado em lifecycle (útil pra cold_start/install)
6388
+ app_version: import_zod.z.string().optional(),
6389
+ device_type: import_zod.z.enum(["ios", "android"]).optional(),
6390
+ os_version: import_zod.z.string().optional(),
6391
+ // Timestamps opcionais — o EventBatcher injeta `timestamp` no envelope
6392
+ started_at: import_zod.z.string().optional(),
6393
+ ended_at: import_zod.z.string().optional()
6394
+ }).strict();
6395
+ identifyEventSchema = import_zod.z.object({
6396
+ distinct_id: import_zod.z.string().min(1),
6397
+ email: import_zod.z.email().optional(),
6398
+ traits: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
6399
+ attribution: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
6400
+ device: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
6401
+ }).strict();
6402
+ paywallEventSchema = import_zod.z.object({
6403
+ type: import_zod.z.enum(["viewed", "closed", "purchased"]),
6404
+ paywall_id: import_zod.z.string().min(1),
6405
+ placement: import_zod.z.string().optional(),
6406
+ variant_id: import_zod.z.string().optional(),
6407
+ variant_key: import_zod.z.string().optional(),
6408
+ campaign_id: import_zod.z.string().optional(),
6409
+ // `closed`-specific
6410
+ closed_at: import_zod.z.string().optional(),
6411
+ duration_s: import_zod.z.number().nonnegative().optional(),
6412
+ close_reason: import_zod.z.enum(["dismiss", "cta", "purchase", "error", "timeout"]).optional(),
6413
+ scroll_depth: import_zod.z.number().min(0).max(1).optional(),
6414
+ // `purchased`-specific (mínimo — transaction canônica leva detalhes)
6415
+ product_id: import_zod.z.string().optional()
6416
+ }).strict();
6417
+ transactionEventSchema = import_zod.z.object({
6418
+ type: import_zod.z.enum([
6419
+ "completed",
6420
+ "failed",
6421
+ "refunded",
6422
+ "renewed",
6423
+ "canceled",
6424
+ "expired"
6425
+ ]),
6426
+ tx_id: import_zod.z.string().min(1),
6427
+ amount: import_zod.z.number().optional(),
6428
+ currency: import_zod.z.string().length(3).optional(),
6429
+ product_id: import_zod.z.string().optional(),
6430
+ subscription_id: import_zod.z.string().optional(),
6431
+ paywall_id: import_zod.z.string().optional(),
6432
+ variant_id: import_zod.z.string().optional(),
6433
+ reason: import_zod.z.string().optional()
6434
+ }).strict();
6435
+ onboardingEventSchema = import_zod.z.object({
6436
+ type: import_zod.z.enum(["step", "complete", "drop"]),
6437
+ step_index: import_zod.z.number().int().nonnegative().optional(),
6438
+ step_name: import_zod.z.string().optional(),
6439
+ variant_key: import_zod.z.string().optional(),
6440
+ time_on_prev_s: import_zod.z.number().nonnegative().optional()
6441
+ }).strict();
6442
+ notificationEventSchema = import_zod.z.object({
6443
+ type: import_zod.z.enum(["delivered", "displayed", "clicked", "dismissed"]),
6444
+ notification_id: import_zod.z.string().optional(),
6445
+ campaign_id: import_zod.z.string().optional(),
6446
+ variant_key: import_zod.z.string().nullable().optional(),
6447
+ message_id: import_zod.z.string().optional(),
6448
+ device_id: import_zod.z.string().optional(),
6449
+ push_platform: import_zod.z.enum(["ios", "android"]).optional(),
6450
+ timezone: import_zod.z.string().optional(),
6451
+ failure_reason: import_zod.z.string().optional(),
6452
+ app_user_id: import_zod.z.string().optional()
6453
+ }).strict();
6454
+ customEventSchema = import_zod.z.object({
6455
+ name: import_zod.z.string().min(1).regex(
6456
+ /^(\$[a-zA-Z0-9_]+|[a-z][a-z0-9_]*)$/,
6457
+ "custom event name must be snake_case or $reserved"
6458
+ ),
6459
+ props: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
6460
+ }).strict();
6461
+ EVENT_SCHEMAS = {
6462
+ lifecycle: lifecycleEventSchema,
6463
+ identify: identifyEventSchema,
6464
+ paywall: paywallEventSchema,
6465
+ transaction: transactionEventSchema,
6466
+ onboarding: onboardingEventSchema,
6467
+ notification: notificationEventSchema,
6468
+ custom: customEventSchema
6469
+ };
6470
+ }
6471
+ });
6472
+
6473
+ // src/core/events/eventFamilies.ts
6474
+ function isDeprecatedEvent(eventName) {
6475
+ return DEPRECATED_EVENT_NAMES.includes(eventName);
6476
+ }
6477
+ function isCanonicalFamily(name) {
6478
+ return CANONICAL_FAMILIES.includes(name);
6479
+ }
6480
+ function detectFamily(eventName) {
6481
+ if (isCanonicalFamily(eventName) && eventName !== "custom") {
6482
+ return eventName;
6483
+ }
6484
+ return "custom";
6485
+ }
6486
+ function validateEvent(eventName, properties) {
6487
+ const family = detectFamily(eventName);
6488
+ const schema = EVENT_SCHEMAS[family];
6489
+ const input = family === "custom" ? { name: eventName, ...properties ? { props: properties } : {} } : properties ?? {};
6490
+ const parsed = schema.safeParse(input);
6491
+ if (parsed.success) {
6492
+ return { ok: true, family, data: parsed.data };
6493
+ }
6494
+ return { ok: false, family, error: parsed.error };
6495
+ }
6496
+ function formatValidationError(error) {
6497
+ return error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
6498
+ }
6499
+ var CANONICAL_FAMILIES, DEPRECATED_EVENT_NAMES;
6500
+ var init_eventFamilies = __esm({
6501
+ "src/core/events/eventFamilies.ts"() {
6502
+ "use strict";
6503
+ init_schemas();
6504
+ CANONICAL_FAMILIES = [
6505
+ "lifecycle",
6506
+ "identify",
6507
+ "paywall",
6508
+ "transaction",
6509
+ "onboarding",
6510
+ "notification",
6511
+ "custom"
6512
+ ];
6513
+ DEPRECATED_EVENT_NAMES = [
6514
+ "$paywall_purchased",
6515
+ "$paywall_product_selected",
6516
+ "$core_action",
6517
+ "$campaign_impression",
6518
+ "$app_open",
6519
+ "$app_background",
6520
+ "$app_foreground",
6521
+ "session.end"
6522
+ ];
6523
+ }
6524
+ });
6525
+
6526
+ // src/core/PaywalloAnalytics.ts
6527
+ function isValidEventName(name) {
6528
+ if (name.startsWith("$")) return /^\$[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(name);
6529
+ return /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(name);
6530
+ }
6531
+ async function trackEvent(apiClient, state, eventName, options) {
6532
+ const distinctId = identityManager.getDistinctId();
6533
+ if (!distinctId) {
6534
+ console.error("[Paywallo:DROP] event dropped \u2014 no distinctId available yet", { eventName });
6535
+ return;
6536
+ }
6537
+ if (!isValidEventName(eventName)) {
6538
+ const msg = `Invalid event name: "${eventName}". Use snake_case or $reserved_name.`;
6539
+ throw new ClientError(CLIENT_ERROR_CODES.INVALID_EVENT_NAME, msg);
6540
+ }
6541
+ if (state.config?.debug && eventName.startsWith("$purchase")) console.log(`[Paywallo TRACK] ${eventName}`, options?.properties ?? {});
6542
+ const sessionId = sessionManager.getSessionId();
6543
+ const family = detectFamily(eventName);
6544
+ const isCustomFamily = family === "custom";
6545
+ const identityProps = isCustomFamily ? identityManager.getProperties() : {};
6546
+ const sessionProp = isCustomFamily && sessionId ? { sessionId } : {};
6547
+ await apiClient.trackEvent(
6548
+ eventName,
6549
+ distinctId,
6550
+ { ...identityProps, ...options?.properties, ...sessionProp },
6551
+ options?.timestamp,
6552
+ options?.priority ? { priority: options.priority } : void 0
6553
+ );
6554
+ }
6555
+ async function trackOnboardingStep(apiClient, state, index, name) {
6556
+ const distinctId = identityManager.getDistinctId();
6557
+ if (!distinctId) return;
6558
+ if (!Number.isInteger(index) || index < 0) {
6559
+ const msg = `Invalid stepIndex: "${index}". Must be a non-negative integer.`;
6560
+ throw new AnalyticsError(ANALYTICS_ERROR_CODES.INVALID_STEP_INDEX, msg);
6561
+ }
6562
+ const now = Date.now();
6563
+ const timeOnPreviousStep = state.lastOnboardingStepTimestamp !== null ? Math.round((now - state.lastOnboardingStepTimestamp) / 1e3) : 0;
6564
+ state.lastOnboardingStepTimestamp = now;
6565
+ await apiClient.trackEvent("$onboarding_step", distinctId, { stepIndex: index, stepName: name, timeOnPreviousStep });
6566
+ }
6567
+ function validateCoreAction(actionName) {
6568
+ if (!actionName || typeof actionName !== "string") {
6569
+ const msg = `Invalid actionName: "${actionName}". Must be a non-empty string.`;
6570
+ throw new AnalyticsError(ANALYTICS_ERROR_CODES.INVALID_ACTION_NAME, msg);
6571
+ }
6572
+ }
6573
+ var init_PaywalloAnalytics = __esm({
6574
+ "src/core/PaywalloAnalytics.ts"() {
6575
+ "use strict";
6576
+ init_analytics();
6577
+ init_identity();
6578
+ init_session();
6579
+ init_ClientError();
6580
+ init_eventFamilies();
6581
+ }
6582
+ });
6583
+
6584
+ // src/core/PaywalloFlags.ts
6585
+ async function getVariantCached(apiClient, flagKey, defaultValue) {
6586
+ const distinctId = identityManager.getDistinctId();
6587
+ const result = await apiClient.getVariantFromCache(flagKey, distinctId);
6588
+ if (result) return result;
6589
+ void apiClient.getVariant(flagKey, distinctId).catch(() => {
6590
+ });
6591
+ return { variant: defaultValue ?? null, payload: void 0 };
6592
+ }
6593
+ async function getVariant(apiClient, state, flagKey) {
6594
+ try {
6595
+ const distinctId = identityManager.getDistinctId();
6596
+ if (!distinctId) {
6597
+ if (state.config?.debug) console.log(`[Paywallo FLAG] ${flagKey} \u2192 null (no distinctId)`);
6598
+ return { variant: null };
6599
+ }
6600
+ const result = await apiClient.getVariant(flagKey, distinctId);
6601
+ if (state.config?.debug) console.log(`[Paywallo FLAG] ${flagKey} \u2192`, result.variant);
6602
+ return result;
6603
+ } catch (error) {
6604
+ if (state.config?.debug) console.log(`[Paywallo FLAG] ${flagKey} \u2192 error`, error);
6605
+ if (state.apiClient) void state.apiClient.reportError("GET_VARIANT_FAILED", error instanceof Error ? error.message : String(error));
6606
+ return { variant: null };
6607
+ }
6608
+ }
6609
+ function evaluateFlags(apiClient, keys) {
6610
+ return apiClient.evaluateFlags(keys, identityManager.getDistinctId());
6611
+ }
6612
+ async function getConditionalFlag(apiClient, state, flagKey, context) {
6613
+ try {
6614
+ const distinctId = identityManager.getDistinctId();
6615
+ if (!distinctId) return false;
6616
+ const result = await apiClient.getConditionalFlag(flagKey, { distinctId, ...context });
6617
+ return result.value;
6618
+ } catch (error) {
6619
+ if (state.apiClient) void state.apiClient.reportError("GET_CONDITIONAL_FLAG_FAILED", error instanceof Error ? error.message : String(error));
6124
6620
  return false;
6125
6621
  }
6126
6622
  }
@@ -6346,49 +6842,49 @@ var init_SubscriptionCache = __esm({
6346
6842
  await this.storage.remove(keyFor(distinctId));
6347
6843
  return null;
6348
6844
  }
6349
- const cached = parsed;
6350
- if (cached.data.subscription?.expiresAt) {
6351
- cached.data.subscription.expiresAt = new Date(cached.data.subscription.expiresAt);
6845
+ const cached2 = parsed;
6846
+ if (cached2.data.subscription?.expiresAt) {
6847
+ cached2.data.subscription.expiresAt = new Date(cached2.data.subscription.expiresAt);
6352
6848
  }
6353
- if (cached.data.subscription?.originalPurchaseDate) {
6354
- cached.data.subscription.originalPurchaseDate = new Date(
6355
- cached.data.subscription.originalPurchaseDate
6849
+ if (cached2.data.subscription?.originalPurchaseDate) {
6850
+ cached2.data.subscription.originalPurchaseDate = new Date(
6851
+ cached2.data.subscription.originalPurchaseDate
6356
6852
  );
6357
6853
  }
6358
- if (cached.data.subscription?.latestPurchaseDate) {
6359
- cached.data.subscription.latestPurchaseDate = new Date(
6360
- cached.data.subscription.latestPurchaseDate
6854
+ if (cached2.data.subscription?.latestPurchaseDate) {
6855
+ cached2.data.subscription.latestPurchaseDate = new Date(
6856
+ cached2.data.subscription.latestPurchaseDate
6361
6857
  );
6362
6858
  }
6363
- if (cached.data.subscription?.cancellationDate) {
6364
- cached.data.subscription.cancellationDate = new Date(
6365
- cached.data.subscription.cancellationDate
6859
+ if (cached2.data.subscription?.cancellationDate) {
6860
+ cached2.data.subscription.cancellationDate = new Date(
6861
+ cached2.data.subscription.cancellationDate
6366
6862
  );
6367
6863
  }
6368
- if (cached.data.subscription?.gracePeriodExpiresAt) {
6369
- cached.data.subscription.gracePeriodExpiresAt = new Date(
6370
- cached.data.subscription.gracePeriodExpiresAt
6864
+ if (cached2.data.subscription?.gracePeriodExpiresAt) {
6865
+ cached2.data.subscription.gracePeriodExpiresAt = new Date(
6866
+ cached2.data.subscription.gracePeriodExpiresAt
6371
6867
  );
6372
6868
  }
6373
- cached.isStale = this.isExpired(cached);
6374
- if (!cached.isStale) {
6375
- this.memoryCache.set(distinctId, cached);
6869
+ cached2.isStale = this.isExpired(cached2);
6870
+ if (!cached2.isStale) {
6871
+ this.memoryCache.set(distinctId, cached2);
6376
6872
  }
6377
- return cached;
6873
+ return cached2;
6378
6874
  } catch {
6379
6875
  return null;
6380
6876
  }
6381
6877
  }
6382
6878
  async set(distinctId, data) {
6383
6879
  if (!distinctId) return;
6384
- const cached = {
6880
+ const cached2 = {
6385
6881
  data,
6386
6882
  cachedAt: Date.now(),
6387
6883
  isStale: false
6388
6884
  };
6389
- this.memoryCache.set(distinctId, cached);
6885
+ this.memoryCache.set(distinctId, cached2);
6390
6886
  try {
6391
- await this.storage.set(keyFor(distinctId), JSON.stringify(cached));
6887
+ await this.storage.set(keyFor(distinctId), JSON.stringify(cached2));
6392
6888
  await this.addToIndex(distinctId);
6393
6889
  } catch {
6394
6890
  }
@@ -6432,8 +6928,8 @@ var init_SubscriptionCache = __esm({
6432
6928
  ...allKeys.map((k) => this.storage.remove(keyFor(k)))
6433
6929
  ]);
6434
6930
  }
6435
- isExpired(cached) {
6436
- return Date.now() - cached.cachedAt > this.ttl;
6931
+ isExpired(cached2) {
6932
+ return Date.now() - cached2.cachedAt > this.ttl;
6437
6933
  }
6438
6934
  setTTL(ttl) {
6439
6935
  this.ttl = ttl;
@@ -6489,9 +6985,9 @@ var init_SubscriptionManager = __esm({
6489
6985
  return this.getEmptyStatus();
6490
6986
  }
6491
6987
  if (!forceRefresh) {
6492
- const cached = await this.cache.get(this.cacheKey());
6493
- if (cached && !cached.isStale) {
6494
- return cached.data;
6988
+ const cached2 = await this.cache.get(this.cacheKey());
6989
+ if (cached2 && !cached2.isStale) {
6990
+ return cached2.data;
6495
6991
  }
6496
6992
  }
6497
6993
  try {
@@ -6501,9 +6997,9 @@ var init_SubscriptionManager = __esm({
6501
6997
  return status;
6502
6998
  } catch (error) {
6503
6999
  this.log("Failed to fetch subscription status", error);
6504
- const cached = await this.cache.get(this.cacheKey());
6505
- if (cached) {
6506
- return cached.data;
7000
+ const cached2 = await this.cache.get(this.cacheKey());
7001
+ if (cached2) {
7002
+ return cached2.data;
6507
7003
  }
6508
7004
  return this.getEmptyStatus();
6509
7005
  }
@@ -6816,262 +7312,105 @@ async function getVariant2(deps, flagKey, distinctId) {
6816
7312
  return v;
6817
7313
  }
6818
7314
  if (!res.ok) {
6819
- const stale = deps.cache.getStaleVariant(key);
6820
- return stale !== void 0 ? stale : { variant: null };
6821
- }
6822
- deps.cache.setVariant(key, res.data);
6823
- await writeFlagToStorage(flagKey, distinctId, res.data);
6824
- return res.data;
6825
- } catch {
6826
- const stale = deps.cache.getStaleVariant(key);
6827
- return stale !== void 0 ? stale : { variant: null };
6828
- }
6829
- }
6830
- async function evaluateFlags2(deps, keys, distinctId) {
6831
- const query = keys.map(encodeURIComponent).join(",");
6832
- try {
6833
- const res = await deps.httpClient.get(`/sdk/flags/evaluate?keys=${query}`, {
6834
- headers: { "X-App-Key": deps.appKey, "x-distinct-id": distinctId }
6835
- });
6836
- if (!res.ok || typeof res.data !== "object" || res.data === null || Array.isArray(res.data)) return {};
6837
- const record = res.data;
6838
- const result = {};
6839
- for (const key of Object.keys(record)) {
6840
- const value = record[key];
6841
- const variant = typeof value === "string" ? value : null;
6842
- result[key] = variant;
6843
- const flagVariant = { variant };
6844
- deps.cache.setVariant(`${key}:${distinctId}`, flagVariant);
6845
- await writeFlagToStorage(key, distinctId, flagVariant);
6846
- }
6847
- return result;
6848
- } catch {
6849
- return {};
6850
- }
6851
- }
6852
- async function getConditionalFlag2(deps, flagKey, context) {
6853
- try {
6854
- const url = buildConditionalFlagUrl(deps.baseUrl, flagKey, context);
6855
- const response = await deps.get(url);
6856
- if (response.status === 404 || !response.ok) return { value: false, flagKey };
6857
- return { value: response.data.value, flagKey };
6858
- } catch {
6859
- return { value: false, flagKey };
6860
- }
6861
- }
6862
- async function getVariantFromCache(deps, flagKey, distinctId) {
6863
- const key = `${flagKey}:${distinctId}`;
6864
- const hit = deps.cache.getVariant(key);
6865
- if (hit !== void 0) return hit;
6866
- const persisted = await readFlagFromStorage(flagKey, distinctId);
6867
- if (persisted !== null) {
6868
- deps.cache.setVariant(key, persisted);
6869
- return persisted;
6870
- }
6871
- return null;
6872
- }
6873
- var init_ApiClientFlags = __esm({
6874
- "src/core/ApiClientFlags.ts"() {
6875
- "use strict";
6876
- init_apiUrlUtils();
6877
- init_FlagStorage();
6878
- }
6879
- });
6880
-
6881
- // src/core/ApiClientQueue.ts
6882
- async function postWithQueue(deps, url, payload, label, priority) {
6883
- if (deps.isOfflineQueueEnabled() && !networkMonitor.isOnline()) {
6884
- await offlineQueue.enqueue(
6885
- "POST",
6886
- url,
6887
- payload,
6888
- { "X-App-Key": deps.getAppKey() },
6889
- void 0,
6890
- priority
6891
- );
6892
- if (deps.isDebug()) console.log("[Paywallo:ApiClient] Queued for offline:", label, priority ?? "normal");
6893
- return;
6894
- }
6895
- try {
6896
- await deps.post(url, payload);
6897
- } catch (error) {
6898
- if (deps.isDebug()) console.log("[Paywallo:ApiClient] Request failed:", label, error);
6899
- if (deps.isOfflineQueueEnabled()) {
6900
- await offlineQueue.enqueue(
6901
- "POST",
6902
- url,
6903
- payload,
6904
- { "X-App-Key": deps.getAppKey() },
6905
- void 0,
6906
- priority
6907
- );
6908
- return;
6909
- }
6910
- throw error;
6911
- }
6912
- }
6913
- var init_ApiClientQueue = __esm({
6914
- "src/core/ApiClientQueue.ts"() {
6915
- "use strict";
6916
- init_network();
6917
- init_queue();
6918
- }
6919
- });
6920
-
6921
- // src/core/events/schemas.ts
6922
- var import_zod, lifecycleEventSchema, identifyEventSchema, paywallEventSchema, transactionEventSchema, onboardingEventSchema, notificationEventSchema, customEventSchema, EVENT_SCHEMAS;
6923
- var init_schemas = __esm({
6924
- "src/core/events/schemas.ts"() {
6925
- "use strict";
6926
- import_zod = require("zod");
6927
- lifecycleEventSchema = import_zod.z.object({
6928
- type: import_zod.z.enum([
6929
- "install",
6930
- "cold_start",
6931
- "foreground",
6932
- "background",
6933
- "session_start",
6934
- "session_end"
6935
- ]),
6936
- session_id: import_zod.z.string().optional(),
6937
- duration_s: import_zod.z.number().nonnegative().optional(),
6938
- first_ever: import_zod.z.boolean().optional(),
6939
- // Contexto leve transportado em lifecycle (útil pra cold_start/install)
6940
- app_version: import_zod.z.string().optional(),
6941
- device_type: import_zod.z.enum(["ios", "android"]).optional(),
6942
- os_version: import_zod.z.string().optional(),
6943
- // Timestamps opcionais — o EventBatcher injeta `timestamp` no envelope
6944
- started_at: import_zod.z.string().optional(),
6945
- ended_at: import_zod.z.string().optional()
6946
- }).strict();
6947
- identifyEventSchema = import_zod.z.object({
6948
- distinct_id: import_zod.z.string().min(1),
6949
- email: import_zod.z.email().optional(),
6950
- traits: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
6951
- attribution: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
6952
- device: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
6953
- }).strict();
6954
- paywallEventSchema = import_zod.z.object({
6955
- type: import_zod.z.enum(["viewed", "closed", "purchased"]),
6956
- paywall_id: import_zod.z.string().min(1),
6957
- placement: import_zod.z.string().optional(),
6958
- variant_id: import_zod.z.string().optional(),
6959
- variant_key: import_zod.z.string().optional(),
6960
- campaign_id: import_zod.z.string().optional(),
6961
- // `closed`-specific
6962
- closed_at: import_zod.z.string().optional(),
6963
- duration_s: import_zod.z.number().nonnegative().optional(),
6964
- close_reason: import_zod.z.enum(["dismiss", "cta", "purchase", "error", "timeout"]).optional(),
6965
- scroll_depth: import_zod.z.number().min(0).max(1).optional(),
6966
- // `purchased`-specific (mínimo — transaction canônica leva detalhes)
6967
- product_id: import_zod.z.string().optional()
6968
- }).strict();
6969
- transactionEventSchema = import_zod.z.object({
6970
- type: import_zod.z.enum([
6971
- "completed",
6972
- "failed",
6973
- "refunded",
6974
- "renewed",
6975
- "canceled",
6976
- "expired"
6977
- ]),
6978
- tx_id: import_zod.z.string().min(1),
6979
- amount: import_zod.z.number().optional(),
6980
- currency: import_zod.z.string().length(3).optional(),
6981
- product_id: import_zod.z.string().optional(),
6982
- subscription_id: import_zod.z.string().optional(),
6983
- paywall_id: import_zod.z.string().optional(),
6984
- variant_id: import_zod.z.string().optional(),
6985
- reason: import_zod.z.string().optional()
6986
- }).strict();
6987
- onboardingEventSchema = import_zod.z.object({
6988
- type: import_zod.z.enum(["step", "complete", "drop"]),
6989
- step_index: import_zod.z.number().int().nonnegative().optional(),
6990
- step_name: import_zod.z.string().optional(),
6991
- variant_key: import_zod.z.string().optional(),
6992
- time_on_prev_s: import_zod.z.number().nonnegative().optional()
6993
- }).strict();
6994
- notificationEventSchema = import_zod.z.object({
6995
- type: import_zod.z.enum(["delivered", "displayed", "clicked", "dismissed"]),
6996
- notification_id: import_zod.z.string().optional(),
6997
- campaign_id: import_zod.z.string().optional(),
6998
- variant_key: import_zod.z.string().nullable().optional(),
6999
- message_id: import_zod.z.string().optional(),
7000
- device_id: import_zod.z.string().optional(),
7001
- push_platform: import_zod.z.enum(["ios", "android"]).optional(),
7002
- timezone: import_zod.z.string().optional(),
7003
- failure_reason: import_zod.z.string().optional(),
7004
- app_user_id: import_zod.z.string().optional()
7005
- }).strict();
7006
- customEventSchema = import_zod.z.object({
7007
- name: import_zod.z.string().min(1).regex(
7008
- /^(\$[a-zA-Z0-9_]+|[a-z][a-z0-9_]*)$/,
7009
- "custom event name must be snake_case or $reserved"
7010
- ),
7011
- props: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
7012
- }).strict();
7013
- EVENT_SCHEMAS = {
7014
- lifecycle: lifecycleEventSchema,
7015
- identify: identifyEventSchema,
7016
- paywall: paywallEventSchema,
7017
- transaction: transactionEventSchema,
7018
- onboarding: onboardingEventSchema,
7019
- notification: notificationEventSchema,
7020
- custom: customEventSchema
7021
- };
7315
+ const stale = deps.cache.getStaleVariant(key);
7316
+ return stale !== void 0 ? stale : { variant: null };
7317
+ }
7318
+ deps.cache.setVariant(key, res.data);
7319
+ await writeFlagToStorage(flagKey, distinctId, res.data);
7320
+ return res.data;
7321
+ } catch {
7322
+ const stale = deps.cache.getStaleVariant(key);
7323
+ return stale !== void 0 ? stale : { variant: null };
7022
7324
  }
7023
- });
7024
-
7025
- // src/core/events/eventFamilies.ts
7026
- function isDeprecatedEvent(eventName) {
7027
- return DEPRECATED_EVENT_NAMES.includes(eventName);
7028
7325
  }
7029
- function isCanonicalFamily(name) {
7030
- return CANONICAL_FAMILIES.includes(name);
7326
+ async function evaluateFlags2(deps, keys, distinctId) {
7327
+ const query = keys.map(encodeURIComponent).join(",");
7328
+ try {
7329
+ const res = await deps.httpClient.get(`/sdk/flags/evaluate?keys=${query}`, {
7330
+ headers: { "X-App-Key": deps.appKey, "x-distinct-id": distinctId }
7331
+ });
7332
+ if (!res.ok || typeof res.data !== "object" || res.data === null || Array.isArray(res.data)) return {};
7333
+ const record = res.data;
7334
+ const result = {};
7335
+ for (const key of Object.keys(record)) {
7336
+ const value = record[key];
7337
+ const variant = typeof value === "string" ? value : null;
7338
+ result[key] = variant;
7339
+ const flagVariant = { variant };
7340
+ deps.cache.setVariant(`${key}:${distinctId}`, flagVariant);
7341
+ await writeFlagToStorage(key, distinctId, flagVariant);
7342
+ }
7343
+ return result;
7344
+ } catch {
7345
+ return {};
7346
+ }
7031
7347
  }
7032
- function detectFamily(eventName) {
7033
- if (isCanonicalFamily(eventName) && eventName !== "custom") {
7034
- return eventName;
7348
+ async function getConditionalFlag2(deps, flagKey, context) {
7349
+ try {
7350
+ const url = buildConditionalFlagUrl(deps.baseUrl, flagKey, context);
7351
+ const response = await deps.get(url);
7352
+ if (response.status === 404 || !response.ok) return { value: false, flagKey };
7353
+ return { value: response.data.value, flagKey };
7354
+ } catch {
7355
+ return { value: false, flagKey };
7035
7356
  }
7036
- return "custom";
7037
7357
  }
7038
- function validateEvent(eventName, properties) {
7039
- const family = detectFamily(eventName);
7040
- const schema = EVENT_SCHEMAS[family];
7041
- const input = family === "custom" ? { name: eventName, ...properties ? { props: properties } : {} } : properties ?? {};
7042
- const parsed = schema.safeParse(input);
7043
- if (parsed.success) {
7044
- return { ok: true, family, data: parsed.data };
7358
+ async function getVariantFromCache(deps, flagKey, distinctId) {
7359
+ const key = `${flagKey}:${distinctId}`;
7360
+ const hit = deps.cache.getVariant(key);
7361
+ if (hit !== void 0) return hit;
7362
+ const persisted = await readFlagFromStorage(flagKey, distinctId);
7363
+ if (persisted !== null) {
7364
+ deps.cache.setVariant(key, persisted);
7365
+ return persisted;
7045
7366
  }
7046
- return { ok: false, family, error: parsed.error };
7367
+ return null;
7047
7368
  }
7048
- function formatValidationError(error) {
7049
- return error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
7369
+ var init_ApiClientFlags = __esm({
7370
+ "src/core/ApiClientFlags.ts"() {
7371
+ "use strict";
7372
+ init_apiUrlUtils();
7373
+ init_FlagStorage();
7374
+ }
7375
+ });
7376
+
7377
+ // src/core/ApiClientQueue.ts
7378
+ async function postWithQueue(deps, url, payload, label, priority) {
7379
+ if (deps.isOfflineQueueEnabled() && !networkMonitor.isOnline()) {
7380
+ await offlineQueue.enqueue(
7381
+ "POST",
7382
+ url,
7383
+ payload,
7384
+ { "X-App-Key": deps.getAppKey() },
7385
+ void 0,
7386
+ priority
7387
+ );
7388
+ if (deps.isDebug()) console.log("[Paywallo:ApiClient] Queued for offline:", label, priority ?? "normal");
7389
+ return;
7390
+ }
7391
+ try {
7392
+ await deps.post(url, payload);
7393
+ } catch (error) {
7394
+ if (deps.isDebug()) console.log("[Paywallo:ApiClient] Request failed:", label, error);
7395
+ if (deps.isOfflineQueueEnabled()) {
7396
+ await offlineQueue.enqueue(
7397
+ "POST",
7398
+ url,
7399
+ payload,
7400
+ { "X-App-Key": deps.getAppKey() },
7401
+ void 0,
7402
+ priority
7403
+ );
7404
+ return;
7405
+ }
7406
+ throw error;
7407
+ }
7050
7408
  }
7051
- var CANONICAL_FAMILIES, DEPRECATED_EVENT_NAMES;
7052
- var init_eventFamilies = __esm({
7053
- "src/core/events/eventFamilies.ts"() {
7409
+ var init_ApiClientQueue = __esm({
7410
+ "src/core/ApiClientQueue.ts"() {
7054
7411
  "use strict";
7055
- init_schemas();
7056
- CANONICAL_FAMILIES = [
7057
- "lifecycle",
7058
- "identify",
7059
- "paywall",
7060
- "transaction",
7061
- "onboarding",
7062
- "notification",
7063
- "custom"
7064
- ];
7065
- DEPRECATED_EVENT_NAMES = [
7066
- "$paywall_purchased",
7067
- "$paywall_product_selected",
7068
- "$core_action",
7069
- "$campaign_impression",
7070
- "$app_open",
7071
- "$app_background",
7072
- "$app_foreground",
7073
- "session.end"
7074
- ];
7412
+ init_network();
7413
+ init_queue();
7075
7414
  }
7076
7415
  });
7077
7416
 
@@ -7098,10 +7437,13 @@ function buildV2Envelope(events, providerContext) {
7098
7437
  };
7099
7438
  });
7100
7439
  if (context.sdk_version === void 0) context.sdk_version = SDK_VERSION;
7101
- if (context.platform === void 0) context.platform = import_react_native21.Platform.OS;
7440
+ if (context.platform === void 0) context.platform = import_react_native23.Platform.OS;
7102
7441
  if (context.distinct_id === void 0 && events.length > 0) {
7103
7442
  context.distinct_id = events[0].distinctId;
7104
7443
  }
7444
+ if (!context.distinct_id || context.distinct_id.length === 0) {
7445
+ console.error("[Paywallo:ENVELOPE] distinct_id is empty after resolution \u2014 envelope will fail schema", { providerContext, eventCount: events.length, firstEventName: events[0]?.eventName });
7446
+ }
7105
7447
  return { context, events: v2Events };
7106
7448
  }
7107
7449
  function resolveProviderContext(provider) {
@@ -7135,11 +7477,11 @@ function splitPayloadAndContext(properties) {
7135
7477
  }
7136
7478
  return { payload, promoted };
7137
7479
  }
7138
- var import_react_native21, import_zod2, v2EventSchema, v2ContextSchema, v2EnvelopeSchema, CONTEXT_KEY_ALIASES;
7480
+ var import_react_native23, import_zod2, v2EventSchema, v2ContextSchema, v2EnvelopeSchema, CONTEXT_KEY_ALIASES;
7139
7481
  var init_eventEnvelopeV2 = __esm({
7140
7482
  "src/core/eventEnvelopeV2.ts"() {
7141
7483
  "use strict";
7142
- import_react_native21 = require("react-native");
7484
+ import_react_native23 = require("react-native");
7143
7485
  import_zod2 = require("zod");
7144
7486
  init_uuid();
7145
7487
  init_eventFamilies();
@@ -7155,12 +7497,18 @@ var init_eventEnvelopeV2 = __esm({
7155
7497
  session_id: import_zod2.z.string().optional(),
7156
7498
  device_id: import_zod2.z.string().optional(),
7157
7499
  app_version: import_zod2.z.string().optional(),
7500
+ app_build: import_zod2.z.string().optional(),
7501
+ bundle_id: import_zod2.z.string().optional(),
7158
7502
  sdk_version: import_zod2.z.string().optional(),
7159
7503
  platform: import_zod2.z.string().optional(),
7160
7504
  os_version: import_zod2.z.string().optional(),
7161
7505
  device_model: import_zod2.z.string().optional(),
7162
7506
  timezone: import_zod2.z.string().optional(),
7163
7507
  locale: import_zod2.z.string().optional(),
7508
+ carrier: import_zod2.z.string().optional(),
7509
+ screen_width: import_zod2.z.number().optional(),
7510
+ screen_height: import_zod2.z.number().optional(),
7511
+ screen_density: import_zod2.z.number().optional(),
7164
7512
  attribution: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.unknown()).optional(),
7165
7513
  ids: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.unknown()).optional()
7166
7514
  });
@@ -7173,12 +7521,21 @@ var init_eventEnvelopeV2 = __esm({
7173
7521
  session_id: ["session_id", "sessionId"],
7174
7522
  device_id: ["device_id", "deviceId"],
7175
7523
  app_version: ["app_version", "appVersion"],
7524
+ // F2.2 — alias só snake_case canônico: campos vêm do context provider, não
7525
+ // de properties de evento. Evita promover (e deletar) chaves do payload do
7526
+ // $app_installed que o server lê (screenWidth etc.).
7527
+ app_build: ["app_build"],
7528
+ bundle_id: ["bundle_id"],
7176
7529
  sdk_version: ["sdk_version", "sdkVersion"],
7177
7530
  platform: ["platform"],
7178
7531
  os_version: ["os_version", "osVersion", "systemVersion"],
7179
7532
  device_model: ["device_model", "deviceModel", "model"],
7180
7533
  timezone: ["timezone", "timeZone"],
7181
7534
  locale: ["locale"],
7535
+ carrier: ["carrier_ctx"],
7536
+ screen_width: ["screen_width"],
7537
+ screen_height: ["screen_height"],
7538
+ screen_density: ["screen_density"],
7182
7539
  attribution: ["attribution"],
7183
7540
  ids: ["ids"]
7184
7541
  };
@@ -7194,11 +7551,11 @@ var init_events = __esm({
7194
7551
  });
7195
7552
 
7196
7553
  // src/core/EventBatcher.ts
7197
- var import_react_native22, BATCH_MAX_SIZE, BATCH_FLUSH_MS, EVENT_NAME_REGEX, V2_BATCH_ENDPOINT, EventBatcher;
7554
+ var import_react_native24, BATCH_MAX_SIZE, BATCH_FLUSH_MS, EVENT_NAME_REGEX, V2_BATCH_ENDPOINT, EventBatcher;
7198
7555
  var init_EventBatcher = __esm({
7199
7556
  "src/core/EventBatcher.ts"() {
7200
7557
  "use strict";
7201
- import_react_native22 = require("react-native");
7558
+ import_react_native24 = require("react-native");
7202
7559
  init_eventEnvelopeV2();
7203
7560
  init_events();
7204
7561
  BATCH_MAX_SIZE = 25;
@@ -7247,7 +7604,7 @@ var init_EventBatcher = __esm({
7247
7604
  const event = {
7248
7605
  eventName,
7249
7606
  distinctId,
7250
- properties: { ...properties, platform: import_react_native22.Platform.OS },
7607
+ properties: { ...properties, platform: import_react_native24.Platform.OS },
7251
7608
  timestamp: timestamp ?? Date.now(),
7252
7609
  environment: environment.toLowerCase()
7253
7610
  };
@@ -7305,8 +7662,8 @@ var init_EventBatcher = __esm({
7305
7662
  for (const event of drained) {
7306
7663
  try {
7307
7664
  await this.postBatch([event], `event_critical:${event.eventName}`, "critical");
7308
- } catch {
7309
- if (this.debug) console.log(`[Paywallo] critical event ${event.eventName} post failed`);
7665
+ } catch (err) {
7666
+ console.error("[Paywallo:CRITICAL] critical event post failed", { eventName: event.eventName, error: err instanceof Error ? err.message : String(err) });
7310
7667
  }
7311
7668
  }
7312
7669
  }
@@ -7319,8 +7676,8 @@ var init_EventBatcher = __esm({
7319
7676
  }
7320
7677
  try {
7321
7678
  await this.postBatch(batch, `event_batch:${batch.length}`);
7322
- } catch {
7323
- if (this.debug) console.log(`[Paywallo] normal batch post failed \u2014 OfflineQueue will handle durability`);
7679
+ } catch (err) {
7680
+ console.error("[Paywallo:NORMAL] batch post failed \u2014 relying on OfflineQueue", { batchSize: batch.length, error: err instanceof Error ? err.message : String(err) });
7324
7681
  }
7325
7682
  }
7326
7683
  async postBatch(events, label, priority = "normal") {
@@ -7335,6 +7692,7 @@ var init_EventBatcher = __esm({
7335
7692
  const parsed = v2EnvelopeSchema.safeParse(envelope);
7336
7693
  if (!parsed.success) {
7337
7694
  if (this.debug) console.log("[Paywallo EVENTS] V2 envelope schema validation failed:", parsed.error.issues);
7695
+ console.error("[Paywallo:CRITICAL] V2 envelope schema validation failed", { issues: parsed.error.issues, eventCount: events.length, firstEventName: events[0]?.eventName, context: providerContext });
7338
7696
  throw parsed.error;
7339
7697
  }
7340
7698
  await this.post(V2_BATCH_ENDPOINT, parsed.data, label, priority);
@@ -7572,11 +7930,11 @@ var init_http = __esm({
7572
7930
  });
7573
7931
 
7574
7932
  // src/core/ApiClient.ts
7575
- var import_react_native23, ApiClient;
7933
+ var import_react_native25, ApiClient;
7576
7934
  var init_ApiClient = __esm({
7577
7935
  "src/core/ApiClient.ts"() {
7578
7936
  "use strict";
7579
- import_react_native23 = require("react-native");
7937
+ import_react_native25 = require("react-native");
7580
7938
  init_apiPurchaseMethods();
7581
7939
  init_ApiCache();
7582
7940
  init_ApiClientFlags();
@@ -7678,7 +8036,7 @@ var init_ApiClient = __esm({
7678
8036
  buildSdkHeaders() {
7679
8037
  return {
7680
8038
  "x-sdk-version": SDK_VERSION,
7681
- "x-sdk-platform": import_react_native23.Platform.OS,
8039
+ "x-sdk-platform": import_react_native25.Platform.OS,
7682
8040
  "x-sdk-environment": this.environment
7683
8041
  };
7684
8042
  }
@@ -7708,7 +8066,7 @@ var init_ApiClient = __esm({
7708
8066
  message,
7709
8067
  context,
7710
8068
  sdkVersion: SDK_VERSION,
7711
- platform: import_react_native23.Platform.OS
8069
+ platform: import_react_native25.Platform.OS
7712
8070
  }, true);
7713
8071
  } catch (err) {
7714
8072
  if (this.debug) console.log("[Paywallo:ApiClient] reportError failed", err);
@@ -7726,7 +8084,7 @@ var init_ApiClient = __esm({
7726
8084
  async identify(distinctId, properties, email, deviceId, pii) {
7727
8085
  const rawTraits = {
7728
8086
  email: email || void 0,
7729
- platform: import_react_native23.Platform.OS,
8087
+ platform: import_react_native25.Platform.OS,
7730
8088
  name: properties?.name,
7731
8089
  country: properties?.country,
7732
8090
  locale: properties?.locale,
@@ -7917,7 +8275,8 @@ function setupNotifications(apiClient, config, environment, eventPipeline) {
7917
8275
  httpClient: apiClient.getHttpClient(),
7918
8276
  secureStorage: new SecureStorage(config.debug),
7919
8277
  getDeviceId: () => identityManager.getDeviceId(),
7920
- getDistinctId: () => identityManager.getDistinctId()
8278
+ getDistinctId: () => identityManager.getDistinctId(),
8279
+ environment: environment === "Sandbox" ? "sandbox" : "production"
7921
8280
  });
7922
8281
  notificationsManager.setupHandlers({ eventBatcher: eventPipeline });
7923
8282
  notificationsManager.initialize({
@@ -8001,6 +8360,36 @@ var init_PaywalloState = __esm({
8001
8360
  }
8002
8361
  });
8003
8362
 
8363
+ // src/utils/detectEnvironment.ts
8364
+ async function detectEnvironment() {
8365
+ if (cached !== null) return cached;
8366
+ if (typeof globalThis.__DEV__ !== "undefined" && globalThis.__DEV__) {
8367
+ cached = "sandbox";
8368
+ return cached;
8369
+ }
8370
+ if (!nativeModule) {
8371
+ cached = "production";
8372
+ return cached;
8373
+ }
8374
+ try {
8375
+ const result = await nativeModule.detect();
8376
+ cached = result === "sandbox" ? "sandbox" : "production";
8377
+ return cached;
8378
+ } catch {
8379
+ cached = "production";
8380
+ return cached;
8381
+ }
8382
+ }
8383
+ var import_react_native26, nativeModule, cached;
8384
+ var init_detectEnvironment = __esm({
8385
+ "src/utils/detectEnvironment.ts"() {
8386
+ "use strict";
8387
+ import_react_native26 = require("react-native");
8388
+ nativeModule = import_react_native26.NativeModules.PaywalloEnv ?? null;
8389
+ cached = null;
8390
+ }
8391
+ });
8392
+
8004
8393
  // src/core/PaywalloInitializer.ts
8005
8394
  async function initWithRetry(state, config) {
8006
8395
  while (state.initAttempts <= MAX_INIT_RETRIES) {
@@ -8080,7 +8469,13 @@ async function resolveSessionFlags(state, keys, apiClient, distinctId) {
8080
8469
  }
8081
8470
  }
8082
8471
  async function doInit(state, config) {
8083
- const environment = config.environment ?? (typeof __DEV__ !== "undefined" && __DEV__ ? "Sandbox" : "Production");
8472
+ let environment;
8473
+ if (config.environment) {
8474
+ environment = config.environment;
8475
+ } else {
8476
+ const detected = await detectEnvironment();
8477
+ environment = detected === "sandbox" ? "Sandbox" : "Production";
8478
+ }
8084
8479
  const offlineQueueEnabled = config.offlineQueueEnabled !== false;
8085
8480
  await Promise.all([
8086
8481
  networkMonitor.initialize({ debug: config.debug }),
@@ -8168,15 +8563,31 @@ async function doInit(state, config) {
8168
8563
  timezone = opts.timeZone || void 0;
8169
8564
  } catch {
8170
8565
  }
8566
+ let screenWidth;
8567
+ let screenHeight;
8568
+ let screenDensity;
8569
+ try {
8570
+ const screen = import_react_native27.Dimensions.get("screen");
8571
+ screenWidth = Math.round(screen.width) || void 0;
8572
+ screenHeight = Math.round(screen.height) || void 0;
8573
+ screenDensity = import_react_native27.PixelRatio.get() || void 0;
8574
+ } catch {
8575
+ }
8171
8576
  return {
8172
8577
  distinct_id: identityManager.getDistinctId() || void 0,
8173
8578
  device_id: identityManager.getDeviceId() ?? void 0,
8174
8579
  session_id: sessionManager.getSessionId() ?? void 0,
8175
8580
  app_version: device?.appVersion && device.appVersion !== "0.0.0" ? device.appVersion : void 0,
8581
+ app_build: device?.buildNumber && device.buildNumber !== "0" ? device.buildNumber : void 0,
8582
+ bundle_id: device?.bundleId || void 0,
8176
8583
  os_version: device?.systemVersion && device.systemVersion !== "0.0.0" ? device.systemVersion : void 0,
8177
8584
  device_model: device?.modelId || device?.model || void 0,
8178
8585
  timezone,
8179
8586
  locale,
8587
+ carrier: device?.carrier || void 0,
8588
+ screen_width: screenWidth,
8589
+ screen_height: screenHeight,
8590
+ screen_density: screenDensity,
8180
8591
  ...attribution && Object.keys(attribution).length > 0 && { attribution },
8181
8592
  ...ids && Object.keys(ids).length > 0 && { ids }
8182
8593
  };
@@ -8226,9 +8637,11 @@ async function doInit(state, config) {
8226
8637
  setupAutoPreload(state, apiClient, config, distinctId);
8227
8638
  startBatchPreload(apiClient, config.debug);
8228
8639
  }
8640
+ var import_react_native27;
8229
8641
  var init_PaywalloInitializer = __esm({
8230
8642
  "src/core/PaywalloInitializer.ts"() {
8231
8643
  "use strict";
8644
+ import_react_native27 = require("react-native");
8232
8645
  init_identity();
8233
8646
  init_AdvertisingIdManager();
8234
8647
  init_DeepLinkAttributionCapture();
@@ -8248,6 +8661,7 @@ var init_PaywalloInitializer = __esm({
8248
8661
  init_PaywalloState();
8249
8662
  init_queue();
8250
8663
  init_NativeStorage();
8664
+ init_detectEnvironment();
8251
8665
  }
8252
8666
  });
8253
8667
 
@@ -8270,6 +8684,7 @@ async function doFullReset(state) {
8270
8684
  offlineQueue.dispose();
8271
8685
  networkMonitor.dispose();
8272
8686
  campaignGateService.clearPreloaded();
8687
+ paywallPreloadService.clear();
8273
8688
  await notificationsManager.invalidateLocalToken().catch(() => void 0);
8274
8689
  notificationsManager.destroy();
8275
8690
  deepLinkAttributionCapture.stop();
@@ -8304,6 +8719,7 @@ var init_PaywalloLifecycle = __esm({
8304
8719
  init_identity();
8305
8720
  init_DeepLinkAttributionCapture();
8306
8721
  init_notifications();
8722
+ init_paywall();
8307
8723
  init_session();
8308
8724
  init_SubscriptionCache();
8309
8725
  init_network();
@@ -8607,6 +9023,15 @@ var init_PaywalloClient = __esm({
8607
9023
  isPreloaded(placement) {
8608
9024
  return campaignGateService.getPreloadedCampaign(placement) !== null;
8609
9025
  }
9026
+ async preloadPaywall(placement) {
9027
+ return paywallPreloadService.preload(this.getApiClientOrThrow(), placement);
9028
+ }
9029
+ async preloadPaywalls(placements) {
9030
+ return paywallPreloadService.preloadMany(this.getApiClientOrThrow(), placements);
9031
+ }
9032
+ isPaywallPreloaded(placement) {
9033
+ return paywallPreloadService.isPreloaded(placement);
9034
+ }
8610
9035
  getAutoPreloadedPlacement() {
8611
9036
  return this.state.autoPreloadedPlacement;
8612
9037
  }
@@ -8652,7 +9077,7 @@ __export(OfferingService_exports, {
8652
9077
  offeringService: () => offeringService
8653
9078
  });
8654
9079
  function resolveProductId(raw) {
8655
- const isIos = import_react_native24.Platform.OS === "ios";
9080
+ const isIos = import_react_native28.Platform.OS === "ios";
8656
9081
  const productId = isIos ? raw.apple_product_id : raw.google_product_id;
8657
9082
  return productId ?? "";
8658
9083
  }
@@ -8671,7 +9096,7 @@ function mapProduct2(raw) {
8671
9096
  };
8672
9097
  }
8673
9098
  function pickPlatformProduct(products) {
8674
- const isIos = import_react_native24.Platform.OS === "ios";
9099
+ const isIos = import_react_native28.Platform.OS === "ios";
8675
9100
  const match = products.find((p) => isIos ? p.apple_product_id : p.google_product_id);
8676
9101
  return match ?? products[0] ?? null;
8677
9102
  }
@@ -8687,11 +9112,11 @@ function mapOffering(raw) {
8687
9112
  product: mapProduct2(rawProduct)
8688
9113
  };
8689
9114
  }
8690
- var import_react_native24, OFFERINGS_CACHE_KEY, DEFAULT_TTL3, OfferingService, offeringService;
9115
+ var import_react_native28, OFFERINGS_CACHE_KEY, DEFAULT_TTL3, OfferingService, offeringService;
8691
9116
  var init_OfferingService = __esm({
8692
9117
  "src/domains/offering/OfferingService.ts"() {
8693
9118
  "use strict";
8694
- import_react_native24 = require("react-native");
9119
+ import_react_native28 = require("react-native");
8695
9120
  init_SecureStorage();
8696
9121
  OFFERINGS_CACHE_KEY = "@paywallo:offerings_cache";
8697
9122
  DEFAULT_TTL3 = 5 * 60 * 1e3;
@@ -8705,8 +9130,8 @@ var init_OfferingService = __esm({
8705
9130
  this.config = config;
8706
9131
  }
8707
9132
  async getOfferings(ids) {
8708
- const cached = this.getMemCache();
8709
- if (cached && !cached.isStale) return this.filterByIds(cached.data, ids);
9133
+ const cached2 = this.getMemCache();
9134
+ if (cached2 && !cached2.isStale) return this.filterByIds(cached2.data, ids);
8710
9135
  if (!this.cache) {
8711
9136
  try {
8712
9137
  const stored = await secureStorage.get(OFFERINGS_CACHE_KEY);
@@ -8852,8 +9277,8 @@ module.exports = __toCommonJS(index_exports);
8852
9277
  init_PaywalloClient();
8853
9278
 
8854
9279
  // src/context/PaywalloContext.ts
8855
- var import_react10 = require("react");
8856
- var PaywalloContext = (0, import_react10.createContext)(null);
9280
+ var import_react12 = require("react");
9281
+ var PaywalloContext = (0, import_react12.createContext)(null);
8857
9282
 
8858
9283
  // src/index.ts
8859
9284
  init_ApiClient();
@@ -8898,7 +9323,7 @@ init_session();
8898
9323
  init_PaywalloError();
8899
9324
 
8900
9325
  // src/hooks/useOfferingPurchase.ts
8901
- var import_react11 = require("react");
9326
+ var import_react13 = require("react");
8902
9327
 
8903
9328
  // src/domains/plan/invalidateCache.ts
8904
9329
  async function invalidatePlanCache() {
@@ -8911,8 +9336,8 @@ async function invalidatePlanCache() {
8911
9336
 
8912
9337
  // src/hooks/useOfferingPurchase.ts
8913
9338
  function useOfferingPurchase() {
8914
- const [state, setState] = (0, import_react11.useState)("idle");
8915
- const purchase = (0, import_react11.useCallback)(async (productId) => {
9339
+ const [state, setState] = (0, import_react13.useState)("idle");
9340
+ const purchase = (0, import_react13.useCallback)(async (productId) => {
8916
9341
  setState("purchasing");
8917
9342
  try {
8918
9343
  const iapService = getIAPService();
@@ -8936,7 +9361,7 @@ function useOfferingPurchase() {
8936
9361
  }
8937
9362
 
8938
9363
  // src/hooks/useOfferings.ts
8939
- var import_react12 = require("react");
9364
+ var import_react14 = require("react");
8940
9365
  async function getOfferingService() {
8941
9366
  try {
8942
9367
  const mod = await Promise.resolve().then(() => (init_OfferingService(), OfferingService_exports));
@@ -8946,10 +9371,10 @@ async function getOfferingService() {
8946
9371
  }
8947
9372
  }
8948
9373
  function useOfferings(ids) {
8949
- const [offerings, setOfferings] = (0, import_react12.useState)([]);
8950
- const [isLoading, setIsLoading] = (0, import_react12.useState)(true);
9374
+ const [offerings, setOfferings] = (0, import_react14.useState)([]);
9375
+ const [isLoading, setIsLoading] = (0, import_react14.useState)(true);
8951
9376
  const idsKey = ids?.join(",") ?? "";
8952
- const fetchOfferings = (0, import_react12.useCallback)(async () => {
9377
+ const fetchOfferings = (0, import_react14.useCallback)(async () => {
8953
9378
  setIsLoading(true);
8954
9379
  try {
8955
9380
  const service = await getOfferingService();
@@ -8965,25 +9390,25 @@ function useOfferings(ids) {
8965
9390
  setIsLoading(false);
8966
9391
  }
8967
9392
  }, [idsKey]);
8968
- (0, import_react12.useEffect)(() => {
9393
+ (0, import_react14.useEffect)(() => {
8969
9394
  void fetchOfferings();
8970
9395
  }, [fetchOfferings]);
8971
9396
  return { offerings, isLoading };
8972
9397
  }
8973
9398
 
8974
9399
  // src/hooks/useOnboarding.ts
8975
- var import_react13 = require("react");
9400
+ var import_react15 = require("react");
8976
9401
  init_onboarding();
8977
9402
  function useOnboarding() {
8978
- const step = (0, import_react13.useCallback)(
9403
+ const step = (0, import_react15.useCallback)(
8979
9404
  (stepName) => onboardingManager.step(stepName),
8980
9405
  []
8981
9406
  );
8982
- const complete = (0, import_react13.useCallback)(
9407
+ const complete = (0, import_react15.useCallback)(
8983
9408
  () => onboardingManager.complete(),
8984
9409
  []
8985
9410
  );
8986
- const drop = (0, import_react13.useCallback)(
9411
+ const drop = (0, import_react15.useCallback)(
8987
9412
  (stepName) => onboardingManager.drop(stepName),
8988
9413
  []
8989
9414
  );
@@ -8991,10 +9416,10 @@ function useOnboarding() {
8991
9416
  }
8992
9417
 
8993
9418
  // src/hooks/usePaywallo.ts
8994
- var import_react14 = require("react");
9419
+ var import_react16 = require("react");
8995
9420
  init_ClientError();
8996
9421
  function usePaywallo() {
8997
- const context = (0, import_react14.useContext)(PaywalloContext);
9422
+ const context = (0, import_react16.useContext)(PaywalloContext);
8998
9423
  if (!context) {
8999
9424
  throw new ClientError(CLIENT_ERROR_CODES.PROVIDER_MISSING, "usePaywallo must be used within a PaywalloProvider");
9000
9425
  }
@@ -9002,11 +9427,11 @@ function usePaywallo() {
9002
9427
  }
9003
9428
 
9004
9429
  // src/hooks/usePlanPurchase.ts
9005
- var import_react15 = require("react");
9430
+ var import_react17 = require("react");
9006
9431
  function usePlanPurchase() {
9007
- const [state, setState] = (0, import_react15.useState)("idle");
9008
- const [error, setError] = (0, import_react15.useState)(null);
9009
- const purchase = (0, import_react15.useCallback)(async (productId) => {
9432
+ const [state, setState] = (0, import_react17.useState)("idle");
9433
+ const [error, setError] = (0, import_react17.useState)(null);
9434
+ const purchase = (0, import_react17.useCallback)(async (productId) => {
9010
9435
  setState("purchasing");
9011
9436
  setError(null);
9012
9437
  try {
@@ -9029,7 +9454,7 @@ function usePlanPurchase() {
9029
9454
  return { success: false, error: purchaseError };
9030
9455
  }
9031
9456
  }, []);
9032
- const restore = (0, import_react15.useCallback)(async () => {
9457
+ const restore = (0, import_react17.useCallback)(async () => {
9033
9458
  setState("restoring");
9034
9459
  setError(null);
9035
9460
  try {
@@ -9058,7 +9483,7 @@ function usePlanPurchase() {
9058
9483
  }
9059
9484
 
9060
9485
  // src/hooks/usePlans.ts
9061
- var import_react16 = require("react");
9486
+ var import_react18 = require("react");
9062
9487
  async function getPlanService() {
9063
9488
  try {
9064
9489
  const mod = await Promise.resolve().then(() => (init_PlanService(), PlanService_exports));
@@ -9068,11 +9493,11 @@ async function getPlanService() {
9068
9493
  }
9069
9494
  }
9070
9495
  function usePlans() {
9071
- const [allPlans, setAllPlans] = (0, import_react16.useState)([]);
9072
- const [currentPlan, setCurrentPlan] = (0, import_react16.useState)(null);
9073
- const [isLoading, setIsLoading] = (0, import_react16.useState)(true);
9074
- const [error, setError] = (0, import_react16.useState)(null);
9075
- const fetchPlans = (0, import_react16.useCallback)(async (forceRefresh = false) => {
9496
+ const [allPlans, setAllPlans] = (0, import_react18.useState)([]);
9497
+ const [currentPlan, setCurrentPlan] = (0, import_react18.useState)(null);
9498
+ const [isLoading, setIsLoading] = (0, import_react18.useState)(true);
9499
+ const [error, setError] = (0, import_react18.useState)(null);
9500
+ const fetchPlans = (0, import_react18.useCallback)(async (forceRefresh = false) => {
9076
9501
  setIsLoading(true);
9077
9502
  setError(null);
9078
9503
  try {
@@ -9093,17 +9518,17 @@ function usePlans() {
9093
9518
  setIsLoading(false);
9094
9519
  }
9095
9520
  }, []);
9096
- const refresh = (0, import_react16.useCallback)(() => {
9521
+ const refresh = (0, import_react18.useCallback)(() => {
9097
9522
  return fetchPlans(true);
9098
9523
  }, [fetchPlans]);
9099
- (0, import_react16.useEffect)(() => {
9524
+ (0, import_react18.useEffect)(() => {
9100
9525
  void fetchPlans(false);
9101
9526
  }, [fetchPlans]);
9102
9527
  return { currentPlan, allPlans, isLoading, error, refresh };
9103
9528
  }
9104
9529
 
9105
9530
  // src/hooks/useProducts.ts
9106
- var import_react17 = require("react");
9531
+ var import_react19 = require("react");
9107
9532
 
9108
9533
  // src/utils/productMappers.ts
9109
9534
  function mapToIAPProduct(product) {
@@ -9142,11 +9567,11 @@ function mapToFormattedProduct(product) {
9142
9567
 
9143
9568
  // src/hooks/useProducts.ts
9144
9569
  function useProducts(initialProductIds) {
9145
- const [products, setProducts] = (0, import_react17.useState)([]);
9146
- const [formattedProducts, setFormattedProducts] = (0, import_react17.useState)([]);
9147
- const [isLoading, setIsLoading] = (0, import_react17.useState)(false);
9148
- const [error, setError] = (0, import_react17.useState)(null);
9149
- const loadProducts = (0, import_react17.useCallback)(async (productIds) => {
9570
+ const [products, setProducts] = (0, import_react19.useState)([]);
9571
+ const [formattedProducts, setFormattedProducts] = (0, import_react19.useState)([]);
9572
+ const [isLoading, setIsLoading] = (0, import_react19.useState)(false);
9573
+ const [error, setError] = (0, import_react19.useState)(null);
9574
+ const loadProducts = (0, import_react19.useCallback)(async (productIds) => {
9150
9575
  setIsLoading(true);
9151
9576
  setError(null);
9152
9577
  try {
@@ -9160,24 +9585,24 @@ function useProducts(initialProductIds) {
9160
9585
  setIsLoading(false);
9161
9586
  }
9162
9587
  }, []);
9163
- (0, import_react17.useEffect)(() => {
9588
+ (0, import_react19.useEffect)(() => {
9164
9589
  if (initialProductIds && initialProductIds.length > 0) {
9165
9590
  void loadProducts(initialProductIds);
9166
9591
  }
9167
9592
  }, []);
9168
- const getProduct = (0, import_react17.useCallback)(
9593
+ const getProduct = (0, import_react19.useCallback)(
9169
9594
  (productId) => {
9170
9595
  return products.find((p) => p.productId === productId);
9171
9596
  },
9172
9597
  [products]
9173
9598
  );
9174
- const getFormattedProduct = (0, import_react17.useCallback)(
9599
+ const getFormattedProduct = (0, import_react19.useCallback)(
9175
9600
  (productId) => {
9176
9601
  return formattedProducts.find((p) => p.productId === productId);
9177
9602
  },
9178
9603
  [formattedProducts]
9179
9604
  );
9180
- const getPriceInfo = (0, import_react17.useCallback)(
9605
+ const getPriceInfo = (0, import_react19.useCallback)(
9181
9606
  (productId) => {
9182
9607
  const p = formattedProducts.find((fp) => fp.productId === productId);
9183
9608
  if (!p) return void 0;
@@ -9206,14 +9631,14 @@ function useProducts(initialProductIds) {
9206
9631
  }
9207
9632
 
9208
9633
  // src/hooks/usePurchase.ts
9209
- var import_react18 = require("react");
9634
+ var import_react20 = require("react");
9210
9635
  function toIAPPurchase(p) {
9211
9636
  return { productId: p.productId, transactionId: p.transactionId, transactionDate: p.transactionDate, transactionReceipt: p.receipt };
9212
9637
  }
9213
9638
  function usePurchase() {
9214
- const [state, setState] = (0, import_react18.useState)("idle");
9215
- const [error, setError] = (0, import_react18.useState)(null);
9216
- const purchase = (0, import_react18.useCallback)(async (productId) => {
9639
+ const [state, setState] = (0, import_react20.useState)("idle");
9640
+ const [error, setError] = (0, import_react20.useState)(null);
9641
+ const purchase = (0, import_react20.useCallback)(async (productId) => {
9217
9642
  setState("purchasing");
9218
9643
  setError(null);
9219
9644
  try {
@@ -9241,7 +9666,7 @@ function usePurchase() {
9241
9666
  return { status: "failed", error: purchaseError };
9242
9667
  }
9243
9668
  }, []);
9244
- const restore = (0, import_react18.useCallback)(async () => {
9669
+ const restore = (0, import_react20.useCallback)(async () => {
9245
9670
  setState("restoring");
9246
9671
  setError(null);
9247
9672
  try {
@@ -9260,14 +9685,14 @@ function usePurchase() {
9260
9685
  }
9261
9686
 
9262
9687
  // src/hooks/useSubscription.ts
9263
- var import_react19 = require("react");
9688
+ var import_react21 = require("react");
9264
9689
  init_SubscriptionManager();
9265
9690
  var EMPTY_ENTITLEMENTS = [];
9266
9691
  function useSubscription() {
9267
- const [status, setStatus] = (0, import_react19.useState)(null);
9268
- const [isLoading, setIsLoading] = (0, import_react19.useState)(true);
9269
- const [error, setError] = (0, import_react19.useState)(null);
9270
- const refresh = (0, import_react19.useCallback)(async () => {
9692
+ const [status, setStatus] = (0, import_react21.useState)(null);
9693
+ const [isLoading, setIsLoading] = (0, import_react21.useState)(true);
9694
+ const [error, setError] = (0, import_react21.useState)(null);
9695
+ const refresh = (0, import_react21.useCallback)(async () => {
9271
9696
  setIsLoading(true);
9272
9697
  setError(null);
9273
9698
  try {
@@ -9280,7 +9705,7 @@ function useSubscription() {
9280
9705
  setIsLoading(false);
9281
9706
  }
9282
9707
  }, []);
9283
- const restore = (0, import_react19.useCallback)(async () => {
9708
+ const restore = (0, import_react21.useCallback)(async () => {
9284
9709
  setIsLoading(true);
9285
9710
  setError(null);
9286
9711
  try {
@@ -9293,7 +9718,7 @@ function useSubscription() {
9293
9718
  setIsLoading(false);
9294
9719
  }
9295
9720
  }, []);
9296
- (0, import_react19.useEffect)(() => {
9721
+ (0, import_react21.useEffect)(() => {
9297
9722
  void subscriptionManager.getSubscriptionStatus().then((s) => {
9298
9723
  setStatus(s);
9299
9724
  setIsLoading(false);
@@ -9321,20 +9746,215 @@ function useSubscription() {
9321
9746
  }
9322
9747
 
9323
9748
  // src/PaywalloProvider.tsx
9324
- var import_react25 = require("react");
9325
- var import_react_native25 = require("react-native");
9749
+ var import_react27 = require("react");
9750
+ var import_react_native29 = require("react-native");
9326
9751
  init_PaywalloClient();
9752
+ init_NativeStorage();
9327
9753
  init_SecureStorage();
9328
9754
  init_campaign();
9329
9755
  init_paywall();
9330
9756
  init_paywall();
9331
9757
  init_paywall();
9332
9758
  init_paywall();
9759
+ init_paywall();
9760
+ init_paywall();
9761
+
9762
+ // src/domains/paywall/SuperwallAutoBridge.ts
9763
+ init_PaywalloClient();
9764
+
9765
+ // src/core/utils/eventId.ts
9766
+ function hashString(input) {
9767
+ let h1 = 3735928559;
9768
+ let h2 = 1103547991;
9769
+ for (let i = 0; i < input.length; i++) {
9770
+ const ch = input.charCodeAt(i);
9771
+ h1 = Math.imul(h1 ^ ch, 2654435769);
9772
+ h2 = Math.imul(h2 ^ ch, 1598716949);
9773
+ }
9774
+ h1 = Math.imul(h1 ^ h1 >>> 16, 73244475);
9775
+ h1 ^= Math.imul(h2 ^ h2 >>> 13, 351452291);
9776
+ h2 = Math.imul(h2 ^ h2 >>> 16, 73244475);
9777
+ h2 ^= Math.imul(h1 ^ h1 >>> 13, 351452291);
9778
+ const toHex = (n) => (n >>> 0).toString(16).padStart(8, "0");
9779
+ return toHex(h1) + toHex(h2);
9780
+ }
9781
+ function buildDeterministicEventId(parts) {
9782
+ const raw = hashString(parts.join("|"));
9783
+ const p1 = raw.slice(0, 8);
9784
+ const p2 = raw.slice(8, 12);
9785
+ const p3 = "4" + raw.slice(13, 16);
9786
+ const p4 = (parseInt(raw[16], 16) & 3 | 8).toString(16) + raw.slice(17, 20);
9787
+ const p5 = raw.padEnd(32, "0").slice(20, 32);
9788
+ return `${p1}-${p2}-${p3}-${p4}-${p5}`;
9789
+ }
9790
+
9791
+ // src/domains/paywall/SuperwallAutoBridge.ts
9792
+ var bridgeStarted = false;
9793
+ var unsubscribe = null;
9794
+ var debugEnabled = false;
9795
+ function log(msg, data) {
9796
+ if (!debugEnabled) return;
9797
+ if (data !== void 0) {
9798
+ console.log(`[Paywallo:SuperwallBridge] ${msg}`, data);
9799
+ } else {
9800
+ console.log(`[Paywallo:SuperwallBridge] ${msg}`);
9801
+ }
9802
+ }
9803
+ function minuteBucket(ts) {
9804
+ return String(Math.floor(ts / 6e4));
9805
+ }
9806
+ function buildEventId(type, identifier, ts) {
9807
+ return buildDeterministicEventId(["sw", type, identifier, minuteBucket(ts)]);
9808
+ }
9809
+ async function trackPaywallEvent(type, identifier, ts, extra) {
9810
+ const _eventId = buildEventId(type, identifier, ts);
9811
+ try {
9812
+ const cleanExtra = {};
9813
+ if (extra) {
9814
+ for (const [k, v] of Object.entries(extra)) if (v !== void 0) cleanExtra[k] = v;
9815
+ }
9816
+ await PaywalloClient.track("paywall", {
9817
+ priority: "critical",
9818
+ properties: {
9819
+ type,
9820
+ paywall_id: identifier,
9821
+ ...cleanExtra
9822
+ }
9823
+ });
9824
+ } catch (err) {
9825
+ log("track error", { type, identifier, eventId: _eventId, err: String(err) });
9826
+ }
9827
+ }
9828
+ function mapDismissToCloseReason(resultType) {
9829
+ switch (resultType) {
9830
+ case "purchased":
9831
+ return "purchased";
9832
+ case "restored":
9833
+ return "purchased";
9834
+ // restore counts as a purchase outcome for funnel
9835
+ case "declined":
9836
+ return "dismissed";
9837
+ default:
9838
+ return void 0;
9839
+ }
9840
+ }
9841
+ function buildCallbacks() {
9842
+ return {
9843
+ onPaywallPresent: (info) => {
9844
+ const ts = Date.now();
9845
+ log("onPaywallPresent", { identifier: info.identifier });
9846
+ void trackPaywallEvent("viewed", info.identifier, ts, {
9847
+ placement: info.presentedByEventWithName,
9848
+ variant_id: info.experiment?.variant?.id,
9849
+ campaign_id: info.experiment?.id
9850
+ });
9851
+ },
9852
+ onPaywallDismiss: (info, result) => {
9853
+ const ts = Date.now();
9854
+ log("onPaywallDismiss", { identifier: info.identifier, result: result.type });
9855
+ void trackPaywallEvent("closed", info.identifier, ts, {
9856
+ placement: info.presentedByEventWithName,
9857
+ variant_id: info.experiment?.variant?.id,
9858
+ campaign_id: info.experiment?.id,
9859
+ close_reason: mapDismissToCloseReason(result.type),
9860
+ closed_at: new Date(ts).toISOString()
9861
+ });
9862
+ },
9863
+ onPurchase: (params) => {
9864
+ const ts = Date.now();
9865
+ const safe = params !== null && typeof params === "object" ? params : {};
9866
+ const productId = "productId" in safe && typeof safe.productId === "string" ? safe.productId : "unknown";
9867
+ const identifier = "paywallIdentifier" in safe && typeof safe.paywallIdentifier === "string" ? safe.paywallIdentifier : productId;
9868
+ log("onPurchase", { productId, identifier });
9869
+ void trackPaywallEvent("purchased", identifier, ts, { product_id: productId });
9870
+ },
9871
+ // Global Superwall event listener — used specifically for `transactionComplete`
9872
+ // which is the only event that exposes the full product (currency/price) +
9873
+ // transaction (storeTransactionId) shape needed to create a server-side
9874
+ // `transactions` row. Other events are skipped (paywallOpen/Dismiss already
9875
+ // handled above with their typed callbacks).
9876
+ onSuperwallEvent: (info) => {
9877
+ const ev = info?.event;
9878
+ if (!ev || ev.event !== "transactionComplete") return;
9879
+ const ts = Date.now();
9880
+ const product = ev.product;
9881
+ const transaction = ev.transaction;
9882
+ if (!product) {
9883
+ log("transactionComplete missing product \u2014 skipping transaction track");
9884
+ return;
9885
+ }
9886
+ const productId = product.productIdentifier ?? product.id ?? "unknown";
9887
+ const currency = product.currencyCode ?? "USD";
9888
+ const price = product.price ?? 0;
9889
+ const transactionId = transaction?.storeTransactionId ?? transaction?.originalTransactionIdentifier ?? `${productId}_${String(ts)}`;
9890
+ const isTrial = product.hasFreeTrial === true || (product.trialPeriodPrice ?? 0) > 0;
9891
+ const paywallIdentifier = ev.paywallInfo?.identifier;
9892
+ log("onSuperwallEvent: transactionComplete", { productId, currency, price, transactionId, isTrial });
9893
+ void (async () => {
9894
+ try {
9895
+ await PaywalloClient.track("transaction", {
9896
+ priority: "critical",
9897
+ properties: {
9898
+ type: isTrial ? "trial_started" : "completed",
9899
+ // Server's IngestTranslator looks for `transaction_id`/`product_id`
9900
+ // /`amount`/`currency`/`paywall_id` keys (not `tx_id`). PurchaseHandler
9901
+ // then resolves type=trial/new/renewal based on amount being 0 vs >0.
9902
+ transaction_id: transactionId,
9903
+ product_id: productId,
9904
+ amount: price,
9905
+ currency,
9906
+ ...paywallIdentifier ? { paywall_id: paywallIdentifier } : {}
9907
+ }
9908
+ });
9909
+ } catch (err) {
9910
+ log("transactionComplete track error", { err: String(err) });
9911
+ }
9912
+ })();
9913
+ }
9914
+ };
9915
+ }
9916
+ function startSuperwallAutoBridge(debug = false) {
9917
+ debugEnabled = debug;
9918
+ if (bridgeStarted) {
9919
+ log("already started, skipping");
9920
+ return;
9921
+ }
9922
+ void detectAndStart();
9923
+ }
9924
+ async function detectAndStart() {
9925
+ let subscribeFn;
9926
+ try {
9927
+ const mod = await import("expo-superwall");
9928
+ if (!mod || typeof mod.subscribeToSuperwallEvents !== "function") {
9929
+ const internal = await import("expo-superwall/build/src/internal/superwallEventBridge");
9930
+ if (typeof internal.subscribeToSuperwallEvents !== "function") {
9931
+ log("expo-superwall subscribeToSuperwallEvents not found \u2014 bridge inactive");
9932
+ return;
9933
+ }
9934
+ subscribeFn = internal.subscribeToSuperwallEvents;
9935
+ } else {
9936
+ subscribeFn = mod.subscribeToSuperwallEvents;
9937
+ }
9938
+ } catch {
9939
+ log("expo-superwall not installed \u2014 bridge inactive");
9940
+ return;
9941
+ }
9942
+ try {
9943
+ const callbacks = buildCallbacks();
9944
+ unsubscribe = subscribeFn(void 0, () => callbacks);
9945
+ bridgeStarted = true;
9946
+ log("bridge started");
9947
+ } catch (err) {
9948
+ log("subscribe failed", { err: String(err) });
9949
+ }
9950
+ }
9951
+
9952
+ // src/PaywalloProvider.tsx
9333
9953
  init_session();
9334
9954
  init_ClientError();
9335
9955
 
9336
9956
  // src/hooks/useAppAutoEvents.ts
9337
- var import_react20 = require("react");
9957
+ var import_react22 = require("react");
9338
9958
 
9339
9959
  // src/utils/platformDetection.ts
9340
9960
  async function detectPlatform3() {
@@ -9356,12 +9976,13 @@ async function detectOsVersion() {
9356
9976
  }
9357
9977
 
9358
9978
  // src/hooks/useAppAutoEvents.ts
9359
- var FIRST_SEEN_KEY = "@paywallo:firstSeen";
9979
+ var FIRST_SEEN_KEY = "@paywallo:firstSeen_v226";
9360
9980
  var DEBOUNCE_MS = 1e3;
9361
9981
  function useAppAutoEvents(config) {
9362
9982
  const { isInitialized, getAppVersion: getAppVersion2, getSessionId, emitLifecycle, storageGet, storageSet, debug } = config;
9363
- const didRunRef = (0, import_react20.useRef)(false);
9364
- (0, import_react20.useEffect)(() => {
9983
+ const didRunRef = (0, import_react22.useRef)(false);
9984
+ (0, import_react22.useEffect)(() => {
9985
+ if (debug) console.log("[Paywallo EVENTS] useEffect fired", { isInitialized, didRunRefCurrent: didRunRef.current });
9365
9986
  if (!isInitialized || didRunRef.current) return;
9366
9987
  const timer = setTimeout(() => {
9367
9988
  if (didRunRef.current) return;
@@ -9371,17 +9992,23 @@ function useAppAutoEvents(config) {
9371
9992
  const appVersion = getAppVersion2();
9372
9993
  const deviceType = await detectPlatform3();
9373
9994
  const osVersion = await detectOsVersion();
9995
+ if (debug) console.log("[Paywallo EVENTS] computed device context", { deviceType, osVersion, appVersion, sessionId, hasSessionId: !!sessionId });
9374
9996
  try {
9997
+ if (debug) console.log("[Paywallo EVENTS] checking install \u2014 about to read FIRST_SEEN_KEY");
9375
9998
  const firstSeen = await storageGet(FIRST_SEEN_KEY);
9376
9999
  if (!firstSeen) {
9377
10000
  await emitLifecycle({ type: "install", device_type: deviceType, os_version: osVersion, app_version: appVersion, ...sessionId ? { session_id: sessionId } : {} }, "critical");
9378
10001
  await storageSet(FIRST_SEEN_KEY, (/* @__PURE__ */ new Date()).toISOString());
10002
+ if (debug) console.log("[Paywallo EVENTS] install event emitted (first time)");
9379
10003
  if (debug) console.log("[Paywallo EVENTS] lifecycle install emitted");
10004
+ } else {
10005
+ if (debug) console.log("[Paywallo EVENTS] install skipped \u2014 firstSeen already exists", { firstSeen });
9380
10006
  }
9381
10007
  } catch (err) {
9382
10008
  if (debug) console.log("[Paywallo EVENTS] lifecycle install check failed", err);
9383
10009
  }
9384
10010
  try {
10011
+ if (debug) console.log("[Paywallo EVENTS] emitting cold_start");
9385
10012
  await emitLifecycle({ type: "cold_start", app_version: appVersion, device_type: deviceType, os_version: osVersion, ...sessionId ? { session_id: sessionId } : {} }, "normal");
9386
10013
  if (debug) console.log("[Paywallo EVENTS] lifecycle cold_start emitted");
9387
10014
  } catch (err) {
@@ -9394,7 +10021,7 @@ function useAppAutoEvents(config) {
9394
10021
  }
9395
10022
 
9396
10023
  // src/hooks/useCampaignPreload.ts
9397
- var import_react21 = require("react");
10024
+ var import_react23 = require("react");
9398
10025
  init_PaywalloClient();
9399
10026
  init_CampaignError();
9400
10027
 
@@ -9465,17 +10092,17 @@ function getCacheEntry(cache, placement) {
9465
10092
 
9466
10093
  // src/hooks/useCampaignPreload.ts
9467
10094
  function useCampaignPreload() {
9468
- const [preloadedWebView, setPreloadedWebView] = (0, import_react21.useState)(null);
9469
- const preloadedCampaignsRef = (0, import_react21.useRef)(/* @__PURE__ */ new Map());
9470
- const isPreloadValid = (0, import_react21.useCallback)(
10095
+ const [preloadedWebView, setPreloadedWebView] = (0, import_react23.useState)(null);
10096
+ const preloadedCampaignsRef = (0, import_react23.useRef)(/* @__PURE__ */ new Map());
10097
+ const isPreloadValid = (0, import_react23.useCallback)(
9471
10098
  (placement) => isCacheEntryValid(preloadedCampaignsRef.current.get(placement)),
9472
10099
  []
9473
10100
  );
9474
- const consumePreloadedCampaign = (0, import_react21.useCallback)(
10101
+ const consumePreloadedCampaign = (0, import_react23.useCallback)(
9475
10102
  (placement) => getCacheEntry(preloadedCampaignsRef.current, placement),
9476
10103
  []
9477
10104
  );
9478
- const preloadCampaign = (0, import_react21.useCallback)(
10105
+ const preloadCampaign = (0, import_react23.useCallback)(
9479
10106
  async (placement, context) => {
9480
10107
  try {
9481
10108
  const campaign = await PaywalloClient.getCampaign(placement, context);
@@ -9544,11 +10171,11 @@ function useCampaignPreload() {
9544
10171
  }
9545
10172
 
9546
10173
  // src/hooks/useProductLoader.ts
9547
- var import_react22 = require("react");
10174
+ var import_react24 = require("react");
9548
10175
  function useProductLoader() {
9549
- const [products, setProducts] = (0, import_react22.useState)(/* @__PURE__ */ new Map());
9550
- const [isLoadingProducts, setIsLoadingProducts] = (0, import_react22.useState)(false);
9551
- const refreshProducts = (0, import_react22.useCallback)(async (productIds) => {
10176
+ const [products, setProducts] = (0, import_react24.useState)(/* @__PURE__ */ new Map());
10177
+ const [isLoadingProducts, setIsLoadingProducts] = (0, import_react24.useState)(false);
10178
+ const refreshProducts = (0, import_react24.useCallback)(async (productIds) => {
9552
10179
  setIsLoadingProducts(true);
9553
10180
  try {
9554
10181
  const iapService = getIAPService();
@@ -9566,11 +10193,11 @@ function useProductLoader() {
9566
10193
  }
9567
10194
 
9568
10195
  // src/hooks/usePurchaseOrchestrator.ts
9569
- var import_react23 = require("react");
10196
+ var import_react25 = require("react");
9570
10197
  init_paywall();
9571
10198
  function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, invalidateSubscriptionCache) {
9572
10199
  const { trackDismissed, trackProductSelected, trackPurchased } = usePaywallTracking();
9573
- const handlePreloadedPurchase = (0, import_react23.useCallback)(
10200
+ const handlePreloadedPurchase = (0, import_react25.useCallback)(
9574
10201
  async (productId) => {
9575
10202
  try {
9576
10203
  if (preloadedWebView) {
@@ -9608,7 +10235,7 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
9608
10235
  },
9609
10236
  [preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed, trackProductSelected, trackPurchased, invalidateSubscriptionCache]
9610
10237
  );
9611
- const handlePreloadedRestore = (0, import_react23.useCallback)(async () => {
10238
+ const handlePreloadedRestore = (0, import_react25.useCallback)(async () => {
9612
10239
  try {
9613
10240
  const transactions = await getIAPService().restore();
9614
10241
  if (transactions.length === 0) return;
@@ -9631,7 +10258,7 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
9631
10258
  }
9632
10259
 
9633
10260
  // src/hooks/useSubscriptionSync.ts
9634
- var import_react24 = require("react");
10261
+ var import_react26 = require("react");
9635
10262
  init_PaywalloClient();
9636
10263
  init_SubscriptionCache();
9637
10264
 
@@ -9696,9 +10323,9 @@ function subscriptionInfoToSub(s) {
9696
10323
  }
9697
10324
  async function resolveOfflineFromCache(distinctId) {
9698
10325
  try {
9699
- const cached = await subscriptionCache.get(distinctId);
9700
- if (!cached) return false;
9701
- const sub = cached.data.subscription;
10326
+ const cached2 = await subscriptionCache.get(distinctId);
10327
+ if (!cached2) return false;
10328
+ const sub = cached2.data.subscription;
9702
10329
  return isSubscriptionStillActive(
9703
10330
  sub ? { status: sub.status, expiresAt: sub.expiresAt ?? null } : null
9704
10331
  );
@@ -9741,18 +10368,18 @@ async function checkPersistedCache(distinctId, onHit) {
9741
10368
 
9742
10369
  // src/hooks/useSubscriptionSync.ts
9743
10370
  function useSubscriptionSync() {
9744
- const [subscription, setSubscription] = (0, import_react24.useState)(null);
9745
- const cacheRef = (0, import_react24.useRef)(null);
9746
- const setCacheAndState = (0, import_react24.useCallback)((sub) => {
10371
+ const [subscription, setSubscription] = (0, import_react26.useState)(null);
10372
+ const cacheRef = (0, import_react26.useRef)(null);
10373
+ const setCacheAndState = (0, import_react26.useCallback)((sub) => {
9747
10374
  cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
9748
10375
  setSubscription(sub);
9749
10376
  }, []);
9750
- const invalidateCache = (0, import_react24.useCallback)(() => {
10377
+ const invalidateCache = (0, import_react26.useCallback)(() => {
9751
10378
  cacheRef.current = null;
9752
10379
  const distinctId = PaywalloClient.getDistinctId();
9753
10380
  if (distinctId) void subscriptionCache.invalidate(distinctId);
9754
10381
  }, []);
9755
- const fetchAndCache = (0, import_react24.useCallback)(async () => {
10382
+ const fetchAndCache = (0, import_react26.useCallback)(async () => {
9756
10383
  const apiClient = PaywalloClient.getApiClient();
9757
10384
  const distinctId = PaywalloClient.getDistinctId();
9758
10385
  if (!apiClient || !distinctId) return null;
@@ -9762,14 +10389,14 @@ function useSubscriptionSync() {
9762
10389
  void subscriptionCache.set(distinctId, toDomainResponse(status));
9763
10390
  return sub;
9764
10391
  }, [setCacheAndState]);
9765
- const hasActiveSubscription = (0, import_react24.useCallback)(async () => {
10392
+ const hasActiveSubscription = (0, import_react26.useCallback)(async () => {
9766
10393
  const apiClient = PaywalloClient.getApiClient();
9767
10394
  const distinctId = PaywalloClient.getDistinctId();
9768
10395
  if (!apiClient || !distinctId) return false;
9769
10396
  const storeKitResult = await checkStoreKitActive(PaywalloClient.getConfig()?.debug);
9770
10397
  if (storeKitResult !== null) return storeKitResult;
9771
- const cached = cacheRef.current;
9772
- if (cached && cached.expiresAt > Date.now()) return isActiveCacheEntry(cached);
10398
+ const cached2 = cacheRef.current;
10399
+ if (cached2 && cached2.expiresAt > Date.now()) return isActiveCacheEntry(cached2);
9773
10400
  const persistedResult = await checkPersistedCache(distinctId, (sub) => setCacheAndState(sub));
9774
10401
  if (persistedResult !== null) return persistedResult;
9775
10402
  try {
@@ -9782,12 +10409,12 @@ function useSubscriptionSync() {
9782
10409
  return resolveOfflineFromCache(distinctId);
9783
10410
  }
9784
10411
  }, [setCacheAndState]);
9785
- const getSubscription = (0, import_react24.useCallback)(async () => {
10412
+ const getSubscription = (0, import_react26.useCallback)(async () => {
9786
10413
  const apiClient = PaywalloClient.getApiClient();
9787
10414
  const distinctId = PaywalloClient.getDistinctId();
9788
10415
  if (!apiClient || !distinctId) return null;
9789
- const cached = cacheRef.current;
9790
- if (cached && cached.expiresAt > Date.now()) return cached.data;
10416
+ const cached2 = cacheRef.current;
10417
+ if (cached2 && cached2.expiresAt > Date.now()) return cached2.data;
9791
10418
  try {
9792
10419
  return await fetchAndCache();
9793
10420
  } catch {
@@ -9805,22 +10432,22 @@ function useSubscriptionSync() {
9805
10432
 
9806
10433
  // src/PaywalloProvider.tsx
9807
10434
  init_localization();
9808
- var import_jsx_runtime3 = require("react/jsx-runtime");
10435
+ var import_jsx_runtime5 = require("react/jsx-runtime");
9809
10436
  function PaywalloProvider({ children, config }) {
9810
- const [isInitialized, setIsInitialized] = (0, import_react25.useState)(false);
9811
- const [initError, setInitError] = (0, import_react25.useState)(null);
9812
- const [distinctId, setDistinctId] = (0, import_react25.useState)(null);
9813
- const [activePaywall, setActivePaywall] = (0, import_react25.useState)(null);
10437
+ const [isInitialized, setIsInitialized] = (0, import_react27.useState)(false);
10438
+ const [initError, setInitError] = (0, import_react27.useState)(null);
10439
+ const [distinctId, setDistinctId] = (0, import_react27.useState)(null);
10440
+ const [activePaywall, setActivePaywall] = (0, import_react27.useState)(null);
9814
10441
  const { products, isLoadingProducts, refreshProducts } = useProductLoader();
9815
10442
  const { subscription, hasActiveSubscription, getSubscription, invalidateCache } = useSubscriptionSync();
9816
10443
  const { preloadedWebView, setPreloadedWebView, preloadCampaign, consumePreloadedCampaign, isPreloadValid } = useCampaignPreload();
9817
- const clearPreloadedWebView = (0, import_react25.useCallback)(() => setPreloadedWebView(null), [setPreloadedWebView]);
9818
- const autoPreloadTriggeredRef = (0, import_react25.useRef)(false);
9819
- const preloadedWebViewRef = (0, import_react25.useRef)(preloadedWebView);
9820
- const pollTimerRef = (0, import_react25.useRef)(null);
9821
- const pollCancelledRef = (0, import_react25.useRef)(false);
9822
- const secureStorageRef = (0, import_react25.useRef)(new SecureStorage(config.debug));
9823
- (0, import_react25.useEffect)(() => {
10444
+ const clearPreloadedWebView = (0, import_react27.useCallback)(() => setPreloadedWebView(null), [setPreloadedWebView]);
10445
+ const autoPreloadTriggeredRef = (0, import_react27.useRef)(false);
10446
+ const preloadedWebViewRef = (0, import_react27.useRef)(preloadedWebView);
10447
+ const pollTimerRef = (0, import_react27.useRef)(null);
10448
+ const pollCancelledRef = (0, import_react27.useRef)(false);
10449
+ const secureStorageRef = (0, import_react27.useRef)(new SecureStorage(config.debug));
10450
+ (0, import_react27.useEffect)(() => {
9824
10451
  preloadedWebViewRef.current = preloadedWebView;
9825
10452
  }, [preloadedWebView]);
9826
10453
  useAppAutoEvents({
@@ -9832,26 +10459,36 @@ function PaywalloProvider({ children, config }) {
9832
10459
  for (const [k, v] of Object.entries(props)) if (v !== void 0) flat[k] = v;
9833
10460
  return PaywalloClient.track("lifecycle", { properties: flat, priority });
9834
10461
  },
9835
- storageGet: (key) => secureStorageRef.current.get(key),
9836
- storageSet: (key, value) => secureStorageRef.current.set(key, value),
10462
+ storageGet: async (key) => {
10463
+ if (key === "@paywallo:firstSeen_v226") {
10464
+ return nativeStorage.get(key);
10465
+ }
10466
+ return secureStorageRef.current.get(key);
10467
+ },
10468
+ storageSet: async (key, value) => {
10469
+ if (key === "@paywallo:firstSeen_v226") {
10470
+ return nativeStorage.set(key, value);
10471
+ }
10472
+ return secureStorageRef.current.set(key, value);
10473
+ },
9837
10474
  debug: config.debug
9838
10475
  });
9839
- const triggerRepreload = (0, import_react25.useCallback)((placement) => {
10476
+ const triggerRepreload = (0, import_react27.useCallback)((placement) => {
9840
10477
  void PaywalloClient.preloadCampaign(placement).catch(() => {
9841
10478
  });
9842
10479
  }, []);
9843
10480
  const { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady: baseHandlePreloadedWebViewReady, handlePreloadedClose: baseHandlePreloadedClose, setShowingPreloadedWebView, presentedAtRef } = usePaywallPresenter(preloadedWebView, setPreloadedWebView);
9844
- const SCREEN_HEIGHT = (0, import_react25.useMemo)(() => import_react_native25.Dimensions.get("window").height, []);
9845
- const slideAnim = (0, import_react25.useRef)(new import_react_native25.Animated.Value(SCREEN_HEIGHT)).current;
9846
- const handlePreloadedWebViewReady = (0, import_react25.useCallback)(() => {
10481
+ const SCREEN_HEIGHT = (0, import_react27.useMemo)(() => import_react_native29.Dimensions.get("window").height, []);
10482
+ const slideAnim = (0, import_react27.useRef)(new import_react_native29.Animated.Value(SCREEN_HEIGHT)).current;
10483
+ const handlePreloadedWebViewReady = (0, import_react27.useCallback)(() => {
9847
10484
  if (PaywalloClient.getConfig()?.debug) {
9848
10485
  console.log(`[Paywallo WEBVIEW] loaded \u2014 placement: ${preloadedWebView?.placement}`);
9849
10486
  }
9850
10487
  baseHandlePreloadedWebViewReady();
9851
10488
  }, [preloadedWebView?.placement, baseHandlePreloadedWebViewReady]);
9852
- const handlePreloadedClose = (0, import_react25.useCallback)(() => {
10489
+ const handlePreloadedClose = (0, import_react27.useCallback)(() => {
9853
10490
  const placement = preloadedWebView?.placement;
9854
- import_react_native25.Animated.timing(slideAnim, { toValue: SCREEN_HEIGHT, duration: 200, easing: import_react_native25.Easing.in(import_react_native25.Easing.cubic), useNativeDriver: true }).start(() => {
10491
+ 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(() => {
9855
10492
  baseHandlePreloadedClose();
9856
10493
  if (placement) triggerRepreload(placement);
9857
10494
  });
@@ -9864,8 +10501,8 @@ function PaywalloProvider({ children, config }) {
9864
10501
  presentedAtRef,
9865
10502
  invalidateCache
9866
10503
  );
9867
- const [isPreloadPurchasing, setIsPreloadPurchasing] = (0, import_react25.useState)(false);
9868
- const handlePreloadedPurchase = (0, import_react25.useCallback)(async (productId) => {
10504
+ const [isPreloadPurchasing, setIsPreloadPurchasing] = (0, import_react27.useState)(false);
10505
+ const handlePreloadedPurchase = (0, import_react27.useCallback)(async (productId) => {
9869
10506
  setIsPreloadPurchasing(true);
9870
10507
  try {
9871
10508
  await rawPreloadedPurchase(productId);
@@ -9873,19 +10510,25 @@ function PaywalloProvider({ children, config }) {
9873
10510
  setIsPreloadPurchasing(false);
9874
10511
  }
9875
10512
  }, [rawPreloadedPurchase]);
9876
- const configRef = (0, import_react25.useRef)(config);
9877
- (0, import_react25.useEffect)(() => {
10513
+ const configRef = (0, import_react27.useRef)(config);
10514
+ (0, import_react27.useEffect)(() => {
9878
10515
  let mounted = true;
9879
10516
  initLocalization({ detectDevice: true });
9880
10517
  PaywalloClient.init(configRef.current).then(async () => {
9881
10518
  if (!mounted) return;
9882
10519
  setDistinctId(PaywalloClient.getDistinctId());
10520
+ startSuperwallAutoBridge(configRef.current.debug);
9883
10521
  setIsInitialized(true);
9884
10522
  if (!autoPreloadTriggeredRef.current) {
9885
10523
  autoPreloadTriggeredRef.current = true;
9886
10524
  const placement = await PaywalloClient.waitForAutoPreloadedPlacement();
9887
10525
  if (mounted && placement) void preloadCampaign(placement).catch(() => {
9888
10526
  });
10527
+ const preloadPlacements = configRef.current.preloadPaywalls;
10528
+ if (mounted && preloadPlacements && preloadPlacements.length > 0) {
10529
+ void PaywalloClient.preloadPaywalls(preloadPlacements).catch(() => {
10530
+ });
10531
+ }
9889
10532
  }
9890
10533
  }).catch((err) => {
9891
10534
  if (!mounted) return;
@@ -9895,14 +10538,28 @@ function PaywalloProvider({ children, config }) {
9895
10538
  mounted = false;
9896
10539
  };
9897
10540
  }, [preloadCampaign]);
9898
- const getPaywallConfig = (0, import_react25.useCallback)(async (p) => {
10541
+ const getPaywallConfig = (0, import_react27.useCallback)(async (p) => {
9899
10542
  const api = PaywalloClient.getApiClient();
9900
10543
  return api ? api.getPaywall(p) : null;
9901
10544
  }, []);
9902
- const presentPaywall = (0, import_react25.useCallback)((placement) => {
9903
- return new Promise((resolve) => setActivePaywall({ placement, resolver: resolve }));
10545
+ const presentPaywall = (0, import_react27.useCallback)((placement) => {
10546
+ const preloaded = paywallPreloadService.getPreloaded(placement);
10547
+ return new Promise((resolve) => setActivePaywall({
10548
+ placement,
10549
+ resolver: resolve,
10550
+ ...preloaded && {
10551
+ paywallConfig: {
10552
+ id: preloaded.config.id,
10553
+ placement: preloaded.config.placement,
10554
+ config: preloaded.config.config,
10555
+ primaryProductId: preloaded.config.primaryProductId,
10556
+ secondaryProductId: preloaded.config.secondaryProductId
10557
+ },
10558
+ preloadedProducts: preloaded.products
10559
+ }
10560
+ }));
9904
10561
  }, []);
9905
- const trackCampaignImpression = (0, import_react25.useCallback)((placement, variantKey, campaignId) => {
10562
+ const trackCampaignImpression = (0, import_react27.useCallback)((placement, variantKey, campaignId) => {
9906
10563
  const api = PaywalloClient.getApiClient();
9907
10564
  if (!api) return;
9908
10565
  const sessionId = PaywalloClient.getSessionId();
@@ -9916,7 +10573,7 @@ function PaywalloProvider({ children, config }) {
9916
10573
  }).catch(() => {
9917
10574
  });
9918
10575
  }, []);
9919
- const presentCampaign = (0, import_react25.useCallback)(
10576
+ const presentCampaign = (0, import_react27.useCallback)(
9920
10577
  async (placement, context) => {
9921
10578
  try {
9922
10579
  if (preloadedWebView?.placement === placement && preloadedWebView.loaded && isPreloadValid(placement)) {
@@ -9990,7 +10647,7 @@ function PaywalloProvider({ children, config }) {
9990
10647
  },
9991
10648
  [preloadedWebView, isPreloadValid, consumePreloadedCampaign, presentPreloadedWebView, trackCampaignImpression]
9992
10649
  );
9993
- const restorePurchases = (0, import_react25.useCallback)(async () => {
10650
+ const restorePurchases = (0, import_react27.useCallback)(async () => {
9994
10651
  invalidateCache();
9995
10652
  try {
9996
10653
  const txs = await getIAPService().restore();
@@ -9999,13 +10656,13 @@ function PaywalloProvider({ children, config }) {
9999
10656
  return { success: false, restoredProducts: [], error: error instanceof Error ? error : new PurchaseError(PURCHASE_ERROR_CODES.RESTORE_FAILED, String(error)) };
10000
10657
  }
10001
10658
  }, [invalidateCache]);
10002
- const handlePaywallResult = (0, import_react25.useCallback)((result) => {
10659
+ const handlePaywallResult = (0, import_react27.useCallback)((result) => {
10003
10660
  if (activePaywall) {
10004
10661
  activePaywall.resolver(result);
10005
10662
  setActivePaywall(null);
10006
10663
  }
10007
10664
  }, [activePaywall]);
10008
- const bridgePaywallPresenter = (0, import_react25.useCallback)((placement) => {
10665
+ const bridgePaywallPresenter = (0, import_react27.useCallback)((placement) => {
10009
10666
  if (PaywalloClient.getConfig()?.debug) {
10010
10667
  console.log(`[Paywallo PRESENT] called \u2014 preloaded: ${preloadedWebViewRef.current?.loaded === true} placement: ${placement}`);
10011
10668
  }
@@ -10034,7 +10691,7 @@ function PaywalloProvider({ children, config }) {
10034
10691
  }
10035
10692
  return presentCampaign(placement);
10036
10693
  }, [presentPreloadedWebView, presentCampaign]);
10037
- (0, import_react25.useEffect)(() => {
10694
+ (0, import_react27.useEffect)(() => {
10038
10695
  if (!isInitialized) return;
10039
10696
  const onEmergency = async (id) => {
10040
10697
  try {
@@ -10061,9 +10718,9 @@ function PaywalloProvider({ children, config }) {
10061
10718
  };
10062
10719
  }, [isInitialized, bridgePaywallPresenter, getSubscription, hasActiveSubscription, restorePurchases]);
10063
10720
  const isReady = isInitialized && !isLoadingProducts;
10064
- const noOp = (0, import_react25.useCallback)(() => {
10721
+ const noOp = (0, import_react27.useCallback)(() => {
10065
10722
  }, []);
10066
- const contextValue = (0, import_react25.useMemo)(() => ({
10723
+ const contextValue = (0, import_react27.useMemo)(() => ({
10067
10724
  config: PaywalloClient.getConfig(),
10068
10725
  isInitialized,
10069
10726
  isReady,
@@ -10097,20 +10754,20 @@ function PaywalloProvider({ children, config }) {
10097
10754
  getPaywallConfig,
10098
10755
  refreshProducts
10099
10756
  ]);
10100
- (0, import_react25.useLayoutEffect)(() => {
10757
+ (0, import_react27.useLayoutEffect)(() => {
10101
10758
  if (showingPreloadedWebView) {
10102
10759
  slideAnim.setValue(SCREEN_HEIGHT);
10103
- import_react_native25.Animated.timing(slideAnim, { toValue: 0, duration: 200, easing: import_react_native25.Easing.out(import_react_native25.Easing.cubic), useNativeDriver: true }).start();
10760
+ import_react_native29.Animated.timing(slideAnim, { toValue: 0, duration: 200, easing: import_react_native29.Easing.out(import_react_native29.Easing.cubic), useNativeDriver: true }).start();
10104
10761
  }
10105
10762
  }, [showingPreloadedWebView, slideAnim, SCREEN_HEIGHT]);
10106
10763
  const preloadStyle = [
10107
- styles3.preloadOverlay,
10108
- showingPreloadedWebView ? styles3.visible : styles3.hidden,
10764
+ styles5.preloadOverlay,
10765
+ showingPreloadedWebView ? styles5.visible : styles5.hidden,
10109
10766
  showingPreloadedWebView ? { transform: [{ translateY: slideAnim }] } : void 0
10110
10767
  ];
10111
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(PaywalloContext.Provider, { value: contextValue, children: [
10768
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(PaywalloContext.Provider, { value: contextValue, children: [
10112
10769
  children,
10113
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PaywallErrorBoundary, { children: !!preloadedWebView?.craftData && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native25.Animated.View, { style: preloadStyle, pointerEvents: showingPreloadedWebView ? "auto" : "none", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
10770
+ /* @__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)(
10114
10771
  PaywallWebView,
10115
10772
  {
10116
10773
  paywallId: preloadedWebView.paywallId,
@@ -10126,7 +10783,7 @@ function PaywalloProvider({ children, config }) {
10126
10783
  onRestore: showingPreloadedWebView ? handlePreloadedRestore : noOp
10127
10784
  }
10128
10785
  ) }) }),
10129
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PaywallErrorBoundary, { children: activePaywall && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
10786
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PaywallErrorBoundary, { children: activePaywall && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
10130
10787
  PaywallModal,
10131
10788
  {
10132
10789
  placement: activePaywall.placement,
@@ -10138,10 +10795,11 @@ function PaywalloProvider({ children, config }) {
10138
10795
  campaignId: activePaywall.campaignId,
10139
10796
  variantId: activePaywall.variantId
10140
10797
  }
10141
- ) })
10798
+ ) }),
10799
+ isInitialized && (config.preloadPaywalls?.length ?? 0) > 0 && PaywalloClient.getWebUrl() !== null && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PaywallWebViewPrewarmer, { webUrl: PaywalloClient.getWebUrl() })
10142
10800
  ] });
10143
10801
  }
10144
- var styles3 = import_react_native25.StyleSheet.create({
10802
+ var styles5 = import_react_native29.StyleSheet.create({
10145
10803
  preloadOverlay: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, backgroundColor: "#ffffff" },
10146
10804
  hidden: { zIndex: -1, opacity: 0, width: 1, height: 1, overflow: "hidden" },
10147
10805
  visible: { zIndex: 9999, opacity: 1 }