@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.mjs CHANGED
@@ -814,9 +814,9 @@ var init_CampaignGateService = __esm({
814
814
  }
815
815
  }
816
816
  getPreloadedCampaign(placement) {
817
- const cached = this.preloadedCampaigns.get(placement);
818
- if (!cached) return null;
819
- const age = Date.now() - cached.preloadedAt;
817
+ const cached2 = this.preloadedCampaigns.get(placement);
818
+ if (!cached2) return null;
819
+ const age = Date.now() - cached2.preloadedAt;
820
820
  if (age >= PRELOAD_CACHE_TTL_MS) {
821
821
  this.preloadedCampaigns.delete(placement);
822
822
  return null;
@@ -825,7 +825,7 @@ var init_CampaignGateService = __esm({
825
825
  void this.preloadCampaign(this.apiClientRef, placement).catch(() => {
826
826
  });
827
827
  }
828
- return cached.campaign;
828
+ return cached2.campaign;
829
829
  }
830
830
  async preloadAllActive(apiClient, debug) {
831
831
  this.apiClientRef = apiClient;
@@ -2157,7 +2157,7 @@ function extractPayload(data) {
2157
2157
  }
2158
2158
  function setupForegroundHandler(deps, onReceive) {
2159
2159
  const { bridge, tracker, debug } = deps;
2160
- const unsubscribe = bridge.onMessage(async (message) => {
2160
+ const unsubscribe2 = bridge.onMessage(async (message) => {
2161
2161
  const data = message.data ?? {};
2162
2162
  const payload = extractPayload({
2163
2163
  ...data,
@@ -2175,7 +2175,7 @@ function setupForegroundHandler(deps, onReceive) {
2175
2175
  messageId
2176
2176
  });
2177
2177
  });
2178
- return unsubscribe;
2178
+ return unsubscribe2;
2179
2179
  }
2180
2180
  var init_ForegroundHandler = __esm({
2181
2181
  "src/domains/notifications/handlers/ForegroundHandler.ts"() {
@@ -2370,8 +2370,8 @@ var init_NotificationHandlerSetup = __esm({
2370
2370
  // src/core/version.ts
2371
2371
  function resolveVersion() {
2372
2372
  try {
2373
- if ("2.2.5") {
2374
- return "2.2.5";
2373
+ if ("2.2.7") {
2374
+ return "2.2.7";
2375
2375
  }
2376
2376
  } catch {
2377
2377
  }
@@ -2444,9 +2444,6 @@ function mapPermissionStatus(status) {
2444
2444
  function detectPlatform2() {
2445
2445
  return Platform6.OS === "ios" ? "ios" : "android";
2446
2446
  }
2447
- function detectEnvironment() {
2448
- return typeof __DEV__ !== "undefined" && __DEV__ ? "sandbox" : "production";
2449
- }
2450
2447
  async function getAppVersion() {
2451
2448
  try {
2452
2449
  const { collectDeviceInfo: collectDeviceInfo2 } = await Promise.resolve().then(() => (init_deviceInfo(), deviceInfo_exports));
@@ -2486,7 +2483,7 @@ async function clearPersistedToken(secureStorage2) {
2486
2483
  } catch {
2487
2484
  }
2488
2485
  }
2489
- async function buildRegistration(token, getDeviceId, getDistinctId) {
2486
+ async function buildRegistration(token, getDeviceId, getDistinctId, environment) {
2490
2487
  const appVersion = await getAppVersion();
2491
2488
  return {
2492
2489
  token,
@@ -2497,7 +2494,7 @@ async function buildRegistration(token, getDeviceId, getDistinctId) {
2497
2494
  deviceId: getDeviceId?.() ?? void 0,
2498
2495
  locale: await getLocale(),
2499
2496
  timezone: getTimezone2(),
2500
- environment: detectEnvironment()
2497
+ environment
2501
2498
  };
2502
2499
  }
2503
2500
  async function registerToken(deps, token) {
@@ -2506,7 +2503,8 @@ async function registerToken(deps, token) {
2506
2503
  if (debug) console.log("[Paywallo PUSH] httpClient not ready, skipping token registration");
2507
2504
  return;
2508
2505
  }
2509
- const registration = await buildRegistration(token, getDeviceId, getDistinctId);
2506
+ const { environment } = deps;
2507
+ const registration = await buildRegistration(token, getDeviceId, getDistinctId, environment);
2510
2508
  try {
2511
2509
  await httpClient.post(
2512
2510
  TOKEN_ENDPOINT,
@@ -2556,7 +2554,8 @@ async function registerAndPersistToken(deps, token) {
2556
2554
  appKey: deps.appKey,
2557
2555
  getDeviceId: deps.getDeviceId,
2558
2556
  getDistinctId: deps.getDistinctId,
2559
- debug: deps.debug
2557
+ debug: deps.debug,
2558
+ environment: deps.environment
2560
2559
  },
2561
2560
  token
2562
2561
  );
@@ -2573,7 +2572,8 @@ function subscribeTokenRefresh(bridge, deps, onToken) {
2573
2572
  appKey: deps.appKey,
2574
2573
  getDeviceId: deps.getDeviceId,
2575
2574
  getDistinctId: deps.getDistinctId,
2576
- debug: deps.debug
2575
+ debug: deps.debug,
2576
+ environment: deps.environment
2577
2577
  },
2578
2578
  newToken
2579
2579
  ).catch(() => void 0);
@@ -2781,6 +2781,7 @@ var init_NotificationsManager = __esm({
2781
2781
  this.getDistinctId = null;
2782
2782
  this.appKey = null;
2783
2783
  this.apiBaseUrl = null;
2784
+ this.environment = "production";
2784
2785
  this.tokenRefreshCleanup = null;
2785
2786
  this.foregroundCleanup = null;
2786
2787
  this.pendingHandlersConfig = null;
@@ -2800,6 +2801,7 @@ var init_NotificationsManager = __esm({
2800
2801
  this.secureStorage = deps.secureStorage;
2801
2802
  this.getDeviceId = deps.getDeviceId;
2802
2803
  if (deps.getDistinctId) this.getDistinctId = deps.getDistinctId;
2804
+ if (deps.environment) this.environment = deps.environment;
2803
2805
  }
2804
2806
  async initialize(config) {
2805
2807
  this.applyConfig(config);
@@ -2842,7 +2844,8 @@ var init_NotificationsManager = __esm({
2842
2844
  appKey: this.appKey,
2843
2845
  getDeviceId: this.getDeviceId,
2844
2846
  getDistinctId: this.getDistinctId,
2845
- debug: this.debug
2847
+ debug: this.debug,
2848
+ environment: this.environment
2846
2849
  };
2847
2850
  }
2848
2851
  async doRegisterToken(token) {
@@ -3038,6 +3041,41 @@ var init_emitPaywallClosed = __esm({
3038
3041
  }
3039
3042
  });
3040
3043
 
3044
+ // src/domains/paywall/extractFirstPageBackground.ts
3045
+ function extractFirstPageBackground(craftData) {
3046
+ if (!craftData) return DEFAULT_BACKGROUND;
3047
+ try {
3048
+ const data = JSON.parse(craftData);
3049
+ const nodes = { ...data.nodes, ...data.sharedNodes };
3050
+ const rootId = data.rootId;
3051
+ if (!rootId || !nodes[rootId]) return DEFAULT_BACKGROUND;
3052
+ const firstPageId = nodes[rootId].children?.[0];
3053
+ if (!firstPageId) return DEFAULT_BACKGROUND;
3054
+ const bg = nodes[firstPageId]?.props?.backgroundColor;
3055
+ return typeof bg === "string" && bg.length > 0 && bg !== "transparent" ? bg : DEFAULT_BACKGROUND;
3056
+ } catch {
3057
+ return DEFAULT_BACKGROUND;
3058
+ }
3059
+ }
3060
+ function isColorDark(color) {
3061
+ const hex = color.replace("#", "").trim();
3062
+ const normalized = hex.length === 3 ? hex.split("").map((c) => c + c).join("") : hex;
3063
+ if (normalized.length !== 6) return false;
3064
+ const r = parseInt(normalized.slice(0, 2), 16);
3065
+ const g = parseInt(normalized.slice(2, 4), 16);
3066
+ const b = parseInt(normalized.slice(4, 6), 16);
3067
+ if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) return false;
3068
+ const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
3069
+ return luminance < 0.5;
3070
+ }
3071
+ var DEFAULT_BACKGROUND;
3072
+ var init_extractFirstPageBackground = __esm({
3073
+ "src/domains/paywall/extractFirstPageBackground.ts"() {
3074
+ "use strict";
3075
+ DEFAULT_BACKGROUND = "#FFFFFF";
3076
+ }
3077
+ });
3078
+
3041
3079
  // src/domains/paywall/parseWebViewMessage.ts
3042
3080
  function deriveMessageId(message) {
3043
3081
  if (message.id) return message.id;
@@ -3280,6 +3318,66 @@ var init_localization = __esm({
3280
3318
  }
3281
3319
  });
3282
3320
 
3321
+ // src/domains/paywall/PaywallSkeleton.tsx
3322
+ import { useEffect, useRef } from "react";
3323
+ import { Animated, StyleSheet, View } from "react-native";
3324
+ import { jsx, jsxs } from "react/jsx-runtime";
3325
+ function SkeletonBlock({ style, color, opacity }) {
3326
+ return /* @__PURE__ */ jsx(Animated.View, { style: [style, { backgroundColor: color, opacity }] });
3327
+ }
3328
+ function PaywallSkeleton({ backgroundColor = "#FFFFFF" }) {
3329
+ const pulse = useRef(new Animated.Value(0.4)).current;
3330
+ useEffect(() => {
3331
+ const loop = Animated.loop(
3332
+ Animated.sequence([
3333
+ Animated.timing(pulse, { toValue: 1, duration: 700, useNativeDriver: true }),
3334
+ Animated.timing(pulse, { toValue: 0.4, duration: 700, useNativeDriver: true })
3335
+ ])
3336
+ );
3337
+ loop.start();
3338
+ return () => loop.stop();
3339
+ }, [pulse]);
3340
+ const placeholder = isColorDark(backgroundColor) ? "rgba(255,255,255,0.13)" : "rgba(0,0,0,0.07)";
3341
+ return /* @__PURE__ */ jsxs(View, { style: [styles.container, { backgroundColor }], pointerEvents: "none", children: [
3342
+ /* @__PURE__ */ jsx(View, { style: styles.header, children: /* @__PURE__ */ jsx(SkeletonBlock, { style: styles.logo, color: placeholder, opacity: pulse }) }),
3343
+ /* @__PURE__ */ jsx(SkeletonBlock, { style: styles.hero, color: placeholder, opacity: pulse }),
3344
+ /* @__PURE__ */ jsxs(View, { style: styles.titleGroup, children: [
3345
+ /* @__PURE__ */ jsx(SkeletonBlock, { style: styles.titleLine, color: placeholder, opacity: pulse }),
3346
+ /* @__PURE__ */ jsx(SkeletonBlock, { style: styles.titleLineShort, color: placeholder, opacity: pulse })
3347
+ ] }),
3348
+ /* @__PURE__ */ jsxs(View, { style: styles.cardsRow, children: [
3349
+ /* @__PURE__ */ jsx(SkeletonBlock, { style: styles.card, color: placeholder, opacity: pulse }),
3350
+ /* @__PURE__ */ jsx(SkeletonBlock, { style: styles.card, color: placeholder, opacity: pulse })
3351
+ ] }),
3352
+ /* @__PURE__ */ jsxs(View, { style: styles.footerGroup, children: [
3353
+ /* @__PURE__ */ jsx(SkeletonBlock, { style: styles.cta, color: placeholder, opacity: pulse }),
3354
+ /* @__PURE__ */ jsx(SkeletonBlock, { style: styles.footer, color: placeholder, opacity: pulse })
3355
+ ] })
3356
+ ] });
3357
+ }
3358
+ var RADIUS, styles;
3359
+ var init_PaywallSkeleton = __esm({
3360
+ "src/domains/paywall/PaywallSkeleton.tsx"() {
3361
+ "use strict";
3362
+ init_extractFirstPageBackground();
3363
+ RADIUS = 14;
3364
+ styles = StyleSheet.create({
3365
+ card: { borderRadius: RADIUS, flex: 1, height: 120 },
3366
+ cardsRow: { flexDirection: "row", gap: 12, marginTop: "auto", paddingHorizontal: 24 },
3367
+ container: { flex: 1, paddingBottom: 40, paddingTop: 64 },
3368
+ cta: { borderRadius: 30, height: 58, marginHorizontal: 24 },
3369
+ footer: { alignSelf: "center", borderRadius: 6, height: 12, marginTop: 20, width: 180 },
3370
+ footerGroup: { marginTop: 28 },
3371
+ header: { alignItems: "center", paddingVertical: 12 },
3372
+ hero: { alignSelf: "center", borderRadius: 20, height: 220, marginTop: 16, width: "70%" },
3373
+ logo: { borderRadius: 6, height: 22, width: 110 },
3374
+ titleGroup: { alignItems: "center", gap: 12, marginTop: 28, paddingHorizontal: 24 },
3375
+ titleLine: { borderRadius: 8, height: 26, width: "85%" },
3376
+ titleLineShort: { borderRadius: 8, height: 26, width: "60%" }
3377
+ });
3378
+ }
3379
+ });
3380
+
3283
3381
  // src/domains/paywall/paywallScripts.ts
3284
3382
  function buildPaywallInjectionScript(data) {
3285
3383
  const paywallData = {
@@ -3324,7 +3422,7 @@ var init_paywallScripts = __esm({
3324
3422
  });
3325
3423
 
3326
3424
  // src/domains/paywall/PaywallWebView.tsx
3327
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3425
+ import { useCallback, useEffect as useEffect2, useMemo, useRef as useRef2, useState } from "react";
3328
3426
  import {
3329
3427
  BackHandler,
3330
3428
  findNodeHandle,
@@ -3333,11 +3431,11 @@ import {
3333
3431
  NativeModules as NativeModules6,
3334
3432
  Platform as Platform9,
3335
3433
  requireNativeComponent,
3336
- StyleSheet,
3434
+ StyleSheet as StyleSheet2,
3337
3435
  UIManager,
3338
- View
3436
+ View as View2
3339
3437
  } from "react-native";
3340
- import { jsx } from "react/jsx-runtime";
3438
+ import { jsx as jsx2 } from "react/jsx-runtime";
3341
3439
  function getNativeWebView() {
3342
3440
  if (NativeWebViewComponent) return NativeWebViewComponent;
3343
3441
  if (nativeWebViewLookupFailed) return null;
@@ -3395,16 +3493,16 @@ function PaywallWebView({
3395
3493
  onReady,
3396
3494
  onError
3397
3495
  }) {
3398
- const paywallDataSent = useRef(false);
3399
- const loadEndReceived = useRef(false);
3496
+ const paywallDataSent = useRef2(false);
3497
+ const loadEndReceived = useRef2(false);
3400
3498
  const [scriptToInject, setScriptToInject] = useState(void 0);
3401
- const webViewRef = useRef(null);
3402
- const onErrorRef = useRef(onError);
3499
+ const webViewRef = useRef2(null);
3500
+ const onErrorRef = useRef2(onError);
3403
3501
  onErrorRef.current = onError;
3404
- const onReadyRef = useRef(onReady);
3502
+ const onReadyRef = useRef2(onReady);
3405
3503
  onReadyRef.current = onReady;
3406
3504
  const NativeWebView = useMemo(() => getNativeWebView(), []);
3407
- useEffect(() => {
3505
+ useEffect2(() => {
3408
3506
  if (!NativeWebView) {
3409
3507
  onErrorRef.current?.({
3410
3508
  code: -1,
@@ -3415,7 +3513,7 @@ function PaywallWebView({
3415
3513
  const [resolvedWebUrl, setResolvedWebUrl] = useState(
3416
3514
  () => webUrlProp ?? PaywalloClient.getWebUrl()
3417
3515
  );
3418
- useEffect(() => {
3516
+ useEffect2(() => {
3419
3517
  if (webUrlProp !== void 0) {
3420
3518
  setResolvedWebUrl(webUrlProp ?? null);
3421
3519
  return;
@@ -3434,7 +3532,7 @@ function PaywallWebView({
3434
3532
  }, 200);
3435
3533
  return () => clearInterval(interval);
3436
3534
  }, [webUrlProp]);
3437
- useEffect(() => {
3535
+ useEffect2(() => {
3438
3536
  if (!resolvedWebUrl) {
3439
3537
  onErrorRef.current?.({
3440
3538
  code: -1,
@@ -3462,9 +3560,9 @@ function PaywallWebView({
3462
3560
  });
3463
3561
  injectScript(script);
3464
3562
  }, [craftData, products, primaryProductId, secondaryProductId, injectScript]);
3465
- const processedIdsRef = useRef(/* @__PURE__ */ new Set());
3466
- const dedupeTimersRef = useRef(/* @__PURE__ */ new Map());
3467
- useEffect(() => {
3563
+ const processedIdsRef = useRef2(/* @__PURE__ */ new Set());
3564
+ const dedupeTimersRef = useRef2(/* @__PURE__ */ new Map());
3565
+ useEffect2(() => {
3468
3566
  return () => {
3469
3567
  for (const timerId of dedupeTimersRef.current.values()) clearTimeout(timerId);
3470
3568
  dedupeTimersRef.current.clear();
@@ -3516,7 +3614,7 @@ function PaywallWebView({
3516
3614
  },
3517
3615
  [buildAndSetInjectionScript, onPurchase, onClose, onRestore]
3518
3616
  );
3519
- useEffect(() => {
3617
+ useEffect2(() => {
3520
3618
  const emitter = getWebViewEmitter();
3521
3619
  if (!emitter) return;
3522
3620
  const loadEndSub = emitter.addListener("PaywalloWebViewLoadEnd", () => {
@@ -3541,7 +3639,7 @@ function PaywallWebView({
3541
3639
  errorSub.remove();
3542
3640
  };
3543
3641
  }, [buildAndSetInjectionScript, handleParsedMessage]);
3544
- useEffect(() => {
3642
+ useEffect2(() => {
3545
3643
  const timer = setTimeout(() => {
3546
3644
  if (!paywallDataSent.current) {
3547
3645
  onErrorRef.current?.({
@@ -3581,11 +3679,11 @@ function PaywallWebView({
3581
3679
  },
3582
3680
  [handleParsedMessage]
3583
3681
  );
3584
- useEffect(() => {
3682
+ useEffect2(() => {
3585
3683
  if (!paywallDataSent.current) return;
3586
3684
  injectScript(buildPurchaseStateScript(isPurchasing));
3587
3685
  }, [isPurchasing, injectScript]);
3588
- useEffect(() => {
3686
+ useEffect2(() => {
3589
3687
  if (Platform9.OS !== "android") return;
3590
3688
  const onBackPress = () => {
3591
3689
  if (!paywallDataSent.current) return false;
@@ -3597,7 +3695,7 @@ function PaywallWebView({
3597
3695
  }, [injectScript]);
3598
3696
  if (!resolvedWebUrl || !NativeWebView) return null;
3599
3697
  const paywallUrl = `${resolvedWebUrl}/paywall/${paywallId}`;
3600
- return /* @__PURE__ */ jsx(View, { style: styles.container, children: /* @__PURE__ */ jsx(
3698
+ return /* @__PURE__ */ jsx2(View2, { style: styles2.container, children: /* @__PURE__ */ jsx2(
3601
3699
  NativeWebView,
3602
3700
  {
3603
3701
  ref: webViewRef,
@@ -3606,11 +3704,11 @@ function PaywallWebView({
3606
3704
  onMessage: handleMessage,
3607
3705
  onLoadEnd: handleLoadEnd,
3608
3706
  onError: handleNativeError,
3609
- style: styles.webview
3707
+ style: styles2.webview
3610
3708
  }
3611
3709
  ) });
3612
3710
  }
3613
- var NativeWebViewComponent, nativeWebViewLookupFailed, webViewEmitter, styles;
3711
+ var NativeWebViewComponent, nativeWebViewLookupFailed, webViewEmitter, styles2;
3614
3712
  var init_PaywallWebView = __esm({
3615
3713
  "src/domains/paywall/PaywallWebView.tsx"() {
3616
3714
  "use strict";
@@ -3620,7 +3718,7 @@ var init_PaywallWebView = __esm({
3620
3718
  NativeWebViewComponent = null;
3621
3719
  nativeWebViewLookupFailed = false;
3622
3720
  webViewEmitter = null;
3623
- styles = StyleSheet.create({
3721
+ styles2 = StyleSheet2.create({
3624
3722
  container: { flex: 1 },
3625
3723
  webview: { flex: 1, backgroundColor: "transparent" }
3626
3724
  });
@@ -3914,6 +4012,32 @@ var init_IAPTransactionEmitter = __esm({
3914
4012
  if (this.getDebug()) console.log("[Paywallo PURCHASE] transaction event emission failed", err);
3915
4013
  }
3916
4014
  }
4015
+ /**
4016
+ * Emits `checkout_started` quando o usuário inicia a compra (toca em comprar),
4017
+ * antes do StoreKit/Billing abrir — conta mesmo que a sheet nativa seja cancelada.
4018
+ * Vira InitiateCheckout no Meta/TikTok. Fire-and-forget — uma falha de tracking
4019
+ * nunca pode travar a compra.
4020
+ */
4021
+ async emitCheckoutStarted(productId) {
4022
+ try {
4023
+ const apiClient = this.getApiClientFn();
4024
+ if (!apiClient) return;
4025
+ const distinctId = PaywalloClient.getDistinctId();
4026
+ if (!distinctId) return;
4027
+ const product = this.getProductFromCache(productId);
4028
+ const properties = {
4029
+ product_id: productId,
4030
+ amount: product?.priceValue ?? 0,
4031
+ currency: product?.currency ?? "USD"
4032
+ };
4033
+ await apiClient.trackEvent("checkout_started", distinctId, properties, void 0, {
4034
+ priority: "critical"
4035
+ });
4036
+ if (this.getDebug()) console.log("[Paywallo PURCHASE] emitted checkout_started", productId);
4037
+ } catch (err) {
4038
+ if (this.getDebug()) console.log("[Paywallo PURCHASE] checkout_started emission failed", err);
4039
+ }
4040
+ }
3917
4041
  /**
3918
4042
  * Forwards a native transaction update to the EventBatcher.
3919
4043
  * Public so tests can exercise the wiring without a fake NativeEventEmitter.
@@ -3936,9 +4060,9 @@ var init_IAPTransactionEmitter = __esm({
3936
4060
  product_id: update.productId
3937
4061
  };
3938
4062
  if (update.type !== "canceled") {
3939
- const cached = this.getProductFromCache(update.productId);
3940
- const amount = update.amount ?? cached?.priceValue;
3941
- const currency = update.currency ?? cached?.currency;
4063
+ const cached2 = this.getProductFromCache(update.productId);
4064
+ const amount = update.amount ?? cached2?.priceValue;
4065
+ const currency = update.currency ?? cached2?.currency;
3942
4066
  if (typeof amount === "number" && amount > 0) properties.amount = amount;
3943
4067
  if (currency) properties.currency = currency;
3944
4068
  }
@@ -4117,10 +4241,10 @@ var init_OfflineQueueStorage = __esm({
4117
4241
  init_NativeStorage();
4118
4242
  init_QueueJournal();
4119
4243
  OfflineQueueStorage = class {
4120
- constructor(config, emit, log) {
4244
+ constructor(config, emit, log2) {
4121
4245
  this.config = config;
4122
4246
  this.emit = emit;
4123
- this.log = log;
4247
+ this.log = log2;
4124
4248
  }
4125
4249
  async loadFromStorage(queue) {
4126
4250
  const { merged, recovered, hadJournal } = await mergeJournalIntoMain(
@@ -4739,6 +4863,7 @@ var init_QueueProcessor = __esm({
4739
4863
  for (const item of batch) {
4740
4864
  retryIds.add(item.id);
4741
4865
  }
4866
+ console.error("[Paywallo:QUEUE] event batch network error \u2014 items will retry", { size: batch.length, error: error instanceof Error ? error.message : String(error) });
4742
4867
  this.log("Event batch threw network error - will retry", {
4743
4868
  size: batch.length,
4744
4869
  error: error instanceof Error ? error.message : String(error)
@@ -4756,14 +4881,25 @@ var init_QueueProcessor = __esm({
4756
4881
  await offlineQueue.removeItem(item.id);
4757
4882
  failed++;
4758
4883
  }
4884
+ const responseError = typeof response.data === "object" && response.data !== null && "error" in response.data ? response.data.error : void 0;
4885
+ const isDuplicate = typeof responseError === "string" && responseError.toUpperCase().includes("DUPLICATE");
4886
+ if (isDuplicate) {
4887
+ if (this.config.debug) {
4888
+ console.log("[Paywallo:QUEUE] event batch deduplicated by server \u2014 items dropped (benign)", { status: response.status, size: batch.length, error: responseError });
4889
+ }
4890
+ } else {
4891
+ 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) });
4892
+ }
4759
4893
  this.log("Event batch failed permanently (4xx) - removing from queue", {
4760
4894
  status: response.status,
4761
- size: batch.length
4895
+ size: batch.length,
4896
+ isDuplicate
4762
4897
  });
4763
4898
  } else {
4764
4899
  for (const item of batch) {
4765
4900
  retryIds.add(item.id);
4766
4901
  }
4902
+ console.error("[Paywallo:QUEUE] event batch server error (5xx) \u2014 will retry", { status: response.status, size: batch.length });
4767
4903
  this.log("Event batch failed with server error - will retry", {
4768
4904
  status: response.status,
4769
4905
  size: batch.length
@@ -4793,6 +4929,7 @@ var init_QueueProcessor = __esm({
4793
4929
  skipRetry: false
4794
4930
  });
4795
4931
  } catch (error) {
4932
+ 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) });
4796
4933
  this.log("Request threw network error - will retry", {
4797
4934
  id: item.id,
4798
4935
  error: error instanceof Error ? error.message : String(error)
@@ -5089,6 +5226,7 @@ var init_IAPService = __esm({
5089
5226
  this.purchaseInFlight = productId;
5090
5227
  if (this.debug) console.log(`[Paywallo PURCHASE] starting ${productId}`);
5091
5228
  try {
5229
+ void this.emitter.emitCheckoutStarted(productId);
5092
5230
  const purchase = await nativeStoreKit.purchase(productId);
5093
5231
  if (!purchase) {
5094
5232
  if (this.debug) console.log(`[Paywallo PURCHASE] ${productId} cancelled`);
@@ -5221,21 +5359,21 @@ var init_usePaywallTracking = __esm({
5221
5359
  });
5222
5360
 
5223
5361
  // src/domains/paywall/usePaywallActions.ts
5224
- import { useCallback as useCallback3, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
5225
- import { Animated, AppState as AppState3, Dimensions as Dimensions2 } from "react-native";
5362
+ import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef3, useState as useState2 } from "react";
5363
+ import { Animated as Animated2, AppState as AppState3, Dimensions as Dimensions2 } from "react-native";
5226
5364
  function usePaywallActions(placement, paywallConfig, onResult, variantKey, campaignId, openAnim, variantId) {
5227
5365
  const [isPurchasing, setIsPurchasing] = useState2(false);
5228
- const isPurchasingRef = useRef2(false);
5366
+ const isPurchasingRef = useRef3(false);
5229
5367
  const [isClosing, setIsClosing] = useState2(false);
5230
- const slideAnim = useRef2(new Animated.Value(0)).current;
5231
- const presentedAtRef = useRef2(Date.now());
5232
- const closedRef = useRef2(false);
5368
+ const slideAnim = useRef3(new Animated2.Value(0)).current;
5369
+ const presentedAtRef = useRef3(Date.now());
5370
+ const closedRef = useRef3(false);
5233
5371
  const { trackDismissed, trackProductSelected, trackPurchased } = usePaywallTracking();
5234
5372
  const closeWithReason = useCallback3((reason, resultOverride) => {
5235
5373
  if (closedRef.current) return;
5236
5374
  closedRef.current = true;
5237
5375
  const animTarget = openAnim ?? slideAnim;
5238
- Animated.timing(animTarget, {
5376
+ Animated2.timing(animTarget, {
5239
5377
  toValue: Dimensions2.get("window").height,
5240
5378
  duration: 350,
5241
5379
  useNativeDriver: true
@@ -5255,7 +5393,7 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
5255
5393
  setIsClosing(true);
5256
5394
  closeWithReason("dismissed");
5257
5395
  }, [isClosing, closeWithReason]);
5258
- useEffect2(() => {
5396
+ useEffect3(() => {
5259
5397
  const sub = AppState3.addEventListener("change", (nextState) => {
5260
5398
  if (isPurchasingRef.current) return;
5261
5399
  if (nextState === "background" || nextState === "inactive") {
@@ -5264,7 +5402,7 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
5264
5402
  });
5265
5403
  return () => sub.remove();
5266
5404
  }, [closeWithReason]);
5267
- useEffect2(() => {
5405
+ useEffect3(() => {
5268
5406
  const timer = setTimeout(() => {
5269
5407
  closeWithReason("timeout");
5270
5408
  }, PAYWALL_TIMEOUT_MS);
@@ -5335,17 +5473,17 @@ var init_usePaywallActions = __esm({
5335
5473
  });
5336
5474
 
5337
5475
  // src/domains/paywall/usePaywallLoader.ts
5338
- import { useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
5476
+ import { useEffect as useEffect4, useRef as useRef4, useState as useState3 } from "react";
5339
5477
  function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts, variantKey, campaignId, variantId) {
5340
5478
  const [error, setError] = useState3(null);
5341
5479
  const [paywallConfig, setPaywallConfig] = useState3(null);
5342
5480
  const [products, setProducts] = useState3(/* @__PURE__ */ new Map());
5343
- const preloadedConfigRef = useRef3(preloadedConfig);
5481
+ const preloadedConfigRef = useRef4(preloadedConfig);
5344
5482
  preloadedConfigRef.current = preloadedConfig;
5345
- const preloadedProductsRef = useRef3(preloadedProducts);
5483
+ const preloadedProductsRef = useRef4(preloadedProducts);
5346
5484
  preloadedProductsRef.current = preloadedProducts;
5347
- const trackingDoneRef = useRef3(false);
5348
- useEffect3(() => {
5485
+ const trackingDoneRef = useRef4(false);
5486
+ useEffect4(() => {
5349
5487
  if (!visible) {
5350
5488
  trackingDoneRef.current = false;
5351
5489
  return;
@@ -5440,28 +5578,28 @@ var init_usePaywallLoader = __esm({
5440
5578
  });
5441
5579
 
5442
5580
  // src/domains/paywall/PaywallModal.tsx
5443
- import { useCallback as useCallback4, useEffect as useEffect4, useRef as useRef4, useState as useState4 } from "react";
5581
+ import { useCallback as useCallback4, useEffect as useEffect5, useRef as useRef5, useState as useState4 } from "react";
5444
5582
  import {
5445
- Animated as Animated2,
5583
+ Animated as Animated3,
5446
5584
  Dimensions as Dimensions3,
5447
5585
  Easing,
5448
5586
  Modal,
5449
5587
  Pressable,
5450
- StyleSheet as StyleSheet2,
5588
+ StyleSheet as StyleSheet3,
5451
5589
  Text,
5452
- View as View2
5590
+ View as View3
5453
5591
  } from "react-native";
5454
- import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
5592
+ import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
5455
5593
  function ErrorFallback({ description, onRetry, onClose }) {
5456
5594
  const overrides = PaywalloClient.getConfig()?.errorStrings;
5457
5595
  const title = overrides?.title ?? getSdkString("paywallErrorTitle");
5458
5596
  const retryLabel = overrides?.retry ?? getSdkString("paywallRetryButton");
5459
5597
  const closeLabel = overrides?.close ?? getSdkString("paywallCloseButton");
5460
- return /* @__PURE__ */ jsx2(View2, { style: styles2.fallbackContainer, children: /* @__PURE__ */ jsxs(View2, { style: styles2.fallbackCard, children: [
5461
- /* @__PURE__ */ jsx2(Text, { style: styles2.fallbackTitle, children: title }),
5462
- /* @__PURE__ */ jsx2(Text, { style: styles2.fallbackDescription, children: description }),
5463
- /* @__PURE__ */ jsx2(Pressable, { style: styles2.retryButton, onPress: onRetry, children: /* @__PURE__ */ jsx2(Text, { style: styles2.retryButtonText, children: retryLabel }) }),
5464
- /* @__PURE__ */ jsx2(Pressable, { style: styles2.closeButton, onPress: onClose, children: /* @__PURE__ */ jsx2(Text, { style: styles2.closeButtonText, children: closeLabel }) })
5598
+ return /* @__PURE__ */ jsx3(View3, { style: styles3.fallbackContainer, children: /* @__PURE__ */ jsxs2(View3, { style: styles3.fallbackCard, children: [
5599
+ /* @__PURE__ */ jsx3(Text, { style: styles3.fallbackTitle, children: title }),
5600
+ /* @__PURE__ */ jsx3(Text, { style: styles3.fallbackDescription, children: description }),
5601
+ /* @__PURE__ */ jsx3(Pressable, { style: styles3.retryButton, onPress: onRetry, children: /* @__PURE__ */ jsx3(Text, { style: styles3.retryButtonText, children: retryLabel }) }),
5602
+ /* @__PURE__ */ jsx3(Pressable, { style: styles3.closeButton, onPress: onClose, children: /* @__PURE__ */ jsx3(Text, { style: styles3.closeButtonText, children: closeLabel }) })
5465
5603
  ] }) });
5466
5604
  }
5467
5605
  function PaywallModal({
@@ -5484,7 +5622,7 @@ function PaywallModal({
5484
5622
  variantId
5485
5623
  );
5486
5624
  const screenHeight = Dimensions3.get("window").height;
5487
- const openAnim = useRef4(new Animated2.Value(screenHeight)).current;
5625
+ const openAnim = useRef5(new Animated3.Value(screenHeight)).current;
5488
5626
  const [modalVisible, setModalVisible] = useState4(false);
5489
5627
  const handleResult = useCallback4((result) => {
5490
5628
  setModalVisible(false);
@@ -5493,7 +5631,9 @@ function PaywallModal({
5493
5631
  const { isPurchasing, slideAnim, handleClose, handlePurchase, handleRestore } = usePaywallActions(placement, paywallConfig, handleResult, variantKey, campaignId, openAnim, variantId);
5494
5632
  const [webViewError, setWebViewError] = useState4(null);
5495
5633
  const [retryCount, setRetryCount] = useState4(0);
5496
- const retryCountRef = useRef4(0);
5634
+ const [showSkeleton, setShowSkeleton] = useState4(true);
5635
+ const skeletonOpacity = useRef5(new Animated3.Value(1)).current;
5636
+ const retryCountRef = useRef5(0);
5497
5637
  const handleWebViewError = useCallback4(
5498
5638
  (err) => {
5499
5639
  if (retryCountRef.current >= 1) {
@@ -5509,21 +5649,39 @@ function PaywallModal({
5509
5649
  retryCountRef.current = 0;
5510
5650
  setWebViewError(null);
5511
5651
  setRetryCount(0);
5512
- }, []);
5513
- useEffect4(() => {
5652
+ setShowSkeleton(true);
5653
+ skeletonOpacity.setValue(1);
5654
+ }, [skeletonOpacity]);
5655
+ const handleWebViewReady = useCallback4(() => {
5656
+ Animated3.timing(skeletonOpacity, {
5657
+ toValue: 0,
5658
+ duration: 250,
5659
+ easing: Easing.out(Easing.cubic),
5660
+ useNativeDriver: true
5661
+ }).start(() => setShowSkeleton(false));
5662
+ }, [skeletonOpacity]);
5663
+ useEffect5(() => {
5664
+ if (!visible || !showSkeleton) return;
5665
+ const timer = setTimeout(handleWebViewReady, 8e3);
5666
+ return () => clearTimeout(timer);
5667
+ }, [visible, showSkeleton, handleWebViewReady]);
5668
+ useEffect5(() => {
5514
5669
  if (visible) {
5515
5670
  setModalVisible(true);
5671
+ setShowSkeleton(true);
5672
+ skeletonOpacity.setValue(1);
5516
5673
  openAnim.setValue(Dimensions3.get("window").height);
5517
- Animated2.timing(openAnim, {
5674
+ Animated3.timing(openAnim, {
5518
5675
  toValue: 0,
5519
5676
  duration: 550,
5520
5677
  easing: Easing.out(Easing.cubic),
5521
5678
  useNativeDriver: true
5522
5679
  }).start();
5523
5680
  }
5524
- }, [visible, openAnim]);
5681
+ }, [visible, openAnim, skeletonOpacity]);
5525
5682
  if (!modalVisible && !visible) return null;
5526
- return /* @__PURE__ */ jsx2(
5683
+ const skeletonBackground = paywallConfig && "craftData" in paywallConfig.config ? extractFirstPageBackground(paywallConfig.config.craftData) : "#FFFFFF";
5684
+ return /* @__PURE__ */ jsx3(
5527
5685
  Modal,
5528
5686
  {
5529
5687
  visible: modalVisible,
@@ -5532,16 +5690,16 @@ function PaywallModal({
5532
5690
  onRequestClose: handleClose,
5533
5691
  transparent: true,
5534
5692
  statusBarTranslucent: true,
5535
- children: /* @__PURE__ */ jsx2(View2, { style: styles2.overlay, children: /* @__PURE__ */ jsx2(Animated2.View, { style: [styles2.animatedContainer, { transform: [{ translateY: openAnim }] }], children: /* @__PURE__ */ jsxs(View2, { style: styles2.container, children: [
5536
- error && /* @__PURE__ */ jsx2(View2, { style: styles2.errorContainer, children: /* @__PURE__ */ jsx2(Text, { style: styles2.errorText, children: error.message }) }),
5537
- !error && paywallConfig && "craftData" in paywallConfig.config && /* @__PURE__ */ jsx2(Fragment, { children: webViewError ? /* @__PURE__ */ jsx2(
5693
+ children: /* @__PURE__ */ jsx3(View3, { style: styles3.overlay, children: /* @__PURE__ */ jsx3(Animated3.View, { style: [styles3.animatedContainer, { transform: [{ translateY: openAnim }] }], children: /* @__PURE__ */ jsxs2(View3, { style: styles3.container, children: [
5694
+ error && /* @__PURE__ */ jsx3(View3, { style: styles3.errorContainer, children: /* @__PURE__ */ jsx3(Text, { style: styles3.errorText, children: error.message }) }),
5695
+ !error && paywallConfig && "craftData" in paywallConfig.config && /* @__PURE__ */ jsx3(Fragment, { children: webViewError ? /* @__PURE__ */ jsx3(
5538
5696
  ErrorFallback,
5539
5697
  {
5540
5698
  description: webViewError.description,
5541
5699
  onRetry: handleRetry,
5542
5700
  onClose: handleClose
5543
5701
  }
5544
- ) : /* @__PURE__ */ jsx2(
5702
+ ) : /* @__PURE__ */ jsx3(
5545
5703
  PaywallWebView,
5546
5704
  {
5547
5705
  paywallId: paywallConfig.id,
@@ -5550,27 +5708,38 @@ function PaywallModal({
5550
5708
  primaryProductId: paywallConfig.primaryProductId,
5551
5709
  secondaryProductId: paywallConfig.secondaryProductId,
5552
5710
  isPurchasing,
5711
+ onReady: handleWebViewReady,
5553
5712
  onClose: handleClose,
5554
5713
  onPurchase: handlePurchase,
5555
5714
  onRestore: handleRestore,
5556
5715
  onError: handleWebViewError
5557
5716
  },
5558
5717
  retryCount
5559
- ) })
5718
+ ) }),
5719
+ showSkeleton && !error && !webViewError && /* @__PURE__ */ jsx3(
5720
+ Animated3.View,
5721
+ {
5722
+ style: [StyleSheet3.absoluteFill, { opacity: skeletonOpacity }],
5723
+ pointerEvents: "none",
5724
+ children: /* @__PURE__ */ jsx3(PaywallSkeleton, { backgroundColor: skeletonBackground })
5725
+ }
5726
+ )
5560
5727
  ] }) }) })
5561
5728
  }
5562
5729
  );
5563
5730
  }
5564
- var styles2;
5731
+ var styles3;
5565
5732
  var init_PaywallModal = __esm({
5566
5733
  "src/domains/paywall/PaywallModal.tsx"() {
5567
5734
  "use strict";
5568
5735
  init_PaywalloClient();
5569
5736
  init_localization();
5737
+ init_extractFirstPageBackground();
5738
+ init_PaywallSkeleton();
5570
5739
  init_PaywallWebView();
5571
5740
  init_usePaywallActions();
5572
5741
  init_usePaywallLoader();
5573
- styles2 = StyleSheet2.create({
5742
+ styles3 = StyleSheet3.create({
5574
5743
  overlay: { flex: 1, backgroundColor: "rgba(0,0,0,0.3)" },
5575
5744
  animatedContainer: { flex: 1, backgroundColor: "#fff" },
5576
5745
  container: { flex: 1, backgroundColor: "#fff" },
@@ -5626,13 +5795,176 @@ var init_PaywallModal = __esm({
5626
5795
  }
5627
5796
  });
5628
5797
 
5798
+ // src/domains/paywall/PaywallPreloadService.ts
5799
+ var PRELOAD_TTL_MS, STALE_THRESHOLD, PaywallPreloadService, paywallPreloadService;
5800
+ var init_PaywallPreloadService = __esm({
5801
+ "src/domains/paywall/PaywallPreloadService.ts"() {
5802
+ "use strict";
5803
+ init_PaywalloClient();
5804
+ init_IAPService();
5805
+ init_PaywallError();
5806
+ PRELOAD_TTL_MS = 5 * 60 * 1e3;
5807
+ STALE_THRESHOLD = 0.75;
5808
+ PaywallPreloadService = class {
5809
+ constructor() {
5810
+ this.preloaded = /* @__PURE__ */ new Map();
5811
+ this.activePreloads = /* @__PURE__ */ new Map();
5812
+ this.apiClientRef = null;
5813
+ }
5814
+ async preload(apiClient, placement) {
5815
+ this.apiClientRef = apiClient;
5816
+ const existing = this.activePreloads.get(placement);
5817
+ if (existing) return existing;
5818
+ const promise = this.doPreload(apiClient, placement);
5819
+ this.activePreloads.set(placement, promise);
5820
+ try {
5821
+ return await promise;
5822
+ } finally {
5823
+ this.activePreloads.delete(placement);
5824
+ }
5825
+ }
5826
+ async preloadMany(apiClient, placements) {
5827
+ this.apiClientRef = apiClient;
5828
+ if (placements.length === 0) return;
5829
+ const debug = PaywalloClient.getConfig()?.debug;
5830
+ if (debug) console.log(`[Paywallo PRELOAD] paywalls: ${placements.length} placements`);
5831
+ for (let i = 0; i < placements.length; i++) {
5832
+ if (i > 0) await new Promise((r) => setTimeout(r, 200));
5833
+ void this.preload(apiClient, placements[i]).catch((err) => {
5834
+ if (debug) console.log(`[Paywallo PRELOAD] paywall failed ${placements[i]}:`, err);
5835
+ });
5836
+ }
5837
+ }
5838
+ getPreloaded(placement) {
5839
+ const cached2 = this.preloaded.get(placement);
5840
+ if (!cached2) return null;
5841
+ const age = Date.now() - cached2.preloadedAt;
5842
+ if (age >= PRELOAD_TTL_MS) {
5843
+ this.preloaded.delete(placement);
5844
+ return null;
5845
+ }
5846
+ if (age >= PRELOAD_TTL_MS * STALE_THRESHOLD && this.apiClientRef) {
5847
+ void this.preload(this.apiClientRef, placement).catch(() => {
5848
+ });
5849
+ }
5850
+ return { config: cached2.config, products: new Map(cached2.products) };
5851
+ }
5852
+ isPreloaded(placement) {
5853
+ return this.getPreloaded(placement) !== null;
5854
+ }
5855
+ clear() {
5856
+ this.preloaded.clear();
5857
+ this.activePreloads.clear();
5858
+ }
5859
+ async doPreload(apiClient, placement) {
5860
+ try {
5861
+ const config = await apiClient.getPaywall(placement);
5862
+ if (!config) {
5863
+ return {
5864
+ success: false,
5865
+ error: new PaywallError(
5866
+ PAYWALL_ERROR_CODES.NOT_FOUND,
5867
+ `Paywall not found for placement: ${placement}`
5868
+ )
5869
+ };
5870
+ }
5871
+ const products = await this.loadProductsFor(config);
5872
+ this.preloaded.set(placement, { config, products, preloadedAt: Date.now() });
5873
+ if (PaywalloClient.getConfig()?.debug) {
5874
+ console.log(`[Paywallo PRELOAD] paywall cached \u2014 placement: ${placement}`);
5875
+ }
5876
+ return { success: true };
5877
+ } catch (error) {
5878
+ return {
5879
+ success: false,
5880
+ error: error instanceof Error ? error : new PaywallError(PAYWALL_ERROR_CODES.LOAD_FAILED, String(error))
5881
+ };
5882
+ }
5883
+ }
5884
+ async loadProductsFor(config) {
5885
+ const productIds = [];
5886
+ if (config.primaryProductId) productIds.push(config.primaryProductId);
5887
+ if (config.secondaryProductId) productIds.push(config.secondaryProductId);
5888
+ if (productIds.length === 0) return /* @__PURE__ */ new Map();
5889
+ try {
5890
+ const loaded = await getIAPService().loadProducts(productIds);
5891
+ const map = /* @__PURE__ */ new Map();
5892
+ for (const p of loaded) map.set(p.productId, p);
5893
+ return map;
5894
+ } catch (err) {
5895
+ if (PaywalloClient.getConfig()?.debug) {
5896
+ console.log("[Paywallo PRELOAD] products failed, caching config only:", err);
5897
+ }
5898
+ return /* @__PURE__ */ new Map();
5899
+ }
5900
+ }
5901
+ };
5902
+ paywallPreloadService = new PaywallPreloadService();
5903
+ }
5904
+ });
5905
+
5906
+ // src/domains/paywall/PaywallWebViewPrewarmer.tsx
5907
+ import { useEffect as useEffect6, useMemo as useMemo2, useState as useState5 } from "react";
5908
+ import { requireNativeComponent as requireNativeComponent2, StyleSheet as StyleSheet4, View as View4 } from "react-native";
5909
+ import { jsx as jsx4 } from "react/jsx-runtime";
5910
+ function getNativeWebView2() {
5911
+ if (NativeWebViewComponent2) return NativeWebViewComponent2;
5912
+ if (nativeLookupFailed) return null;
5913
+ try {
5914
+ NativeWebViewComponent2 = requireNativeComponent2("PaywalloWebView");
5915
+ return NativeWebViewComponent2;
5916
+ } catch {
5917
+ nativeLookupFailed = true;
5918
+ return null;
5919
+ }
5920
+ }
5921
+ function PaywallWebViewPrewarmer({
5922
+ webUrl,
5923
+ durationMs = DEFAULT_DURATION_MS
5924
+ }) {
5925
+ const NativeWebView = useMemo2(() => getNativeWebView2(), []);
5926
+ const [mounted, setMounted] = useState5(true);
5927
+ useEffect6(() => {
5928
+ if (!NativeWebView) return;
5929
+ const timer = setTimeout(() => setMounted(false), durationMs);
5930
+ return () => clearTimeout(timer);
5931
+ }, [NativeWebView, durationMs]);
5932
+ if (!NativeWebView || !mounted) return null;
5933
+ const preheatUrl = `${webUrl}/paywall/preheat`;
5934
+ if (PaywalloClient.getConfig()?.debug) {
5935
+ console.log(`[Paywallo PREWARM] mounting hidden WebView: ${preheatUrl}`);
5936
+ }
5937
+ return /* @__PURE__ */ jsx4(View4, { pointerEvents: "none", style: styles4.hidden, children: /* @__PURE__ */ jsx4(NativeWebView, { sourceUrl: preheatUrl, style: styles4.fill }) });
5938
+ }
5939
+ var NativeWebViewComponent2, nativeLookupFailed, DEFAULT_DURATION_MS, styles4;
5940
+ var init_PaywallWebViewPrewarmer = __esm({
5941
+ "src/domains/paywall/PaywallWebViewPrewarmer.tsx"() {
5942
+ "use strict";
5943
+ init_PaywalloClient();
5944
+ NativeWebViewComponent2 = null;
5945
+ nativeLookupFailed = false;
5946
+ DEFAULT_DURATION_MS = 60 * 1e3;
5947
+ styles4 = StyleSheet4.create({
5948
+ hidden: {
5949
+ height: 1,
5950
+ left: -9999,
5951
+ opacity: 0,
5952
+ position: "absolute",
5953
+ top: -9999,
5954
+ width: 1
5955
+ },
5956
+ fill: { flex: 1 }
5957
+ });
5958
+ }
5959
+ });
5960
+
5629
5961
  // src/domains/paywall/usePaywallSession.ts
5630
- import { useCallback as useCallback5, useEffect as useEffect5, useRef as useRef5 } from "react";
5962
+ import { useCallback as useCallback5, useEffect as useEffect7, useRef as useRef6 } from "react";
5631
5963
  function usePaywallSession(options) {
5632
- const closedRef = useRef5(false);
5633
- const optsRef = useRef5(options);
5964
+ const closedRef = useRef6(false);
5965
+ const optsRef = useRef6(options);
5634
5966
  optsRef.current = options;
5635
- useEffect5(() => {
5967
+ useEffect7(() => {
5636
5968
  if (!options) return;
5637
5969
  closedRef.current = false;
5638
5970
  startHeartbeat({
@@ -5676,11 +6008,11 @@ var init_usePaywallSession = __esm({
5676
6008
  });
5677
6009
 
5678
6010
  // src/domains/paywall/usePaywallPresenter.ts
5679
- import { useCallback as useCallback6, useEffect as useEffect6, useRef as useRef6, useState as useState5 } from "react";
6011
+ import { useCallback as useCallback6, useEffect as useEffect8, useRef as useRef7, useState as useState6 } from "react";
5680
6012
  function usePaywallPresenter(preloadedWebView, setPreloadedWebView) {
5681
- const [showingPreloadedWebView, setShowingPreloadedWebView] = useState5(false);
5682
- const resolverRef = useRef6(null);
5683
- const presentedAtRef = useRef6(Date.now());
6013
+ const [showingPreloadedWebView, setShowingPreloadedWebView] = useState6(false);
6014
+ const resolverRef = useRef7(null);
6015
+ const presentedAtRef = useRef7(Date.now());
5684
6016
  const sessionOpts = showingPreloadedWebView && preloadedWebView ? {
5685
6017
  placement: preloadedWebView.placement,
5686
6018
  paywallId: preloadedWebView.paywallId,
@@ -5690,7 +6022,7 @@ function usePaywallPresenter(preloadedWebView, setPreloadedWebView) {
5690
6022
  ...preloadedWebView.campaignId && { campaignId: preloadedWebView.campaignId }
5691
6023
  } : null;
5692
6024
  const { markClosed } = usePaywallSession(sessionOpts);
5693
- useEffect6(() => {
6025
+ useEffect8(() => {
5694
6026
  return () => {
5695
6027
  if (resolverRef.current) {
5696
6028
  resolverRef.current({ presented: false, purchased: false, cancelled: true, restored: false });
@@ -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,112 +6366,274 @@ 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"() {
6080
- "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));
6124
- return false;
6125
- }
6126
- }
6127
- var init_PaywalloFlags = __esm({
6128
- "src/core/PaywalloFlags.ts"() {
6129
- "use strict";
6130
- init_identity();
6131
- }
6132
- });
6133
-
6134
- // src/domains/onboarding/OnboardingError.ts
6135
- var OnboardingError, ONBOARDING_ERROR_CODES;
6136
- var init_OnboardingError = __esm({
6137
- "src/domains/onboarding/OnboardingError.ts"() {
6369
+ // src/core/events/schemas.ts
6370
+ import { z } from "zod";
6371
+ var lifecycleEventSchema, identifyEventSchema, paywallEventSchema, transactionEventSchema, onboardingEventSchema, notificationEventSchema, customEventSchema, EVENT_SCHEMAS;
6372
+ var init_schemas = __esm({
6373
+ "src/core/events/schemas.ts"() {
6138
6374
  "use strict";
6139
- init_PaywalloError();
6140
- OnboardingError = class extends PaywalloError {
6375
+ lifecycleEventSchema = z.object({
6376
+ type: z.enum([
6377
+ "install",
6378
+ "cold_start",
6379
+ "foreground",
6380
+ "background",
6381
+ "session_start",
6382
+ "session_end"
6383
+ ]),
6384
+ session_id: z.string().optional(),
6385
+ duration_s: z.number().nonnegative().optional(),
6386
+ first_ever: z.boolean().optional(),
6387
+ // Contexto leve transportado em lifecycle (útil pra cold_start/install)
6388
+ app_version: z.string().optional(),
6389
+ device_type: z.enum(["ios", "android"]).optional(),
6390
+ os_version: z.string().optional(),
6391
+ // Timestamps opcionais — o EventBatcher injeta `timestamp` no envelope
6392
+ started_at: z.string().optional(),
6393
+ ended_at: z.string().optional()
6394
+ }).strict();
6395
+ identifyEventSchema = z.object({
6396
+ distinct_id: z.string().min(1),
6397
+ email: z.email().optional(),
6398
+ traits: z.record(z.string(), z.unknown()).optional(),
6399
+ attribution: z.record(z.string(), z.unknown()).optional(),
6400
+ device: z.record(z.string(), z.unknown()).optional()
6401
+ }).strict();
6402
+ paywallEventSchema = z.object({
6403
+ type: z.enum(["viewed", "closed", "purchased"]),
6404
+ paywall_id: z.string().min(1),
6405
+ placement: z.string().optional(),
6406
+ variant_id: z.string().optional(),
6407
+ variant_key: z.string().optional(),
6408
+ campaign_id: z.string().optional(),
6409
+ // `closed`-specific
6410
+ closed_at: z.string().optional(),
6411
+ duration_s: z.number().nonnegative().optional(),
6412
+ close_reason: z.enum(["dismiss", "cta", "purchase", "error", "timeout"]).optional(),
6413
+ scroll_depth: z.number().min(0).max(1).optional(),
6414
+ // `purchased`-specific (mínimo — transaction canônica leva detalhes)
6415
+ product_id: z.string().optional()
6416
+ }).strict();
6417
+ transactionEventSchema = z.object({
6418
+ type: z.enum([
6419
+ "completed",
6420
+ "failed",
6421
+ "refunded",
6422
+ "renewed",
6423
+ "canceled",
6424
+ "expired"
6425
+ ]),
6426
+ tx_id: z.string().min(1),
6427
+ amount: z.number().optional(),
6428
+ currency: z.string().length(3).optional(),
6429
+ product_id: z.string().optional(),
6430
+ subscription_id: z.string().optional(),
6431
+ paywall_id: z.string().optional(),
6432
+ variant_id: z.string().optional(),
6433
+ reason: z.string().optional()
6434
+ }).strict();
6435
+ onboardingEventSchema = z.object({
6436
+ type: z.enum(["step", "complete", "drop"]),
6437
+ step_index: z.number().int().nonnegative().optional(),
6438
+ step_name: z.string().optional(),
6439
+ variant_key: z.string().optional(),
6440
+ time_on_prev_s: z.number().nonnegative().optional()
6441
+ }).strict();
6442
+ notificationEventSchema = z.object({
6443
+ type: z.enum(["delivered", "displayed", "clicked", "dismissed"]),
6444
+ notification_id: z.string().optional(),
6445
+ campaign_id: z.string().optional(),
6446
+ variant_key: z.string().nullable().optional(),
6447
+ message_id: z.string().optional(),
6448
+ device_id: z.string().optional(),
6449
+ push_platform: z.enum(["ios", "android"]).optional(),
6450
+ timezone: z.string().optional(),
6451
+ failure_reason: z.string().optional(),
6452
+ app_user_id: z.string().optional()
6453
+ }).strict();
6454
+ customEventSchema = z.object({
6455
+ name: 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: z.record(z.string(), 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));
6620
+ return false;
6621
+ }
6622
+ }
6623
+ var init_PaywalloFlags = __esm({
6624
+ "src/core/PaywalloFlags.ts"() {
6625
+ "use strict";
6626
+ init_identity();
6627
+ }
6628
+ });
6629
+
6630
+ // src/domains/onboarding/OnboardingError.ts
6631
+ var OnboardingError, ONBOARDING_ERROR_CODES;
6632
+ var init_OnboardingError = __esm({
6633
+ "src/domains/onboarding/OnboardingError.ts"() {
6634
+ "use strict";
6635
+ init_PaywalloError();
6636
+ OnboardingError = class extends PaywalloError {
6141
6637
  constructor(code, message) {
6142
6638
  super("onboarding", code, message);
6143
6639
  this.name = "OnboardingError";
@@ -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
  }
@@ -6790,288 +7286,131 @@ async function writeFlagToStorage(flagKey, distinctId, flag) {
6790
7286
  }
6791
7287
  }
6792
7288
  var FLAG_MAX_AGE_MS;
6793
- var init_FlagStorage = __esm({
6794
- "src/core/FlagStorage.ts"() {
6795
- "use strict";
6796
- init_SecureStorage();
6797
- FLAG_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
6798
- }
6799
- });
6800
-
6801
- // src/core/ApiClientFlags.ts
6802
- async function getVariant2(deps, flagKey, distinctId) {
6803
- const key = `${flagKey}:${distinctId}`;
6804
- const hit = deps.cache.getVariant(key);
6805
- if (hit !== void 0) return hit;
6806
- const persisted = await readFlagFromStorage(flagKey, distinctId);
6807
- if (persisted !== null) {
6808
- deps.cache.setVariant(key, persisted);
6809
- return persisted;
6810
- }
6811
- try {
6812
- const res = await deps.get(`/sdk/flags/${flagKey}?distinctId=${encodeURIComponent(distinctId)}`);
6813
- if (res.status === 404) {
6814
- const v = { variant: null };
6815
- deps.cache.setVariant(key, v, deps.cache.nullTTL);
6816
- return v;
6817
- }
6818
- 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
- import { z } from "zod";
6923
- var lifecycleEventSchema, identifyEventSchema, paywallEventSchema, transactionEventSchema, onboardingEventSchema, notificationEventSchema, customEventSchema, EVENT_SCHEMAS;
6924
- var init_schemas = __esm({
6925
- "src/core/events/schemas.ts"() {
6926
- "use strict";
6927
- lifecycleEventSchema = z.object({
6928
- type: z.enum([
6929
- "install",
6930
- "cold_start",
6931
- "foreground",
6932
- "background",
6933
- "session_start",
6934
- "session_end"
6935
- ]),
6936
- session_id: z.string().optional(),
6937
- duration_s: z.number().nonnegative().optional(),
6938
- first_ever: z.boolean().optional(),
6939
- // Contexto leve transportado em lifecycle (útil pra cold_start/install)
6940
- app_version: z.string().optional(),
6941
- device_type: z.enum(["ios", "android"]).optional(),
6942
- os_version: z.string().optional(),
6943
- // Timestamps opcionais — o EventBatcher injeta `timestamp` no envelope
6944
- started_at: z.string().optional(),
6945
- ended_at: z.string().optional()
6946
- }).strict();
6947
- identifyEventSchema = z.object({
6948
- distinct_id: z.string().min(1),
6949
- email: z.email().optional(),
6950
- traits: z.record(z.string(), z.unknown()).optional(),
6951
- attribution: z.record(z.string(), z.unknown()).optional(),
6952
- device: z.record(z.string(), z.unknown()).optional()
6953
- }).strict();
6954
- paywallEventSchema = z.object({
6955
- type: z.enum(["viewed", "closed", "purchased"]),
6956
- paywall_id: z.string().min(1),
6957
- placement: z.string().optional(),
6958
- variant_id: z.string().optional(),
6959
- variant_key: z.string().optional(),
6960
- campaign_id: z.string().optional(),
6961
- // `closed`-specific
6962
- closed_at: z.string().optional(),
6963
- duration_s: z.number().nonnegative().optional(),
6964
- close_reason: z.enum(["dismiss", "cta", "purchase", "error", "timeout"]).optional(),
6965
- scroll_depth: z.number().min(0).max(1).optional(),
6966
- // `purchased`-specific (mínimo — transaction canônica leva detalhes)
6967
- product_id: z.string().optional()
6968
- }).strict();
6969
- transactionEventSchema = z.object({
6970
- type: z.enum([
6971
- "completed",
6972
- "failed",
6973
- "refunded",
6974
- "renewed",
6975
- "canceled",
6976
- "expired"
6977
- ]),
6978
- tx_id: z.string().min(1),
6979
- amount: z.number().optional(),
6980
- currency: z.string().length(3).optional(),
6981
- product_id: z.string().optional(),
6982
- subscription_id: z.string().optional(),
6983
- paywall_id: z.string().optional(),
6984
- variant_id: z.string().optional(),
6985
- reason: z.string().optional()
6986
- }).strict();
6987
- onboardingEventSchema = z.object({
6988
- type: z.enum(["step", "complete", "drop"]),
6989
- step_index: z.number().int().nonnegative().optional(),
6990
- step_name: z.string().optional(),
6991
- variant_key: z.string().optional(),
6992
- time_on_prev_s: z.number().nonnegative().optional()
6993
- }).strict();
6994
- notificationEventSchema = z.object({
6995
- type: z.enum(["delivered", "displayed", "clicked", "dismissed"]),
6996
- notification_id: z.string().optional(),
6997
- campaign_id: z.string().optional(),
6998
- variant_key: z.string().nullable().optional(),
6999
- message_id: z.string().optional(),
7000
- device_id: z.string().optional(),
7001
- push_platform: z.enum(["ios", "android"]).optional(),
7002
- timezone: z.string().optional(),
7003
- failure_reason: z.string().optional(),
7004
- app_user_id: z.string().optional()
7005
- }).strict();
7006
- customEventSchema = z.object({
7007
- name: 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: z.record(z.string(), 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
- };
7289
+ var init_FlagStorage = __esm({
7290
+ "src/core/FlagStorage.ts"() {
7291
+ "use strict";
7292
+ init_SecureStorage();
7293
+ FLAG_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
7022
7294
  }
7023
7295
  });
7024
7296
 
7025
- // src/core/events/eventFamilies.ts
7026
- function isDeprecatedEvent(eventName) {
7027
- return DEPRECATED_EVENT_NAMES.includes(eventName);
7297
+ // src/core/ApiClientFlags.ts
7298
+ async function getVariant2(deps, flagKey, distinctId) {
7299
+ const key = `${flagKey}:${distinctId}`;
7300
+ const hit = deps.cache.getVariant(key);
7301
+ if (hit !== void 0) return hit;
7302
+ const persisted = await readFlagFromStorage(flagKey, distinctId);
7303
+ if (persisted !== null) {
7304
+ deps.cache.setVariant(key, persisted);
7305
+ return persisted;
7306
+ }
7307
+ try {
7308
+ const res = await deps.get(`/sdk/flags/${flagKey}?distinctId=${encodeURIComponent(distinctId)}`);
7309
+ if (res.status === 404) {
7310
+ const v = { variant: null };
7311
+ deps.cache.setVariant(key, v, deps.cache.nullTTL);
7312
+ return v;
7313
+ }
7314
+ if (!res.ok) {
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 };
7324
+ }
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
 
@@ -7104,6 +7443,9 @@ function buildV2Envelope(events, providerContext) {
7104
7443
  if (context.distinct_id === void 0 && events.length > 0) {
7105
7444
  context.distinct_id = events[0].distinctId;
7106
7445
  }
7446
+ if (!context.distinct_id || context.distinct_id.length === 0) {
7447
+ console.error("[Paywallo:ENVELOPE] distinct_id is empty after resolution \u2014 envelope will fail schema", { providerContext, eventCount: events.length, firstEventName: events[0]?.eventName });
7448
+ }
7107
7449
  return { context, events: v2Events };
7108
7450
  }
7109
7451
  function resolveProviderContext(provider) {
@@ -7155,12 +7497,18 @@ var init_eventEnvelopeV2 = __esm({
7155
7497
  session_id: z2.string().optional(),
7156
7498
  device_id: z2.string().optional(),
7157
7499
  app_version: z2.string().optional(),
7500
+ app_build: z2.string().optional(),
7501
+ bundle_id: z2.string().optional(),
7158
7502
  sdk_version: z2.string().optional(),
7159
7503
  platform: z2.string().optional(),
7160
7504
  os_version: z2.string().optional(),
7161
7505
  device_model: z2.string().optional(),
7162
7506
  timezone: z2.string().optional(),
7163
7507
  locale: z2.string().optional(),
7508
+ carrier: z2.string().optional(),
7509
+ screen_width: z2.number().optional(),
7510
+ screen_height: z2.number().optional(),
7511
+ screen_density: z2.number().optional(),
7164
7512
  attribution: z2.record(z2.string(), z2.unknown()).optional(),
7165
7513
  ids: z2.record(z2.string(), z2.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
  };
@@ -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);
@@ -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,7 +8360,38 @@ var init_PaywalloState = __esm({
8001
8360
  }
8002
8361
  });
8003
8362
 
8363
+ // src/utils/detectEnvironment.ts
8364
+ import { NativeModules as NativeModules8 } from "react-native";
8365
+ async function detectEnvironment() {
8366
+ if (cached !== null) return cached;
8367
+ if (typeof globalThis.__DEV__ !== "undefined" && globalThis.__DEV__) {
8368
+ cached = "sandbox";
8369
+ return cached;
8370
+ }
8371
+ if (!nativeModule) {
8372
+ cached = "production";
8373
+ return cached;
8374
+ }
8375
+ try {
8376
+ const result = await nativeModule.detect();
8377
+ cached = result === "sandbox" ? "sandbox" : "production";
8378
+ return cached;
8379
+ } catch {
8380
+ cached = "production";
8381
+ return cached;
8382
+ }
8383
+ }
8384
+ var nativeModule, cached;
8385
+ var init_detectEnvironment = __esm({
8386
+ "src/utils/detectEnvironment.ts"() {
8387
+ "use strict";
8388
+ nativeModule = NativeModules8.PaywalloEnv ?? null;
8389
+ cached = null;
8390
+ }
8391
+ });
8392
+
8004
8393
  // src/core/PaywalloInitializer.ts
8394
+ import { Dimensions as Dimensions4, PixelRatio as PixelRatio2 } from "react-native";
8005
8395
  async function initWithRetry(state, config) {
8006
8396
  while (state.initAttempts <= MAX_INIT_RETRIES) {
8007
8397
  try {
@@ -8080,7 +8470,13 @@ async function resolveSessionFlags(state, keys, apiClient, distinctId) {
8080
8470
  }
8081
8471
  }
8082
8472
  async function doInit(state, config) {
8083
- const environment = config.environment ?? (typeof __DEV__ !== "undefined" && __DEV__ ? "Sandbox" : "Production");
8473
+ let environment;
8474
+ if (config.environment) {
8475
+ environment = config.environment;
8476
+ } else {
8477
+ const detected = await detectEnvironment();
8478
+ environment = detected === "sandbox" ? "Sandbox" : "Production";
8479
+ }
8084
8480
  const offlineQueueEnabled = config.offlineQueueEnabled !== false;
8085
8481
  await Promise.all([
8086
8482
  networkMonitor.initialize({ debug: config.debug }),
@@ -8168,15 +8564,31 @@ async function doInit(state, config) {
8168
8564
  timezone = opts.timeZone || void 0;
8169
8565
  } catch {
8170
8566
  }
8567
+ let screenWidth;
8568
+ let screenHeight;
8569
+ let screenDensity;
8570
+ try {
8571
+ const screen = Dimensions4.get("screen");
8572
+ screenWidth = Math.round(screen.width) || void 0;
8573
+ screenHeight = Math.round(screen.height) || void 0;
8574
+ screenDensity = PixelRatio2.get() || void 0;
8575
+ } catch {
8576
+ }
8171
8577
  return {
8172
8578
  distinct_id: identityManager.getDistinctId() || void 0,
8173
8579
  device_id: identityManager.getDeviceId() ?? void 0,
8174
8580
  session_id: sessionManager.getSessionId() ?? void 0,
8175
8581
  app_version: device?.appVersion && device.appVersion !== "0.0.0" ? device.appVersion : void 0,
8582
+ app_build: device?.buildNumber && device.buildNumber !== "0" ? device.buildNumber : void 0,
8583
+ bundle_id: device?.bundleId || void 0,
8176
8584
  os_version: device?.systemVersion && device.systemVersion !== "0.0.0" ? device.systemVersion : void 0,
8177
8585
  device_model: device?.modelId || device?.model || void 0,
8178
8586
  timezone,
8179
8587
  locale,
8588
+ carrier: device?.carrier || void 0,
8589
+ screen_width: screenWidth,
8590
+ screen_height: screenHeight,
8591
+ screen_density: screenDensity,
8180
8592
  ...attribution && Object.keys(attribution).length > 0 && { attribution },
8181
8593
  ...ids && Object.keys(ids).length > 0 && { ids }
8182
8594
  };
@@ -8248,6 +8660,7 @@ var init_PaywalloInitializer = __esm({
8248
8660
  init_PaywalloState();
8249
8661
  init_queue();
8250
8662
  init_NativeStorage();
8663
+ init_detectEnvironment();
8251
8664
  }
8252
8665
  });
8253
8666
 
@@ -8270,6 +8683,7 @@ async function doFullReset(state) {
8270
8683
  offlineQueue.dispose();
8271
8684
  networkMonitor.dispose();
8272
8685
  campaignGateService.clearPreloaded();
8686
+ paywallPreloadService.clear();
8273
8687
  await notificationsManager.invalidateLocalToken().catch(() => void 0);
8274
8688
  notificationsManager.destroy();
8275
8689
  deepLinkAttributionCapture.stop();
@@ -8304,6 +8718,7 @@ var init_PaywalloLifecycle = __esm({
8304
8718
  init_identity();
8305
8719
  init_DeepLinkAttributionCapture();
8306
8720
  init_notifications();
8721
+ init_paywall();
8307
8722
  init_session();
8308
8723
  init_SubscriptionCache();
8309
8724
  init_network();
@@ -8607,6 +9022,15 @@ var init_PaywalloClient = __esm({
8607
9022
  isPreloaded(placement) {
8608
9023
  return campaignGateService.getPreloadedCampaign(placement) !== null;
8609
9024
  }
9025
+ async preloadPaywall(placement) {
9026
+ return paywallPreloadService.preload(this.getApiClientOrThrow(), placement);
9027
+ }
9028
+ async preloadPaywalls(placements) {
9029
+ return paywallPreloadService.preloadMany(this.getApiClientOrThrow(), placements);
9030
+ }
9031
+ isPaywallPreloaded(placement) {
9032
+ return paywallPreloadService.isPreloaded(placement);
9033
+ }
8610
9034
  getAutoPreloadedPlacement() {
8611
9035
  return this.state.autoPreloadedPlacement;
8612
9036
  }
@@ -8705,8 +9129,8 @@ var init_OfferingService = __esm({
8705
9129
  this.config = config;
8706
9130
  }
8707
9131
  async getOfferings(ids) {
8708
- const cached = this.getMemCache();
8709
- if (cached && !cached.isStale) return this.filterByIds(cached.data, ids);
9132
+ const cached2 = this.getMemCache();
9133
+ if (cached2 && !cached2.isStale) return this.filterByIds(cached2.data, ids);
8710
9134
  if (!this.cache) {
8711
9135
  try {
8712
9136
  const stored = await secureStorage.get(OFFERINGS_CACHE_KEY);
@@ -8821,7 +9245,7 @@ init_session();
8821
9245
  init_PaywalloError();
8822
9246
 
8823
9247
  // src/hooks/useOfferingPurchase.ts
8824
- import { useCallback as useCallback7, useState as useState6 } from "react";
9248
+ import { useCallback as useCallback7, useState as useState7 } from "react";
8825
9249
 
8826
9250
  // src/domains/plan/invalidateCache.ts
8827
9251
  async function invalidatePlanCache() {
@@ -8834,7 +9258,7 @@ async function invalidatePlanCache() {
8834
9258
 
8835
9259
  // src/hooks/useOfferingPurchase.ts
8836
9260
  function useOfferingPurchase() {
8837
- const [state, setState] = useState6("idle");
9261
+ const [state, setState] = useState7("idle");
8838
9262
  const purchase = useCallback7(async (productId) => {
8839
9263
  setState("purchasing");
8840
9264
  try {
@@ -8859,7 +9283,7 @@ function useOfferingPurchase() {
8859
9283
  }
8860
9284
 
8861
9285
  // src/hooks/useOfferings.ts
8862
- import { useCallback as useCallback8, useEffect as useEffect7, useState as useState7 } from "react";
9286
+ import { useCallback as useCallback8, useEffect as useEffect9, useState as useState8 } from "react";
8863
9287
  async function getOfferingService() {
8864
9288
  try {
8865
9289
  const mod = await Promise.resolve().then(() => (init_OfferingService(), OfferingService_exports));
@@ -8869,8 +9293,8 @@ async function getOfferingService() {
8869
9293
  }
8870
9294
  }
8871
9295
  function useOfferings(ids) {
8872
- const [offerings, setOfferings] = useState7([]);
8873
- const [isLoading, setIsLoading] = useState7(true);
9296
+ const [offerings, setOfferings] = useState8([]);
9297
+ const [isLoading, setIsLoading] = useState8(true);
8874
9298
  const idsKey = ids?.join(",") ?? "";
8875
9299
  const fetchOfferings = useCallback8(async () => {
8876
9300
  setIsLoading(true);
@@ -8888,7 +9312,7 @@ function useOfferings(ids) {
8888
9312
  setIsLoading(false);
8889
9313
  }
8890
9314
  }, [idsKey]);
8891
- useEffect7(() => {
9315
+ useEffect9(() => {
8892
9316
  void fetchOfferings();
8893
9317
  }, [fetchOfferings]);
8894
9318
  return { offerings, isLoading };
@@ -8925,10 +9349,10 @@ function usePaywallo() {
8925
9349
  }
8926
9350
 
8927
9351
  // src/hooks/usePlanPurchase.ts
8928
- import { useCallback as useCallback10, useState as useState8 } from "react";
9352
+ import { useCallback as useCallback10, useState as useState9 } from "react";
8929
9353
  function usePlanPurchase() {
8930
- const [state, setState] = useState8("idle");
8931
- const [error, setError] = useState8(null);
9354
+ const [state, setState] = useState9("idle");
9355
+ const [error, setError] = useState9(null);
8932
9356
  const purchase = useCallback10(async (productId) => {
8933
9357
  setState("purchasing");
8934
9358
  setError(null);
@@ -8981,7 +9405,7 @@ function usePlanPurchase() {
8981
9405
  }
8982
9406
 
8983
9407
  // src/hooks/usePlans.ts
8984
- import { useCallback as useCallback11, useEffect as useEffect8, useState as useState9 } from "react";
9408
+ import { useCallback as useCallback11, useEffect as useEffect10, useState as useState10 } from "react";
8985
9409
  async function getPlanService() {
8986
9410
  try {
8987
9411
  const mod = await Promise.resolve().then(() => (init_PlanService(), PlanService_exports));
@@ -8991,10 +9415,10 @@ async function getPlanService() {
8991
9415
  }
8992
9416
  }
8993
9417
  function usePlans() {
8994
- const [allPlans, setAllPlans] = useState9([]);
8995
- const [currentPlan, setCurrentPlan] = useState9(null);
8996
- const [isLoading, setIsLoading] = useState9(true);
8997
- const [error, setError] = useState9(null);
9418
+ const [allPlans, setAllPlans] = useState10([]);
9419
+ const [currentPlan, setCurrentPlan] = useState10(null);
9420
+ const [isLoading, setIsLoading] = useState10(true);
9421
+ const [error, setError] = useState10(null);
8998
9422
  const fetchPlans = useCallback11(async (forceRefresh = false) => {
8999
9423
  setIsLoading(true);
9000
9424
  setError(null);
@@ -9019,14 +9443,14 @@ function usePlans() {
9019
9443
  const refresh = useCallback11(() => {
9020
9444
  return fetchPlans(true);
9021
9445
  }, [fetchPlans]);
9022
- useEffect8(() => {
9446
+ useEffect10(() => {
9023
9447
  void fetchPlans(false);
9024
9448
  }, [fetchPlans]);
9025
9449
  return { currentPlan, allPlans, isLoading, error, refresh };
9026
9450
  }
9027
9451
 
9028
9452
  // src/hooks/useProducts.ts
9029
- import { useCallback as useCallback12, useEffect as useEffect9, useState as useState10 } from "react";
9453
+ import { useCallback as useCallback12, useEffect as useEffect11, useState as useState11 } from "react";
9030
9454
 
9031
9455
  // src/utils/productMappers.ts
9032
9456
  function mapToIAPProduct(product) {
@@ -9065,10 +9489,10 @@ function mapToFormattedProduct(product) {
9065
9489
 
9066
9490
  // src/hooks/useProducts.ts
9067
9491
  function useProducts(initialProductIds) {
9068
- const [products, setProducts] = useState10([]);
9069
- const [formattedProducts, setFormattedProducts] = useState10([]);
9070
- const [isLoading, setIsLoading] = useState10(false);
9071
- const [error, setError] = useState10(null);
9492
+ const [products, setProducts] = useState11([]);
9493
+ const [formattedProducts, setFormattedProducts] = useState11([]);
9494
+ const [isLoading, setIsLoading] = useState11(false);
9495
+ const [error, setError] = useState11(null);
9072
9496
  const loadProducts = useCallback12(async (productIds) => {
9073
9497
  setIsLoading(true);
9074
9498
  setError(null);
@@ -9083,7 +9507,7 @@ function useProducts(initialProductIds) {
9083
9507
  setIsLoading(false);
9084
9508
  }
9085
9509
  }, []);
9086
- useEffect9(() => {
9510
+ useEffect11(() => {
9087
9511
  if (initialProductIds && initialProductIds.length > 0) {
9088
9512
  void loadProducts(initialProductIds);
9089
9513
  }
@@ -9129,13 +9553,13 @@ function useProducts(initialProductIds) {
9129
9553
  }
9130
9554
 
9131
9555
  // src/hooks/usePurchase.ts
9132
- import { useCallback as useCallback13, useState as useState11 } from "react";
9556
+ import { useCallback as useCallback13, useState as useState12 } from "react";
9133
9557
  function toIAPPurchase(p) {
9134
9558
  return { productId: p.productId, transactionId: p.transactionId, transactionDate: p.transactionDate, transactionReceipt: p.receipt };
9135
9559
  }
9136
9560
  function usePurchase() {
9137
- const [state, setState] = useState11("idle");
9138
- const [error, setError] = useState11(null);
9561
+ const [state, setState] = useState12("idle");
9562
+ const [error, setError] = useState12(null);
9139
9563
  const purchase = useCallback13(async (productId) => {
9140
9564
  setState("purchasing");
9141
9565
  setError(null);
@@ -9183,13 +9607,13 @@ function usePurchase() {
9183
9607
  }
9184
9608
 
9185
9609
  // src/hooks/useSubscription.ts
9186
- import { useCallback as useCallback14, useEffect as useEffect10, useState as useState12 } from "react";
9610
+ import { useCallback as useCallback14, useEffect as useEffect12, useState as useState13 } from "react";
9187
9611
  init_SubscriptionManager();
9188
9612
  var EMPTY_ENTITLEMENTS = [];
9189
9613
  function useSubscription() {
9190
- const [status, setStatus] = useState12(null);
9191
- const [isLoading, setIsLoading] = useState12(true);
9192
- const [error, setError] = useState12(null);
9614
+ const [status, setStatus] = useState13(null);
9615
+ const [isLoading, setIsLoading] = useState13(true);
9616
+ const [error, setError] = useState13(null);
9193
9617
  const refresh = useCallback14(async () => {
9194
9618
  setIsLoading(true);
9195
9619
  setError(null);
@@ -9216,7 +9640,7 @@ function useSubscription() {
9216
9640
  setIsLoading(false);
9217
9641
  }
9218
9642
  }, []);
9219
- useEffect10(() => {
9643
+ useEffect12(() => {
9220
9644
  void subscriptionManager.getSubscriptionStatus().then((s) => {
9221
9645
  setStatus(s);
9222
9646
  setIsLoading(false);
@@ -9244,20 +9668,215 @@ function useSubscription() {
9244
9668
  }
9245
9669
 
9246
9670
  // src/PaywalloProvider.tsx
9247
- import { useCallback as useCallback19, useEffect as useEffect12, useLayoutEffect, useMemo as useMemo2, useRef as useRef10, useState as useState16 } from "react";
9248
- import { Animated as Animated3, Dimensions as Dimensions4, Easing as Easing2, StyleSheet as StyleSheet3 } from "react-native";
9671
+ import { useCallback as useCallback19, useEffect as useEffect14, useLayoutEffect, useMemo as useMemo3, useRef as useRef11, useState as useState17 } from "react";
9672
+ import { Animated as Animated4, Dimensions as Dimensions5, Easing as Easing2, StyleSheet as StyleSheet5 } from "react-native";
9249
9673
  init_PaywalloClient();
9674
+ init_NativeStorage();
9250
9675
  init_SecureStorage();
9251
9676
  init_campaign();
9252
9677
  init_paywall();
9253
9678
  init_paywall();
9254
9679
  init_paywall();
9255
9680
  init_paywall();
9681
+ init_paywall();
9682
+ init_paywall();
9683
+
9684
+ // src/domains/paywall/SuperwallAutoBridge.ts
9685
+ init_PaywalloClient();
9686
+
9687
+ // src/core/utils/eventId.ts
9688
+ function hashString(input) {
9689
+ let h1 = 3735928559;
9690
+ let h2 = 1103547991;
9691
+ for (let i = 0; i < input.length; i++) {
9692
+ const ch = input.charCodeAt(i);
9693
+ h1 = Math.imul(h1 ^ ch, 2654435769);
9694
+ h2 = Math.imul(h2 ^ ch, 1598716949);
9695
+ }
9696
+ h1 = Math.imul(h1 ^ h1 >>> 16, 73244475);
9697
+ h1 ^= Math.imul(h2 ^ h2 >>> 13, 351452291);
9698
+ h2 = Math.imul(h2 ^ h2 >>> 16, 73244475);
9699
+ h2 ^= Math.imul(h1 ^ h1 >>> 13, 351452291);
9700
+ const toHex = (n) => (n >>> 0).toString(16).padStart(8, "0");
9701
+ return toHex(h1) + toHex(h2);
9702
+ }
9703
+ function buildDeterministicEventId(parts) {
9704
+ const raw = hashString(parts.join("|"));
9705
+ const p1 = raw.slice(0, 8);
9706
+ const p2 = raw.slice(8, 12);
9707
+ const p3 = "4" + raw.slice(13, 16);
9708
+ const p4 = (parseInt(raw[16], 16) & 3 | 8).toString(16) + raw.slice(17, 20);
9709
+ const p5 = raw.padEnd(32, "0").slice(20, 32);
9710
+ return `${p1}-${p2}-${p3}-${p4}-${p5}`;
9711
+ }
9712
+
9713
+ // src/domains/paywall/SuperwallAutoBridge.ts
9714
+ var bridgeStarted = false;
9715
+ var unsubscribe = null;
9716
+ var debugEnabled = false;
9717
+ function log(msg, data) {
9718
+ if (!debugEnabled) return;
9719
+ if (data !== void 0) {
9720
+ console.log(`[Paywallo:SuperwallBridge] ${msg}`, data);
9721
+ } else {
9722
+ console.log(`[Paywallo:SuperwallBridge] ${msg}`);
9723
+ }
9724
+ }
9725
+ function minuteBucket(ts) {
9726
+ return String(Math.floor(ts / 6e4));
9727
+ }
9728
+ function buildEventId(type, identifier, ts) {
9729
+ return buildDeterministicEventId(["sw", type, identifier, minuteBucket(ts)]);
9730
+ }
9731
+ async function trackPaywallEvent(type, identifier, ts, extra) {
9732
+ const _eventId = buildEventId(type, identifier, ts);
9733
+ try {
9734
+ const cleanExtra = {};
9735
+ if (extra) {
9736
+ for (const [k, v] of Object.entries(extra)) if (v !== void 0) cleanExtra[k] = v;
9737
+ }
9738
+ await PaywalloClient.track("paywall", {
9739
+ priority: "critical",
9740
+ properties: {
9741
+ type,
9742
+ paywall_id: identifier,
9743
+ ...cleanExtra
9744
+ }
9745
+ });
9746
+ } catch (err) {
9747
+ log("track error", { type, identifier, eventId: _eventId, err: String(err) });
9748
+ }
9749
+ }
9750
+ function mapDismissToCloseReason(resultType) {
9751
+ switch (resultType) {
9752
+ case "purchased":
9753
+ return "purchased";
9754
+ case "restored":
9755
+ return "purchased";
9756
+ // restore counts as a purchase outcome for funnel
9757
+ case "declined":
9758
+ return "dismissed";
9759
+ default:
9760
+ return void 0;
9761
+ }
9762
+ }
9763
+ function buildCallbacks() {
9764
+ return {
9765
+ onPaywallPresent: (info) => {
9766
+ const ts = Date.now();
9767
+ log("onPaywallPresent", { identifier: info.identifier });
9768
+ void trackPaywallEvent("viewed", info.identifier, ts, {
9769
+ placement: info.presentedByEventWithName,
9770
+ variant_id: info.experiment?.variant?.id,
9771
+ campaign_id: info.experiment?.id
9772
+ });
9773
+ },
9774
+ onPaywallDismiss: (info, result) => {
9775
+ const ts = Date.now();
9776
+ log("onPaywallDismiss", { identifier: info.identifier, result: result.type });
9777
+ void trackPaywallEvent("closed", info.identifier, ts, {
9778
+ placement: info.presentedByEventWithName,
9779
+ variant_id: info.experiment?.variant?.id,
9780
+ campaign_id: info.experiment?.id,
9781
+ close_reason: mapDismissToCloseReason(result.type),
9782
+ closed_at: new Date(ts).toISOString()
9783
+ });
9784
+ },
9785
+ onPurchase: (params) => {
9786
+ const ts = Date.now();
9787
+ const safe = params !== null && typeof params === "object" ? params : {};
9788
+ const productId = "productId" in safe && typeof safe.productId === "string" ? safe.productId : "unknown";
9789
+ const identifier = "paywallIdentifier" in safe && typeof safe.paywallIdentifier === "string" ? safe.paywallIdentifier : productId;
9790
+ log("onPurchase", { productId, identifier });
9791
+ void trackPaywallEvent("purchased", identifier, ts, { product_id: productId });
9792
+ },
9793
+ // Global Superwall event listener — used specifically for `transactionComplete`
9794
+ // which is the only event that exposes the full product (currency/price) +
9795
+ // transaction (storeTransactionId) shape needed to create a server-side
9796
+ // `transactions` row. Other events are skipped (paywallOpen/Dismiss already
9797
+ // handled above with their typed callbacks).
9798
+ onSuperwallEvent: (info) => {
9799
+ const ev = info?.event;
9800
+ if (!ev || ev.event !== "transactionComplete") return;
9801
+ const ts = Date.now();
9802
+ const product = ev.product;
9803
+ const transaction = ev.transaction;
9804
+ if (!product) {
9805
+ log("transactionComplete missing product \u2014 skipping transaction track");
9806
+ return;
9807
+ }
9808
+ const productId = product.productIdentifier ?? product.id ?? "unknown";
9809
+ const currency = product.currencyCode ?? "USD";
9810
+ const price = product.price ?? 0;
9811
+ const transactionId = transaction?.storeTransactionId ?? transaction?.originalTransactionIdentifier ?? `${productId}_${String(ts)}`;
9812
+ const isTrial = product.hasFreeTrial === true || (product.trialPeriodPrice ?? 0) > 0;
9813
+ const paywallIdentifier = ev.paywallInfo?.identifier;
9814
+ log("onSuperwallEvent: transactionComplete", { productId, currency, price, transactionId, isTrial });
9815
+ void (async () => {
9816
+ try {
9817
+ await PaywalloClient.track("transaction", {
9818
+ priority: "critical",
9819
+ properties: {
9820
+ type: isTrial ? "trial_started" : "completed",
9821
+ // Server's IngestTranslator looks for `transaction_id`/`product_id`
9822
+ // /`amount`/`currency`/`paywall_id` keys (not `tx_id`). PurchaseHandler
9823
+ // then resolves type=trial/new/renewal based on amount being 0 vs >0.
9824
+ transaction_id: transactionId,
9825
+ product_id: productId,
9826
+ amount: price,
9827
+ currency,
9828
+ ...paywallIdentifier ? { paywall_id: paywallIdentifier } : {}
9829
+ }
9830
+ });
9831
+ } catch (err) {
9832
+ log("transactionComplete track error", { err: String(err) });
9833
+ }
9834
+ })();
9835
+ }
9836
+ };
9837
+ }
9838
+ function startSuperwallAutoBridge(debug = false) {
9839
+ debugEnabled = debug;
9840
+ if (bridgeStarted) {
9841
+ log("already started, skipping");
9842
+ return;
9843
+ }
9844
+ void detectAndStart();
9845
+ }
9846
+ async function detectAndStart() {
9847
+ let subscribeFn;
9848
+ try {
9849
+ const mod = await import("expo-superwall");
9850
+ if (!mod || typeof mod.subscribeToSuperwallEvents !== "function") {
9851
+ const internal = await import("expo-superwall/build/src/internal/superwallEventBridge");
9852
+ if (typeof internal.subscribeToSuperwallEvents !== "function") {
9853
+ log("expo-superwall subscribeToSuperwallEvents not found \u2014 bridge inactive");
9854
+ return;
9855
+ }
9856
+ subscribeFn = internal.subscribeToSuperwallEvents;
9857
+ } else {
9858
+ subscribeFn = mod.subscribeToSuperwallEvents;
9859
+ }
9860
+ } catch {
9861
+ log("expo-superwall not installed \u2014 bridge inactive");
9862
+ return;
9863
+ }
9864
+ try {
9865
+ const callbacks = buildCallbacks();
9866
+ unsubscribe = subscribeFn(void 0, () => callbacks);
9867
+ bridgeStarted = true;
9868
+ log("bridge started");
9869
+ } catch (err) {
9870
+ log("subscribe failed", { err: String(err) });
9871
+ }
9872
+ }
9873
+
9874
+ // src/PaywalloProvider.tsx
9256
9875
  init_session();
9257
9876
  init_ClientError();
9258
9877
 
9259
9878
  // src/hooks/useAppAutoEvents.ts
9260
- import { useEffect as useEffect11, useRef as useRef7 } from "react";
9879
+ import { useEffect as useEffect13, useRef as useRef8 } from "react";
9261
9880
 
9262
9881
  // src/utils/platformDetection.ts
9263
9882
  async function detectPlatform3() {
@@ -9279,12 +9898,13 @@ async function detectOsVersion() {
9279
9898
  }
9280
9899
 
9281
9900
  // src/hooks/useAppAutoEvents.ts
9282
- var FIRST_SEEN_KEY = "@paywallo:firstSeen";
9901
+ var FIRST_SEEN_KEY = "@paywallo:firstSeen_v226";
9283
9902
  var DEBOUNCE_MS = 1e3;
9284
9903
  function useAppAutoEvents(config) {
9285
9904
  const { isInitialized, getAppVersion: getAppVersion2, getSessionId, emitLifecycle, storageGet, storageSet, debug } = config;
9286
- const didRunRef = useRef7(false);
9287
- useEffect11(() => {
9905
+ const didRunRef = useRef8(false);
9906
+ useEffect13(() => {
9907
+ if (debug) console.log("[Paywallo EVENTS] useEffect fired", { isInitialized, didRunRefCurrent: didRunRef.current });
9288
9908
  if (!isInitialized || didRunRef.current) return;
9289
9909
  const timer = setTimeout(() => {
9290
9910
  if (didRunRef.current) return;
@@ -9294,17 +9914,23 @@ function useAppAutoEvents(config) {
9294
9914
  const appVersion = getAppVersion2();
9295
9915
  const deviceType = await detectPlatform3();
9296
9916
  const osVersion = await detectOsVersion();
9917
+ if (debug) console.log("[Paywallo EVENTS] computed device context", { deviceType, osVersion, appVersion, sessionId, hasSessionId: !!sessionId });
9297
9918
  try {
9919
+ if (debug) console.log("[Paywallo EVENTS] checking install \u2014 about to read FIRST_SEEN_KEY");
9298
9920
  const firstSeen = await storageGet(FIRST_SEEN_KEY);
9299
9921
  if (!firstSeen) {
9300
9922
  await emitLifecycle({ type: "install", device_type: deviceType, os_version: osVersion, app_version: appVersion, ...sessionId ? { session_id: sessionId } : {} }, "critical");
9301
9923
  await storageSet(FIRST_SEEN_KEY, (/* @__PURE__ */ new Date()).toISOString());
9924
+ if (debug) console.log("[Paywallo EVENTS] install event emitted (first time)");
9302
9925
  if (debug) console.log("[Paywallo EVENTS] lifecycle install emitted");
9926
+ } else {
9927
+ if (debug) console.log("[Paywallo EVENTS] install skipped \u2014 firstSeen already exists", { firstSeen });
9303
9928
  }
9304
9929
  } catch (err) {
9305
9930
  if (debug) console.log("[Paywallo EVENTS] lifecycle install check failed", err);
9306
9931
  }
9307
9932
  try {
9933
+ if (debug) console.log("[Paywallo EVENTS] emitting cold_start");
9308
9934
  await emitLifecycle({ type: "cold_start", app_version: appVersion, device_type: deviceType, os_version: osVersion, ...sessionId ? { session_id: sessionId } : {} }, "normal");
9309
9935
  if (debug) console.log("[Paywallo EVENTS] lifecycle cold_start emitted");
9310
9936
  } catch (err) {
@@ -9319,7 +9945,7 @@ function useAppAutoEvents(config) {
9319
9945
  // src/hooks/useCampaignPreload.ts
9320
9946
  init_PaywalloClient();
9321
9947
  init_CampaignError();
9322
- import { useCallback as useCallback15, useRef as useRef8, useState as useState13 } from "react";
9948
+ import { useCallback as useCallback15, useRef as useRef9, useState as useState14 } from "react";
9323
9949
 
9324
9950
  // src/utils/loadCampaignProducts.ts
9325
9951
  init_IAPService();
@@ -9388,8 +10014,8 @@ function getCacheEntry(cache, placement) {
9388
10014
 
9389
10015
  // src/hooks/useCampaignPreload.ts
9390
10016
  function useCampaignPreload() {
9391
- const [preloadedWebView, setPreloadedWebView] = useState13(null);
9392
- const preloadedCampaignsRef = useRef8(/* @__PURE__ */ new Map());
10017
+ const [preloadedWebView, setPreloadedWebView] = useState14(null);
10018
+ const preloadedCampaignsRef = useRef9(/* @__PURE__ */ new Map());
9393
10019
  const isPreloadValid = useCallback15(
9394
10020
  (placement) => isCacheEntryValid(preloadedCampaignsRef.current.get(placement)),
9395
10021
  []
@@ -9467,10 +10093,10 @@ function useCampaignPreload() {
9467
10093
  }
9468
10094
 
9469
10095
  // src/hooks/useProductLoader.ts
9470
- import { useCallback as useCallback16, useState as useState14 } from "react";
10096
+ import { useCallback as useCallback16, useState as useState15 } from "react";
9471
10097
  function useProductLoader() {
9472
- const [products, setProducts] = useState14(/* @__PURE__ */ new Map());
9473
- const [isLoadingProducts, setIsLoadingProducts] = useState14(false);
10098
+ const [products, setProducts] = useState15(/* @__PURE__ */ new Map());
10099
+ const [isLoadingProducts, setIsLoadingProducts] = useState15(false);
9474
10100
  const refreshProducts = useCallback16(async (productIds) => {
9475
10101
  setIsLoadingProducts(true);
9476
10102
  try {
@@ -9556,7 +10182,7 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
9556
10182
  // src/hooks/useSubscriptionSync.ts
9557
10183
  init_PaywalloClient();
9558
10184
  init_SubscriptionCache();
9559
- import { useCallback as useCallback18, useRef as useRef9, useState as useState15 } from "react";
10185
+ import { useCallback as useCallback18, useRef as useRef10, useState as useState16 } from "react";
9560
10186
 
9561
10187
  // src/hooks/subscriptionSyncHelpers.ts
9562
10188
  init_SubscriptionCache();
@@ -9619,9 +10245,9 @@ function subscriptionInfoToSub(s) {
9619
10245
  }
9620
10246
  async function resolveOfflineFromCache(distinctId) {
9621
10247
  try {
9622
- const cached = await subscriptionCache.get(distinctId);
9623
- if (!cached) return false;
9624
- const sub = cached.data.subscription;
10248
+ const cached2 = await subscriptionCache.get(distinctId);
10249
+ if (!cached2) return false;
10250
+ const sub = cached2.data.subscription;
9625
10251
  return isSubscriptionStillActive(
9626
10252
  sub ? { status: sub.status, expiresAt: sub.expiresAt ?? null } : null
9627
10253
  );
@@ -9664,8 +10290,8 @@ async function checkPersistedCache(distinctId, onHit) {
9664
10290
 
9665
10291
  // src/hooks/useSubscriptionSync.ts
9666
10292
  function useSubscriptionSync() {
9667
- const [subscription, setSubscription] = useState15(null);
9668
- const cacheRef = useRef9(null);
10293
+ const [subscription, setSubscription] = useState16(null);
10294
+ const cacheRef = useRef10(null);
9669
10295
  const setCacheAndState = useCallback18((sub) => {
9670
10296
  cacheRef.current = { data: sub, expiresAt: Date.now() + CACHE_TTL_MS2 };
9671
10297
  setSubscription(sub);
@@ -9691,8 +10317,8 @@ function useSubscriptionSync() {
9691
10317
  if (!apiClient || !distinctId) return false;
9692
10318
  const storeKitResult = await checkStoreKitActive(PaywalloClient.getConfig()?.debug);
9693
10319
  if (storeKitResult !== null) return storeKitResult;
9694
- const cached = cacheRef.current;
9695
- if (cached && cached.expiresAt > Date.now()) return isActiveCacheEntry(cached);
10320
+ const cached2 = cacheRef.current;
10321
+ if (cached2 && cached2.expiresAt > Date.now()) return isActiveCacheEntry(cached2);
9696
10322
  const persistedResult = await checkPersistedCache(distinctId, (sub) => setCacheAndState(sub));
9697
10323
  if (persistedResult !== null) return persistedResult;
9698
10324
  try {
@@ -9709,8 +10335,8 @@ function useSubscriptionSync() {
9709
10335
  const apiClient = PaywalloClient.getApiClient();
9710
10336
  const distinctId = PaywalloClient.getDistinctId();
9711
10337
  if (!apiClient || !distinctId) return null;
9712
- const cached = cacheRef.current;
9713
- if (cached && cached.expiresAt > Date.now()) return cached.data;
10338
+ const cached2 = cacheRef.current;
10339
+ if (cached2 && cached2.expiresAt > Date.now()) return cached2.data;
9714
10340
  try {
9715
10341
  return await fetchAndCache();
9716
10342
  } catch {
@@ -9728,22 +10354,22 @@ function useSubscriptionSync() {
9728
10354
 
9729
10355
  // src/PaywalloProvider.tsx
9730
10356
  init_localization();
9731
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
10357
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
9732
10358
  function PaywalloProvider({ children, config }) {
9733
- const [isInitialized, setIsInitialized] = useState16(false);
9734
- const [initError, setInitError] = useState16(null);
9735
- const [distinctId, setDistinctId] = useState16(null);
9736
- const [activePaywall, setActivePaywall] = useState16(null);
10359
+ const [isInitialized, setIsInitialized] = useState17(false);
10360
+ const [initError, setInitError] = useState17(null);
10361
+ const [distinctId, setDistinctId] = useState17(null);
10362
+ const [activePaywall, setActivePaywall] = useState17(null);
9737
10363
  const { products, isLoadingProducts, refreshProducts } = useProductLoader();
9738
10364
  const { subscription, hasActiveSubscription, getSubscription, invalidateCache } = useSubscriptionSync();
9739
10365
  const { preloadedWebView, setPreloadedWebView, preloadCampaign, consumePreloadedCampaign, isPreloadValid } = useCampaignPreload();
9740
10366
  const clearPreloadedWebView = useCallback19(() => setPreloadedWebView(null), [setPreloadedWebView]);
9741
- const autoPreloadTriggeredRef = useRef10(false);
9742
- const preloadedWebViewRef = useRef10(preloadedWebView);
9743
- const pollTimerRef = useRef10(null);
9744
- const pollCancelledRef = useRef10(false);
9745
- const secureStorageRef = useRef10(new SecureStorage(config.debug));
9746
- useEffect12(() => {
10367
+ const autoPreloadTriggeredRef = useRef11(false);
10368
+ const preloadedWebViewRef = useRef11(preloadedWebView);
10369
+ const pollTimerRef = useRef11(null);
10370
+ const pollCancelledRef = useRef11(false);
10371
+ const secureStorageRef = useRef11(new SecureStorage(config.debug));
10372
+ useEffect14(() => {
9747
10373
  preloadedWebViewRef.current = preloadedWebView;
9748
10374
  }, [preloadedWebView]);
9749
10375
  useAppAutoEvents({
@@ -9755,8 +10381,18 @@ function PaywalloProvider({ children, config }) {
9755
10381
  for (const [k, v] of Object.entries(props)) if (v !== void 0) flat[k] = v;
9756
10382
  return PaywalloClient.track("lifecycle", { properties: flat, priority });
9757
10383
  },
9758
- storageGet: (key) => secureStorageRef.current.get(key),
9759
- storageSet: (key, value) => secureStorageRef.current.set(key, value),
10384
+ storageGet: async (key) => {
10385
+ if (key === "@paywallo:firstSeen_v226") {
10386
+ return nativeStorage.get(key);
10387
+ }
10388
+ return secureStorageRef.current.get(key);
10389
+ },
10390
+ storageSet: async (key, value) => {
10391
+ if (key === "@paywallo:firstSeen_v226") {
10392
+ return nativeStorage.set(key, value);
10393
+ }
10394
+ return secureStorageRef.current.set(key, value);
10395
+ },
9760
10396
  debug: config.debug
9761
10397
  });
9762
10398
  const triggerRepreload = useCallback19((placement) => {
@@ -9764,8 +10400,8 @@ function PaywalloProvider({ children, config }) {
9764
10400
  });
9765
10401
  }, []);
9766
10402
  const { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady: baseHandlePreloadedWebViewReady, handlePreloadedClose: baseHandlePreloadedClose, setShowingPreloadedWebView, presentedAtRef } = usePaywallPresenter(preloadedWebView, setPreloadedWebView);
9767
- const SCREEN_HEIGHT = useMemo2(() => Dimensions4.get("window").height, []);
9768
- const slideAnim = useRef10(new Animated3.Value(SCREEN_HEIGHT)).current;
10403
+ const SCREEN_HEIGHT = useMemo3(() => Dimensions5.get("window").height, []);
10404
+ const slideAnim = useRef11(new Animated4.Value(SCREEN_HEIGHT)).current;
9769
10405
  const handlePreloadedWebViewReady = useCallback19(() => {
9770
10406
  if (PaywalloClient.getConfig()?.debug) {
9771
10407
  console.log(`[Paywallo WEBVIEW] loaded \u2014 placement: ${preloadedWebView?.placement}`);
@@ -9774,7 +10410,7 @@ function PaywalloProvider({ children, config }) {
9774
10410
  }, [preloadedWebView?.placement, baseHandlePreloadedWebViewReady]);
9775
10411
  const handlePreloadedClose = useCallback19(() => {
9776
10412
  const placement = preloadedWebView?.placement;
9777
- Animated3.timing(slideAnim, { toValue: SCREEN_HEIGHT, duration: 200, easing: Easing2.in(Easing2.cubic), useNativeDriver: true }).start(() => {
10413
+ Animated4.timing(slideAnim, { toValue: SCREEN_HEIGHT, duration: 200, easing: Easing2.in(Easing2.cubic), useNativeDriver: true }).start(() => {
9778
10414
  baseHandlePreloadedClose();
9779
10415
  if (placement) triggerRepreload(placement);
9780
10416
  });
@@ -9787,7 +10423,7 @@ function PaywalloProvider({ children, config }) {
9787
10423
  presentedAtRef,
9788
10424
  invalidateCache
9789
10425
  );
9790
- const [isPreloadPurchasing, setIsPreloadPurchasing] = useState16(false);
10426
+ const [isPreloadPurchasing, setIsPreloadPurchasing] = useState17(false);
9791
10427
  const handlePreloadedPurchase = useCallback19(async (productId) => {
9792
10428
  setIsPreloadPurchasing(true);
9793
10429
  try {
@@ -9796,19 +10432,25 @@ function PaywalloProvider({ children, config }) {
9796
10432
  setIsPreloadPurchasing(false);
9797
10433
  }
9798
10434
  }, [rawPreloadedPurchase]);
9799
- const configRef = useRef10(config);
9800
- useEffect12(() => {
10435
+ const configRef = useRef11(config);
10436
+ useEffect14(() => {
9801
10437
  let mounted = true;
9802
10438
  initLocalization({ detectDevice: true });
9803
10439
  PaywalloClient.init(configRef.current).then(async () => {
9804
10440
  if (!mounted) return;
9805
10441
  setDistinctId(PaywalloClient.getDistinctId());
10442
+ startSuperwallAutoBridge(configRef.current.debug);
9806
10443
  setIsInitialized(true);
9807
10444
  if (!autoPreloadTriggeredRef.current) {
9808
10445
  autoPreloadTriggeredRef.current = true;
9809
10446
  const placement = await PaywalloClient.waitForAutoPreloadedPlacement();
9810
10447
  if (mounted && placement) void preloadCampaign(placement).catch(() => {
9811
10448
  });
10449
+ const preloadPlacements = configRef.current.preloadPaywalls;
10450
+ if (mounted && preloadPlacements && preloadPlacements.length > 0) {
10451
+ void PaywalloClient.preloadPaywalls(preloadPlacements).catch(() => {
10452
+ });
10453
+ }
9812
10454
  }
9813
10455
  }).catch((err) => {
9814
10456
  if (!mounted) return;
@@ -9823,7 +10465,21 @@ function PaywalloProvider({ children, config }) {
9823
10465
  return api ? api.getPaywall(p) : null;
9824
10466
  }, []);
9825
10467
  const presentPaywall = useCallback19((placement) => {
9826
- return new Promise((resolve) => setActivePaywall({ placement, resolver: resolve }));
10468
+ const preloaded = paywallPreloadService.getPreloaded(placement);
10469
+ return new Promise((resolve) => setActivePaywall({
10470
+ placement,
10471
+ resolver: resolve,
10472
+ ...preloaded && {
10473
+ paywallConfig: {
10474
+ id: preloaded.config.id,
10475
+ placement: preloaded.config.placement,
10476
+ config: preloaded.config.config,
10477
+ primaryProductId: preloaded.config.primaryProductId,
10478
+ secondaryProductId: preloaded.config.secondaryProductId
10479
+ },
10480
+ preloadedProducts: preloaded.products
10481
+ }
10482
+ }));
9827
10483
  }, []);
9828
10484
  const trackCampaignImpression = useCallback19((placement, variantKey, campaignId) => {
9829
10485
  const api = PaywalloClient.getApiClient();
@@ -9957,7 +10613,7 @@ function PaywalloProvider({ children, config }) {
9957
10613
  }
9958
10614
  return presentCampaign(placement);
9959
10615
  }, [presentPreloadedWebView, presentCampaign]);
9960
- useEffect12(() => {
10616
+ useEffect14(() => {
9961
10617
  if (!isInitialized) return;
9962
10618
  const onEmergency = async (id) => {
9963
10619
  try {
@@ -9986,7 +10642,7 @@ function PaywalloProvider({ children, config }) {
9986
10642
  const isReady = isInitialized && !isLoadingProducts;
9987
10643
  const noOp = useCallback19(() => {
9988
10644
  }, []);
9989
- const contextValue = useMemo2(() => ({
10645
+ const contextValue = useMemo3(() => ({
9990
10646
  config: PaywalloClient.getConfig(),
9991
10647
  isInitialized,
9992
10648
  isReady,
@@ -10023,17 +10679,17 @@ function PaywalloProvider({ children, config }) {
10023
10679
  useLayoutEffect(() => {
10024
10680
  if (showingPreloadedWebView) {
10025
10681
  slideAnim.setValue(SCREEN_HEIGHT);
10026
- Animated3.timing(slideAnim, { toValue: 0, duration: 200, easing: Easing2.out(Easing2.cubic), useNativeDriver: true }).start();
10682
+ Animated4.timing(slideAnim, { toValue: 0, duration: 200, easing: Easing2.out(Easing2.cubic), useNativeDriver: true }).start();
10027
10683
  }
10028
10684
  }, [showingPreloadedWebView, slideAnim, SCREEN_HEIGHT]);
10029
10685
  const preloadStyle = [
10030
- styles3.preloadOverlay,
10031
- showingPreloadedWebView ? styles3.visible : styles3.hidden,
10686
+ styles5.preloadOverlay,
10687
+ showingPreloadedWebView ? styles5.visible : styles5.hidden,
10032
10688
  showingPreloadedWebView ? { transform: [{ translateY: slideAnim }] } : void 0
10033
10689
  ];
10034
- return /* @__PURE__ */ jsxs2(PaywalloContext.Provider, { value: contextValue, children: [
10690
+ return /* @__PURE__ */ jsxs3(PaywalloContext.Provider, { value: contextValue, children: [
10035
10691
  children,
10036
- /* @__PURE__ */ jsx3(PaywallErrorBoundary, { children: !!preloadedWebView?.craftData && /* @__PURE__ */ jsx3(Animated3.View, { style: preloadStyle, pointerEvents: showingPreloadedWebView ? "auto" : "none", children: /* @__PURE__ */ jsx3(
10692
+ /* @__PURE__ */ jsx5(PaywallErrorBoundary, { children: !!preloadedWebView?.craftData && /* @__PURE__ */ jsx5(Animated4.View, { style: preloadStyle, pointerEvents: showingPreloadedWebView ? "auto" : "none", children: /* @__PURE__ */ jsx5(
10037
10693
  PaywallWebView,
10038
10694
  {
10039
10695
  paywallId: preloadedWebView.paywallId,
@@ -10049,7 +10705,7 @@ function PaywalloProvider({ children, config }) {
10049
10705
  onRestore: showingPreloadedWebView ? handlePreloadedRestore : noOp
10050
10706
  }
10051
10707
  ) }) }),
10052
- /* @__PURE__ */ jsx3(PaywallErrorBoundary, { children: activePaywall && /* @__PURE__ */ jsx3(
10708
+ /* @__PURE__ */ jsx5(PaywallErrorBoundary, { children: activePaywall && /* @__PURE__ */ jsx5(
10053
10709
  PaywallModal,
10054
10710
  {
10055
10711
  placement: activePaywall.placement,
@@ -10061,10 +10717,11 @@ function PaywalloProvider({ children, config }) {
10061
10717
  campaignId: activePaywall.campaignId,
10062
10718
  variantId: activePaywall.variantId
10063
10719
  }
10064
- ) })
10720
+ ) }),
10721
+ isInitialized && (config.preloadPaywalls?.length ?? 0) > 0 && PaywalloClient.getWebUrl() !== null && /* @__PURE__ */ jsx5(PaywallWebViewPrewarmer, { webUrl: PaywalloClient.getWebUrl() })
10065
10722
  ] });
10066
10723
  }
10067
- var styles3 = StyleSheet3.create({
10724
+ var styles5 = StyleSheet5.create({
10068
10725
  preloadOverlay: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, backgroundColor: "#ffffff" },
10069
10726
  hidden: { zIndex: -1, opacity: 0, width: 1, height: 1, overflow: "hidden" },
10070
10727
  visible: { zIndex: 9999, opacity: 1 }