@virex-tech/paywallo-sdk 2.1.0 → 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":
@@ -5038,8 +5208,8 @@ var init_utils = __esm({
5038
5208
  // src/core/version.ts
5039
5209
  function resolveVersion() {
5040
5210
  try {
5041
- if ("2.1.0") {
5042
- return "2.1.0";
5211
+ if ("2.2.1") {
5212
+ return "2.2.1";
5043
5213
  }
5044
5214
  } catch {
5045
5215
  }
@@ -5121,7 +5291,7 @@ var init_TokenRegistration = __esm({
5121
5291
  init_utils();
5122
5292
  init_version();
5123
5293
  FCM_TOKEN_KEY = "@panel:push_token";
5124
- TOKEN_ENDPOINT = "/push-tokens";
5294
+ TOKEN_ENDPOINT = "/sdk/push-tokens";
5125
5295
  }
5126
5296
  });
5127
5297
 
@@ -5413,58 +5583,400 @@ var init_NotificationsManager = __esm({
5413
5583
  this.currentToken = null;
5414
5584
  }
5415
5585
  };
5416
- 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();
5417
5933
  }
5418
5934
  });
5419
5935
 
5420
- // src/domains/notifications/index.ts
5421
- var init_notifications = __esm({
5422
- "src/domains/notifications/index.ts"() {
5936
+ // src/domains/onboarding/index.ts
5937
+ var init_onboarding = __esm({
5938
+ "src/domains/onboarding/index.ts"() {
5423
5939
  "use strict";
5424
- init_NotificationsManager();
5425
- init_NativePushBridge();
5426
- init_NotificationsError();
5940
+ init_OnboardingManager();
5941
+ init_OnboardingError();
5427
5942
  }
5428
5943
  });
5429
5944
 
5430
- // src/domains/session/index.ts
5431
- var init_session = __esm({
5432
- "src/domains/session/index.ts"() {
5433
- "use strict";
5434
- init_SessionManager();
5435
- 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();
5436
5974
  }
5437
- });
5438
-
5439
- // src/core/PaywalloState.ts
5440
- function createPaywalloState() {
5441
- return {
5442
- config: null,
5443
- apiClient: null,
5444
- initPromise: null,
5445
- readyPromise: null,
5446
- resolveReady: null,
5447
- paywallPresenter: null,
5448
- campaignPresenter: null,
5449
- subscriptionGetter: null,
5450
- activeChecker: null,
5451
- restoreHandler: null,
5452
- lastOnboardingStepTimestamp: null,
5453
- pendingConfig: null,
5454
- networkCleanup: null,
5455
- initAttempts: 0,
5456
- autoPreloadedPlacement: null,
5457
- autoPreloadPlacementPromise: null,
5458
- sessionFlagsMap: /* @__PURE__ */ new Map(),
5459
- pendingIdentifies: []
5460
- };
5461
5975
  }
5462
- var MAX_INIT_RETRIES, RETRY_DELAYS;
5463
- var init_PaywalloState = __esm({
5464
- "src/core/PaywalloState.ts"() {
5976
+ var init_paywallHeartbeatRecovery = __esm({
5977
+ "src/domains/paywall/paywallHeartbeatRecovery.ts"() {
5465
5978
  "use strict";
5466
- MAX_INIT_RETRIES = 3;
5467
- RETRY_DELAYS = [2e3, 5e3, 1e4];
5979
+ init_paywallHeartbeat();
5468
5980
  }
5469
5981
  });
5470
5982
 
@@ -5472,7 +5984,7 @@ var init_PaywalloState = __esm({
5472
5984
  function keyFor(distinctId) {
5473
5985
  return `${CACHE_KEY_PREFIX}${distinctId}`;
5474
5986
  }
5475
- 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;
5476
5988
  var init_SubscriptionCache = __esm({
5477
5989
  "src/domains/subscription/SubscriptionCache.ts"() {
5478
5990
  "use strict";
@@ -5480,9 +5992,9 @@ var init_SubscriptionCache = __esm({
5480
5992
  LEGACY_CACHE_KEY = "@panel:subscription_cache";
5481
5993
  CACHE_KEY_PREFIX = "subscription_cache:";
5482
5994
  CACHE_INDEX_KEY = "subscription_cache:__index__";
5483
- DEFAULT_TTL = 24 * 60 * 60 * 1e3;
5995
+ DEFAULT_TTL2 = 24 * 60 * 60 * 1e3;
5484
5996
  SubscriptionCache = class {
5485
- constructor(ttl = DEFAULT_TTL) {
5997
+ constructor(ttl = DEFAULT_TTL2) {
5486
5998
  this.memoryCache = /* @__PURE__ */ new Map();
5487
5999
  this.legacyCleanupDone = false;
5488
6000
  this.ttl = ttl;
@@ -5689,7 +6201,7 @@ var init_SubscriptionManager = __esm({
5689
6201
  if (!this.config) {
5690
6202
  return this.getEmptyStatus();
5691
6203
  }
5692
- 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}`);
5693
6205
  if (this.userId) {
5694
6206
  url.searchParams.set("distinctId", this.userId);
5695
6207
  }
@@ -5929,7 +6441,7 @@ async function getSubscriptionStatus(client, baseUrl, appKey, distinctId) {
5929
6441
  }
5930
6442
  async function getEmergencyPaywall(client) {
5931
6443
  try {
5932
- const response = await client.get("/emergency-paywall");
6444
+ const response = await client.get("/sdk/emergency-paywall");
5933
6445
  if (response.status === 404) return { enabled: false };
5934
6446
  return response.data;
5935
6447
  } catch {
@@ -6546,7 +7058,7 @@ var init_EventBatcher = __esm({
6546
7058
  BATCH_MAX_SIZE = 25;
6547
7059
  BATCH_FLUSH_MS = 1e4;
6548
7060
  EVENT_NAME_REGEX = /^(\$[a-zA-Z0-9_]+|[a-z][a-z0-9_]*)$/;
6549
- V2_BATCH_ENDPOINT = "/ingest/batch";
7061
+ V2_BATCH_ENDPOINT = "/sdk/ingest/batch";
6550
7062
  V1_BATCH_ENDPOINT = "/sdk/events/batch";
6551
7063
  V1_SINGLE_ENDPOINT = "/sdk/events";
6552
7064
  EventBatcher = class {
@@ -6910,7 +7422,7 @@ var init_ApiClient = __esm({
6910
7422
  dispose() {
6911
7423
  this.batcher.dispose();
6912
7424
  }
6913
- async identify(distinctId, properties, email, deviceId) {
7425
+ async identify(distinctId, properties, email, deviceId, pii) {
6914
7426
  await postWithQueue(
6915
7427
  {
6916
7428
  isOfflineQueueEnabled: () => this.offlineQueueEnabled,
@@ -6918,8 +7430,19 @@ var init_ApiClient = __esm({
6918
7430
  isDebug: () => this.debug,
6919
7431
  post: (path, body, skipRetry) => this.post(path, body, skipRetry)
6920
7432
  },
6921
- "/identify",
6922
- { 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
+ },
6923
7446
  "identify"
6924
7447
  );
6925
7448
  }
@@ -6946,246 +7469,85 @@ var init_ApiClient = __esm({
6946
7469
  this.cache.setPaywall(placement, res.data);
6947
7470
  return res.data;
6948
7471
  } catch {
6949
- const stale = this.cache.getStalePaywall(placement);
6950
- return stale !== void 0 ? stale : null;
6951
- }
6952
- }
6953
- async getEmergencyPaywall() {
6954
- return getEmergencyPaywall(this);
6955
- }
6956
- async getCampaign(placement, distinctId, context) {
6957
- const key = `${placement}:${distinctId}`;
6958
- const hit = this.cache.getCampaign(key);
6959
- if (hit !== void 0) return hit;
6960
- try {
6961
- const result = await getCampaignData(this, this.baseUrl, placement, distinctId, context);
6962
- if (!result) {
6963
- this.cache.setCampaign(key, null, this.cache.nullTTL);
6964
- return null;
6965
- }
6966
- this.cache.setCampaign(key, result);
6967
- return result;
6968
- } catch {
6969
- const stale = this.cache.getStaleCampaign(key);
6970
- return stale !== void 0 ? stale : null;
6971
- }
6972
- }
6973
- async getPrimaryCampaign(distinctId) {
6974
- const key = `primary:${distinctId}`;
6975
- const hit = this.cache.getCampaign(key);
6976
- if (hit !== void 0) return hit;
6977
- try {
6978
- const res = await this.get(`/campaigns/public/primary?distinctId=${encodeURIComponent(distinctId)}`);
6979
- if (res.status === 404) {
6980
- this.cache.setCampaign(key, null, this.cache.nullTTL);
6981
- return null;
6982
- }
6983
- if (!res.ok) {
6984
- const stale = this.cache.getStaleCampaign(key);
6985
- return stale !== void 0 ? stale : null;
6986
- }
6987
- this.cache.setCampaign(key, res.data);
6988
- return res.data;
6989
- } catch {
6990
- const stale = this.cache.getStaleCampaign(key);
6991
- return stale !== void 0 ? stale : null;
6992
- }
6993
- }
6994
- async validatePurchase(platform, receipt, productId, transactionId, priceLocal, currency, country, distinctId, paywallPlacement, variantKey) {
6995
- return validatePurchase(this, this.appKey, platform, receipt, productId, transactionId, priceLocal, currency, country, distinctId, paywallPlacement, variantKey);
6996
- }
6997
- async getSubscriptionStatus(distinctId) {
6998
- return getSubscriptionStatus(this, this.baseUrl, this.appKey, distinctId);
6999
- }
7000
- async evaluateFlags(keys, distinctId) {
7001
- return evaluateFlags(this.flagDeps(), keys, distinctId);
7002
- }
7003
- async getConditionalFlag(flagKey, context) {
7004
- return getConditionalFlag(this.flagDeps(), flagKey, context);
7005
- }
7006
- async getVariantFromCache(flagKey, distinctId) {
7007
- return getVariantFromCache(this.flagDeps(), flagKey, distinctId);
7008
- }
7009
- flagDeps() {
7010
- return {
7011
- get: (path) => this.get(path),
7012
- httpClient: this.httpClient,
7013
- cache: this.cache,
7014
- appKey: this.appKey,
7015
- baseUrl: this.baseUrl
7016
- };
7017
- }
7018
- };
7019
- }
7020
- });
7021
-
7022
- // src/domains/onboarding/OnboardingError.ts
7023
- var OnboardingError, ONBOARDING_ERROR_CODES;
7024
- var init_OnboardingError = __esm({
7025
- "src/domains/onboarding/OnboardingError.ts"() {
7026
- "use strict";
7027
- init_PaywalloError();
7028
- OnboardingError = class extends PaywalloError {
7029
- constructor(code, message) {
7030
- super("onboarding", code, message);
7031
- this.name = "OnboardingError";
7032
- }
7033
- };
7034
- ONBOARDING_ERROR_CODES = {
7035
- NOT_INITIALIZED: "ONBOARDING_NOT_INITIALIZED",
7036
- INVALID_STEP_NAME: "ONBOARDING_INVALID_STEP_NAME"
7037
- };
7038
- }
7039
- });
7040
-
7041
- // src/domains/onboarding/OnboardingManager.ts
7042
- var OnboardingManager, onboardingManager;
7043
- var init_OnboardingManager = __esm({
7044
- "src/domains/onboarding/OnboardingManager.ts"() {
7045
- "use strict";
7046
- init_OnboardingError();
7047
- OnboardingManager = class {
7048
- constructor() {
7049
- this.pipeline = null;
7050
- this.debug = false;
7051
- this.lastStep = null;
7052
- this.finished = false;
7053
- }
7054
- /**
7055
- * Injects runtime dependencies. Called by PaywalloClient during init.
7056
- */
7057
- injectDeps(config) {
7058
- this.pipeline = config.pipeline;
7059
- this.debug = config.debug ?? false;
7060
- }
7061
- /**
7062
- * Tracks an onboarding step view.
7063
- * Saves the step name internally for implicit drop detection on the backend.
7064
- *
7065
- * @param stepName - Unique name for this onboarding step.
7066
- */
7067
- async step(stepName) {
7068
- this.assertConfig();
7069
- this.validateStepName(stepName, "step");
7070
- this.lastStep = stepName;
7071
- const payload = {
7072
- family: "onboarding",
7073
- type: "step",
7074
- ...{ step_name: stepName }
7075
- };
7076
- return this.emit("onboarding", payload, "step");
7077
- }
7078
- /**
7079
- * Tracks successful completion of the onboarding flow.
7080
- * Marks the flow as finished.
7081
- */
7082
- async complete() {
7083
- this.assertConfig();
7084
- this.finished = true;
7085
- return this.emit("onboarding", { family: "onboarding", type: "complete" }, "complete");
7086
- }
7087
- /**
7088
- * Tracks an explicit drop (abandonment) at the given step.
7089
- * Marks the flow as finished.
7090
- *
7091
- * @param stepName - The step name where the user dropped off.
7092
- */
7093
- async drop(stepName) {
7094
- this.assertConfig();
7095
- this.validateStepName(stepName, "drop");
7096
- this.finished = true;
7097
- const payload = {
7098
- family: "onboarding",
7099
- type: "drop",
7100
- ...{ step_name: stepName }
7101
- };
7102
- return this.emit("onboarding", payload, "drop");
7103
- }
7104
- /** Returns the last step name seen, or null if no step was tracked yet. */
7105
- getLastStep() {
7106
- return this.lastStep;
7107
- }
7108
- /** Returns whether the flow has been marked as finished (complete or drop). */
7109
- isFinished() {
7110
- return this.finished;
7111
- }
7112
- async emit(eventName, properties, logLabel) {
7113
- const pipeline = this.pipeline;
7114
- const distinctId = pipeline.getDistinctId();
7115
- if (!distinctId) {
7116
- if (this.debug) console.log(`[Paywallo:Onboarding] skipped (no distinctId): ${logLabel}`);
7117
- return;
7472
+ const stale = this.cache.getStalePaywall(placement);
7473
+ return stale !== void 0 ? stale : null;
7118
7474
  }
7119
- await pipeline.trackEvent(eventName, distinctId, properties);
7120
- if (this.debug) {
7121
- 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;
7122
7494
  }
7123
7495
  }
7124
- assertConfig() {
7125
- if (!this.pipeline) {
7126
- throw new OnboardingError(
7127
- ONBOARDING_ERROR_CODES.NOT_INITIALIZED,
7128
- "OnboardingManager not initialized. Call Paywallo.init() first."
7129
- );
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 [];
7130
7503
  }
7131
7504
  }
7132
- validateStepName(stepName, method) {
7133
- if (typeof stepName !== "string" || stepName.trim().length === 0) {
7134
- throw new OnboardingError(
7135
- ONBOARDING_ERROR_CODES.INVALID_STEP_NAME,
7136
- `OnboardingManager.${method}(): stepName must be a non-empty string.`
7137
- );
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;
7138
7524
  }
7139
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
+ }
7140
7550
  };
7141
- onboardingManager = new OnboardingManager();
7142
- }
7143
- });
7144
-
7145
- // src/domains/onboarding/index.ts
7146
- var init_onboarding = __esm({
7147
- "src/domains/onboarding/index.ts"() {
7148
- "use strict";
7149
- init_OnboardingManager();
7150
- init_OnboardingError();
7151
- }
7152
- });
7153
-
7154
- // src/domains/paywall/paywallHeartbeatRecovery.ts
7155
- async function recoverPaywallHeartbeat(apiClient, distinctId, sessionId, debug) {
7156
- const snapshot = await readPersistedHeartbeat();
7157
- if (!snapshot) return false;
7158
- try {
7159
- const closedAt = new Date(snapshot.lastSeen);
7160
- const durationS = Math.max(0, Math.round((snapshot.lastSeen - snapshot.presentedAt) / 1e3));
7161
- await apiClient.trackEvent("paywall", distinctId, {
7162
- type: "closed",
7163
- paywall_id: snapshot.paywallId,
7164
- placement: snapshot.placement,
7165
- closed_at: closedAt.toISOString(),
7166
- duration_s: durationS,
7167
- close_reason: "error",
7168
- ...snapshot.variantId && { variant_id: snapshot.variantId },
7169
- ...snapshot.variantKey && { variant_key: snapshot.variantKey },
7170
- ...snapshot.campaignId && { campaign_id: snapshot.campaignId },
7171
- ...sessionId && { sessionId }
7172
- });
7173
- if (debug) console.log("[Paywallo HEARTBEAT] recovered orphan paywall close", {
7174
- paywallId: snapshot.paywallId,
7175
- durationS
7176
- });
7177
- return true;
7178
- } catch (err) {
7179
- if (debug) console.log("[Paywallo HEARTBEAT] recovery failed", err);
7180
- return false;
7181
- } finally {
7182
- await clearPersistedHeartbeat();
7183
- }
7184
- }
7185
- var init_paywallHeartbeatRecovery = __esm({
7186
- "src/domains/paywall/paywallHeartbeatRecovery.ts"() {
7187
- "use strict";
7188
- init_paywallHeartbeat();
7189
7551
  }
7190
7552
  });
7191
7553
 
@@ -7229,6 +7591,8 @@ var init_eventPipelineBridge = __esm({
7229
7591
 
7230
7592
  // src/core/PaywalloInitHelpers.ts
7231
7593
  function setupNotifications(apiClient, config, environment, eventPipeline) {
7594
+ if (config.notifications === false) return;
7595
+ if (config.notifications === void 0 && !isNativePushAvailable()) return;
7232
7596
  try {
7233
7597
  notificationsManager.injectDeps({
7234
7598
  httpClient: apiClient.getHttpClient(),
@@ -7270,6 +7634,10 @@ function setupAutoPreload(state, apiClient, config, distinctId) {
7270
7634
  return null;
7271
7635
  }).catch(() => null);
7272
7636
  }
7637
+ function startBatchPreload(apiClient, debug) {
7638
+ void campaignGateService.preloadAllActive(apiClient, debug).catch(() => {
7639
+ });
7640
+ }
7273
7641
  var init_PaywalloInitHelpers = __esm({
7274
7642
  "src/core/PaywalloInitHelpers.ts"() {
7275
7643
  "use strict";
@@ -7277,6 +7645,7 @@ var init_PaywalloInitHelpers = __esm({
7277
7645
  init_identity();
7278
7646
  init_notifications();
7279
7647
  init_SecureStorage();
7648
+ init_NativePushBridge();
7280
7649
  init_constants();
7281
7650
  }
7282
7651
  });
@@ -7390,6 +7759,7 @@ async function doInit(state, config) {
7390
7759
  cacheTTL: config.subscriptionCacheTTL,
7391
7760
  debug: config.debug
7392
7761
  });
7762
+ planService.init({ apiClient, debug: config.debug ?? false });
7393
7763
  await identityManager.initialize(apiClient, config.debug);
7394
7764
  if (state.pendingIdentifies.length > 0) {
7395
7765
  for (const pending of state.pendingIdentifies) {
@@ -7412,6 +7782,9 @@ async function doInit(state, config) {
7412
7782
  } catch (err) {
7413
7783
  if (config.debug) console.log("[Paywallo ATTR] start error", err);
7414
7784
  }
7785
+ void nativeDeviceInfo.getDeviceInfo().catch(() => null);
7786
+ void advertisingIdManager.collect(false).catch(() => null);
7787
+ void metaBridge.getAnonymousID().catch(() => null);
7415
7788
  apiClient.setEventContextProvider(() => {
7416
7789
  const attr = attributionTracker.get();
7417
7790
  const attrEntries = attr ? [
@@ -7426,11 +7799,35 @@ async function doInit(state, config) {
7426
7799
  ["referrer", attr.referrer]
7427
7800
  ].filter((pair) => pair[1] !== void 0 && pair[1] !== "") : [];
7428
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
+ }
7429
7820
  return {
7430
7821
  distinct_id: identityManager.getDistinctId() || void 0,
7431
7822
  device_id: identityManager.getDeviceId() ?? void 0,
7432
7823
  session_id: sessionManager.getSessionId() ?? void 0,
7433
- ...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 }
7434
7831
  };
7435
7832
  });
7436
7833
  await sessionManager.initialize(
@@ -7464,7 +7861,7 @@ async function doInit(state, config) {
7464
7861
  state.resolveReady();
7465
7862
  state.resolveReady = null;
7466
7863
  }
7467
- if (config.debug) console.warn("[Paywallo INIT] complete");
7864
+ console.warn("[Paywallo] initialized successfully");
7468
7865
  if (distinctId) {
7469
7866
  apiClient.getSubscriptionStatus(distinctId).then(() => {
7470
7867
  if (config.debug) console.log("[Paywallo SUB] preloaded on boot");
@@ -7475,26 +7872,30 @@ async function doInit(state, config) {
7475
7872
  });
7476
7873
  }
7477
7874
  setupAutoPreload(state, apiClient, config, distinctId);
7875
+ startBatchPreload(apiClient, config.debug);
7478
7876
  }
7479
7877
  var init_PaywalloInitializer = __esm({
7480
7878
  "src/core/PaywalloInitializer.ts"() {
7481
7879
  "use strict";
7482
- init_SubscriptionManager();
7483
- init_ApiClient();
7484
7880
  init_identity();
7881
+ init_plan();
7882
+ init_AdvertisingIdManager();
7485
7883
  init_DeepLinkAttributionCapture();
7486
7884
  init_InstallTracker();
7487
- init_network();
7488
- init_queue();
7489
- init_session();
7885
+ init_MetaBridge();
7490
7886
  init_onboarding();
7491
- init_NativeStorage();
7492
- init_NativeDeviceInfo();
7493
7887
  init_paywallHeartbeatRecovery();
7888
+ init_session();
7889
+ init_SubscriptionManager();
7890
+ init_NativeDeviceInfo();
7891
+ init_ApiClient();
7892
+ init_constants();
7494
7893
  init_eventPipelineBridge();
7495
- init_PaywalloState();
7894
+ init_network();
7496
7895
  init_PaywalloInitHelpers();
7497
- init_constants();
7896
+ init_PaywalloState();
7897
+ init_queue();
7898
+ init_NativeStorage();
7498
7899
  }
7499
7900
  });
7500
7901
 
@@ -7748,6 +8149,7 @@ var init_PaywalloClient = __esm({
7748
8149
  init_PaywalloAnalytics();
7749
8150
  init_PaywalloSubscription();
7750
8151
  init_PaywalloFlags();
8152
+ init_plan();
7751
8153
  PaywalloClientClass = class {
7752
8154
  constructor() {
7753
8155
  this.state = createPaywalloState();
@@ -7999,6 +8401,14 @@ var init_PaywalloClient = __esm({
7999
8401
  async processOfflineQueue() {
8000
8402
  return queueProcessor.processQueue();
8001
8403
  }
8404
+ async getPlans() {
8405
+ this.ensureInitialized();
8406
+ return planService.getAllPlans();
8407
+ }
8408
+ async getCurrentPlan() {
8409
+ this.ensureInitialized();
8410
+ return planService.getCurrentPlan();
8411
+ }
8002
8412
  getApiClientOrThrow() {
8003
8413
  if (!this.state.config || !this.state.apiClient) throw new ClientError(CLIENT_ERROR_CODES.NOT_INITIALIZED, "SDK not initialized. Call Paywallo.init() first");
8004
8414
  return this.state.apiClient;
@@ -8041,6 +8451,8 @@ __export(index_exports, {
8041
8451
  PaywalloContext: () => PaywalloContext,
8042
8452
  PaywalloError: () => PaywalloError,
8043
8453
  PaywalloProvider: () => PaywalloProvider,
8454
+ PlanCache: () => PlanCache,
8455
+ PlanService: () => PlanService,
8044
8456
  PurchaseError: () => PurchaseError,
8045
8457
  SESSION_ERROR_CODES: () => SESSION_ERROR_CODES,
8046
8458
  SessionError: () => SessionError,
@@ -8063,6 +8475,8 @@ __export(index_exports, {
8063
8475
  offlineQueue: () => offlineQueue,
8064
8476
  onboardingManager: () => onboardingManager,
8065
8477
  parseVariables: () => parseVariables,
8478
+ planCache: () => planCache,
8479
+ planService: () => planService,
8066
8480
  queueProcessor: () => queueProcessor,
8067
8481
  resolveText: () => resolveText,
8068
8482
  sessionManager: () => sessionManager,
@@ -8072,6 +8486,8 @@ __export(index_exports, {
8072
8486
  useOnboarding: () => useOnboarding,
8073
8487
  usePaywallContext: () => usePaywallContext,
8074
8488
  usePaywallo: () => usePaywallo,
8489
+ usePlanPurchase: () => usePlanPurchase,
8490
+ usePlans: () => usePlans,
8075
8491
  useProducts: () => useProducts,
8076
8492
  usePurchase: () => usePurchase,
8077
8493
  useSubscription: () => useSubscription
@@ -8146,8 +8562,124 @@ function usePaywallo() {
8146
8562
  return context;
8147
8563
  }
8148
8564
 
8149
- // src/hooks/useProducts.ts
8565
+ // src/hooks/usePlans.ts
8150
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");
8151
8683
 
8152
8684
  // src/utils/productMappers.ts
8153
8685
  function mapToIAPProduct(product) {
@@ -8186,11 +8718,11 @@ function mapToFormattedProduct(product) {
8186
8718
 
8187
8719
  // src/hooks/useProducts.ts
8188
8720
  function useProducts(initialProductIds) {
8189
- const [products, setProducts] = (0, import_react13.useState)([]);
8190
- const [formattedProducts, setFormattedProducts] = (0, import_react13.useState)([]);
8191
- const [isLoading, setIsLoading] = (0, import_react13.useState)(false);
8192
- const [error, setError] = (0, import_react13.useState)(null);
8193
- 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) => {
8194
8726
  setIsLoading(true);
8195
8727
  setError(null);
8196
8728
  try {
@@ -8204,24 +8736,24 @@ function useProducts(initialProductIds) {
8204
8736
  setIsLoading(false);
8205
8737
  }
8206
8738
  }, []);
8207
- (0, import_react13.useEffect)(() => {
8739
+ (0, import_react15.useEffect)(() => {
8208
8740
  if (initialProductIds && initialProductIds.length > 0) {
8209
8741
  void loadProducts(initialProductIds);
8210
8742
  }
8211
8743
  }, []);
8212
- const getProduct = (0, import_react13.useCallback)(
8744
+ const getProduct = (0, import_react15.useCallback)(
8213
8745
  (productId) => {
8214
8746
  return products.find((p) => p.productId === productId);
8215
8747
  },
8216
8748
  [products]
8217
8749
  );
8218
- const getFormattedProduct = (0, import_react13.useCallback)(
8750
+ const getFormattedProduct = (0, import_react15.useCallback)(
8219
8751
  (productId) => {
8220
8752
  return formattedProducts.find((p) => p.productId === productId);
8221
8753
  },
8222
8754
  [formattedProducts]
8223
8755
  );
8224
- const getPriceInfo = (0, import_react13.useCallback)(
8756
+ const getPriceInfo = (0, import_react15.useCallback)(
8225
8757
  (productId) => {
8226
8758
  const p = formattedProducts.find((fp) => fp.productId === productId);
8227
8759
  if (!p) return void 0;
@@ -8250,11 +8782,11 @@ function useProducts(initialProductIds) {
8250
8782
  }
8251
8783
 
8252
8784
  // src/hooks/usePurchase.ts
8253
- var import_react14 = require("react");
8785
+ var import_react16 = require("react");
8254
8786
  function usePurchase() {
8255
- const [state, setState] = (0, import_react14.useState)("idle");
8256
- const [error, setError] = (0, import_react14.useState)(null);
8257
- 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) => {
8258
8790
  setState("purchasing");
8259
8791
  setError(null);
8260
8792
  try {
@@ -8290,7 +8822,7 @@ function usePurchase() {
8290
8822
  return { status: "failed", error: purchaseError };
8291
8823
  }
8292
8824
  }, []);
8293
- const restore = (0, import_react14.useCallback)(async () => {
8825
+ const restore = (0, import_react16.useCallback)(async () => {
8294
8826
  setState("restoring");
8295
8827
  setError(null);
8296
8828
  try {
@@ -8321,14 +8853,14 @@ function usePurchase() {
8321
8853
  }
8322
8854
 
8323
8855
  // src/hooks/useSubscription.ts
8324
- var import_react15 = require("react");
8856
+ var import_react17 = require("react");
8325
8857
  init_SubscriptionManager();
8326
8858
  var EMPTY_ENTITLEMENTS = [];
8327
8859
  function useSubscription() {
8328
- const [status, setStatus] = (0, import_react15.useState)(null);
8329
- const [isLoading, setIsLoading] = (0, import_react15.useState)(true);
8330
- const [error, setError] = (0, import_react15.useState)(null);
8331
- 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 () => {
8332
8864
  setIsLoading(true);
8333
8865
  setError(null);
8334
8866
  try {
@@ -8341,7 +8873,7 @@ function useSubscription() {
8341
8873
  setIsLoading(false);
8342
8874
  }
8343
8875
  }, []);
8344
- const restore = (0, import_react15.useCallback)(async () => {
8876
+ const restore = (0, import_react17.useCallback)(async () => {
8345
8877
  setIsLoading(true);
8346
8878
  setError(null);
8347
8879
  try {
@@ -8354,7 +8886,7 @@ function useSubscription() {
8354
8886
  setIsLoading(false);
8355
8887
  }
8356
8888
  }, []);
8357
- (0, import_react15.useEffect)(() => {
8889
+ (0, import_react17.useEffect)(() => {
8358
8890
  void subscriptionManager.getSubscriptionStatus().then((s) => {
8359
8891
  setStatus(s);
8360
8892
  setIsLoading(false);
@@ -8381,8 +8913,11 @@ function useSubscription() {
8381
8913
  };
8382
8914
  }
8383
8915
 
8916
+ // src/index.ts
8917
+ init_plan();
8918
+
8384
8919
  // src/PaywalloProvider.tsx
8385
- var import_react21 = require("react");
8920
+ var import_react23 = require("react");
8386
8921
  var import_react_native24 = require("react-native");
8387
8922
  init_paywall();
8388
8923
  init_paywall();
@@ -8390,7 +8925,7 @@ init_paywall();
8390
8925
  init_PaywalloClient();
8391
8926
 
8392
8927
  // src/hooks/useAppAutoEvents.ts
8393
- var import_react16 = require("react");
8928
+ var import_react18 = require("react");
8394
8929
  var FIRST_SEEN_KEY = "@panel:firstSeen";
8395
8930
  var DEBOUNCE_MS = 1e3;
8396
8931
  async function detectPlatform3() {
@@ -8420,8 +8955,8 @@ function useAppAutoEvents(config) {
8420
8955
  storageSet,
8421
8956
  debug
8422
8957
  } = config;
8423
- const didRunRef = (0, import_react16.useRef)(false);
8424
- (0, import_react16.useEffect)(() => {
8958
+ const didRunRef = (0, import_react18.useRef)(false);
8959
+ (0, import_react18.useEffect)(() => {
8425
8960
  if (!isInitialized || didRunRef.current) return;
8426
8961
  const timer = setTimeout(() => {
8427
8962
  if (didRunRef.current) return;
@@ -8466,7 +9001,7 @@ function useAppAutoEvents(config) {
8466
9001
  }
8467
9002
 
8468
9003
  // src/hooks/useCampaignPreload.ts
8469
- var import_react17 = require("react");
9004
+ var import_react19 = require("react");
8470
9005
  init_PaywalloClient();
8471
9006
  init_CampaignError();
8472
9007
 
@@ -8537,17 +9072,17 @@ function getCacheEntry(cache, placement) {
8537
9072
 
8538
9073
  // src/hooks/useCampaignPreload.ts
8539
9074
  function useCampaignPreload() {
8540
- const [preloadedWebView, setPreloadedWebView] = (0, import_react17.useState)(null);
8541
- const preloadedCampaignsRef = (0, import_react17.useRef)(/* @__PURE__ */ new Map());
8542
- 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)(
8543
9078
  (placement) => isCacheEntryValid(preloadedCampaignsRef.current.get(placement)),
8544
9079
  []
8545
9080
  );
8546
- const consumePreloadedCampaign = (0, import_react17.useCallback)(
9081
+ const consumePreloadedCampaign = (0, import_react19.useCallback)(
8547
9082
  (placement) => getCacheEntry(preloadedCampaignsRef.current, placement),
8548
9083
  []
8549
9084
  );
8550
- const preloadCampaign = (0, import_react17.useCallback)(
9085
+ const preloadCampaign = (0, import_react19.useCallback)(
8551
9086
  async (placement, context) => {
8552
9087
  try {
8553
9088
  const campaign = await PaywalloClient.getCampaign(placement, context);
@@ -8621,12 +9156,12 @@ init_SecureStorage();
8621
9156
  init_paywall();
8622
9157
 
8623
9158
  // src/hooks/useProductLoader.ts
8624
- var import_react18 = require("react");
9159
+ var import_react20 = require("react");
8625
9160
  init_PaywalloClient();
8626
9161
  function useProductLoader() {
8627
- const [products, setProducts] = (0, import_react18.useState)(/* @__PURE__ */ new Map());
8628
- const [isLoadingProducts, setIsLoadingProducts] = (0, import_react18.useState)(false);
8629
- 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) => {
8630
9165
  setIsLoadingProducts(true);
8631
9166
  try {
8632
9167
  const iapService = getIAPService();
@@ -8644,12 +9179,12 @@ function useProductLoader() {
8644
9179
  }
8645
9180
 
8646
9181
  // src/hooks/usePurchaseOrchestrator.ts
8647
- var import_react19 = require("react");
9182
+ var import_react21 = require("react");
8648
9183
  init_PaywalloClient();
8649
9184
  init_paywall();
8650
9185
  function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, invalidateSubscriptionCache) {
8651
9186
  const { trackDismissed, trackProductSelected, trackPurchased } = usePaywallTracking();
8652
- const handlePreloadedPurchase = (0, import_react19.useCallback)(
9187
+ const handlePreloadedPurchase = (0, import_react21.useCallback)(
8653
9188
  async (productId) => {
8654
9189
  try {
8655
9190
  if (preloadedWebView) {
@@ -8687,7 +9222,7 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
8687
9222
  },
8688
9223
  [preloadedWebView, resolvePreloadedPaywall, clearPreloadedWebView, setShowingPreloadedWebView, presentedAtRef, trackDismissed, trackProductSelected, trackPurchased, invalidateSubscriptionCache]
8689
9224
  );
8690
- const handlePreloadedRestore = (0, import_react19.useCallback)(async () => {
9225
+ const handlePreloadedRestore = (0, import_react21.useCallback)(async () => {
8691
9226
  try {
8692
9227
  const transactions = await getIAPService().restore();
8693
9228
  if (transactions.length === 0) return;
@@ -8710,7 +9245,7 @@ function usePurchaseOrchestrator(preloadedWebView, resolvePreloadedPaywall, clea
8710
9245
  }
8711
9246
 
8712
9247
  // src/hooks/useSubscriptionSync.ts
8713
- var import_react20 = require("react");
9248
+ var import_react22 = require("react");
8714
9249
  init_PaywalloClient();
8715
9250
  init_SubscriptionCache();
8716
9251
  var CACHE_TTL_MS2 = 5 * 60 * 1e3;
@@ -8777,14 +9312,14 @@ function isActiveCacheEntry(entry) {
8777
9312
  return isSubscriptionStillActive(entry.data);
8778
9313
  }
8779
9314
  function useSubscriptionSync() {
8780
- const [subscription, setSubscription] = (0, import_react20.useState)(null);
8781
- const cacheRef = (0, import_react20.useRef)(null);
8782
- 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)(() => {
8783
9318
  cacheRef.current = null;
8784
9319
  const distinctId = PaywalloClient.getDistinctId();
8785
9320
  if (distinctId) void subscriptionCache.invalidate(distinctId);
8786
9321
  }, []);
8787
- const fetchAndCache = (0, import_react20.useCallback)(async () => {
9322
+ const fetchAndCache = (0, import_react22.useCallback)(async () => {
8788
9323
  const apiClient = PaywalloClient.getApiClient();
8789
9324
  const distinctId = PaywalloClient.getDistinctId();
8790
9325
  if (!apiClient || !distinctId) return null;
@@ -8798,7 +9333,7 @@ function useSubscriptionSync() {
8798
9333
  void subscriptionCache.set(distinctId, toDomainResponse(status));
8799
9334
  return sub;
8800
9335
  }, []);
8801
- const hasActiveSubscription = (0, import_react20.useCallback)(async () => {
9336
+ const hasActiveSubscription = (0, import_react22.useCallback)(async () => {
8802
9337
  const apiClient = PaywalloClient.getApiClient();
8803
9338
  const distinctId = PaywalloClient.getDistinctId();
8804
9339
  if (!apiClient || !distinctId) return false;
@@ -8841,7 +9376,7 @@ function useSubscriptionSync() {
8841
9376
  return resolveOfflineFromCache(distinctId);
8842
9377
  }
8843
9378
  }, []);
8844
- const getSubscription = (0, import_react20.useCallback)(async () => {
9379
+ const getSubscription = (0, import_react22.useCallback)(async () => {
8845
9380
  const apiClient = PaywalloClient.getApiClient();
8846
9381
  const distinctId = PaywalloClient.getDistinctId();
8847
9382
  if (!apiClient || !distinctId) return null;
@@ -8868,20 +9403,20 @@ init_ClientError();
8868
9403
  init_localization();
8869
9404
  var import_jsx_runtime3 = require("react/jsx-runtime");
8870
9405
  function PaywalloProvider({ children, config }) {
8871
- const [isInitialized, setIsInitialized] = (0, import_react21.useState)(false);
8872
- const [initError, setInitError] = (0, import_react21.useState)(null);
8873
- const [distinctId, setDistinctId] = (0, import_react21.useState)(null);
8874
- 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);
8875
9410
  const { products, isLoadingProducts, refreshProducts } = useProductLoader();
8876
9411
  const { subscription, hasActiveSubscription, getSubscription, invalidateCache } = useSubscriptionSync();
8877
9412
  const { preloadedWebView, setPreloadedWebView, preloadCampaign, consumePreloadedCampaign, isPreloadValid } = useCampaignPreload();
8878
- const clearPreloadedWebView = (0, import_react21.useCallback)(() => setPreloadedWebView(null), [setPreloadedWebView]);
8879
- const autoPreloadTriggeredRef = (0, import_react21.useRef)(false);
8880
- const preloadedWebViewRef = (0, import_react21.useRef)(preloadedWebView);
8881
- const pollTimerRef = (0, import_react21.useRef)(null);
8882
- const pollCancelledRef = (0, import_react21.useRef)(false);
8883
- const secureStorageRef = (0, import_react21.useRef)(new SecureStorage(config.debug));
8884
- (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)(() => {
8885
9420
  preloadedWebViewRef.current = preloadedWebView;
8886
9421
  }, [preloadedWebView]);
8887
9422
  useAppAutoEvents({
@@ -8897,20 +9432,20 @@ function PaywalloProvider({ children, config }) {
8897
9432
  storageSet: (key, value) => secureStorageRef.current.set(key, value),
8898
9433
  debug: config.debug
8899
9434
  });
8900
- const triggerRepreload = (0, import_react21.useCallback)((placement) => {
9435
+ const triggerRepreload = (0, import_react23.useCallback)((placement) => {
8901
9436
  void PaywalloClient.preloadCampaign(placement).catch(() => {
8902
9437
  });
8903
9438
  }, []);
8904
9439
  const { showingPreloadedWebView, presentPreloadedWebView, resolvePreloadedPaywall, handlePreloadedWebViewReady: baseHandlePreloadedWebViewReady, handlePreloadedClose: baseHandlePreloadedClose, setShowingPreloadedWebView, presentedAtRef } = usePaywallPresenter(preloadedWebView, setPreloadedWebView);
8905
- const SCREEN_HEIGHT = (0, import_react21.useMemo)(() => import_react_native24.Dimensions.get("window").height, []);
8906
- const slideAnim = (0, import_react21.useRef)(new import_react_native24.Animated.Value(SCREEN_HEIGHT)).current;
8907
- 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)(() => {
8908
9443
  if (PaywalloClient.getConfig()?.debug) {
8909
9444
  console.log(`[Paywallo WEBVIEW] loaded \u2014 placement: ${preloadedWebView?.placement}`);
8910
9445
  }
8911
9446
  baseHandlePreloadedWebViewReady();
8912
9447
  }, [preloadedWebView?.placement, baseHandlePreloadedWebViewReady]);
8913
- const handlePreloadedClose = (0, import_react21.useCallback)(() => {
9448
+ const handlePreloadedClose = (0, import_react23.useCallback)(() => {
8914
9449
  const placement = preloadedWebView?.placement;
8915
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(() => {
8916
9451
  baseHandlePreloadedClose();
@@ -8925,8 +9460,8 @@ function PaywalloProvider({ children, config }) {
8925
9460
  presentedAtRef,
8926
9461
  invalidateCache
8927
9462
  );
8928
- const [isPreloadPurchasing, setIsPreloadPurchasing] = (0, import_react21.useState)(false);
8929
- 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) => {
8930
9465
  setIsPreloadPurchasing(true);
8931
9466
  try {
8932
9467
  await rawPreloadedPurchase(productId);
@@ -8934,8 +9469,8 @@ function PaywalloProvider({ children, config }) {
8934
9469
  setIsPreloadPurchasing(false);
8935
9470
  }
8936
9471
  }, [rawPreloadedPurchase]);
8937
- const configRef = (0, import_react21.useRef)(config);
8938
- (0, import_react21.useEffect)(() => {
9472
+ const configRef = (0, import_react23.useRef)(config);
9473
+ (0, import_react23.useEffect)(() => {
8939
9474
  let mounted = true;
8940
9475
  initLocalization({ detectDevice: true });
8941
9476
  PaywalloClient.init(configRef.current).then(async () => {
@@ -8956,14 +9491,14 @@ function PaywalloProvider({ children, config }) {
8956
9491
  mounted = false;
8957
9492
  };
8958
9493
  }, [preloadCampaign]);
8959
- const getPaywallConfig = (0, import_react21.useCallback)(async (p) => {
9494
+ const getPaywallConfig = (0, import_react23.useCallback)(async (p) => {
8960
9495
  const api = PaywalloClient.getApiClient();
8961
9496
  return api ? api.getPaywall(p) : null;
8962
9497
  }, []);
8963
- const presentPaywall = (0, import_react21.useCallback)((placement) => {
9498
+ const presentPaywall = (0, import_react23.useCallback)((placement) => {
8964
9499
  return new Promise((resolve) => setActivePaywall({ placement, resolver: resolve }));
8965
9500
  }, []);
8966
- const trackCampaignImpression = (0, import_react21.useCallback)((placement, variantKey, campaignId) => {
9501
+ const trackCampaignImpression = (0, import_react23.useCallback)((placement, variantKey, campaignId) => {
8967
9502
  const api = PaywalloClient.getApiClient();
8968
9503
  if (!api) return;
8969
9504
  const sessionId = PaywalloClient.getSessionId();
@@ -8977,7 +9512,7 @@ function PaywalloProvider({ children, config }) {
8977
9512
  }).catch(() => {
8978
9513
  });
8979
9514
  }, []);
8980
- const presentCampaign = (0, import_react21.useCallback)(
9515
+ const presentCampaign = (0, import_react23.useCallback)(
8981
9516
  async (placement, context) => {
8982
9517
  try {
8983
9518
  if (preloadedWebView?.placement === placement && preloadedWebView.loaded && isPreloadValid(placement)) {
@@ -9051,7 +9586,7 @@ function PaywalloProvider({ children, config }) {
9051
9586
  },
9052
9587
  [preloadedWebView, isPreloadValid, consumePreloadedCampaign, presentPreloadedWebView, trackCampaignImpression]
9053
9588
  );
9054
- const restorePurchases = (0, import_react21.useCallback)(async () => {
9589
+ const restorePurchases = (0, import_react23.useCallback)(async () => {
9055
9590
  invalidateCache();
9056
9591
  try {
9057
9592
  const txs = await getIAPService().restore();
@@ -9060,13 +9595,13 @@ function PaywalloProvider({ children, config }) {
9060
9595
  return { success: false, restoredProducts: [], error: error instanceof Error ? error : new PurchaseError(PURCHASE_ERROR_CODES.RESTORE_FAILED, String(error)) };
9061
9596
  }
9062
9597
  }, [invalidateCache]);
9063
- const handlePaywallResult = (0, import_react21.useCallback)((result) => {
9598
+ const handlePaywallResult = (0, import_react23.useCallback)((result) => {
9064
9599
  if (activePaywall) {
9065
9600
  activePaywall.resolver(result);
9066
9601
  setActivePaywall(null);
9067
9602
  }
9068
9603
  }, [activePaywall]);
9069
- const bridgePaywallPresenter = (0, import_react21.useCallback)((placement) => {
9604
+ const bridgePaywallPresenter = (0, import_react23.useCallback)((placement) => {
9070
9605
  if (PaywalloClient.getConfig()?.debug) {
9071
9606
  console.log(`[Paywallo PRESENT] called \u2014 preloaded: ${preloadedWebViewRef.current?.loaded === true} placement: ${placement}`);
9072
9607
  }
@@ -9095,7 +9630,7 @@ function PaywalloProvider({ children, config }) {
9095
9630
  }
9096
9631
  return presentCampaign(placement);
9097
9632
  }, [presentPreloadedWebView, presentCampaign]);
9098
- (0, import_react21.useEffect)(() => {
9633
+ (0, import_react23.useEffect)(() => {
9099
9634
  if (!isInitialized) return;
9100
9635
  const onEmergency = async (id) => {
9101
9636
  try {
@@ -9122,9 +9657,9 @@ function PaywalloProvider({ children, config }) {
9122
9657
  };
9123
9658
  }, [isInitialized, bridgePaywallPresenter, getSubscription, hasActiveSubscription, restorePurchases]);
9124
9659
  const isReady = isInitialized && !isLoadingProducts;
9125
- const noOp = (0, import_react21.useCallback)(() => {
9660
+ const noOp = (0, import_react23.useCallback)(() => {
9126
9661
  }, []);
9127
- const contextValue = (0, import_react21.useMemo)(() => ({
9662
+ const contextValue = (0, import_react23.useMemo)(() => ({
9128
9663
  config: PaywalloClient.getConfig(),
9129
9664
  isInitialized,
9130
9665
  isReady,
@@ -9158,7 +9693,7 @@ function PaywalloProvider({ children, config }) {
9158
9693
  getPaywallConfig,
9159
9694
  refreshProducts
9160
9695
  ]);
9161
- (0, import_react21.useLayoutEffect)(() => {
9696
+ (0, import_react23.useLayoutEffect)(() => {
9162
9697
  if (showingPreloadedWebView) {
9163
9698
  slideAnim.setValue(SCREEN_HEIGHT);
9164
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();
@@ -9365,6 +9900,8 @@ var index_default = Paywallo;
9365
9900
  PaywalloContext,
9366
9901
  PaywalloError,
9367
9902
  PaywalloProvider,
9903
+ PlanCache,
9904
+ PlanService,
9368
9905
  PurchaseError,
9369
9906
  SESSION_ERROR_CODES,
9370
9907
  SessionError,
@@ -9386,6 +9923,8 @@ var index_default = Paywallo;
9386
9923
  offlineQueue,
9387
9924
  onboardingManager,
9388
9925
  parseVariables,
9926
+ planCache,
9927
+ planService,
9389
9928
  queueProcessor,
9390
9929
  resolveText,
9391
9930
  sessionManager,
@@ -9395,6 +9934,8 @@ var index_default = Paywallo;
9395
9934
  useOnboarding,
9396
9935
  usePaywallContext,
9397
9936
  usePaywallo,
9937
+ usePlanPurchase,
9938
+ usePlans,
9398
9939
  useProducts,
9399
9940
  usePurchase,
9400
9941
  useSubscription