@virex-tech/paywallo-sdk 2.0.2 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -212,6 +212,9 @@ function isAvailable() {
212
212
  function clearCache() {
213
213
  cachedDeviceInfo = null;
214
214
  }
215
+ function getCachedDeviceInfo() {
216
+ return cachedDeviceInfo;
217
+ }
215
218
  var import_react_native, FALLBACK, _debug, warnedOnce, cachedDeviceInfo, nativeDeviceInfo;
216
219
  var init_NativeDeviceInfo = __esm({
217
220
  "src/utils/NativeDeviceInfo.ts"() {
@@ -239,6 +242,7 @@ var init_NativeDeviceInfo = __esm({
239
242
  cachedDeviceInfo = null;
240
243
  nativeDeviceInfo = {
241
244
  getDeviceInfo,
245
+ getCachedDeviceInfo,
242
246
  getInstallReferrer,
243
247
  isAvailable,
244
248
  clearCache
@@ -499,7 +503,7 @@ var init_IdentityStorage = __esm({
499
503
  });
500
504
 
501
505
  // src/domains/identity/IdentityManager.ts
502
- var EMAIL_REGEX, IdentityManager, identityManager;
506
+ var USER_PHONE_KEY, USER_FIRST_NAME_KEY, USER_LAST_NAME_KEY, USER_DOB_KEY, USER_GENDER_KEY, EMAIL_REGEX, IdentityManager, identityManager;
503
507
  var init_IdentityManager = __esm({
504
508
  "src/domains/identity/IdentityManager.ts"() {
505
509
  "use strict";
@@ -509,6 +513,11 @@ var init_IdentityManager = __esm({
509
513
  init_SecureStorage();
510
514
  init_StorageMigration();
511
515
  init_IdentityStorage();
516
+ USER_PHONE_KEY = "@panel:user_phone";
517
+ USER_FIRST_NAME_KEY = "@panel:user_first_name";
518
+ USER_LAST_NAME_KEY = "@panel:user_last_name";
519
+ USER_DOB_KEY = "@panel:user_dob";
520
+ USER_GENDER_KEY = "@panel:user_gender";
512
521
  EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
513
522
  IdentityManager = class {
514
523
  constructor() {
@@ -516,6 +525,11 @@ var init_IdentityManager = __esm({
516
525
  this.anonId = null;
517
526
  this.email = null;
518
527
  this.properties = {};
528
+ this.phone = null;
529
+ this.firstName = null;
530
+ this.lastName = null;
531
+ this.dateOfBirth = null;
532
+ this.gender = null;
519
533
  this.apiClient = null;
520
534
  this.secureStorage = null;
521
535
  this.debug = false;
@@ -571,7 +585,7 @@ var init_IdentityManager = __esm({
571
585
  let email;
572
586
  let properties;
573
587
  if (options) {
574
- if ("email" in options || "properties" in options) {
588
+ if ("email" in options || "properties" in options || "phone" in options || "firstName" in options || "lastName" in options || "dateOfBirth" in options || "gender" in options) {
575
589
  email = options.email;
576
590
  properties = options.properties;
577
591
  } else {
@@ -591,9 +605,36 @@ var init_IdentityManager = __esm({
591
605
  this.properties = { ...this.properties, ...properties };
592
606
  await this.secureStorage.set(USER_PROPERTIES_KEY, JSON.stringify(this.properties));
593
607
  }
608
+ const opts = options;
609
+ if (opts?.phone) {
610
+ this.phone = opts.phone;
611
+ await this.secureStorage.set(USER_PHONE_KEY, this.phone);
612
+ }
613
+ if (opts?.firstName) {
614
+ this.firstName = opts.firstName;
615
+ await this.secureStorage.set(USER_FIRST_NAME_KEY, this.firstName);
616
+ }
617
+ if (opts?.lastName) {
618
+ this.lastName = opts.lastName;
619
+ await this.secureStorage.set(USER_LAST_NAME_KEY, this.lastName);
620
+ }
621
+ if (opts?.dateOfBirth) {
622
+ this.dateOfBirth = opts.dateOfBirth;
623
+ await this.secureStorage.set(USER_DOB_KEY, this.dateOfBirth);
624
+ }
625
+ if (opts?.gender) {
626
+ this.gender = opts.gender;
627
+ await this.secureStorage.set(USER_GENDER_KEY, this.gender);
628
+ }
594
629
  if (this.apiClient && this.anonId) {
595
630
  try {
596
- await this.apiClient.identify(this.anonId, properties, email, this.deviceId ?? void 0);
631
+ await this.apiClient.identify(this.anonId, properties, email, this.deviceId ?? void 0, {
632
+ phone: this.phone ?? void 0,
633
+ firstName: this.firstName ?? void 0,
634
+ lastName: this.lastName ?? void 0,
635
+ dateOfBirth: this.dateOfBirth ?? void 0,
636
+ gender: this.gender ?? void 0
637
+ });
597
638
  } catch {
598
639
  }
599
640
  }
@@ -628,17 +669,32 @@ var init_IdentityManager = __esm({
628
669
  this.distinctId = null;
629
670
  this.email = null;
630
671
  this.properties = {};
672
+ this.phone = null;
673
+ this.firstName = null;
674
+ this.lastName = null;
675
+ this.dateOfBirth = null;
676
+ this.gender = null;
631
677
  await Promise.all([
632
678
  this.secureStorage?.set(ANON_ID_KEY, newAnonId) ?? Promise.resolve(false),
633
679
  this.secureStorage?.remove(USER_EMAIL_KEY) ?? Promise.resolve(false),
634
- this.secureStorage?.remove(USER_PROPERTIES_KEY) ?? Promise.resolve(false)
680
+ this.secureStorage?.remove(USER_PROPERTIES_KEY) ?? Promise.resolve(false),
681
+ this.secureStorage?.remove(USER_PHONE_KEY) ?? Promise.resolve(false),
682
+ this.secureStorage?.remove(USER_FIRST_NAME_KEY) ?? Promise.resolve(false),
683
+ this.secureStorage?.remove(USER_LAST_NAME_KEY) ?? Promise.resolve(false),
684
+ this.secureStorage?.remove(USER_DOB_KEY) ?? Promise.resolve(false),
685
+ this.secureStorage?.remove(USER_GENDER_KEY) ?? Promise.resolve(false)
635
686
  ]);
636
687
  }
637
688
  getState() {
638
689
  return {
639
690
  deviceId: this.deviceId ?? "",
640
691
  email: this.email,
641
- properties: this.getProperties()
692
+ properties: this.getProperties(),
693
+ phone: this.phone,
694
+ firstName: this.firstName,
695
+ lastName: this.lastName,
696
+ dateOfBirth: this.dateOfBirth,
697
+ gender: this.gender
642
698
  };
643
699
  }
644
700
  isSecureStorageAvailable() {
@@ -663,6 +719,18 @@ var init_IdentityManager = __esm({
663
719
  if (state.email) this.email = state.email;
664
720
  if (state.propertiesJson) this.properties = parseProperties(state.propertiesJson);
665
721
  if (state.distinctId) this.distinctId = state.distinctId;
722
+ const [phone, firstName, lastName, dateOfBirth, gender] = await Promise.all([
723
+ this.secureStorage.get(USER_PHONE_KEY),
724
+ this.secureStorage.get(USER_FIRST_NAME_KEY),
725
+ this.secureStorage.get(USER_LAST_NAME_KEY),
726
+ this.secureStorage.get(USER_DOB_KEY),
727
+ this.secureStorage.get(USER_GENDER_KEY)
728
+ ]);
729
+ if (phone) this.phone = phone;
730
+ if (firstName) this.firstName = firstName;
731
+ if (lastName) this.lastName = lastName;
732
+ if (dateOfBirth) this.dateOfBirth = dateOfBirth;
733
+ if (gender) this.gender = gender;
666
734
  }
667
735
  ensureInitialized() {
668
736
  if (!this.initialized) {
@@ -803,7 +871,8 @@ var init_NativeStoreKit = __esm({
803
871
  );
804
872
  }
805
873
  const distinctId = identityManager.getDistinctId();
806
- const appAccountToken = isValidUUIDv4(distinctId) ? distinctId : null;
874
+ const rawUuid = distinctId?.startsWith("$paywallo_anon:") ? distinctId.slice("$paywallo_anon:".length) : distinctId;
875
+ const appAccountToken = isValidUUIDv4(rawUuid) ? rawUuid : null;
807
876
  if (distinctId && appAccountToken === null) {
808
877
  if (PaywalloClient.getConfig()?.debug) console.log("[Paywallo] distinctId is not a valid UUID v4 \u2014 passing null to appAccountToken:", distinctId);
809
878
  }
@@ -2283,6 +2352,7 @@ var init_usePaywallTracking = __esm({
2283
2352
  // src/domains/paywall/usePaywallActions.ts
2284
2353
  function usePaywallActions(placement, paywallConfig, onResult, variantKey, campaignId, openAnim, variantId) {
2285
2354
  const [isPurchasing, setIsPurchasing] = (0, import_react2.useState)(false);
2355
+ const isPurchasingRef = (0, import_react2.useRef)(false);
2286
2356
  const [isClosing, setIsClosing] = (0, import_react2.useState)(false);
2287
2357
  const slideAnim = (0, import_react2.useRef)(new import_react_native7.Animated.Value(0)).current;
2288
2358
  const presentedAtRef = (0, import_react2.useRef)(Date.now());
@@ -2314,6 +2384,7 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
2314
2384
  }, [isClosing, closeWithReason]);
2315
2385
  (0, import_react2.useEffect)(() => {
2316
2386
  const sub = import_react_native7.AppState.addEventListener("change", (nextState) => {
2387
+ if (isPurchasingRef.current) return;
2317
2388
  if (nextState === "background" || nextState === "inactive") {
2318
2389
  closeWithReason("backgrounded");
2319
2390
  }
@@ -2329,6 +2400,7 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
2329
2400
  const handlePurchase = (0, import_react2.useCallback)(async (productId) => {
2330
2401
  if (isPurchasing) return;
2331
2402
  setIsPurchasing(true);
2403
+ isPurchasingRef.current = true;
2332
2404
  try {
2333
2405
  if (paywallConfig) trackProductSelected({ placement, paywallId: paywallConfig.id, productId, variantKey, campaignId, variantId });
2334
2406
  const result = await getIAPService().purchase(productId, {
@@ -2351,12 +2423,14 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
2351
2423
  } catch (error) {
2352
2424
  onResult({ presented: true, purchased: false, cancelled: false, restored: false });
2353
2425
  } finally {
2426
+ isPurchasingRef.current = false;
2354
2427
  setIsPurchasing(false);
2355
2428
  }
2356
2429
  }, [isPurchasing, onResult, paywallConfig, placement, trackDismissed, trackProductSelected, trackPurchased, variantKey, campaignId, variantId]);
2357
2430
  const handleRestore = (0, import_react2.useCallback)(async () => {
2358
2431
  if (isPurchasing) return;
2359
2432
  setIsPurchasing(true);
2433
+ isPurchasingRef.current = true;
2360
2434
  try {
2361
2435
  const transactions = await getIAPService().restore();
2362
2436
  if (transactions.length > 0) {
@@ -2371,6 +2445,7 @@ function usePaywallActions(placement, paywallConfig, onResult, variantKey, campa
2371
2445
  } catch (error) {
2372
2446
  onResult({ presented: true, purchased: false, cancelled: false, restored: false });
2373
2447
  } finally {
2448
+ isPurchasingRef.current = false;
2374
2449
  setIsPurchasing(false);
2375
2450
  }
2376
2451
  }, [isPurchasing, onResult, paywallConfig, placement, trackDismissed, variantKey, campaignId, variantId]);
@@ -2473,7 +2548,8 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
2473
2548
  if (!trackingDoneRef.current && !cancelled) {
2474
2549
  trackingDoneRef.current = true;
2475
2550
  const sessionId = PaywalloClient.getSessionId();
2476
- await apiClient.trackEvent("$paywall_viewed", PaywalloClient.getDistinctId(), {
2551
+ const distinctId = PaywalloClient.getDistinctId();
2552
+ await apiClient.trackEvent("$paywall_viewed", distinctId, {
2477
2553
  placement,
2478
2554
  paywallId: config.id,
2479
2555
  ...sessionId && { sessionId },
@@ -2481,6 +2557,16 @@ function usePaywallLoader(placement, visible, preloadedConfig, preloadedProducts
2481
2557
  ...campaignId && { campaignId },
2482
2558
  ...variantId && { variant_id: variantId }
2483
2559
  });
2560
+ await apiClient.trackEvent("paywall", distinctId, {
2561
+ type: "open",
2562
+ paywall_id: config.id,
2563
+ placement,
2564
+ opened_at: (/* @__PURE__ */ new Date()).toISOString(),
2565
+ ...sessionId && { sessionId },
2566
+ ...variantKey && { variant_key: variantKey },
2567
+ ...campaignId && { campaign_id: campaignId },
2568
+ ...variantId && { variant_id: variantId }
2569
+ });
2484
2570
  }
2485
2571
  } catch (err) {
2486
2572
  if (cancelled) return;
@@ -2657,15 +2743,21 @@ var init_paywallScripts = __esm({
2657
2743
 
2658
2744
  // src/domains/paywall/PaywallWebView.tsx
2659
2745
  function getNativeWebView() {
2660
- if (!NativeWebViewComponent) {
2746
+ if (NativeWebViewComponent) return NativeWebViewComponent;
2747
+ if (nativeWebViewLookupFailed) return null;
2748
+ try {
2661
2749
  NativeWebViewComponent = (0, import_react_native9.requireNativeComponent)("PaywalloWebView");
2750
+ return NativeWebViewComponent;
2751
+ } catch {
2752
+ nativeWebViewLookupFailed = true;
2753
+ return null;
2662
2754
  }
2663
- return NativeWebViewComponent;
2664
2755
  }
2665
2756
  function getWebViewEmitter() {
2666
- if (!webViewEmitter) {
2667
- webViewEmitter = new import_react_native9.NativeEventEmitter(import_react_native9.NativeModules.PaywalloWebViewModule);
2668
- }
2757
+ if (webViewEmitter) return webViewEmitter;
2758
+ const mod = import_react_native9.NativeModules.PaywalloWebViewModule;
2759
+ if (!mod) return null;
2760
+ webViewEmitter = new import_react_native9.NativeEventEmitter(mod);
2669
2761
  return webViewEmitter;
2670
2762
  }
2671
2763
  function parseErrorEvent(raw) {
@@ -2697,6 +2789,14 @@ function PaywallWebView({
2697
2789
  const onReadyRef = (0, import_react4.useRef)(onReady);
2698
2790
  onReadyRef.current = onReady;
2699
2791
  const NativeWebView = (0, import_react4.useMemo)(() => getNativeWebView(), []);
2792
+ (0, import_react4.useEffect)(() => {
2793
+ if (!NativeWebView) {
2794
+ onErrorRef.current?.({
2795
+ code: -1,
2796
+ description: "Paywallo native module not linked. Run `pod install` (iOS) or rebuild the Android app."
2797
+ });
2798
+ }
2799
+ }, [NativeWebView]);
2700
2800
  const [resolvedWebUrl, setResolvedWebUrl] = (0, import_react4.useState)(
2701
2801
  () => webUrlProp ?? PaywalloClient.getWebUrl()
2702
2802
  );
@@ -2788,6 +2888,7 @@ function PaywallWebView({
2788
2888
  );
2789
2889
  (0, import_react4.useEffect)(() => {
2790
2890
  const emitter = getWebViewEmitter();
2891
+ if (!emitter) return;
2791
2892
  const loadEndSub = emitter.addListener("PaywalloWebViewLoadEnd", () => {
2792
2893
  if (!loadEndReceived.current) {
2793
2894
  loadEndReceived.current = true;
@@ -2854,7 +2955,7 @@ function PaywallWebView({
2854
2955
  if (!paywallDataSent.current) return;
2855
2956
  setScriptToInject(buildPurchaseStateScript(isPurchasing));
2856
2957
  }, [isPurchasing]);
2857
- if (!resolvedWebUrl) return null;
2958
+ if (!resolvedWebUrl || !NativeWebView) return null;
2858
2959
  const paywallUrl = `${resolvedWebUrl}/paywall/${paywallId}`;
2859
2960
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native9.View, { style: styles.container, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2860
2961
  NativeWebView,
@@ -2868,7 +2969,7 @@ function PaywallWebView({
2868
2969
  }
2869
2970
  ) });
2870
2971
  }
2871
- var import_react4, import_react_native9, import_jsx_runtime, NativeWebViewComponent, webViewEmitter, styles;
2972
+ var import_react4, import_react_native9, import_jsx_runtime, NativeWebViewComponent, nativeWebViewLookupFailed, webViewEmitter, styles;
2872
2973
  var init_PaywallWebView = __esm({
2873
2974
  "src/domains/paywall/PaywallWebView.tsx"() {
2874
2975
  "use strict";
@@ -2879,6 +2980,7 @@ var init_PaywallWebView = __esm({
2879
2980
  init_paywallScripts();
2880
2981
  import_jsx_runtime = require("react/jsx-runtime");
2881
2982
  NativeWebViewComponent = null;
2983
+ nativeWebViewLookupFailed = false;
2882
2984
  webViewEmitter = null;
2883
2985
  styles = import_react_native9.StyleSheet.create({
2884
2986
  container: { flex: 1 },
@@ -3196,7 +3298,8 @@ function usePaywallPresenter(preloadedWebView, setPreloadedWebView) {
3196
3298
  const apiClient = PaywalloClient.getApiClient();
3197
3299
  if (apiClient && preloadedWebView) {
3198
3300
  const sessionId = PaywalloClient.getSessionId();
3199
- void apiClient.trackEvent("$paywall_viewed", PaywalloClient.getDistinctId(), {
3301
+ const distinctId = PaywalloClient.getDistinctId();
3302
+ void apiClient.trackEvent("$paywall_viewed", distinctId, {
3200
3303
  placement: preloadedWebView.placement,
3201
3304
  paywallId: preloadedWebView.paywallId,
3202
3305
  preloaded: true,
@@ -3206,6 +3309,18 @@ function usePaywallPresenter(preloadedWebView, setPreloadedWebView) {
3206
3309
  ...sessionId && { sessionId }
3207
3310
  }).catch(() => {
3208
3311
  });
3312
+ void apiClient.trackEvent("paywall", distinctId, {
3313
+ type: "open",
3314
+ paywall_id: preloadedWebView.paywallId,
3315
+ placement: preloadedWebView.placement,
3316
+ opened_at: (/* @__PURE__ */ new Date()).toISOString(),
3317
+ preloaded: true,
3318
+ ...preloadedWebView.variantKey && { variant_key: preloadedWebView.variantKey },
3319
+ ...preloadedWebView.campaignId && { campaign_id: preloadedWebView.campaignId },
3320
+ ...preloadedWebView.variantId && { variant_id: preloadedWebView.variantId },
3321
+ ...sessionId && { sessionId }
3322
+ }).catch(() => {
3323
+ });
3209
3324
  }
3210
3325
  });
3211
3326
  }, [preloadedWebView]);
@@ -3300,6 +3415,8 @@ var init_CampaignGateService = __esm({
3300
3415
  constructor() {
3301
3416
  this.preloadedCampaigns = /* @__PURE__ */ new Map();
3302
3417
  this.pendingPreloads = /* @__PURE__ */ new Set();
3418
+ this.apiClientRef = null;
3419
+ this.STALE_THRESHOLD = 0.8;
3303
3420
  this.activePreloadPromises = /* @__PURE__ */ new Map();
3304
3421
  }
3305
3422
  async getCampaign(apiClient, placement, context) {
@@ -3382,12 +3499,29 @@ var init_CampaignGateService = __esm({
3382
3499
  getPreloadedCampaign(placement) {
3383
3500
  const cached = this.preloadedCampaigns.get(placement);
3384
3501
  if (!cached) return null;
3385
- if (Date.now() - cached.preloadedAt >= PRELOAD_CACHE_TTL_MS) {
3502
+ const age = Date.now() - cached.preloadedAt;
3503
+ if (age >= PRELOAD_CACHE_TTL_MS) {
3386
3504
  this.preloadedCampaigns.delete(placement);
3387
3505
  return null;
3388
3506
  }
3507
+ if (age >= PRELOAD_CACHE_TTL_MS * this.STALE_THRESHOLD && this.apiClientRef) {
3508
+ void this.preloadCampaign(this.apiClientRef, placement).catch(() => {
3509
+ });
3510
+ }
3389
3511
  return cached.campaign;
3390
3512
  }
3513
+ async preloadAllActive(apiClient, debug) {
3514
+ this.apiClientRef = apiClient;
3515
+ const placements = await apiClient.getActivePlacements();
3516
+ if (placements.length === 0) return;
3517
+ if (debug) console.log(`[Paywallo PRELOAD] auto-preloading ${placements.length} placements`);
3518
+ for (let i = 0; i < placements.length; i++) {
3519
+ if (i > 0) await new Promise((r) => setTimeout(r, 500));
3520
+ void this.preloadCampaign(apiClient, placements[i]).catch((err) => {
3521
+ if (debug) console.log(`[Paywallo PRELOAD] failed ${placements[i]}:`, err);
3522
+ });
3523
+ }
3524
+ }
3391
3525
  clearPreloaded() {
3392
3526
  this.preloadedCampaigns.clear();
3393
3527
  this.pendingPreloads.clear();
@@ -3407,7 +3541,7 @@ var init_campaign = __esm({
3407
3541
  });
3408
3542
 
3409
3543
  // src/domains/identity/AdvertisingIdManager.ts
3410
- var import_react_native11, ZERO_IDFA, AdvertisingIdManager;
3544
+ var import_react_native11, ZERO_IDFA, AdvertisingIdManager, advertisingIdManager;
3411
3545
  var init_AdvertisingIdManager = __esm({
3412
3546
  "src/domains/identity/AdvertisingIdManager.ts"() {
3413
3547
  "use strict";
@@ -3418,6 +3552,14 @@ var init_AdvertisingIdManager = __esm({
3418
3552
  constructor() {
3419
3553
  this.cached = null;
3420
3554
  }
3555
+ /**
3556
+ * Synchronous accessor for the cached collect() result. Returns null until
3557
+ * `collect()` resolves at least once. Used by the V2 envelope context
3558
+ * provider — which is sync — to populate `context.ids` without awaiting.
3559
+ */
3560
+ getCached() {
3561
+ return this.cached;
3562
+ }
3421
3563
  async collect(requestATT = false) {
3422
3564
  if (this.cached) return this.cached;
3423
3565
  const fallback = { idfv: null, idfa: null, gaid: null, attStatus: "unavailable" };
@@ -3468,136 +3610,26 @@ var init_AdvertisingIdManager = __esm({
3468
3610
  return id;
3469
3611
  }
3470
3612
  };
3613
+ advertisingIdManager = new AdvertisingIdManager();
3471
3614
  }
3472
3615
  });
3473
3616
 
3474
- // src/domains/identity/MetaBridge.ts
3475
- var import_react_native12, TIMEOUT_MS, NativeBridge, MetaBridge, metaBridge;
3476
- var init_MetaBridge = __esm({
3477
- "src/domains/identity/MetaBridge.ts"() {
3478
- "use strict";
3479
- import_react_native12 = require("react-native");
3480
- TIMEOUT_MS = 2e3;
3481
- NativeBridge = import_react_native12.NativeModules.PaywalloFBBridge ?? null;
3482
- MetaBridge = class {
3483
- async getAnonymousID() {
3484
- if (!NativeBridge) return null;
3485
- try {
3486
- let timerId;
3487
- const timeout = new Promise((resolve) => {
3488
- timerId = setTimeout(() => resolve(null), TIMEOUT_MS);
3489
- });
3490
- const result = await Promise.race([NativeBridge.getAnonymousID(), timeout]);
3491
- clearTimeout(timerId);
3492
- return result;
3493
- } catch {
3494
- return null;
3495
- }
3496
- }
3497
- async logEvent(name, params = {}) {
3498
- if (!NativeBridge) return;
3499
- try {
3500
- await NativeBridge.logEvent(name, params);
3501
- } catch (error) {
3502
- console.warn("[Paywallo:MetaBridge] logEvent failed", { name, error });
3503
- }
3504
- }
3505
- async logPurchase(amount, currency, params = {}) {
3506
- if (!NativeBridge) return;
3507
- try {
3508
- await NativeBridge.logPurchase(amount, currency, params);
3509
- } catch (error) {
3510
- console.warn("[Paywallo:MetaBridge] logPurchase failed", { amount, currency, error });
3511
- }
3512
- }
3513
- setUserID(userId) {
3514
- if (!NativeBridge) return;
3515
- try {
3516
- NativeBridge.setUserID(userId);
3517
- } catch (error) {
3518
- console.warn("[Paywallo:MetaBridge] setUserID failed", { error });
3519
- }
3520
- }
3521
- flush() {
3522
- if (!NativeBridge) return;
3523
- try {
3524
- NativeBridge.flush();
3525
- } catch (error) {
3526
- console.warn("[Paywallo:MetaBridge] flush failed", { error });
3527
- }
3528
- }
3529
- };
3530
- metaBridge = new MetaBridge();
3617
+ // src/domains/session/distinctIdGuard.ts
3618
+ async function waitForDistinctId(provider, retries = DISTINCT_ID_WAIT_RETRIES, intervalMs = DISTINCT_ID_WAIT_INTERVAL_MS) {
3619
+ if (!provider) return true;
3620
+ if (provider()) return true;
3621
+ for (let attempt = 0; attempt < retries; attempt++) {
3622
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
3623
+ if (provider()) return true;
3531
3624
  }
3532
- });
3533
-
3534
- // src/domains/identity/InstallReferrerManager.ts
3535
- var import_react_native13, REFERRER_TIMEOUT_MS, InstallReferrerManager, installReferrerManager;
3536
- var init_InstallReferrerManager = __esm({
3537
- "src/domains/identity/InstallReferrerManager.ts"() {
3625
+ return false;
3626
+ }
3627
+ var DISTINCT_ID_WAIT_RETRIES, DISTINCT_ID_WAIT_INTERVAL_MS;
3628
+ var init_distinctIdGuard = __esm({
3629
+ "src/domains/session/distinctIdGuard.ts"() {
3538
3630
  "use strict";
3539
- import_react_native13 = require("react-native");
3540
- init_NativeDeviceInfo();
3541
- REFERRER_TIMEOUT_MS = 5e3;
3542
- InstallReferrerManager = class {
3543
- async getReferrer() {
3544
- if (import_react_native13.Platform.OS !== "android") {
3545
- return this.emptyResult();
3546
- }
3547
- let timerId;
3548
- const timeoutPromise = new Promise((resolve) => {
3549
- timerId = setTimeout(() => resolve(this.emptyResult()), REFERRER_TIMEOUT_MS);
3550
- });
3551
- const fetchPromise = this.fetchReferrer();
3552
- const result = await Promise.race([fetchPromise, timeoutPromise]);
3553
- clearTimeout(timerId);
3554
- return result;
3555
- }
3556
- async fetchReferrer() {
3557
- try {
3558
- const referrer = await nativeDeviceInfo.getInstallReferrer();
3559
- if (!referrer) return this.emptyResult();
3560
- const capped = referrer.slice(0, 2048);
3561
- const params = this.parseReferrer(capped);
3562
- return {
3563
- rawReferrer: capped,
3564
- trackingId: params["tracking_id"] || null,
3565
- fbclid: params["fbclid"] || null,
3566
- utmSource: params["utm_source"] || null,
3567
- utmMedium: params["utm_medium"] || null,
3568
- utmCampaign: params["utm_campaign"] || null
3569
- };
3570
- } catch {
3571
- return this.emptyResult();
3572
- }
3573
- }
3574
- parseReferrer(referrer) {
3575
- const result = {};
3576
- for (const pair of referrer.split("&")) {
3577
- const eqIndex = pair.indexOf("=");
3578
- if (eqIndex === -1) continue;
3579
- try {
3580
- const key = decodeURIComponent(pair.slice(0, eqIndex).replace(/\+/g, " "));
3581
- const raw = pair.slice(eqIndex + 1).replace(/\+/g, " ");
3582
- const value = decodeURIComponent(raw);
3583
- if (key) result[key] = value;
3584
- } catch {
3585
- }
3586
- }
3587
- return result;
3588
- }
3589
- emptyResult() {
3590
- return {
3591
- rawReferrer: null,
3592
- trackingId: null,
3593
- fbclid: null,
3594
- utmSource: null,
3595
- utmMedium: null,
3596
- utmCampaign: null
3597
- };
3598
- }
3599
- };
3600
- installReferrerManager = new InstallReferrerManager();
3631
+ DISTINCT_ID_WAIT_RETRIES = 10;
3632
+ DISTINCT_ID_WAIT_INTERVAL_MS = 100;
3601
3633
  }
3602
3634
  });
3603
3635
 
@@ -3622,25 +3654,6 @@ var init_SessionError = __esm({
3622
3654
  }
3623
3655
  });
3624
3656
 
3625
- // src/domains/session/distinctIdGuard.ts
3626
- async function waitForDistinctId(provider, retries = DISTINCT_ID_WAIT_RETRIES, intervalMs = DISTINCT_ID_WAIT_INTERVAL_MS) {
3627
- if (!provider) return true;
3628
- if (provider()) return true;
3629
- for (let attempt = 0; attempt < retries; attempt++) {
3630
- await new Promise((resolve) => setTimeout(resolve, intervalMs));
3631
- if (provider()) return true;
3632
- }
3633
- return false;
3634
- }
3635
- var DISTINCT_ID_WAIT_RETRIES, DISTINCT_ID_WAIT_INTERVAL_MS;
3636
- var init_distinctIdGuard = __esm({
3637
- "src/domains/session/distinctIdGuard.ts"() {
3638
- "use strict";
3639
- DISTINCT_ID_WAIT_RETRIES = 10;
3640
- DISTINCT_ID_WAIT_INTERVAL_MS = 100;
3641
- }
3642
- });
3643
-
3644
3657
  // src/utils/deviceInfo.ts
3645
3658
  var deviceInfo_exports = {};
3646
3659
  __export(deviceInfo_exports, {
@@ -3651,7 +3664,9 @@ async function collectDeviceInfo() {
3651
3664
  return {
3652
3665
  appVersion: result.appVersion,
3653
3666
  deviceModel: result.model,
3654
- osVersion: result.systemVersion
3667
+ osVersion: result.systemVersion,
3668
+ darwinVersion: result.darwinVersion,
3669
+ webkitVersion: result.webkitVersion
3655
3670
  };
3656
3671
  }
3657
3672
  var init_deviceInfo = __esm({
@@ -3719,11 +3734,11 @@ var init_sessionTracking = __esm({
3719
3734
  });
3720
3735
 
3721
3736
  // src/domains/session/SessionManager.ts
3722
- var import_react_native14, SESSION_ID_KEY, SESSION_START_KEY, EMERGENCY_PAYWALL_SHOWN_KEY, LEGACY_SESSION_ID_KEY, LEGACY_SESSION_START_KEY, SESSION_TIMEOUT_MS, BACKGROUND_FLUSH_TIMEOUT_MS, SessionManager, sessionManager;
3737
+ var import_react_native12, SESSION_ID_KEY, SESSION_START_KEY, EMERGENCY_PAYWALL_SHOWN_KEY, LEGACY_SESSION_ID_KEY, LEGACY_SESSION_START_KEY, SESSION_TIMEOUT_MS, BACKGROUND_FLUSH_TIMEOUT_MS, SessionManager, sessionManager;
3723
3738
  var init_SessionManager = __esm({
3724
3739
  "src/domains/session/SessionManager.ts"() {
3725
3740
  "use strict";
3726
- import_react_native14 = require("react-native");
3741
+ import_react_native12 = require("react-native");
3727
3742
  init_SessionError();
3728
3743
  init_uuid();
3729
3744
  init_distinctIdGuard();
@@ -3882,7 +3897,7 @@ var init_SessionManager = __esm({
3882
3897
  if (this.appStateSubscription) {
3883
3898
  this.appStateSubscription.remove();
3884
3899
  }
3885
- this.appStateSubscription = import_react_native14.AppState.addEventListener("change", this.handleAppStateChange);
3900
+ this.appStateSubscription = import_react_native12.AppState.addEventListener("change", this.handleAppStateChange);
3886
3901
  }
3887
3902
  async handleForeground() {
3888
3903
  const elapsed = this.backgroundTimestamp ? Date.now() - this.backgroundTimestamp : null;
@@ -3951,57 +3966,6 @@ var init_SessionManager = __esm({
3951
3966
  }
3952
3967
  });
3953
3968
 
3954
- // src/domains/identity/InstallIdempotency.ts
3955
- async function checkAndArmInstallGuard(storage) {
3956
- const alreadyTracked = await storage.get(INSTALL_KEYS.INSTALL_TRACKED);
3957
- if (alreadyTracked) return true;
3958
- let fallbackTracked = null;
3959
- try {
3960
- fallbackTracked = await nativeStorage.get(INSTALL_KEYS.LEGACY_INSTALL_TRACKED);
3961
- } catch {
3962
- }
3963
- if (fallbackTracked) return true;
3964
- await nativeStorage.set(INSTALL_KEYS.INSTALL_SENT, "1");
3965
- return false;
3966
- }
3967
- async function markInstallTracked(storage, installedAt) {
3968
- await storage.set(INSTALL_KEYS.INSTALL_TRACKED, String(installedAt));
3969
- try {
3970
- await nativeStorage.set(INSTALL_KEYS.LEGACY_INSTALL_TRACKED, String(installedAt));
3971
- } catch {
3972
- }
3973
- }
3974
- async function getOrCreateInstallEventId() {
3975
- const existing = await nativeStorage.get(INSTALL_KEYS.INSTALL_EVENT_ID);
3976
- if (existing) return existing;
3977
- const { generateUUID: generateUUID2 } = await Promise.resolve().then(() => (init_uuid(), uuid_exports));
3978
- const id = generateUUID2();
3979
- await nativeStorage.set(INSTALL_KEYS.INSTALL_EVENT_ID, id).catch(() => {
3980
- });
3981
- return id;
3982
- }
3983
- var INSTALL_KEYS;
3984
- var init_InstallIdempotency = __esm({
3985
- "src/domains/identity/InstallIdempotency.ts"() {
3986
- "use strict";
3987
- init_NativeStorage();
3988
- INSTALL_KEYS = {
3989
- /** Primary flag — set AFTER a successful $app_installed dispatch. */
3990
- INSTALL_TRACKED: "@panel:install_tracked",
3991
- /** Legacy fallback flag written by older SDK versions. */
3992
- LEGACY_INSTALL_TRACKED: "@paywallo:install_tracked",
3993
- /** Pre-send guard — set BEFORE dispatching to survive concurrent cold-starts. */
3994
- INSTALL_SENT: "@paywallo:app_installed_sent",
3995
- /** Stable event ID for backend dedup — generated once, reused on retries. */
3996
- INSTALL_EVENT_ID: "@panel:install_event_id",
3997
- /** Deferred match flag — set once iOS fingerprint match succeeds. */
3998
- DEFERRED_MATCH_DONE: "@panel:deferred_match_done",
3999
- /** Anonymous ID fallback when FB SDK not available. */
4000
- ANON_ID: "@panel:anon_id"
4001
- };
4002
- }
4003
- });
4004
-
4005
3969
  // src/domains/identity/AttributionTracker.ts
4006
3970
  function hasAnyField(data) {
4007
3971
  return Object.values(data).some((v) => v !== void 0 && v !== null && v !== "");
@@ -4073,21 +4037,212 @@ var init_AttributionTracker = __esm({
4073
4037
  }
4074
4038
  });
4075
4039
 
4076
- // src/domains/identity/InstallTracker.ts
4077
- var import_react_native15, InstallTracker;
4078
- var init_InstallTracker = __esm({
4079
- "src/domains/identity/InstallTracker.ts"() {
4080
- "use strict";
4081
- import_react_native15 = require("react-native");
4082
- init_NativeDeviceInfo();
4083
- init_SecureStorage();
4084
- init_AdvertisingIdManager();
4085
- init_MetaBridge();
4086
- init_IdentityManager();
4087
- init_InstallReferrerManager();
4088
- init_SessionManager();
4040
+ // src/domains/identity/InstallIdempotency.ts
4041
+ async function checkAndArmInstallGuard(storage) {
4042
+ const alreadyTracked = await storage.get(INSTALL_KEYS.INSTALL_TRACKED);
4043
+ if (alreadyTracked) return true;
4044
+ let fallbackTracked = null;
4045
+ try {
4046
+ fallbackTracked = await nativeStorage.get(INSTALL_KEYS.LEGACY_INSTALL_TRACKED);
4047
+ } catch {
4048
+ }
4049
+ if (fallbackTracked) return true;
4050
+ await nativeStorage.set(INSTALL_KEYS.INSTALL_SENT, "1");
4051
+ return false;
4052
+ }
4053
+ async function markInstallTracked(storage, installedAt) {
4054
+ await storage.set(INSTALL_KEYS.INSTALL_TRACKED, String(installedAt));
4055
+ try {
4056
+ await nativeStorage.set(INSTALL_KEYS.LEGACY_INSTALL_TRACKED, String(installedAt));
4057
+ } catch {
4058
+ }
4059
+ }
4060
+ async function getOrCreateInstallEventId() {
4061
+ const existing = await nativeStorage.get(INSTALL_KEYS.INSTALL_EVENT_ID);
4062
+ if (existing) return existing;
4063
+ const { generateUUID: generateUUID2 } = await Promise.resolve().then(() => (init_uuid(), uuid_exports));
4064
+ const id = generateUUID2();
4065
+ await nativeStorage.set(INSTALL_KEYS.INSTALL_EVENT_ID, id).catch(() => {
4066
+ });
4067
+ return id;
4068
+ }
4069
+ var INSTALL_KEYS;
4070
+ var init_InstallIdempotency = __esm({
4071
+ "src/domains/identity/InstallIdempotency.ts"() {
4072
+ "use strict";
4073
+ init_NativeStorage();
4074
+ INSTALL_KEYS = {
4075
+ /** Primary flag — set AFTER a successful $app_installed dispatch. */
4076
+ INSTALL_TRACKED: "@panel:install_tracked",
4077
+ /** Legacy fallback flag written by older SDK versions. */
4078
+ LEGACY_INSTALL_TRACKED: "@paywallo:install_tracked",
4079
+ /** Pre-send guard — set BEFORE dispatching to survive concurrent cold-starts. */
4080
+ INSTALL_SENT: "@paywallo:app_installed_sent",
4081
+ /** Stable event ID for backend dedup — generated once, reused on retries. */
4082
+ INSTALL_EVENT_ID: "@panel:install_event_id",
4083
+ /** Deferred match flag — set once iOS fingerprint match succeeds. */
4084
+ DEFERRED_MATCH_DONE: "@panel:deferred_match_done",
4085
+ /** Anonymous ID fallback when FB SDK not available. */
4086
+ ANON_ID: "@panel:anon_id"
4087
+ };
4088
+ }
4089
+ });
4090
+
4091
+ // src/domains/identity/InstallReferrerManager.ts
4092
+ var import_react_native13, REFERRER_TIMEOUT_MS, InstallReferrerManager, installReferrerManager;
4093
+ var init_InstallReferrerManager = __esm({
4094
+ "src/domains/identity/InstallReferrerManager.ts"() {
4095
+ "use strict";
4096
+ import_react_native13 = require("react-native");
4097
+ init_NativeDeviceInfo();
4098
+ REFERRER_TIMEOUT_MS = 5e3;
4099
+ InstallReferrerManager = class {
4100
+ async getReferrer() {
4101
+ if (import_react_native13.Platform.OS !== "android") {
4102
+ return this.emptyResult();
4103
+ }
4104
+ let timerId;
4105
+ const timeoutPromise = new Promise((resolve) => {
4106
+ timerId = setTimeout(() => resolve(this.emptyResult()), REFERRER_TIMEOUT_MS);
4107
+ });
4108
+ const fetchPromise = this.fetchReferrer();
4109
+ const result = await Promise.race([fetchPromise, timeoutPromise]);
4110
+ clearTimeout(timerId);
4111
+ return result;
4112
+ }
4113
+ async fetchReferrer() {
4114
+ try {
4115
+ const referrer = await nativeDeviceInfo.getInstallReferrer();
4116
+ if (!referrer) return this.emptyResult();
4117
+ const capped = referrer.slice(0, 2048);
4118
+ const params = this.parseReferrer(capped);
4119
+ return {
4120
+ rawReferrer: capped,
4121
+ trackingId: params["tracking_id"] || null,
4122
+ fbclid: params["fbclid"] || null,
4123
+ utmSource: params["utm_source"] || null,
4124
+ utmMedium: params["utm_medium"] || null,
4125
+ utmCampaign: params["utm_campaign"] || null
4126
+ };
4127
+ } catch {
4128
+ return this.emptyResult();
4129
+ }
4130
+ }
4131
+ parseReferrer(referrer) {
4132
+ const result = {};
4133
+ for (const pair of referrer.split("&")) {
4134
+ const eqIndex = pair.indexOf("=");
4135
+ if (eqIndex === -1) continue;
4136
+ try {
4137
+ const key = decodeURIComponent(pair.slice(0, eqIndex).replace(/\+/g, " "));
4138
+ const raw = pair.slice(eqIndex + 1).replace(/\+/g, " ");
4139
+ const value = decodeURIComponent(raw);
4140
+ if (key) result[key] = value;
4141
+ } catch {
4142
+ }
4143
+ }
4144
+ return result;
4145
+ }
4146
+ emptyResult() {
4147
+ return {
4148
+ rawReferrer: null,
4149
+ trackingId: null,
4150
+ fbclid: null,
4151
+ utmSource: null,
4152
+ utmMedium: null,
4153
+ utmCampaign: null
4154
+ };
4155
+ }
4156
+ };
4157
+ installReferrerManager = new InstallReferrerManager();
4158
+ }
4159
+ });
4160
+
4161
+ // src/domains/identity/MetaBridge.ts
4162
+ var import_react_native14, TIMEOUT_MS, NativeBridge, MetaBridge, metaBridge;
4163
+ var init_MetaBridge = __esm({
4164
+ "src/domains/identity/MetaBridge.ts"() {
4165
+ "use strict";
4166
+ import_react_native14 = require("react-native");
4167
+ TIMEOUT_MS = 2e3;
4168
+ NativeBridge = import_react_native14.NativeModules.PaywalloFBBridge ?? null;
4169
+ MetaBridge = class {
4170
+ constructor() {
4171
+ this.cachedAnonId = null;
4172
+ }
4173
+ /** Sync read of the cached anonymous ID. Returns null until `getAnonymousID()` resolves once. */
4174
+ getCachedAnonymousID() {
4175
+ return this.cachedAnonId;
4176
+ }
4177
+ async getAnonymousID() {
4178
+ if (this.cachedAnonId) return this.cachedAnonId;
4179
+ if (!NativeBridge) return null;
4180
+ try {
4181
+ let timerId;
4182
+ const timeout = new Promise((resolve) => {
4183
+ timerId = setTimeout(() => resolve(null), TIMEOUT_MS);
4184
+ });
4185
+ const result = await Promise.race([NativeBridge.getAnonymousID(), timeout]);
4186
+ clearTimeout(timerId);
4187
+ if (result) this.cachedAnonId = result;
4188
+ return result;
4189
+ } catch {
4190
+ return null;
4191
+ }
4192
+ }
4193
+ async logEvent(name, params = {}) {
4194
+ if (!NativeBridge) return;
4195
+ try {
4196
+ await NativeBridge.logEvent(name, params);
4197
+ } catch (error) {
4198
+ console.warn("[Paywallo:MetaBridge] logEvent failed", { name, error });
4199
+ }
4200
+ }
4201
+ async logPurchase(amount, currency, params = {}) {
4202
+ if (!NativeBridge) return;
4203
+ try {
4204
+ await NativeBridge.logPurchase(amount, currency, params);
4205
+ } catch (error) {
4206
+ console.warn("[Paywallo:MetaBridge] logPurchase failed", { amount, currency, error });
4207
+ }
4208
+ }
4209
+ setUserID(userId) {
4210
+ if (!NativeBridge) return;
4211
+ try {
4212
+ NativeBridge.setUserID(userId);
4213
+ } catch (error) {
4214
+ console.warn("[Paywallo:MetaBridge] setUserID failed", { error });
4215
+ }
4216
+ }
4217
+ flush() {
4218
+ if (!NativeBridge) return;
4219
+ try {
4220
+ NativeBridge.flush();
4221
+ } catch (error) {
4222
+ console.warn("[Paywallo:MetaBridge] flush failed", { error });
4223
+ }
4224
+ }
4225
+ };
4226
+ metaBridge = new MetaBridge();
4227
+ }
4228
+ });
4229
+
4230
+ // src/domains/identity/InstallTracker.ts
4231
+ var import_react_native15, InstallTracker;
4232
+ var init_InstallTracker = __esm({
4233
+ "src/domains/identity/InstallTracker.ts"() {
4234
+ "use strict";
4235
+ import_react_native15 = require("react-native");
4236
+ init_SecureStorage();
4237
+ init_NativeDeviceInfo();
4089
4238
  init_distinctIdGuard();
4239
+ init_SessionManager();
4240
+ init_AdvertisingIdManager();
4241
+ init_AttributionTracker();
4242
+ init_IdentityManager();
4090
4243
  init_InstallIdempotency();
4244
+ init_InstallReferrerManager();
4245
+ init_MetaBridge();
4091
4246
  init_AttributionTracker();
4092
4247
  InstallTracker = class {
4093
4248
  constructor(debug = false, optionsOrManager) {
@@ -4098,7 +4253,7 @@ var init_InstallTracker = __esm({
4098
4253
  this.distinctIdProvider = () => identityManager.getDistinctId();
4099
4254
  } else {
4100
4255
  const opts = optionsOrManager ?? {};
4101
- this.advertisingIdManager = opts.advertisingIdManager ?? new AdvertisingIdManager();
4256
+ this.advertisingIdManager = opts.advertisingIdManager ?? advertisingIdManager;
4102
4257
  this.distinctIdProvider = opts.distinctIdProvider ?? (() => identityManager.getDistinctId());
4103
4258
  }
4104
4259
  }
@@ -4136,6 +4291,7 @@ var init_InstallTracker = __esm({
4136
4291
  const screen = import_react_native15.Dimensions.get("screen");
4137
4292
  const screenWidth = Math.round(screen.width ?? 0);
4138
4293
  const screenHeight = Math.round(screen.height ?? 0);
4294
+ const screenDensity = import_react_native15.PixelRatio.get();
4139
4295
  const locale = Intl.DateTimeFormat().resolvedOptions().locale ?? "unknown";
4140
4296
  const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone ?? "unknown";
4141
4297
  const info = await nativeDeviceInfo.getDeviceInfo();
@@ -4182,6 +4338,7 @@ var init_InstallTracker = __esm({
4182
4338
  buildNumber,
4183
4339
  ...screenWidth > 0 && { screenWidth },
4184
4340
  ...screenHeight > 0 && { screenHeight },
4341
+ ...screenDensity > 0 && { screenDensity },
4185
4342
  timezone,
4186
4343
  locale,
4187
4344
  ...carrier !== "unknown" && { carrier },
@@ -4199,6 +4356,16 @@ var init_InstallTracker = __esm({
4199
4356
  this.advertisingIdManager.collect(requestATT),
4200
4357
  getOrCreateInstallEventId()
4201
4358
  ]);
4359
+ if (referrer.fbclid || referrer.utmSource || referrer.utmMedium || referrer.utmCampaign) {
4360
+ attributionTracker.capture({
4361
+ ...referrer.fbclid && { fbclid: referrer.fbclid },
4362
+ ...referrer.utmSource && { utmSource: referrer.utmSource },
4363
+ ...referrer.utmMedium && { utmMedium: referrer.utmMedium },
4364
+ ...referrer.utmCampaign && { utmCampaign: referrer.utmCampaign },
4365
+ ...referrer.rawReferrer && { referrer: referrer.rawReferrer }
4366
+ }).catch(() => {
4367
+ });
4368
+ }
4202
4369
  let effectiveAnonId = fbAnonId;
4203
4370
  if (!effectiveAnonId) {
4204
4371
  const stored = await storage.get(INSTALL_KEYS.ANON_ID);
@@ -4243,7 +4410,7 @@ var init_InstallTracker = __esm({
4243
4410
  const baseUrl = apiClient.getBaseUrl();
4244
4411
  const appKey = apiClient.getAppKey();
4245
4412
  const referrer = await installReferrerManager.getReferrer();
4246
- await fetch(`${baseUrl}/attribution/deferred-match/${appKey}`, {
4413
+ await fetch(`${baseUrl}/sdk/attribution/deferred-match/${appKey}`, {
4247
4414
  method: "POST",
4248
4415
  headers: { "Content-Type": "application/json", "X-App-Key": appKey },
4249
4416
  body: JSON.stringify({
@@ -4374,6 +4541,9 @@ var init_identity = __esm({
4374
4541
  });
4375
4542
 
4376
4543
  // src/domains/notifications/bridges/NativePushBridge.ts
4544
+ function isNativePushAvailable() {
4545
+ return !!import_react_native17.NativeModules.PaywalloNotifications;
4546
+ }
4377
4547
  function mapStatusToNumber(status) {
4378
4548
  switch (status) {
4379
4549
  case "granted":
@@ -4891,6 +5061,16 @@ var init_PermissionManager = __esm({
4891
5061
  }
4892
5062
  });
4893
5063
 
5064
+ // src/core/constants.ts
5065
+ var DEFAULT_API_URL, DEFAULT_WEB_URL;
5066
+ var init_constants = __esm({
5067
+ "src/core/constants.ts"() {
5068
+ "use strict";
5069
+ DEFAULT_API_URL = "https://panel.lucasqueiroga.shop";
5070
+ DEFAULT_WEB_URL = "https://paywallo.com.br";
5071
+ }
5072
+ });
5073
+
4894
5074
  // src/domains/notifications/config.ts
4895
5075
  function resolveApiBaseUrl(config) {
4896
5076
  if (config.apiBaseUrl) return config.apiBaseUrl;
@@ -4901,8 +5081,9 @@ var DEFAULT_API_BASE_URL, SANDBOX_API_BASE_URL;
4901
5081
  var init_config = __esm({
4902
5082
  "src/domains/notifications/config.ts"() {
4903
5083
  "use strict";
4904
- DEFAULT_API_BASE_URL = "https://api.paywallo.com.br";
4905
- SANDBOX_API_BASE_URL = "https://sandbox.paywallo.com.br";
5084
+ init_constants();
5085
+ DEFAULT_API_BASE_URL = DEFAULT_API_URL;
5086
+ SANDBOX_API_BASE_URL = DEFAULT_API_URL;
4906
5087
  }
4907
5088
  });
4908
5089
 
@@ -4996,6 +5177,9 @@ function mapPermissionStatus2(status) {
4996
5177
  function detectPlatform2() {
4997
5178
  return import_react_native20.Platform.OS === "ios" ? "ios" : "android";
4998
5179
  }
5180
+ function detectEnvironment() {
5181
+ return typeof __DEV__ !== "undefined" && __DEV__ ? "sandbox" : "production";
5182
+ }
4999
5183
  async function getAppVersion() {
5000
5184
  try {
5001
5185
  const { collectDeviceInfo: collectDeviceInfo2 } = await Promise.resolve().then(() => (init_deviceInfo(), deviceInfo_exports));
@@ -5024,8 +5208,8 @@ var init_utils = __esm({
5024
5208
  // src/core/version.ts
5025
5209
  function resolveVersion() {
5026
5210
  try {
5027
- if ("2.0.2") {
5028
- return "2.0.2";
5211
+ if ("2.2.1") {
5212
+ return "2.2.1";
5029
5213
  }
5030
5214
  } catch {
5031
5215
  }
@@ -5064,7 +5248,8 @@ async function buildRegistration(token, getDeviceId, getDistinctId) {
5064
5248
  sdk_version: SDK_VERSION,
5065
5249
  deviceId: getDeviceId?.() ?? void 0,
5066
5250
  locale: getLocale(),
5067
- timezone: getTimezone2()
5251
+ timezone: getTimezone2(),
5252
+ environment: detectEnvironment()
5068
5253
  };
5069
5254
  }
5070
5255
  async function registerToken(deps, token) {
@@ -5106,7 +5291,7 @@ var init_TokenRegistration = __esm({
5106
5291
  init_utils();
5107
5292
  init_version();
5108
5293
  FCM_TOKEN_KEY = "@panel:push_token";
5109
- TOKEN_ENDPOINT = "/push-tokens";
5294
+ TOKEN_ENDPOINT = "/sdk/push-tokens";
5110
5295
  }
5111
5296
  });
5112
5297
 
@@ -5398,58 +5583,400 @@ var init_NotificationsManager = __esm({
5398
5583
  this.currentToken = null;
5399
5584
  }
5400
5585
  };
5401
- notificationsManager = new NotificationsManager();
5586
+ notificationsManager = new NotificationsManager();
5587
+ }
5588
+ });
5589
+
5590
+ // src/domains/notifications/index.ts
5591
+ var init_notifications = __esm({
5592
+ "src/domains/notifications/index.ts"() {
5593
+ "use strict";
5594
+ init_NotificationsManager();
5595
+ init_NativePushBridge();
5596
+ init_NotificationsError();
5597
+ }
5598
+ });
5599
+
5600
+ // src/domains/session/index.ts
5601
+ var init_session = __esm({
5602
+ "src/domains/session/index.ts"() {
5603
+ "use strict";
5604
+ init_SessionManager();
5605
+ init_SessionError();
5606
+ }
5607
+ });
5608
+
5609
+ // src/core/PaywalloState.ts
5610
+ function createPaywalloState() {
5611
+ return {
5612
+ config: null,
5613
+ apiClient: null,
5614
+ initPromise: null,
5615
+ readyPromise: null,
5616
+ resolveReady: null,
5617
+ paywallPresenter: null,
5618
+ campaignPresenter: null,
5619
+ subscriptionGetter: null,
5620
+ activeChecker: null,
5621
+ restoreHandler: null,
5622
+ lastOnboardingStepTimestamp: null,
5623
+ pendingConfig: null,
5624
+ networkCleanup: null,
5625
+ initAttempts: 0,
5626
+ autoPreloadedPlacement: null,
5627
+ autoPreloadPlacementPromise: null,
5628
+ sessionFlagsMap: /* @__PURE__ */ new Map(),
5629
+ pendingIdentifies: []
5630
+ };
5631
+ }
5632
+ var MAX_INIT_RETRIES, RETRY_DELAYS;
5633
+ var init_PaywalloState = __esm({
5634
+ "src/core/PaywalloState.ts"() {
5635
+ "use strict";
5636
+ MAX_INIT_RETRIES = 3;
5637
+ RETRY_DELAYS = [2e3, 5e3, 1e4];
5638
+ }
5639
+ });
5640
+
5641
+ // src/domains/plan/PlanCache.ts
5642
+ var DEFAULT_TTL, PlanCache, planCache;
5643
+ var init_PlanCache = __esm({
5644
+ "src/domains/plan/PlanCache.ts"() {
5645
+ "use strict";
5646
+ DEFAULT_TTL = 5 * 60 * 1e3;
5647
+ PlanCache = class {
5648
+ constructor() {
5649
+ this.cache = null;
5650
+ this.ttl = DEFAULT_TTL;
5651
+ }
5652
+ get() {
5653
+ if (!this.cache) return null;
5654
+ const isStale = Date.now() - this.cache.cachedAt > this.ttl;
5655
+ return { ...this.cache, isStale };
5656
+ }
5657
+ set(data) {
5658
+ this.cache = { data, cachedAt: Date.now(), isStale: false };
5659
+ }
5660
+ invalidate() {
5661
+ this.cache = null;
5662
+ }
5663
+ setTTL(ttl) {
5664
+ this.ttl = ttl;
5665
+ }
5666
+ };
5667
+ planCache = new PlanCache();
5668
+ }
5669
+ });
5670
+
5671
+ // src/domains/plan/PlanError.ts
5672
+ var PlanError, PLAN_ERROR_CODES;
5673
+ var init_PlanError = __esm({
5674
+ "src/domains/plan/PlanError.ts"() {
5675
+ "use strict";
5676
+ init_PaywalloError();
5677
+ PlanError = class extends PaywalloError {
5678
+ constructor(code, message) {
5679
+ super("plan", code, message);
5680
+ this.name = "PlanError";
5681
+ }
5682
+ };
5683
+ PLAN_ERROR_CODES = {
5684
+ NOT_INITIALIZED: "PLAN_NOT_INITIALIZED",
5685
+ FETCH_FAILED: "PLAN_FETCH_FAILED"
5686
+ };
5687
+ }
5688
+ });
5689
+
5690
+ // src/domains/plan/PlanService.ts
5691
+ var PlanService_exports = {};
5692
+ __export(PlanService_exports, {
5693
+ PlanService: () => PlanService,
5694
+ planService: () => planService
5695
+ });
5696
+ function mapProduct(raw) {
5697
+ return {
5698
+ id: raw.id,
5699
+ name: raw.name,
5700
+ description: raw.description,
5701
+ type: raw.type,
5702
+ appleProductId: raw.apple_product_id,
5703
+ googleProductId: raw.google_product_id,
5704
+ billingPeriod: raw.billing_period,
5705
+ trialDays: raw.trial_days,
5706
+ priceUsd: raw.price_usd,
5707
+ regionalPrices: raw.regional_prices,
5708
+ displayOrder: raw.display_order,
5709
+ isActive: raw.is_active
5710
+ };
5711
+ }
5712
+ function mapPlan(raw) {
5713
+ return {
5714
+ id: raw.id,
5715
+ identifier: raw.identifier,
5716
+ name: raw.name,
5717
+ description: raw.description,
5718
+ metadata: raw.metadata,
5719
+ products: raw.products.map(mapProduct)
5720
+ };
5721
+ }
5722
+ var PlanService, planService;
5723
+ var init_PlanService = __esm({
5724
+ "src/domains/plan/PlanService.ts"() {
5725
+ "use strict";
5726
+ init_PlanCache();
5727
+ init_PlanError();
5728
+ PlanService = class {
5729
+ constructor() {
5730
+ this.config = null;
5731
+ this.cache = planCache;
5732
+ }
5733
+ init(config) {
5734
+ this.config = config;
5735
+ }
5736
+ async getCurrentPlan(forceRefresh = false) {
5737
+ if (!forceRefresh) {
5738
+ const cached = this.cache.get();
5739
+ if (cached && !cached.isStale) {
5740
+ const current = cached.data.plans.find(
5741
+ (p) => p.identifier === cached.data.currentIdentifier
5742
+ );
5743
+ return current ?? null;
5744
+ }
5745
+ }
5746
+ return this.fetchCurrentPlan();
5747
+ }
5748
+ async getAllPlans(forceRefresh = false) {
5749
+ if (!forceRefresh) {
5750
+ const cached = this.cache.get();
5751
+ if (cached && !cached.isStale) return cached.data;
5752
+ }
5753
+ return this.fetchAllPlans();
5754
+ }
5755
+ invalidateCache() {
5756
+ this.cache.invalidate();
5757
+ }
5758
+ getApiClient() {
5759
+ if (!this.config) {
5760
+ throw new PlanError(PLAN_ERROR_CODES.NOT_INITIALIZED, "PlanService not initialized. Call Paywallo.init() first");
5761
+ }
5762
+ return this.config.apiClient;
5763
+ }
5764
+ async fetchCurrentPlan() {
5765
+ try {
5766
+ const res = await this.getApiClient().get("/sdk/plans");
5767
+ if (!res.ok) {
5768
+ this.log("fetchCurrentPlan failed", res.status);
5769
+ return null;
5770
+ }
5771
+ const raw = res.data.data.plan;
5772
+ return raw ? mapPlan(raw) : null;
5773
+ } catch (err) {
5774
+ this.log("fetchCurrentPlan error", err);
5775
+ return null;
5776
+ }
5777
+ }
5778
+ async fetchAllPlans() {
5779
+ try {
5780
+ const res = await this.getApiClient().get("/sdk/plans/all");
5781
+ if (!res.ok) {
5782
+ this.log("fetchAllPlans failed", res.status);
5783
+ return { plans: [], currentIdentifier: null };
5784
+ }
5785
+ const result = {
5786
+ plans: res.data.data.plans.map(mapPlan),
5787
+ currentIdentifier: res.data.data.current_identifier
5788
+ };
5789
+ this.cache.set(result);
5790
+ return result;
5791
+ } catch (err) {
5792
+ this.log("fetchAllPlans error", err);
5793
+ return { plans: [], currentIdentifier: null };
5794
+ }
5795
+ }
5796
+ log(...args) {
5797
+ if (this.config?.debug) console.log("[Paywallo Plans]", ...args);
5798
+ }
5799
+ };
5800
+ planService = new PlanService();
5801
+ }
5802
+ });
5803
+
5804
+ // src/domains/plan/index.ts
5805
+ var init_plan = __esm({
5806
+ "src/domains/plan/index.ts"() {
5807
+ "use strict";
5808
+ init_PlanCache();
5809
+ init_PlanService();
5810
+ }
5811
+ });
5812
+
5813
+ // src/domains/onboarding/OnboardingError.ts
5814
+ var OnboardingError, ONBOARDING_ERROR_CODES;
5815
+ var init_OnboardingError = __esm({
5816
+ "src/domains/onboarding/OnboardingError.ts"() {
5817
+ "use strict";
5818
+ init_PaywalloError();
5819
+ OnboardingError = class extends PaywalloError {
5820
+ constructor(code, message) {
5821
+ super("onboarding", code, message);
5822
+ this.name = "OnboardingError";
5823
+ }
5824
+ };
5825
+ ONBOARDING_ERROR_CODES = {
5826
+ NOT_INITIALIZED: "ONBOARDING_NOT_INITIALIZED",
5827
+ INVALID_STEP_NAME: "ONBOARDING_INVALID_STEP_NAME"
5828
+ };
5829
+ }
5830
+ });
5831
+
5832
+ // src/domains/onboarding/OnboardingManager.ts
5833
+ var OnboardingManager, onboardingManager;
5834
+ var init_OnboardingManager = __esm({
5835
+ "src/domains/onboarding/OnboardingManager.ts"() {
5836
+ "use strict";
5837
+ init_OnboardingError();
5838
+ OnboardingManager = class {
5839
+ constructor() {
5840
+ this.pipeline = null;
5841
+ this.debug = false;
5842
+ this.lastStep = null;
5843
+ this.finished = false;
5844
+ }
5845
+ /**
5846
+ * Injects runtime dependencies. Called by PaywalloClient during init.
5847
+ */
5848
+ injectDeps(config) {
5849
+ this.pipeline = config.pipeline;
5850
+ this.debug = config.debug ?? false;
5851
+ }
5852
+ /**
5853
+ * Tracks an onboarding step view.
5854
+ * Saves the step name internally for implicit drop detection on the backend.
5855
+ *
5856
+ * @param stepName - Unique name for this onboarding step.
5857
+ */
5858
+ async step(stepName) {
5859
+ this.assertConfig();
5860
+ this.validateStepName(stepName, "step");
5861
+ this.lastStep = stepName;
5862
+ const payload = {
5863
+ family: "onboarding",
5864
+ type: "step",
5865
+ ...{ step_name: stepName }
5866
+ };
5867
+ return this.emit("onboarding", payload, "step");
5868
+ }
5869
+ /**
5870
+ * Tracks successful completion of the onboarding flow.
5871
+ * Marks the flow as finished.
5872
+ */
5873
+ async complete() {
5874
+ this.assertConfig();
5875
+ this.finished = true;
5876
+ return this.emit("onboarding", { family: "onboarding", type: "complete" }, "complete");
5877
+ }
5878
+ /**
5879
+ * Tracks an explicit drop (abandonment) at the given step.
5880
+ * Marks the flow as finished.
5881
+ *
5882
+ * @param stepName - The step name where the user dropped off.
5883
+ */
5884
+ async drop(stepName) {
5885
+ this.assertConfig();
5886
+ this.validateStepName(stepName, "drop");
5887
+ this.finished = true;
5888
+ const payload = {
5889
+ family: "onboarding",
5890
+ type: "drop",
5891
+ ...{ step_name: stepName }
5892
+ };
5893
+ return this.emit("onboarding", payload, "drop");
5894
+ }
5895
+ /** Returns the last step name seen, or null if no step was tracked yet. */
5896
+ getLastStep() {
5897
+ return this.lastStep;
5898
+ }
5899
+ /** Returns whether the flow has been marked as finished (complete or drop). */
5900
+ isFinished() {
5901
+ return this.finished;
5902
+ }
5903
+ async emit(eventName, properties, logLabel) {
5904
+ const pipeline = this.pipeline;
5905
+ const distinctId = pipeline.getDistinctId();
5906
+ if (!distinctId) {
5907
+ if (this.debug) console.log(`[Paywallo:Onboarding] skipped (no distinctId): ${logLabel}`);
5908
+ return;
5909
+ }
5910
+ await pipeline.trackEvent(eventName, distinctId, properties);
5911
+ if (this.debug) {
5912
+ console.log(`[Paywallo:Onboarding] event emitted: ${eventName}.${logLabel}`, properties);
5913
+ }
5914
+ }
5915
+ assertConfig() {
5916
+ if (!this.pipeline) {
5917
+ throw new OnboardingError(
5918
+ ONBOARDING_ERROR_CODES.NOT_INITIALIZED,
5919
+ "OnboardingManager not initialized. Call Paywallo.init() first."
5920
+ );
5921
+ }
5922
+ }
5923
+ validateStepName(stepName, method) {
5924
+ if (typeof stepName !== "string" || stepName.trim().length === 0) {
5925
+ throw new OnboardingError(
5926
+ ONBOARDING_ERROR_CODES.INVALID_STEP_NAME,
5927
+ `OnboardingManager.${method}(): stepName must be a non-empty string.`
5928
+ );
5929
+ }
5930
+ }
5931
+ };
5932
+ onboardingManager = new OnboardingManager();
5402
5933
  }
5403
5934
  });
5404
5935
 
5405
- // src/domains/notifications/index.ts
5406
- var init_notifications = __esm({
5407
- "src/domains/notifications/index.ts"() {
5936
+ // src/domains/onboarding/index.ts
5937
+ var init_onboarding = __esm({
5938
+ "src/domains/onboarding/index.ts"() {
5408
5939
  "use strict";
5409
- init_NotificationsManager();
5410
- init_NativePushBridge();
5411
- init_NotificationsError();
5940
+ init_OnboardingManager();
5941
+ init_OnboardingError();
5412
5942
  }
5413
5943
  });
5414
5944
 
5415
- // src/domains/session/index.ts
5416
- var init_session = __esm({
5417
- "src/domains/session/index.ts"() {
5418
- "use strict";
5419
- init_SessionManager();
5420
- init_SessionError();
5945
+ // src/domains/paywall/paywallHeartbeatRecovery.ts
5946
+ async function recoverPaywallHeartbeat(apiClient, distinctId, sessionId, debug) {
5947
+ const snapshot = await readPersistedHeartbeat();
5948
+ if (!snapshot) return false;
5949
+ try {
5950
+ const closedAt = new Date(snapshot.lastSeen);
5951
+ const durationS = Math.max(0, Math.round((snapshot.lastSeen - snapshot.presentedAt) / 1e3));
5952
+ await apiClient.trackEvent("paywall", distinctId, {
5953
+ type: "closed",
5954
+ paywall_id: snapshot.paywallId,
5955
+ placement: snapshot.placement,
5956
+ closed_at: closedAt.toISOString(),
5957
+ duration_s: durationS,
5958
+ close_reason: "error",
5959
+ ...snapshot.variantId && { variant_id: snapshot.variantId },
5960
+ ...snapshot.variantKey && { variant_key: snapshot.variantKey },
5961
+ ...snapshot.campaignId && { campaign_id: snapshot.campaignId },
5962
+ ...sessionId && { sessionId }
5963
+ });
5964
+ if (debug) console.log("[Paywallo HEARTBEAT] recovered orphan paywall close", {
5965
+ paywallId: snapshot.paywallId,
5966
+ durationS
5967
+ });
5968
+ return true;
5969
+ } catch (err) {
5970
+ if (debug) console.log("[Paywallo HEARTBEAT] recovery failed", err);
5971
+ return false;
5972
+ } finally {
5973
+ await clearPersistedHeartbeat();
5421
5974
  }
5422
- });
5423
-
5424
- // src/core/PaywalloState.ts
5425
- function createPaywalloState() {
5426
- return {
5427
- config: null,
5428
- apiClient: null,
5429
- initPromise: null,
5430
- readyPromise: null,
5431
- resolveReady: null,
5432
- paywallPresenter: null,
5433
- campaignPresenter: null,
5434
- subscriptionGetter: null,
5435
- activeChecker: null,
5436
- restoreHandler: null,
5437
- lastOnboardingStepTimestamp: null,
5438
- pendingConfig: null,
5439
- networkCleanup: null,
5440
- initAttempts: 0,
5441
- autoPreloadedPlacement: null,
5442
- autoPreloadPlacementPromise: null,
5443
- sessionFlagsMap: /* @__PURE__ */ new Map(),
5444
- pendingIdentifies: []
5445
- };
5446
5975
  }
5447
- var MAX_INIT_RETRIES, RETRY_DELAYS;
5448
- var init_PaywalloState = __esm({
5449
- "src/core/PaywalloState.ts"() {
5976
+ var init_paywallHeartbeatRecovery = __esm({
5977
+ "src/domains/paywall/paywallHeartbeatRecovery.ts"() {
5450
5978
  "use strict";
5451
- MAX_INIT_RETRIES = 3;
5452
- RETRY_DELAYS = [2e3, 5e3, 1e4];
5979
+ init_paywallHeartbeat();
5453
5980
  }
5454
5981
  });
5455
5982
 
@@ -5457,7 +5984,7 @@ var init_PaywalloState = __esm({
5457
5984
  function keyFor(distinctId) {
5458
5985
  return `${CACHE_KEY_PREFIX}${distinctId}`;
5459
5986
  }
5460
- var LEGACY_CACHE_KEY, CACHE_KEY_PREFIX, CACHE_INDEX_KEY, DEFAULT_TTL, SubscriptionCache, subscriptionCache;
5987
+ var LEGACY_CACHE_KEY, CACHE_KEY_PREFIX, CACHE_INDEX_KEY, DEFAULT_TTL2, SubscriptionCache, subscriptionCache;
5461
5988
  var init_SubscriptionCache = __esm({
5462
5989
  "src/domains/subscription/SubscriptionCache.ts"() {
5463
5990
  "use strict";
@@ -5465,9 +5992,9 @@ var init_SubscriptionCache = __esm({
5465
5992
  LEGACY_CACHE_KEY = "@panel:subscription_cache";
5466
5993
  CACHE_KEY_PREFIX = "subscription_cache:";
5467
5994
  CACHE_INDEX_KEY = "subscription_cache:__index__";
5468
- DEFAULT_TTL = 24 * 60 * 60 * 1e3;
5995
+ DEFAULT_TTL2 = 24 * 60 * 60 * 1e3;
5469
5996
  SubscriptionCache = class {
5470
- constructor(ttl = DEFAULT_TTL) {
5997
+ constructor(ttl = DEFAULT_TTL2) {
5471
5998
  this.memoryCache = /* @__PURE__ */ new Map();
5472
5999
  this.legacyCleanupDone = false;
5473
6000
  this.ttl = ttl;
@@ -5674,7 +6201,7 @@ var init_SubscriptionManager = __esm({
5674
6201
  if (!this.config) {
5675
6202
  return this.getEmptyStatus();
5676
6203
  }
5677
- const url = new URL(`${this.config.serverUrl}/purchases/status/${this.config.appKey}`);
6204
+ const url = new URL(`${this.config.serverUrl}/sdk/purchases/status/${this.config.appKey}`);
5678
6205
  if (this.userId) {
5679
6206
  url.searchParams.set("distinctId", this.userId);
5680
6207
  }
@@ -5758,11 +6285,10 @@ function buildConditionalFlagUrl(baseUrl, flagKey, context) {
5758
6285
  if (context.distinctId) url.searchParams.set("distinctId", context.distinctId);
5759
6286
  return url.toString();
5760
6287
  }
5761
- var DEFAULT_WEB_URL;
5762
6288
  var init_apiUrlUtils = __esm({
5763
6289
  "src/core/apiUrlUtils.ts"() {
5764
6290
  "use strict";
5765
- DEFAULT_WEB_URL = "https://paywallo.com.br";
6291
+ init_constants();
5766
6292
  }
5767
6293
  });
5768
6294
 
@@ -5915,7 +6441,7 @@ async function getSubscriptionStatus(client, baseUrl, appKey, distinctId) {
5915
6441
  }
5916
6442
  async function getEmergencyPaywall(client) {
5917
6443
  try {
5918
- const response = await client.get("/emergency-paywall");
6444
+ const response = await client.get("/sdk/emergency-paywall");
5919
6445
  if (response.status === 404) return { enabled: false };
5920
6446
  return response.data;
5921
6447
  } catch {
@@ -6532,7 +7058,7 @@ var init_EventBatcher = __esm({
6532
7058
  BATCH_MAX_SIZE = 25;
6533
7059
  BATCH_FLUSH_MS = 1e4;
6534
7060
  EVENT_NAME_REGEX = /^(\$[a-zA-Z0-9_]+|[a-z][a-z0-9_]*)$/;
6535
- V2_BATCH_ENDPOINT = "/ingest/batch";
7061
+ V2_BATCH_ENDPOINT = "/sdk/ingest/batch";
6536
7062
  V1_BATCH_ENDPOINT = "/sdk/events/batch";
6537
7063
  V1_SINGLE_ENDPOINT = "/sdk/events";
6538
7064
  EventBatcher = class {
@@ -6896,7 +7422,7 @@ var init_ApiClient = __esm({
6896
7422
  dispose() {
6897
7423
  this.batcher.dispose();
6898
7424
  }
6899
- async identify(distinctId, properties, email, deviceId) {
7425
+ async identify(distinctId, properties, email, deviceId, pii) {
6900
7426
  await postWithQueue(
6901
7427
  {
6902
7428
  isOfflineQueueEnabled: () => this.offlineQueueEnabled,
@@ -6904,8 +7430,19 @@ var init_ApiClient = __esm({
6904
7430
  isDebug: () => this.debug,
6905
7431
  post: (path, body, skipRetry) => this.post(path, body, skipRetry)
6906
7432
  },
6907
- "/identify",
6908
- { distinctId, properties: properties ?? {}, email, platform: import_react_native23.Platform.OS, ...deviceId && { deviceId } },
7433
+ "/sdk/identify",
7434
+ {
7435
+ distinctId,
7436
+ properties: properties ?? {},
7437
+ email,
7438
+ platform: import_react_native23.Platform.OS,
7439
+ ...deviceId && { deviceId },
7440
+ ...pii?.phone && { phone: pii.phone },
7441
+ ...pii?.firstName && { firstName: pii.firstName },
7442
+ ...pii?.lastName && { lastName: pii.lastName },
7443
+ ...pii?.dateOfBirth && { dateOfBirth: pii.dateOfBirth },
7444
+ ...pii?.gender && { gender: pii.gender }
7445
+ },
6909
7446
  "identify"
6910
7447
  );
6911
7448
  }
@@ -6930,248 +7467,87 @@ var init_ApiClient = __esm({
6930
7467
  return stale !== void 0 ? stale : null;
6931
7468
  }
6932
7469
  this.cache.setPaywall(placement, res.data);
6933
- return res.data;
6934
- } catch {
6935
- const stale = this.cache.getStalePaywall(placement);
6936
- return stale !== void 0 ? stale : null;
6937
- }
6938
- }
6939
- async getEmergencyPaywall() {
6940
- return getEmergencyPaywall(this);
6941
- }
6942
- async getCampaign(placement, distinctId, context) {
6943
- const key = `${placement}:${distinctId}`;
6944
- const hit = this.cache.getCampaign(key);
6945
- if (hit !== void 0) return hit;
6946
- try {
6947
- const result = await getCampaignData(this, this.baseUrl, placement, distinctId, context);
6948
- if (!result) {
6949
- this.cache.setCampaign(key, null, this.cache.nullTTL);
6950
- return null;
6951
- }
6952
- this.cache.setCampaign(key, result);
6953
- return result;
6954
- } catch {
6955
- const stale = this.cache.getStaleCampaign(key);
6956
- return stale !== void 0 ? stale : null;
6957
- }
6958
- }
6959
- async getPrimaryCampaign(distinctId) {
6960
- const key = `primary:${distinctId}`;
6961
- const hit = this.cache.getCampaign(key);
6962
- if (hit !== void 0) return hit;
6963
- try {
6964
- const res = await this.get(`/campaigns/public/primary?distinctId=${encodeURIComponent(distinctId)}`);
6965
- if (res.status === 404) {
6966
- this.cache.setCampaign(key, null, this.cache.nullTTL);
6967
- return null;
6968
- }
6969
- if (!res.ok) {
6970
- const stale = this.cache.getStaleCampaign(key);
6971
- return stale !== void 0 ? stale : null;
6972
- }
6973
- this.cache.setCampaign(key, res.data);
6974
- return res.data;
6975
- } catch {
6976
- const stale = this.cache.getStaleCampaign(key);
6977
- return stale !== void 0 ? stale : null;
6978
- }
6979
- }
6980
- async validatePurchase(platform, receipt, productId, transactionId, priceLocal, currency, country, distinctId, paywallPlacement, variantKey) {
6981
- return validatePurchase(this, this.appKey, platform, receipt, productId, transactionId, priceLocal, currency, country, distinctId, paywallPlacement, variantKey);
6982
- }
6983
- async getSubscriptionStatus(distinctId) {
6984
- return getSubscriptionStatus(this, this.baseUrl, this.appKey, distinctId);
6985
- }
6986
- async evaluateFlags(keys, distinctId) {
6987
- return evaluateFlags(this.flagDeps(), keys, distinctId);
6988
- }
6989
- async getConditionalFlag(flagKey, context) {
6990
- return getConditionalFlag(this.flagDeps(), flagKey, context);
6991
- }
6992
- async getVariantFromCache(flagKey, distinctId) {
6993
- return getVariantFromCache(this.flagDeps(), flagKey, distinctId);
6994
- }
6995
- flagDeps() {
6996
- return {
6997
- get: (path) => this.get(path),
6998
- httpClient: this.httpClient,
6999
- cache: this.cache,
7000
- appKey: this.appKey,
7001
- baseUrl: this.baseUrl
7002
- };
7003
- }
7004
- };
7005
- }
7006
- });
7007
-
7008
- // src/domains/onboarding/OnboardingError.ts
7009
- var OnboardingError, ONBOARDING_ERROR_CODES;
7010
- var init_OnboardingError = __esm({
7011
- "src/domains/onboarding/OnboardingError.ts"() {
7012
- "use strict";
7013
- init_PaywalloError();
7014
- OnboardingError = class extends PaywalloError {
7015
- constructor(code, message) {
7016
- super("onboarding", code, message);
7017
- this.name = "OnboardingError";
7018
- }
7019
- };
7020
- ONBOARDING_ERROR_CODES = {
7021
- NOT_INITIALIZED: "ONBOARDING_NOT_INITIALIZED",
7022
- INVALID_STEP_NAME: "ONBOARDING_INVALID_STEP_NAME"
7023
- };
7024
- }
7025
- });
7026
-
7027
- // src/domains/onboarding/OnboardingManager.ts
7028
- var OnboardingManager, onboardingManager;
7029
- var init_OnboardingManager = __esm({
7030
- "src/domains/onboarding/OnboardingManager.ts"() {
7031
- "use strict";
7032
- init_OnboardingError();
7033
- OnboardingManager = class {
7034
- constructor() {
7035
- this.pipeline = null;
7036
- this.debug = false;
7037
- this.lastStep = null;
7038
- this.finished = false;
7039
- }
7040
- /**
7041
- * Injects runtime dependencies. Called by PaywalloClient during init.
7042
- */
7043
- injectDeps(config) {
7044
- this.pipeline = config.pipeline;
7045
- this.debug = config.debug ?? false;
7046
- }
7047
- /**
7048
- * Tracks an onboarding step view.
7049
- * Saves the step name internally for implicit drop detection on the backend.
7050
- *
7051
- * @param stepName - Unique name for this onboarding step.
7052
- */
7053
- async step(stepName) {
7054
- this.assertConfig();
7055
- this.validateStepName(stepName, "step");
7056
- this.lastStep = stepName;
7057
- const payload = {
7058
- family: "onboarding",
7059
- type: "step",
7060
- ...{ step_name: stepName }
7061
- };
7062
- return this.emit("onboarding", payload, "step");
7063
- }
7064
- /**
7065
- * Tracks successful completion of the onboarding flow.
7066
- * Marks the flow as finished.
7067
- */
7068
- async complete() {
7069
- this.assertConfig();
7070
- this.finished = true;
7071
- return this.emit("onboarding", { family: "onboarding", type: "complete" }, "complete");
7072
- }
7073
- /**
7074
- * Tracks an explicit drop (abandonment) at the given step.
7075
- * Marks the flow as finished.
7076
- *
7077
- * @param stepName - The step name where the user dropped off.
7078
- */
7079
- async drop(stepName) {
7080
- this.assertConfig();
7081
- this.validateStepName(stepName, "drop");
7082
- this.finished = true;
7083
- const payload = {
7084
- family: "onboarding",
7085
- type: "drop",
7086
- ...{ step_name: stepName }
7087
- };
7088
- return this.emit("onboarding", payload, "drop");
7089
- }
7090
- /** Returns the last step name seen, or null if no step was tracked yet. */
7091
- getLastStep() {
7092
- return this.lastStep;
7093
- }
7094
- /** Returns whether the flow has been marked as finished (complete or drop). */
7095
- isFinished() {
7096
- return this.finished;
7097
- }
7098
- async emit(eventName, properties, logLabel) {
7099
- const pipeline = this.pipeline;
7100
- const distinctId = pipeline.getDistinctId();
7101
- if (!distinctId) {
7102
- if (this.debug) console.log(`[Paywallo:Onboarding] skipped (no distinctId): ${logLabel}`);
7103
- return;
7470
+ return res.data;
7471
+ } catch {
7472
+ const stale = this.cache.getStalePaywall(placement);
7473
+ return stale !== void 0 ? stale : null;
7104
7474
  }
7105
- await pipeline.trackEvent(eventName, distinctId, properties);
7106
- if (this.debug) {
7107
- console.log(`[Paywallo:Onboarding] event emitted: ${eventName}.${logLabel}`, properties);
7475
+ }
7476
+ async getEmergencyPaywall() {
7477
+ return getEmergencyPaywall(this);
7478
+ }
7479
+ async getCampaign(placement, distinctId, context) {
7480
+ const key = `${placement}:${distinctId}`;
7481
+ const hit = this.cache.getCampaign(key);
7482
+ if (hit !== void 0) return hit;
7483
+ try {
7484
+ const result = await getCampaignData(this, this.baseUrl, placement, distinctId, context);
7485
+ if (!result) {
7486
+ this.cache.setCampaign(key, null, this.cache.nullTTL);
7487
+ return null;
7488
+ }
7489
+ this.cache.setCampaign(key, result);
7490
+ return result;
7491
+ } catch {
7492
+ const stale = this.cache.getStaleCampaign(key);
7493
+ return stale !== void 0 ? stale : null;
7108
7494
  }
7109
7495
  }
7110
- assertConfig() {
7111
- if (!this.pipeline) {
7112
- throw new OnboardingError(
7113
- ONBOARDING_ERROR_CODES.NOT_INITIALIZED,
7114
- "OnboardingManager not initialized. Call Paywallo.init() first."
7115
- );
7496
+ async getActivePlacements() {
7497
+ try {
7498
+ const res = await this.get("/sdk/campaigns/placements");
7499
+ if (!res.ok || !Array.isArray(res.data?.data)) return [];
7500
+ return res.data.data;
7501
+ } catch {
7502
+ return [];
7116
7503
  }
7117
7504
  }
7118
- validateStepName(stepName, method) {
7119
- if (typeof stepName !== "string" || stepName.trim().length === 0) {
7120
- throw new OnboardingError(
7121
- ONBOARDING_ERROR_CODES.INVALID_STEP_NAME,
7122
- `OnboardingManager.${method}(): stepName must be a non-empty string.`
7123
- );
7505
+ async getPrimaryCampaign(distinctId) {
7506
+ const key = `primary:${distinctId}`;
7507
+ const hit = this.cache.getCampaign(key);
7508
+ if (hit !== void 0) return hit;
7509
+ try {
7510
+ const res = await this.get(`/sdk/campaigns/public/primary?distinctId=${encodeURIComponent(distinctId)}`);
7511
+ if (res.status === 404) {
7512
+ this.cache.setCampaign(key, null, this.cache.nullTTL);
7513
+ return null;
7514
+ }
7515
+ if (!res.ok) {
7516
+ const stale = this.cache.getStaleCampaign(key);
7517
+ return stale !== void 0 ? stale : null;
7518
+ }
7519
+ this.cache.setCampaign(key, res.data);
7520
+ return res.data;
7521
+ } catch {
7522
+ const stale = this.cache.getStaleCampaign(key);
7523
+ return stale !== void 0 ? stale : null;
7124
7524
  }
7125
7525
  }
7526
+ async validatePurchase(platform, receipt, productId, transactionId, priceLocal, currency, country, distinctId, paywallPlacement, variantKey) {
7527
+ return validatePurchase(this, this.appKey, platform, receipt, productId, transactionId, priceLocal, currency, country, distinctId, paywallPlacement, variantKey);
7528
+ }
7529
+ async getSubscriptionStatus(distinctId) {
7530
+ return getSubscriptionStatus(this, this.baseUrl, this.appKey, distinctId);
7531
+ }
7532
+ async evaluateFlags(keys, distinctId) {
7533
+ return evaluateFlags(this.flagDeps(), keys, distinctId);
7534
+ }
7535
+ async getConditionalFlag(flagKey, context) {
7536
+ return getConditionalFlag(this.flagDeps(), flagKey, context);
7537
+ }
7538
+ async getVariantFromCache(flagKey, distinctId) {
7539
+ return getVariantFromCache(this.flagDeps(), flagKey, distinctId);
7540
+ }
7541
+ flagDeps() {
7542
+ return {
7543
+ get: (path) => this.get(path),
7544
+ httpClient: this.httpClient,
7545
+ cache: this.cache,
7546
+ appKey: this.appKey,
7547
+ baseUrl: this.baseUrl
7548
+ };
7549
+ }
7126
7550
  };
7127
- onboardingManager = new OnboardingManager();
7128
- }
7129
- });
7130
-
7131
- // src/domains/onboarding/index.ts
7132
- var init_onboarding = __esm({
7133
- "src/domains/onboarding/index.ts"() {
7134
- "use strict";
7135
- init_OnboardingManager();
7136
- init_OnboardingError();
7137
- }
7138
- });
7139
-
7140
- // src/domains/paywall/paywallHeartbeatRecovery.ts
7141
- async function recoverPaywallHeartbeat(apiClient, distinctId, sessionId, debug) {
7142
- const snapshot = await readPersistedHeartbeat();
7143
- if (!snapshot) return false;
7144
- try {
7145
- const closedAt = new Date(snapshot.lastSeen);
7146
- const durationS = Math.max(0, Math.round((snapshot.lastSeen - snapshot.presentedAt) / 1e3));
7147
- await apiClient.trackEvent("paywall", distinctId, {
7148
- type: "closed",
7149
- paywall_id: snapshot.paywallId,
7150
- placement: snapshot.placement,
7151
- closed_at: closedAt.toISOString(),
7152
- duration_s: durationS,
7153
- close_reason: "error",
7154
- ...snapshot.variantId && { variant_id: snapshot.variantId },
7155
- ...snapshot.variantKey && { variant_key: snapshot.variantKey },
7156
- ...snapshot.campaignId && { campaign_id: snapshot.campaignId },
7157
- ...sessionId && { sessionId }
7158
- });
7159
- if (debug) console.log("[Paywallo HEARTBEAT] recovered orphan paywall close", {
7160
- paywallId: snapshot.paywallId,
7161
- durationS
7162
- });
7163
- return true;
7164
- } catch (err) {
7165
- if (debug) console.log("[Paywallo HEARTBEAT] recovery failed", err);
7166
- return false;
7167
- } finally {
7168
- await clearPersistedHeartbeat();
7169
- }
7170
- }
7171
- var init_paywallHeartbeatRecovery = __esm({
7172
- "src/domains/paywall/paywallHeartbeatRecovery.ts"() {
7173
- "use strict";
7174
- init_paywallHeartbeat();
7175
7551
  }
7176
7552
  });
7177
7553
 
@@ -7215,6 +7591,8 @@ var init_eventPipelineBridge = __esm({
7215
7591
 
7216
7592
  // src/core/PaywalloInitHelpers.ts
7217
7593
  function setupNotifications(apiClient, config, environment, eventPipeline) {
7594
+ if (config.notifications === false) return;
7595
+ if (config.notifications === void 0 && !isNativePushAvailable()) return;
7218
7596
  try {
7219
7597
  notificationsManager.injectDeps({
7220
7598
  httpClient: apiClient.getHttpClient(),
@@ -7256,7 +7634,10 @@ function setupAutoPreload(state, apiClient, config, distinctId) {
7256
7634
  return null;
7257
7635
  }).catch(() => null);
7258
7636
  }
7259
- var DEFAULT_API_URL;
7637
+ function startBatchPreload(apiClient, debug) {
7638
+ void campaignGateService.preloadAllActive(apiClient, debug).catch(() => {
7639
+ });
7640
+ }
7260
7641
  var init_PaywalloInitHelpers = __esm({
7261
7642
  "src/core/PaywalloInitHelpers.ts"() {
7262
7643
  "use strict";
@@ -7264,7 +7645,8 @@ var init_PaywalloInitHelpers = __esm({
7264
7645
  init_identity();
7265
7646
  init_notifications();
7266
7647
  init_SecureStorage();
7267
- DEFAULT_API_URL = "https://paywallo.com.br";
7648
+ init_NativePushBridge();
7649
+ init_constants();
7268
7650
  }
7269
7651
  });
7270
7652
 
@@ -7313,7 +7695,7 @@ function reportInitFailure(state, config, error, reason) {
7313
7695
  try {
7314
7696
  const tempClient = new ApiClient(
7315
7697
  config.appKey,
7316
- config.apiUrl ?? DEFAULT_API_URL2,
7698
+ config.apiUrl ?? DEFAULT_API_URL,
7317
7699
  config.debug
7318
7700
  );
7319
7701
  void tempClient.reportError("INIT_FAILED", reason, {
@@ -7357,7 +7739,7 @@ async function doInit(state, config) {
7357
7739
  ]);
7358
7740
  const apiClient = new ApiClient(
7359
7741
  config.appKey,
7360
- config.apiUrl ?? DEFAULT_API_URL2,
7742
+ config.apiUrl ?? DEFAULT_API_URL,
7361
7743
  config.debug,
7362
7744
  environment,
7363
7745
  offlineQueueEnabled,
@@ -7377,6 +7759,7 @@ async function doInit(state, config) {
7377
7759
  cacheTTL: config.subscriptionCacheTTL,
7378
7760
  debug: config.debug
7379
7761
  });
7762
+ planService.init({ apiClient, debug: config.debug ?? false });
7380
7763
  await identityManager.initialize(apiClient, config.debug);
7381
7764
  if (state.pendingIdentifies.length > 0) {
7382
7765
  for (const pending of state.pendingIdentifies) {
@@ -7399,6 +7782,9 @@ async function doInit(state, config) {
7399
7782
  } catch (err) {
7400
7783
  if (config.debug) console.log("[Paywallo ATTR] start error", err);
7401
7784
  }
7785
+ void nativeDeviceInfo.getDeviceInfo().catch(() => null);
7786
+ void advertisingIdManager.collect(false).catch(() => null);
7787
+ void metaBridge.getAnonymousID().catch(() => null);
7402
7788
  apiClient.setEventContextProvider(() => {
7403
7789
  const attr = attributionTracker.get();
7404
7790
  const attrEntries = attr ? [
@@ -7413,11 +7799,35 @@ async function doInit(state, config) {
7413
7799
  ["referrer", attr.referrer]
7414
7800
  ].filter((pair) => pair[1] !== void 0 && pair[1] !== "") : [];
7415
7801
  const attribution = attrEntries.length > 0 ? Object.fromEntries(attrEntries) : void 0;
7802
+ const adIds = advertisingIdManager.getCached();
7803
+ const fbAnonId = metaBridge.getCachedAnonymousID();
7804
+ const idsEntries = [
7805
+ ["idfa", adIds?.idfa ?? null],
7806
+ ["idfv", adIds?.idfv ?? null],
7807
+ ["gaid", adIds?.gaid ?? null],
7808
+ ["fb_anon_id", fbAnonId]
7809
+ ].filter((pair) => pair[1] !== null && pair[1] !== "");
7810
+ const ids = idsEntries.length > 0 ? Object.fromEntries(idsEntries) : void 0;
7811
+ const device = nativeDeviceInfo.getCachedDeviceInfo();
7812
+ let locale;
7813
+ let timezone;
7814
+ try {
7815
+ const opts = Intl.DateTimeFormat().resolvedOptions();
7816
+ locale = opts.locale || void 0;
7817
+ timezone = opts.timeZone || void 0;
7818
+ } catch {
7819
+ }
7416
7820
  return {
7417
7821
  distinct_id: identityManager.getDistinctId() || void 0,
7418
7822
  device_id: identityManager.getDeviceId() ?? void 0,
7419
7823
  session_id: sessionManager.getSessionId() ?? void 0,
7420
- ...attribution && Object.keys(attribution).length > 0 && { attribution }
7824
+ app_version: device?.appVersion && device.appVersion !== "0.0.0" ? device.appVersion : void 0,
7825
+ os_version: device?.systemVersion && device.systemVersion !== "0.0.0" ? device.systemVersion : void 0,
7826
+ device_model: device?.modelId || device?.model || void 0,
7827
+ timezone,
7828
+ locale,
7829
+ ...attribution && Object.keys(attribution).length > 0 && { attribution },
7830
+ ...ids && Object.keys(ids).length > 0 && { ids }
7421
7831
  };
7422
7832
  });
7423
7833
  await sessionManager.initialize(
@@ -7451,7 +7861,7 @@ async function doInit(state, config) {
7451
7861
  state.resolveReady();
7452
7862
  state.resolveReady = null;
7453
7863
  }
7454
- if (config.debug) console.warn("[Paywallo INIT] complete");
7864
+ console.warn("[Paywallo] initialized successfully");
7455
7865
  if (distinctId) {
7456
7866
  apiClient.getSubscriptionStatus(distinctId).then(() => {
7457
7867
  if (config.debug) console.log("[Paywallo SUB] preloaded on boot");
@@ -7462,27 +7872,30 @@ async function doInit(state, config) {
7462
7872
  });
7463
7873
  }
7464
7874
  setupAutoPreload(state, apiClient, config, distinctId);
7875
+ startBatchPreload(apiClient, config.debug);
7465
7876
  }
7466
- var DEFAULT_API_URL2;
7467
7877
  var init_PaywalloInitializer = __esm({
7468
7878
  "src/core/PaywalloInitializer.ts"() {
7469
7879
  "use strict";
7470
- init_SubscriptionManager();
7471
- init_ApiClient();
7472
7880
  init_identity();
7881
+ init_plan();
7882
+ init_AdvertisingIdManager();
7473
7883
  init_DeepLinkAttributionCapture();
7474
7884
  init_InstallTracker();
7475
- init_network();
7476
- init_queue();
7477
- init_session();
7885
+ init_MetaBridge();
7478
7886
  init_onboarding();
7479
- init_NativeStorage();
7480
- init_NativeDeviceInfo();
7481
7887
  init_paywallHeartbeatRecovery();
7888
+ init_session();
7889
+ init_SubscriptionManager();
7890
+ init_NativeDeviceInfo();
7891
+ init_ApiClient();
7892
+ init_constants();
7482
7893
  init_eventPipelineBridge();
7483
- init_PaywalloState();
7894
+ init_network();
7484
7895
  init_PaywalloInitHelpers();
7485
- DEFAULT_API_URL2 = "https://paywallo.com.br";
7896
+ init_PaywalloState();
7897
+ init_queue();
7898
+ init_NativeStorage();
7486
7899
  }
7487
7900
  });
7488
7901
 
@@ -7736,6 +8149,7 @@ var init_PaywalloClient = __esm({
7736
8149
  init_PaywalloAnalytics();
7737
8150
  init_PaywalloSubscription();
7738
8151
  init_PaywalloFlags();
8152
+ init_plan();
7739
8153
  PaywalloClientClass = class {
7740
8154
  constructor() {
7741
8155
  this.state = createPaywalloState();
@@ -7987,6 +8401,14 @@ var init_PaywalloClient = __esm({
7987
8401
  async processOfflineQueue() {
7988
8402
  return queueProcessor.processQueue();
7989
8403
  }
8404
+ async getPlans() {
8405
+ this.ensureInitialized();
8406
+ return planService.getAllPlans();
8407
+ }
8408
+ async getCurrentPlan() {
8409
+ this.ensureInitialized();
8410
+ return planService.getCurrentPlan();
8411
+ }
7990
8412
  getApiClientOrThrow() {
7991
8413
  if (!this.state.config || !this.state.apiClient) throw new ClientError(CLIENT_ERROR_CODES.NOT_INITIALIZED, "SDK not initialized. Call Paywallo.init() first");
7992
8414
  return this.state.apiClient;
@@ -8029,6 +8451,8 @@ __export(index_exports, {
8029
8451
  PaywalloContext: () => PaywalloContext,
8030
8452
  PaywalloError: () => PaywalloError,
8031
8453
  PaywalloProvider: () => PaywalloProvider,
8454
+ PlanCache: () => PlanCache,
8455
+ PlanService: () => PlanService,
8032
8456
  PurchaseError: () => PurchaseError,
8033
8457
  SESSION_ERROR_CODES: () => SESSION_ERROR_CODES,
8034
8458
  SessionError: () => SessionError,
@@ -8051,6 +8475,8 @@ __export(index_exports, {
8051
8475
  offlineQueue: () => offlineQueue,
8052
8476
  onboardingManager: () => onboardingManager,
8053
8477
  parseVariables: () => parseVariables,
8478
+ planCache: () => planCache,
8479
+ planService: () => planService,
8054
8480
  queueProcessor: () => queueProcessor,
8055
8481
  resolveText: () => resolveText,
8056
8482
  sessionManager: () => sessionManager,
@@ -8060,6 +8486,8 @@ __export(index_exports, {
8060
8486
  useOnboarding: () => useOnboarding,
8061
8487
  usePaywallContext: () => usePaywallContext,
8062
8488
  usePaywallo: () => usePaywallo,
8489
+ usePlanPurchase: () => usePlanPurchase,
8490
+ usePlans: () => usePlans,
8063
8491
  useProducts: () => useProducts,
8064
8492
  usePurchase: () => usePurchase,
8065
8493
  useSubscription: () => useSubscription
@@ -8134,8 +8562,124 @@ function usePaywallo() {
8134
8562
  return context;
8135
8563
  }
8136
8564
 
8137
- // src/hooks/useProducts.ts
8565
+ // src/hooks/usePlans.ts
8138
8566
  var import_react13 = require("react");
8567
+ async function getPlanService() {
8568
+ try {
8569
+ const mod = await Promise.resolve().then(() => (init_PlanService(), PlanService_exports));
8570
+ return mod.planService ?? null;
8571
+ } catch {
8572
+ return null;
8573
+ }
8574
+ }
8575
+ function usePlans() {
8576
+ const [allPlans, setAllPlans] = (0, import_react13.useState)([]);
8577
+ const [currentPlan, setCurrentPlan] = (0, import_react13.useState)(null);
8578
+ const [isLoading, setIsLoading] = (0, import_react13.useState)(true);
8579
+ const [error, setError] = (0, import_react13.useState)(null);
8580
+ const fetchPlans = (0, import_react13.useCallback)(async (forceRefresh = false) => {
8581
+ setIsLoading(true);
8582
+ setError(null);
8583
+ try {
8584
+ const service = await getPlanService();
8585
+ if (!service) {
8586
+ throw new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, "PlanService not available");
8587
+ }
8588
+ const response = await service.getAllPlans(forceRefresh);
8589
+ const { plans, currentIdentifier } = response;
8590
+ setAllPlans(plans);
8591
+ setCurrentPlan(
8592
+ currentIdentifier != null ? plans.find((p) => p.identifier === currentIdentifier) ?? null : null
8593
+ );
8594
+ } catch (err) {
8595
+ const e = err instanceof PurchaseError ? err : new PurchaseError(PURCHASE_ERROR_CODES.PURCHASE_FAILED, String(err));
8596
+ setError(e);
8597
+ } finally {
8598
+ setIsLoading(false);
8599
+ }
8600
+ }, []);
8601
+ const refresh = (0, import_react13.useCallback)(() => {
8602
+ return fetchPlans(true);
8603
+ }, [fetchPlans]);
8604
+ (0, import_react13.useEffect)(() => {
8605
+ void fetchPlans(false);
8606
+ }, [fetchPlans]);
8607
+ return { currentPlan, allPlans, isLoading, error, refresh };
8608
+ }
8609
+
8610
+ // src/hooks/usePlanPurchase.ts
8611
+ var import_react14 = require("react");
8612
+ async function invalidatePlanCache() {
8613
+ try {
8614
+ const mod = await Promise.resolve().then(() => (init_PlanService(), PlanService_exports));
8615
+ mod.planService?.invalidateCache();
8616
+ } catch {
8617
+ }
8618
+ }
8619
+ function usePlanPurchase() {
8620
+ const [state, setState] = (0, import_react14.useState)("idle");
8621
+ const [error, setError] = (0, import_react14.useState)(null);
8622
+ const purchase = (0, import_react14.useCallback)(async (productId) => {
8623
+ setState("purchasing");
8624
+ setError(null);
8625
+ try {
8626
+ const iapService = getIAPService();
8627
+ const result = await iapService.purchase(productId);
8628
+ setState("idle");
8629
+ if (result.success) {
8630
+ await invalidatePlanCache();
8631
+ }
8632
+ return result.success ? {
8633
+ success: true,
8634
+ purchase: result.purchase ? {
8635
+ productId: result.purchase.productId,
8636
+ transactionId: result.purchase.transactionId ?? "",
8637
+ transactionDate: result.purchase.transactionDate,
8638
+ receipt: result.purchase.receipt,
8639
+ platform: "ios"
8640
+ } : void 0
8641
+ } : { success: false, error: result.error };
8642
+ } catch (err) {
8643
+ const purchaseError = err instanceof PurchaseError ? err : createPurchaseError("PURCHASE_FAILED", err instanceof Error ? err.message : String(err));
8644
+ if (purchaseError.userCancelled) {
8645
+ setState("idle");
8646
+ return null;
8647
+ }
8648
+ setError(purchaseError);
8649
+ setState("error");
8650
+ return { success: false, error: purchaseError };
8651
+ }
8652
+ }, []);
8653
+ const restore = (0, import_react14.useCallback)(async () => {
8654
+ setState("restoring");
8655
+ setError(null);
8656
+ try {
8657
+ const iapService = getIAPService();
8658
+ const transactions = await iapService.restore();
8659
+ setState("idle");
8660
+ await invalidatePlanCache();
8661
+ return {
8662
+ restoredCount: transactions.length,
8663
+ productIds: transactions.map((tx) => tx.productId)
8664
+ };
8665
+ } catch (err) {
8666
+ const e = err instanceof PurchaseError ? err : createPurchaseError("RESTORE_FAILED", String(err));
8667
+ setError(e);
8668
+ setState("error");
8669
+ return null;
8670
+ }
8671
+ }, []);
8672
+ return {
8673
+ purchase,
8674
+ restore,
8675
+ isPurchasing: state === "purchasing",
8676
+ isRestoring: state === "restoring",
8677
+ error
8678
+ };
8679
+ }
8680
+
8681
+ // src/hooks/useProducts.ts
8682
+ var import_react15 = require("react");
8139
8683
 
8140
8684
  // src/utils/productMappers.ts
8141
8685
  function mapToIAPProduct(product) {
@@ -8174,11 +8718,11 @@ function mapToFormattedProduct(product) {
8174
8718
 
8175
8719
  // src/hooks/useProducts.ts
8176
8720
  function useProducts(initialProductIds) {
8177
- const [products, setProducts] = (0, import_react13.useState)([]);
8178
- const [formattedProducts, setFormattedProducts] = (0, import_react13.useState)([]);
8179
- const [isLoading, setIsLoading] = (0, import_react13.useState)(false);
8180
- const [error, setError] = (0, import_react13.useState)(null);
8181
- const loadProducts = (0, import_react13.useCallback)(async (productIds) => {
8721
+ const [products, setProducts] = (0, import_react15.useState)([]);
8722
+ const [formattedProducts, setFormattedProducts] = (0, import_react15.useState)([]);
8723
+ const [isLoading, setIsLoading] = (0, import_react15.useState)(false);
8724
+ const [error, setError] = (0, import_react15.useState)(null);
8725
+ const loadProducts = (0, import_react15.useCallback)(async (productIds) => {
8182
8726
  setIsLoading(true);
8183
8727
  setError(null);
8184
8728
  try {
@@ -8192,24 +8736,24 @@ function useProducts(initialProductIds) {
8192
8736
  setIsLoading(false);
8193
8737
  }
8194
8738
  }, []);
8195
- (0, import_react13.useEffect)(() => {
8739
+ (0, import_react15.useEffect)(() => {
8196
8740
  if (initialProductIds && initialProductIds.length > 0) {
8197
8741
  void loadProducts(initialProductIds);
8198
8742
  }
8199
8743
  }, []);
8200
- const getProduct = (0, import_react13.useCallback)(
8744
+ const getProduct = (0, import_react15.useCallback)(
8201
8745
  (productId) => {
8202
8746
  return products.find((p) => p.productId === productId);
8203
8747
  },
8204
8748
  [products]
8205
8749
  );
8206
- const getFormattedProduct = (0, import_react13.useCallback)(
8750
+ const getFormattedProduct = (0, import_react15.useCallback)(
8207
8751
  (productId) => {
8208
8752
  return formattedProducts.find((p) => p.productId === productId);
8209
8753
  },
8210
8754
  [formattedProducts]
8211
8755
  );
8212
- const getPriceInfo = (0, import_react13.useCallback)(
8756
+ const getPriceInfo = (0, import_react15.useCallback)(
8213
8757
  (productId) => {
8214
8758
  const p = formattedProducts.find((fp) => fp.productId === productId);
8215
8759
  if (!p) return void 0;
@@ -8238,11 +8782,11 @@ function useProducts(initialProductIds) {
8238
8782
  }
8239
8783
 
8240
8784
  // src/hooks/usePurchase.ts
8241
- var import_react14 = require("react");
8785
+ var import_react16 = require("react");
8242
8786
  function usePurchase() {
8243
- const [state, setState] = (0, import_react14.useState)("idle");
8244
- const [error, setError] = (0, import_react14.useState)(null);
8245
- const purchase = (0, import_react14.useCallback)(async (productId) => {
8787
+ const [state, setState] = (0, import_react16.useState)("idle");
8788
+ const [error, setError] = (0, import_react16.useState)(null);
8789
+ const purchase = (0, import_react16.useCallback)(async (productId) => {
8246
8790
  setState("purchasing");
8247
8791
  setError(null);
8248
8792
  try {
@@ -8278,7 +8822,7 @@ function usePurchase() {
8278
8822
  return { status: "failed", error: purchaseError };
8279
8823
  }
8280
8824
  }, []);
8281
- const restore = (0, import_react14.useCallback)(async () => {
8825
+ const restore = (0, import_react16.useCallback)(async () => {
8282
8826
  setState("restoring");
8283
8827
  setError(null);
8284
8828
  try {
@@ -8309,14 +8853,14 @@ function usePurchase() {
8309
8853
  }
8310
8854
 
8311
8855
  // src/hooks/useSubscription.ts
8312
- var import_react15 = require("react");
8856
+ var import_react17 = require("react");
8313
8857
  init_SubscriptionManager();
8314
8858
  var EMPTY_ENTITLEMENTS = [];
8315
8859
  function useSubscription() {
8316
- const [status, setStatus] = (0, import_react15.useState)(null);
8317
- const [isLoading, setIsLoading] = (0, import_react15.useState)(true);
8318
- const [error, setError] = (0, import_react15.useState)(null);
8319
- const refresh = (0, import_react15.useCallback)(async () => {
8860
+ const [status, setStatus] = (0, import_react17.useState)(null);
8861
+ const [isLoading, setIsLoading] = (0, import_react17.useState)(true);
8862
+ const [error, setError] = (0, import_react17.useState)(null);
8863
+ const refresh = (0, import_react17.useCallback)(async () => {
8320
8864
  setIsLoading(true);
8321
8865
  setError(null);
8322
8866
  try {
@@ -8329,7 +8873,7 @@ function useSubscription() {
8329
8873
  setIsLoading(false);
8330
8874
  }
8331
8875
  }, []);
8332
- const restore = (0, import_react15.useCallback)(async () => {
8876
+ const restore = (0, import_react17.useCallback)(async () => {
8333
8877
  setIsLoading(true);
8334
8878
  setError(null);
8335
8879
  try {
@@ -8342,7 +8886,7 @@ function useSubscription() {
8342
8886
  setIsLoading(false);
8343
8887
  }
8344
8888
  }, []);
8345
- (0, import_react15.useEffect)(() => {
8889
+ (0, import_react17.useEffect)(() => {
8346
8890
  void subscriptionManager.getSubscriptionStatus().then((s) => {
8347
8891
  setStatus(s);
8348
8892
  setIsLoading(false);
@@ -8369,8 +8913,11 @@ function useSubscription() {
8369
8913
  };
8370
8914
  }
8371
8915
 
8916
+ // src/index.ts
8917
+ init_plan();
8918
+
8372
8919
  // src/PaywalloProvider.tsx
8373
- var import_react21 = require("react");
8920
+ var import_react23 = require("react");
8374
8921
  var import_react_native24 = require("react-native");
8375
8922
  init_paywall();
8376
8923
  init_paywall();
@@ -8378,7 +8925,7 @@ init_paywall();
8378
8925
  init_PaywalloClient();
8379
8926
 
8380
8927
  // src/hooks/useAppAutoEvents.ts
8381
- var import_react16 = require("react");
8928
+ var import_react18 = require("react");
8382
8929
  var FIRST_SEEN_KEY = "@panel:firstSeen";
8383
8930
  var DEBOUNCE_MS = 1e3;
8384
8931
  async function detectPlatform3() {
@@ -8408,8 +8955,8 @@ function useAppAutoEvents(config) {
8408
8955
  storageSet,
8409
8956
  debug
8410
8957
  } = config;
8411
- const didRunRef = (0, import_react16.useRef)(false);
8412
- (0, import_react16.useEffect)(() => {
8958
+ const didRunRef = (0, import_react18.useRef)(false);
8959
+ (0, import_react18.useEffect)(() => {
8413
8960
  if (!isInitialized || didRunRef.current) return;
8414
8961
  const timer = setTimeout(() => {
8415
8962
  if (didRunRef.current) return;
@@ -8454,7 +9001,7 @@ function useAppAutoEvents(config) {
8454
9001
  }
8455
9002
 
8456
9003
  // src/hooks/useCampaignPreload.ts
8457
- var import_react17 = require("react");
9004
+ var import_react19 = require("react");
8458
9005
  init_PaywalloClient();
8459
9006
  init_CampaignError();
8460
9007
 
@@ -8525,17 +9072,17 @@ function getCacheEntry(cache, placement) {
8525
9072
 
8526
9073
  // src/hooks/useCampaignPreload.ts
8527
9074
  function useCampaignPreload() {
8528
- const [preloadedWebView, setPreloadedWebView] = (0, import_react17.useState)(null);
8529
- const preloadedCampaignsRef = (0, import_react17.useRef)(/* @__PURE__ */ new Map());
8530
- const isPreloadValid = (0, import_react17.useCallback)(
9075
+ const [preloadedWebView, setPreloadedWebView] = (0, import_react19.useState)(null);
9076
+ const preloadedCampaignsRef = (0, import_react19.useRef)(/* @__PURE__ */ new Map());
9077
+ const isPreloadValid = (0, import_react19.useCallback)(
8531
9078
  (placement) => isCacheEntryValid(preloadedCampaignsRef.current.get(placement)),
8532
9079
  []
8533
9080
  );
8534
- const consumePreloadedCampaign = (0, import_react17.useCallback)(
9081
+ const consumePreloadedCampaign = (0, import_react19.useCallback)(
8535
9082
  (placement) => getCacheEntry(preloadedCampaignsRef.current, placement),
8536
9083
  []
8537
9084
  );
8538
- const preloadCampaign = (0, import_react17.useCallback)(
9085
+ const preloadCampaign = (0, import_react19.useCallback)(
8539
9086
  async (placement, context) => {
8540
9087
  try {
8541
9088
  const campaign = await PaywalloClient.getCampaign(placement, context);
@@ -8609,12 +9156,12 @@ init_SecureStorage();
8609
9156
  init_paywall();
8610
9157
 
8611
9158
  // src/hooks/useProductLoader.ts
8612
- var import_react18 = require("react");
9159
+ var import_react20 = require("react");
8613
9160
  init_PaywalloClient();
8614
9161
  function useProductLoader() {
8615
- const [products, setProducts] = (0, import_react18.useState)(/* @__PURE__ */ new Map());
8616
- const [isLoadingProducts, setIsLoadingProducts] = (0, import_react18.useState)(false);
8617
- const refreshProducts = (0, import_react18.useCallback)(async (productIds) => {
9162
+ const [products, setProducts] = (0, import_react20.useState)(/* @__PURE__ */ new Map());
9163
+ const [isLoadingProducts, setIsLoadingProducts] = (0, import_react20.useState)(false);
9164
+ const refreshProducts = (0, import_react20.useCallback)(async (productIds) => {
8618
9165
  setIsLoadingProducts(true);
8619
9166
  try {
8620
9167
  const iapService = getIAPService();
@@ -8632,12 +9179,12 @@ function useProductLoader() {
8632
9179
  }
8633
9180
 
8634
9181
  // src/hooks/usePurchaseOrchestrator.ts
8635
- var import_react19 = require("react");
9182
+ var import_react21 = require("react");
8636
9183
  init_PaywalloClient();
8637
9184
  init_paywall();
8638
9185
  function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, invalidateSubscriptionCache) {
8639
9186
  const { trackDismissed, trackProductSelected, trackPurchased } = usePaywallTracking();
8640
- const handlePreloadedPurchase = (0, import_react19.useCallback)(
9187
+ const handlePreloadedPurchase = (0, import_react21.useCallback)(
8641
9188
  async (productId) => {
8642
9189
  try {
8643
9190
  if (preloadedWebView) {
@@ -8675,7 +9222,7 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
8675
9222
  },
8676
9223
  [preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed, trackProductSelected, trackPurchased, invalidateSubscriptionCache]
8677
9224
  );
8678
- const handlePreloadedRestore = (0, import_react19.useCallback)(async () => {
9225
+ const handlePreloadedRestore = (0, import_react21.useCallback)(async () => {
8679
9226
  try {
8680
9227
  const transactions = await getIAPService().restore();
8681
9228
  if (transactions.length === 0) return;
@@ -8698,7 +9245,7 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
8698
9245
  }
8699
9246
 
8700
9247
  // src/hooks/useSubscriptionSync.ts
8701
- var import_react20 = require("react");
9248
+ var import_react22 = require("react");
8702
9249
  init_PaywalloClient();
8703
9250
  init_SubscriptionCache();
8704
9251
  var CACHE_TTL_MS2 = 5 * 60 * 1e3;
@@ -8765,14 +9312,14 @@ function isActiveCacheEntry(entry) {
8765
9312
  return isSubscriptionStillActive(entry.data);
8766
9313
  }
8767
9314
  function useSubscriptionSync() {
8768
- const [subscription, setSubscription] = (0, import_react20.useState)(null);
8769
- const cacheRef = (0, import_react20.useRef)(null);
8770
- const invalidateCache = (0, import_react20.useCallback)(() => {
9315
+ const [subscription, setSubscription] = (0, import_react22.useState)(null);
9316
+ const cacheRef = (0, import_react22.useRef)(null);
9317
+ const invalidateCache = (0, import_react22.useCallback)(() => {
8771
9318
  cacheRef.current = null;
8772
9319
  const distinctId = PaywalloClient.getDistinctId();
8773
9320
  if (distinctId) void subscriptionCache.invalidate(distinctId);
8774
9321
  }, []);
8775
- const fetchAndCache = (0, import_react20.useCallback)(async () => {
9322
+ const fetchAndCache = (0, import_react22.useCallback)(async () => {
8776
9323
  const apiClient = PaywalloClient.getApiClient();
8777
9324
  const distinctId = PaywalloClient.getDistinctId();
8778
9325
  if (!apiClient || !distinctId) return null;
@@ -8786,7 +9333,7 @@ function useSubscriptionSync() {
8786
9333
  void subscriptionCache.set(distinctId, toDomainResponse(status));
8787
9334
  return sub;
8788
9335
  }, []);
8789
- const hasActiveSubscription = (0, import_react20.useCallback)(async () => {
9336
+ const hasActiveSubscription = (0, import_react22.useCallback)(async () => {
8790
9337
  const apiClient = PaywalloClient.getApiClient();
8791
9338
  const distinctId = PaywalloClient.getDistinctId();
8792
9339
  if (!apiClient || !distinctId) return false;
@@ -8829,7 +9376,7 @@ function useSubscriptionSync() {
8829
9376
  return resolveOfflineFromCache(distinctId);
8830
9377
  }
8831
9378
  }, []);
8832
- const getSubscription = (0, import_react20.useCallback)(async () => {
9379
+ const getSubscription = (0, import_react22.useCallback)(async () => {
8833
9380
  const apiClient = PaywalloClient.getApiClient();
8834
9381
  const distinctId = PaywalloClient.getDistinctId();
8835
9382
  if (!apiClient || !distinctId) return null;
@@ -8856,20 +9403,20 @@ init_ClientError();
8856
9403
  init_localization();
8857
9404
  var import_jsx_runtime3 = require("react/jsx-runtime");
8858
9405
  function PaywalloProvider({ children, config }) {
8859
- const [isInitialized, setIsInitialized] = (0, import_react21.useState)(false);
8860
- const [initError, setInitError] = (0, import_react21.useState)(null);
8861
- const [distinctId, setDistinctId] = (0, import_react21.useState)(null);
8862
- const [activePaywall, setActivePaywall] = (0, import_react21.useState)(null);
9406
+ const [isInitialized, setIsInitialized] = (0, import_react23.useState)(false);
9407
+ const [initError, setInitError] = (0, import_react23.useState)(null);
9408
+ const [distinctId, setDistinctId] = (0, import_react23.useState)(null);
9409
+ const [activePaywall, setActivePaywall] = (0, import_react23.useState)(null);
8863
9410
  const { products, isLoadingProducts, refreshProducts } = useProductLoader();
8864
9411
  const { subscription, hasActiveSubscription, getSubscription, invalidateCache } = useSubscriptionSync();
8865
9412
  const { preloadedWebView, setPreloadedWebView, preloadCampaign, consumePreloadedCampaign, isPreloadValid } = useCampaignPreload();
8866
- const clearPreloadedWebView = (0, import_react21.useCallback)(() => setPreloadedWebView(null), [setPreloadedWebView]);
8867
- const autoPreloadTriggeredRef = (0, import_react21.useRef)(false);
8868
- const preloadedWebViewRef = (0, import_react21.useRef)(preloadedWebView);
8869
- const pollTimerRef = (0, import_react21.useRef)(null);
8870
- const pollCancelledRef = (0, import_react21.useRef)(false);
8871
- const secureStorageRef = (0, import_react21.useRef)(new SecureStorage(config.debug));
8872
- (0, import_react21.useEffect)(() => {
9413
+ const clearPreloadedWebView = (0, import_react23.useCallback)(() => setPreloadedWebView(null), [setPreloadedWebView]);
9414
+ const autoPreloadTriggeredRef = (0, import_react23.useRef)(false);
9415
+ const preloadedWebViewRef = (0, import_react23.useRef)(preloadedWebView);
9416
+ const pollTimerRef = (0, import_react23.useRef)(null);
9417
+ const pollCancelledRef = (0, import_react23.useRef)(false);
9418
+ const secureStorageRef = (0, import_react23.useRef)(new SecureStorage(config.debug));
9419
+ (0, import_react23.useEffect)(() => {
8873
9420
  preloadedWebViewRef.current = preloadedWebView;
8874
9421
  }, [preloadedWebView]);
8875
9422
  useAppAutoEvents({
@@ -8885,20 +9432,20 @@ function PaywalloProvider({ children, config }) {
8885
9432
  storageSet: (key, value) => secureStorageRef.current.set(key, value),
8886
9433
  debug: config.debug
8887
9434
  });
8888
- const triggerRepreload = (0, import_react21.useCallback)((placement) => {
9435
+ const triggerRepreload = (0, import_react23.useCallback)((placement) => {
8889
9436
  void PaywalloClient.preloadCampaign(placement).catch(() => {
8890
9437
  });
8891
9438
  }, []);
8892
9439
  const { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady: baseHandlePreloadedWebViewReady, handlePreloadedClose: baseHandlePreloadedClose, setShowingPreloadedWebView, presentedAtRef } = usePaywallPresenter(preloadedWebView, setPreloadedWebView);
8893
- const SCREEN_HEIGHT = (0, import_react21.useMemo)(() => import_react_native24.Dimensions.get("window").height, []);
8894
- const slideAnim = (0, import_react21.useRef)(new import_react_native24.Animated.Value(SCREEN_HEIGHT)).current;
8895
- const handlePreloadedWebViewReady = (0, import_react21.useCallback)(() => {
9440
+ const SCREEN_HEIGHT = (0, import_react23.useMemo)(() => import_react_native24.Dimensions.get("window").height, []);
9441
+ const slideAnim = (0, import_react23.useRef)(new import_react_native24.Animated.Value(SCREEN_HEIGHT)).current;
9442
+ const handlePreloadedWebViewReady = (0, import_react23.useCallback)(() => {
8896
9443
  if (PaywalloClient.getConfig()?.debug) {
8897
9444
  console.log(`[Paywallo WEBVIEW] loaded \u2014 placement: ${preloadedWebView?.placement}`);
8898
9445
  }
8899
9446
  baseHandlePreloadedWebViewReady();
8900
9447
  }, [preloadedWebView?.placement, baseHandlePreloadedWebViewReady]);
8901
- const handlePreloadedClose = (0, import_react21.useCallback)(() => {
9448
+ const handlePreloadedClose = (0, import_react23.useCallback)(() => {
8902
9449
  const placement = preloadedWebView?.placement;
8903
9450
  import_react_native24.Animated.timing(slideAnim, { toValue: SCREEN_HEIGHT, duration: 200, easing: import_react_native24.Easing.in(import_react_native24.Easing.cubic), useNativeDriver: true }).start(() => {
8904
9451
  baseHandlePreloadedClose();
@@ -8913,8 +9460,8 @@ function PaywalloProvider({ children, config }) {
8913
9460
  presentedAtRef,
8914
9461
  invalidateCache
8915
9462
  );
8916
- const [isPreloadPurchasing, setIsPreloadPurchasing] = (0, import_react21.useState)(false);
8917
- const handlePreloadedPurchase = (0, import_react21.useCallback)(async (productId) => {
9463
+ const [isPreloadPurchasing, setIsPreloadPurchasing] = (0, import_react23.useState)(false);
9464
+ const handlePreloadedPurchase = (0, import_react23.useCallback)(async (productId) => {
8918
9465
  setIsPreloadPurchasing(true);
8919
9466
  try {
8920
9467
  await rawPreloadedPurchase(productId);
@@ -8922,8 +9469,8 @@ function PaywalloProvider({ children, config }) {
8922
9469
  setIsPreloadPurchasing(false);
8923
9470
  }
8924
9471
  }, [rawPreloadedPurchase]);
8925
- const configRef = (0, import_react21.useRef)(config);
8926
- (0, import_react21.useEffect)(() => {
9472
+ const configRef = (0, import_react23.useRef)(config);
9473
+ (0, import_react23.useEffect)(() => {
8927
9474
  let mounted = true;
8928
9475
  initLocalization({ detectDevice: true });
8929
9476
  PaywalloClient.init(configRef.current).then(async () => {
@@ -8944,14 +9491,14 @@ function PaywalloProvider({ children, config }) {
8944
9491
  mounted = false;
8945
9492
  };
8946
9493
  }, [preloadCampaign]);
8947
- const getPaywallConfig = (0, import_react21.useCallback)(async (p) => {
9494
+ const getPaywallConfig = (0, import_react23.useCallback)(async (p) => {
8948
9495
  const api = PaywalloClient.getApiClient();
8949
9496
  return api ? api.getPaywall(p) : null;
8950
9497
  }, []);
8951
- const presentPaywall = (0, import_react21.useCallback)((placement) => {
9498
+ const presentPaywall = (0, import_react23.useCallback)((placement) => {
8952
9499
  return new Promise((resolve) => setActivePaywall({ placement, resolver: resolve }));
8953
9500
  }, []);
8954
- const trackCampaignImpression = (0, import_react21.useCallback)((placement, variantKey, campaignId) => {
9501
+ const trackCampaignImpression = (0, import_react23.useCallback)((placement, variantKey, campaignId) => {
8955
9502
  const api = PaywalloClient.getApiClient();
8956
9503
  if (!api) return;
8957
9504
  const sessionId = PaywalloClient.getSessionId();
@@ -8965,7 +9512,7 @@ function PaywalloProvider({ children, config }) {
8965
9512
  }).catch(() => {
8966
9513
  });
8967
9514
  }, []);
8968
- const presentCampaign = (0, import_react21.useCallback)(
9515
+ const presentCampaign = (0, import_react23.useCallback)(
8969
9516
  async (placement, context) => {
8970
9517
  try {
8971
9518
  if (preloadedWebView?.placement === placement && preloadedWebView.loaded && isPreloadValid(placement)) {
@@ -9039,7 +9586,7 @@ function PaywalloProvider({ children, config }) {
9039
9586
  },
9040
9587
  [preloadedWebView, isPreloadValid, consumePreloadedCampaign, presentPreloadedWebView, trackCampaignImpression]
9041
9588
  );
9042
- const restorePurchases = (0, import_react21.useCallback)(async () => {
9589
+ const restorePurchases = (0, import_react23.useCallback)(async () => {
9043
9590
  invalidateCache();
9044
9591
  try {
9045
9592
  const txs = await getIAPService().restore();
@@ -9048,13 +9595,13 @@ function PaywalloProvider({ children, config }) {
9048
9595
  return { success: false, restoredProducts: [], error: error instanceof Error ? error : new PurchaseError(PURCHASE_ERROR_CODES.RESTORE_FAILED, String(error)) };
9049
9596
  }
9050
9597
  }, [invalidateCache]);
9051
- const handlePaywallResult = (0, import_react21.useCallback)((result) => {
9598
+ const handlePaywallResult = (0, import_react23.useCallback)((result) => {
9052
9599
  if (activePaywall) {
9053
9600
  activePaywall.resolver(result);
9054
9601
  setActivePaywall(null);
9055
9602
  }
9056
9603
  }, [activePaywall]);
9057
- const bridgePaywallPresenter = (0, import_react21.useCallback)((placement) => {
9604
+ const bridgePaywallPresenter = (0, import_react23.useCallback)((placement) => {
9058
9605
  if (PaywalloClient.getConfig()?.debug) {
9059
9606
  console.log(`[Paywallo PRESENT] called \u2014 preloaded: ${preloadedWebViewRef.current?.loaded === true} placement: ${placement}`);
9060
9607
  }
@@ -9083,7 +9630,7 @@ function PaywalloProvider({ children, config }) {
9083
9630
  }
9084
9631
  return presentCampaign(placement);
9085
9632
  }, [presentPreloadedWebView, presentCampaign]);
9086
- (0, import_react21.useEffect)(() => {
9633
+ (0, import_react23.useEffect)(() => {
9087
9634
  if (!isInitialized) return;
9088
9635
  const onEmergency = async (id) => {
9089
9636
  try {
@@ -9110,9 +9657,9 @@ function PaywalloProvider({ children, config }) {
9110
9657
  };
9111
9658
  }, [isInitialized, bridgePaywallPresenter, getSubscription, hasActiveSubscription, restorePurchases]);
9112
9659
  const isReady = isInitialized && !isLoadingProducts;
9113
- const noOp = (0, import_react21.useCallback)(() => {
9660
+ const noOp = (0, import_react23.useCallback)(() => {
9114
9661
  }, []);
9115
- const contextValue = (0, import_react21.useMemo)(() => ({
9662
+ const contextValue = (0, import_react23.useMemo)(() => ({
9116
9663
  config: PaywalloClient.getConfig(),
9117
9664
  isInitialized,
9118
9665
  isReady,
@@ -9146,7 +9693,7 @@ function PaywalloProvider({ children, config }) {
9146
9693
  getPaywallConfig,
9147
9694
  refreshProducts
9148
9695
  ]);
9149
- (0, import_react21.useLayoutEffect)(() => {
9696
+ (0, import_react23.useLayoutEffect)(() => {
9150
9697
  if (showingPreloadedWebView) {
9151
9698
  slideAnim.setValue(SCREEN_HEIGHT);
9152
9699
  import_react_native24.Animated.timing(slideAnim, { toValue: 0, duration: 200, easing: import_react_native24.Easing.out(import_react_native24.Easing.cubic), useNativeDriver: true }).start();
@@ -9353,6 +9900,8 @@ var index_default = Paywallo;
9353
9900
  PaywalloContext,
9354
9901
  PaywalloError,
9355
9902
  PaywalloProvider,
9903
+ PlanCache,
9904
+ PlanService,
9356
9905
  PurchaseError,
9357
9906
  SESSION_ERROR_CODES,
9358
9907
  SessionError,
@@ -9374,6 +9923,8 @@ var index_default = Paywallo;
9374
9923
  offlineQueue,
9375
9924
  onboardingManager,
9376
9925
  parseVariables,
9926
+ planCache,
9927
+ planService,
9377
9928
  queueProcessor,
9378
9929
  resolveText,
9379
9930
  sessionManager,
@@ -9383,6 +9934,8 @@ var index_default = Paywallo;
9383
9934
  useOnboarding,
9384
9935
  usePaywallContext,
9385
9936
  usePaywallo,
9937
+ usePlanPurchase,
9938
+ usePlans,
9386
9939
  useProducts,
9387
9940
  usePurchase,
9388
9941
  useSubscription